Skip to content

Commit d063aa9

Browse files
committed
feat(linter/plugins): add loc field getter to all AST nodes
1 parent 617e57b commit d063aa9

File tree

31 files changed

+955
-86
lines changed

31 files changed

+955
-86
lines changed

.github/generated/ast_changes_watch_list.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ src:
7676
- 'napi/parser/generated/deserialize/ts.js'
7777
- 'napi/parser/generated/deserialize/ts_parent.js'
7878
- 'napi/parser/generated/deserialize/ts_range.js'
79+
- 'napi/parser/generated/deserialize/ts_range_loc_parent_no_parens.js'
7980
- 'napi/parser/generated/deserialize/ts_range_parent.js'
80-
- 'napi/parser/generated/deserialize/ts_range_parent_no_parens.js'
8181
- 'napi/parser/generated/lazy/constructors.js'
8282
- 'napi/parser/generated/lazy/types.js'
8383
- 'napi/parser/generated/lazy/walk.js'

apps/oxlint/scripts/build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const parserFilePaths = [
3232
'generated/lazy/types.js',
3333
'generated/lazy/walk.js',
3434
*/
35-
'generated/deserialize/ts_range_parent_no_parens.js',
35+
'generated/deserialize/ts_range_loc_parent_no_parens.js',
3636
'generated/visit/keys.js',
3737
'generated/visit/types.js',
3838
'generated/visit/walk.js',

apps/oxlint/src-js/generated/types.d.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Auto-generated code, DO NOT EDIT DIRECTLY!
22
// To edit this generated file you have to edit `tasks/ast_tools/src/generators/typescript.rs`.
33

4+
import { Span } from '../plugins/types.ts';
5+
export { Span };
6+
47
export interface Program extends Span {
58
type: 'Program';
69
body: Array<Directive | Statement>;
@@ -1701,12 +1704,6 @@ export type UnaryOperator = '+' | '-' | '!' | '~' | 'typeof' | 'void' | 'delete'
17011704

17021705
export type UpdateOperator = '++' | '--';
17031706

1704-
export interface Span {
1705-
start: number;
1706-
end: number;
1707-
range: [number, number];
1708-
}
1709-
17101707
export type ModuleKind = 'script' | 'module';
17111708

17121709
export type Node =

apps/oxlint/src-js/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type {
2727
Range,
2828
Ranged,
2929
RuleMeta,
30+
Span,
3031
Token,
3132
Visitor,
3233
VisitorWithHooks,

apps/oxlint/src-js/plugins/location.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { initSourceText, sourceText } from './source_code.js';
22

3-
import type { LineColumn } from './types.ts';
3+
import type { LineColumn, Location, Node } from './types.ts';
4+
5+
const { defineProperty } = Object;
46

57
// Pattern for splitting source text into lines
68
const LINE_BREAK_PATTERN = /\r\n|[\r\n\u2028\u2029]/gu;
@@ -69,6 +71,18 @@ export function getLineColumnFromOffset(offset: number): LineColumn {
6971
);
7072
}
7173

74+
return getLineColumnFromOffsetUnchecked(offset);
75+
}
76+
77+
/**
78+
* Convert a source text index into a (line, column) pair without:
79+
* 1. Checking type of `offset`, or that it's in range.
80+
* 2. Initializing `lineStartOffsets`. Caller must do that before calling this method.
81+
*
82+
* @param offset - The index of a character in a file.
83+
* @returns `{line, column}` location object with 1-indexed line and 0-indexed column.
84+
*/
85+
function getLineColumnFromOffsetUnchecked(offset: number): LineColumn {
7286
// Binary search `lineStartOffsets` for the line containing `offset`
7387
let low = 0, high = lineStartOffsets.length, mid: number;
7488
do {
@@ -138,3 +152,30 @@ export function getOffsetFromLineColumn(loc: LineColumn): number {
138152

139153
throw new TypeError('Expected `loc` to be an object with integer `line` and `column` properties.');
140154
}
155+
156+
/**
157+
* Get the `Location` for an AST node. Used in `loc` getters on AST nodes.
158+
*
159+
* Overwrites the `loc` getter with the calculated `Location`, so accessing `loc` twice on same node
160+
* results in the same object each time.
161+
*
162+
* For internal use only.
163+
*
164+
* @param node - AST node object
165+
* @returns Location
166+
*/
167+
export function getNodeLoc(node: Node): Location {
168+
// Build `lines` and `lineStartOffsets` tables if they haven't been already.
169+
// This also decodes `sourceText` if it wasn't already.
170+
if (lines.length === 0) initLines();
171+
172+
const loc = {
173+
start: getLineColumnFromOffsetUnchecked(node.start),
174+
end: getLineColumnFromOffsetUnchecked(node.end),
175+
};
176+
177+
// Replace `loc` getter with the calculated value
178+
defineProperty(node, 'loc', { value: loc, writable: true });
179+
180+
return loc;
181+
}

apps/oxlint/src-js/plugins/source_code.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@ import {
66
// @ts-expect-error
77
} from '../generated/constants.js';
88
// @ts-expect-error we need to generate `.d.ts` file for this module
9-
// We use the deserializer which removes `ParenthesizedExpression`s from AST to match ESLint
10-
import { deserializeProgramOnly } from '../../dist/generated/deserialize/ts_range_parent_no_parens.js';
11-
import { getLineColumnFromOffset, getOffsetFromLineColumn, initLines, lines, resetLines } from './location.js';
9+
// We use the deserializer which removes `ParenthesizedExpression`s from AST,
10+
// and with `range`, `loc`, and `parent` properties on AST nodes, to match ESLint
11+
import { deserializeProgramOnly } from '../../dist/generated/deserialize/ts_range_loc_parent_no_parens.js';
12+
13+
import {
14+
getLineColumnFromOffset,
15+
getNodeLoc,
16+
getOffsetFromLineColumn,
17+
initLines,
18+
lines,
19+
resetLines,
20+
} from './location.js';
1221

1322
import type { Program } from '../generated/types.d.ts';
1423
import type { Scope, ScopeManager, Variable } from './scope.ts';
@@ -61,7 +70,7 @@ export function initSourceText(): void {
6170
*/
6271
export function initAst(): void {
6372
if (sourceText === null) initSourceText();
64-
ast = deserializeProgramOnly(buffer, sourceText, sourceByteLen);
73+
ast = deserializeProgramOnly(buffer, sourceText, sourceByteLen, getNodeLoc);
6574
}
6675

6776
/**

apps/oxlint/src-js/plugins/types.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@ export interface VisitorWithHooks extends Visitor {
2525
// Visit function for a specific AST node type.
2626
export type VisitFn = (node: Node) => void;
2727

28+
// Range of source offsets.
29+
export type Range = [number, number];
30+
2831
// Interface for any type which has `range` field
2932
export interface Ranged {
3033
range: Range;
3134
}
3235

33-
// Internal interface for any type which has location properties.
34-
interface Spanned extends Ranged {
36+
// Interface for any type which has location properties.
37+
export interface Span extends Ranged {
3538
start: number;
3639
end: number;
37-
loc?: Location;
40+
loc: Location;
3841
}
3942

40-
// Range of source offsets.
41-
export type Range = [number, number];
42-
4343
// Source code location.
4444
export interface Location {
4545
start: LineColumn;
@@ -54,10 +54,10 @@ export interface LineColumn {
5454
}
5555

5656
// AST node type.
57-
export interface Node extends Spanned {}
57+
export interface Node extends Span {}
5858

5959
// AST token type.
60-
export interface Token extends Spanned {
60+
export interface Token extends Span {
6161
type: string;
6262
value: string;
6363
}
@@ -66,7 +66,7 @@ export interface Token extends Spanned {
6666
export type NodeOrToken = Node | Token;
6767

6868
// Comment.
69-
export interface Comment extends Spanned {
69+
export interface Comment extends Span {
7070
type: 'Line' | 'Block';
7171
value: string;
7272
}

apps/oxlint/test/compile-visitor.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ import type { EnterExit, Node, VisitFn } from '../src-js/plugins/types.ts';
2020
const PROGRAM_TYPE_ID = NODE_TYPE_IDS_MAP.get('Program'),
2121
EMPTY_STMT_TYPE_ID = NODE_TYPE_IDS_MAP.get('EmptyStatement');
2222

23-
const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
23+
const SPAN: Node = {
24+
start: 0,
25+
end: 0,
26+
range: [0, 0],
27+
loc: {
28+
start: { line: 0, column: 0 },
29+
end: { line: 0, column: 0 },
30+
},
31+
};
2432

2533
describe('compile visitor', () => {
2634
beforeEach(initCompiledVisitor);

apps/oxlint/test/fixtures/context_properties/plugin.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@ import { sep } from 'node:path';
22

33
import type { Node, Plugin, Rule } from '../../../dist/index.js';
44

5-
const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
5+
const SPAN: Node = {
6+
start: 0,
7+
end: 0,
8+
range: [0, 0],
9+
loc: {
10+
start: { line: 0, column: 0 },
11+
end: { line: 0, column: 0 },
12+
},
13+
};
614

715
const DIR_PATH_LEN = import.meta.dirname.length + 1;
816

apps/oxlint/test/fixtures/createOnce/plugin.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@ import { sep } from 'node:path';
22

33
import type { Node, Plugin, Rule } from '../../../dist/index.js';
44

5-
const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
5+
const SPAN: Node = {
6+
start: 0,
7+
end: 0,
8+
range: [0, 0],
9+
loc: {
10+
start: { line: 0, column: 0 },
11+
end: { line: 0, column: 0 },
12+
},
13+
};
614

715
const DIR_PATH_LEN = import.meta.dirname.length + 1;
816

0 commit comments

Comments
 (0)