tree-sitter/tree-sitter · error · Error
`startPosition` cannot be greater than `endPosition`
Error message
`startPosition` cannot be greater than `endPosition`
What it means
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.
Source
Thrown at lib/binding_web/src/query.ts:746
const endContainingIndex = options.endContainingIndex ?? 0;
const matchLimit = options.matchLimit ?? 0xFFFFFFFF;
const maxStartDepth = options.maxStartDepth ?? 0xFFFFFFFF;
const progressCallback = options.progressCallback;
if (typeof matchLimit !== 'number') {
throw new Error('Arguments must be numbers');
}
this.matchLimit = matchLimit;
if (endIndex !== 0 && startIndex > endIndex) {
throw new Error('`startIndex` cannot be greater than `endIndex`');
}
if (endPosition !== ZERO_POINT && (
startPosition.row > endPosition.row ||
(startPosition.row === endPosition.row && startPosition.column > endPosition.column)
)) {
throw new Error('`startPosition` cannot be greater than `endPosition`');
}
if (endContainingIndex !== 0 && startContainingIndex > endContainingIndex) {
throw new Error('`startContainingIndex` cannot be greater than `endContainingIndex`');
}
if (endContainingPosition !== ZERO_POINT && (
startContainingPosition.row > endContainingPosition.row ||
(startContainingPosition.row === endContainingPosition.row &&
startContainingPosition.column > endContainingPosition.column)
)) {
throw new Error('`startContainingPosition` cannot be greater than `endContainingPosition`');
}
if (progressCallback) {
C.currentQueryProgressCallback = progressCallback;
}
View on GitHub (pinned to dff1fd868c)
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.
Example fix
// before
const opts = { startPosition: sel.head, endPosition: sel.anchor }; // head can precede anchor
const ms = query.matches(node, opts);
// after
const cmp = (a, b) => a.row - b.row || a.column - b.column;
const [startPosition, endPosition] = cmp(sel.anchor, sel.head) <= 0
? [sel.anchor, sel.head]
: [sel.head, sel.anchor];
const ms = query.matches(node, { startPosition, endPosition }); Defensive patterns
Strategy: validation
Validate before calling
const cmpPoint = (a: Point, b: Point) => a.row - b.row || a.column - b.column;
let { startPosition = ZERO_POINT, endPosition = ZERO_POINT } = options;
if (cmpPoint(startPosition, endPosition) > 0) {
[startPosition, endPosition] = [endPosition, startPosition];
options = { ...options, startPosition, endPosition };
}
const matches = query.matches(node, options); Type guard
function isPoint(v: unknown): v is { row: number; column: number } {
return typeof v === 'object' && v !== null
&& Number.isFinite((v as any).row) && Number.isFinite((v as any).column);
} Try / catch
try {
const matches = query.matches(node, options);
} catch (e) {
if (e instanceof Error && e.message === '`startPosition` cannot be greater than `endPosition`') {
const [a, b] = [options.startPosition, options.endPosition].sort(
(x, y) => x!.row - y!.row || x!.column - y!.column);
matches = query.matches(node, { ...options, startPosition: a, endPosition: b });
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- `startContainingPosition` cannot be greater than `endContain
- `startIndex` cannot be greater than `endIndex`
- `startContainingIndex` cannot be greater than `endContaining
- QueryErrorKind.Syntax
- QueryErrorKind.NodeName
AI-assisted analysis of tree-sitter/tree-sitter@dff1fd868c (2026-08-16).
Data as JSON: /api/errors/8371692b453f736a.
Report an issue: GitHub.