{"record":{"id":"dfb926e02a8f746e","repo":"larksuite/cli","slug":"end-q-must-be-at-or-after-start-q-dfb926","errorCode":null,"errorMessage":"end %q must be at or after start %q","messagePattern":"end %q must be at or after start %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shortcuts/sheets/lark_sheet_write_cells.go","lineNumber":1159,"sourceCode":"\t}\n\tparts := strings.SplitN(body, \":\", 2)\n\tout.start = strings.TrimSpace(parts[0])\n\tstartCol, startRow, ok := splitCellRef(out.start)\n\tout.col, out.row = startCol, startRow\n\tif len(parts) == 1 {\n\t\t// single cell, e.g. \"A1\"\n\t\tif !ok {\n\t\t\treturn cellRange{}, fmt.Errorf(\"invalid cell ref %q\", parts[0]) //nolint:forbidigo // intermediate error; callers wrap it into a typed --range/--source-range validation error\n\t\t}\n\t\tout.rows, out.cols, out.anchored = 1, 1, true\n\t\treturn out, nil\n\t}\n\tendCol, endRow, okEnd := splitCellRef(parts[1])\n\tif !ok || !okEnd {\n\t\treturn cellRange{}, fmt.Errorf(\"unsupported range form %q (need rectangular A1:B2)\", body) //nolint:forbidigo // intermediate error; callers wrap it into a typed --range/--source-range validation error\n\t}\n\tif endRow < startRow || endCol < startCol {\n\t\treturn cellRange{}, fmt.Errorf(\"end %q must be at or after start %q\", parts[1], parts[0]) //nolint:forbidigo // intermediate error; callers wrap it into a typed --range/--source-range validation error\n\t}\n\tout.rows, out.cols = endRow-startRow+1, endCol-startCol+1\n\treturn out, nil\n}\n\nfunc rangeDimensions(rangeStr string) (rows, cols int, err error) {\n\tr, err := parseCellRange(rangeStr)\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\treturn r.rows, r.cols, nil\n}\n\n// splitCellRef parses \"A1\" → (col=0, row=0, true). Returns false for any\n// non-rectangular form (pure column \"A\", pure row \"1\", invalid chars).\nfunc splitCellRef(s string) (col, row int, ok bool) {\n\ts = strings.TrimSpace(s)\n\tif s == \"\" {","sourceCodeStart":1141,"sourceCodeEnd":1177,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/shortcuts/sheets/lark_sheet_write_cells.go#L1141-L1177","documentation":"After both halves of a colon-separated range parse, parseCellRange checks that the end cell is at or after the start cell in both row and column. An inverted range like 'B2:A1' or 'A5:A1' is rejected. Callers wrap this into a typed --range/--source-range validation error with flag/param context.","triggerScenarios":"Passing a range whose end cell has a smaller row number or column index than the start cell, e.g. 'B2:A1', 'A5:A1', 'C1:B3'.","commonSituations":"Users specifying ranges right-to-left or bottom-to-top, or code emitting ranges from an unordered selection rectangle.","solutions":["Order the range top-left first: swap the cells, e.g. 'A1:B2' instead of 'B2:A1'.","In generating code, compute start = min corner and end = max corner of the rectangle.","Sanitize user input by normalizing row/col ordering before invoking the command."],"exampleFix":"// before\n--range \"A5:A1\"\n// after\n--range \"A1:A5\"","handlingStrategy":"validation","validationCode":"a, b := parseKey(parts[0]), parseKey(parts[1])\nif a > b { parts[0], parts[1] = parts[1], parts[0] } // normalize before calling the CLI","typeGuard":"func isOrderedRange(s string) bool {\n\tp := strings.Split(s, \":\")\n\treturn len(p) == 2 && cellKey(p[0]) <= cellKey(p[1])\n}","tryCatchPattern":null,"preventionTips":["Emit ranges with the min corner first (row and column ordering).","Normalize drag-selection style input (either direction) before use."],"tags":["validation","sheets","range-parsing","cli"],"backgroundTag":"invalid-range-format","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}