{"record":{"id":"8371692b453f736a","repo":"tree-sitter/tree-sitter","slug":"startposition-cannot-be-greater-than-endpositio","errorCode":null,"errorMessage":"`startPosition` cannot be greater than `endPosition`","messagePattern":"`startPosition` cannot be greater than `endPosition`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/binding_web/src/query.ts","lineNumber":746,"sourceCode":"    const endContainingIndex = options.endContainingIndex ?? 0;\n    const matchLimit = options.matchLimit ?? 0xFFFFFFFF;\n    const maxStartDepth = options.maxStartDepth ?? 0xFFFFFFFF;\n    const progressCallback = options.progressCallback;\n\n    if (typeof matchLimit !== 'number') {\n      throw new Error('Arguments must be numbers');\n    }\n    this.matchLimit = matchLimit;\n\n    if (endIndex !== 0 && startIndex > endIndex) {\n      throw new Error('`startIndex` cannot be greater than `endIndex`');\n    }\n\n    if (endPosition !== ZERO_POINT && (\n      startPosition.row > endPosition.row ||\n      (startPosition.row === endPosition.row && startPosition.column > endPosition.column)\n    )) {\n      throw new Error('`startPosition` cannot be greater than `endPosition`');\n    }\n\n    if (endContainingIndex !== 0 && startContainingIndex > endContainingIndex) {\n      throw new Error('`startContainingIndex` cannot be greater than `endContainingIndex`');\n    }\n\n    if (endContainingPosition !== ZERO_POINT && (\n      startContainingPosition.row > endContainingPosition.row ||\n      (startContainingPosition.row === endContainingPosition.row &&\n        startContainingPosition.column > endContainingPosition.column)\n    )) {\n      throw new Error('`startContainingPosition` cannot be greater than `endContainingPosition`');\n    }\n\n    if (progressCallback) {\n      C.currentQueryProgressCallback = progressCallback;\n    }\n","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/tree-sitter/tree-sitter/blob/dff1fd868c750dbbae179fcd5c43ce987e4e0528/lib/binding_web/src/query.ts#L728-L764","documentation":"Plain Error thrown by Query.matches() when startPosition and endPosition are supplied, endPosition is not the default ZERO_POINT, and startPosition is lexicographically after endPosition (greater row, or equal row with greater column). Points are {row, column} pairs compared row-first. As with the index checks, the default ZERO_POINT endPosition disables the check.","triggerScenarios":"Calling query.matches(node, { startPosition: { row: 10, column: 0 }, endPosition: { row: 5, column: 20 } }); storing Points as {x, y} or {line, character} and mapping fields in the wrong order; not normalizing editor selections (anchor may be after head).","commonSituations":"Converting from editor coordinate types (VS Code Position {line, character}, CodeMirror {line, ch}, LSP Position) to tree-sitter Point and swapping row/column; using selection.anchor/selection.head without sorting; computing a start Point from a later marker than the end Point during incremental updates.","solutions":["Normalize the pair with a point-comparison helper before the call: if (cmpPoint(start, end) > 0) [start, end] = [end, start].","Double-check the mapping when converting from editor APIs: row = line, column = character — and construct Points explicitly, never by field order.","Sort selection endpoints (anchor/focus) into (start, end) before building options.","Skip the query when the range is empty or inverted instead of letting the library throw."],"exampleFix":"// before\nconst opts = { startPosition: sel.head, endPosition: sel.anchor }; // head can precede anchor\nconst ms = query.matches(node, opts);\n\n// after\nconst cmp = (a, b) => a.row - b.row || a.column - b.column;\nconst [startPosition, endPosition] = cmp(sel.anchor, sel.head) <= 0\n  ? [sel.anchor, sel.head]\n  : [sel.head, sel.anchor];\nconst ms = query.matches(node, { startPosition, endPosition });","handlingStrategy":"validation","validationCode":"const cmpPoint = (a: Point, b: Point) => a.row - b.row || a.column - b.column;\nlet { startPosition = ZERO_POINT, endPosition = ZERO_POINT } = options;\nif (cmpPoint(startPosition, endPosition) > 0) {\n  [startPosition, endPosition] = [endPosition, startPosition];\n  options = { ...options, startPosition, endPosition };\n}\nconst matches = query.matches(node, options);","typeGuard":"function isPoint(v: unknown): v is { row: number; column: number } {\n  return typeof v === 'object' && v !== null\n    && Number.isFinite((v as any).row) && Number.isFinite((v as any).column);\n}","tryCatchPattern":"try {\n  const matches = query.matches(node, options);\n} catch (e) {\n  if (e instanceof Error && e.message === '`startPosition` cannot be greater than `endPosition`') {\n    const [a, b] = [options.startPosition, options.endPosition].sort(\n      (x, y) => x!.row - y!.row || x!.column - y!.column);\n    matches = query.matches(node, { ...options, startPosition: a, endPosition: b });\n  } else throw e;\n}","preventionTips":["Use one cmpPoint(start, end) helper and swap when positive — apply it to every Point pair you pass.","Always construct Points with named fields (row, column), never positionally.","Map editor Position{line, character} through a single shared conversion helper.","Sort selection anchor/head before building query options."],"tags":["tree-sitter","web-binding","range-validation","point","query"],"backgroundTag":"invalid-range-arguments","analyzedSha":"dff1fd868c750dbbae179fcd5c43ce987e4e0528","analyzedAt":"2026-08-16T22:01:49.632Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}