Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/generated/ast_changes_watch_list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ src:
- 'napi/parser/generated/deserialize/ts.js'
- 'napi/parser/generated/deserialize/ts_parent.js'
- 'napi/parser/generated/deserialize/ts_range.js'
- 'napi/parser/generated/deserialize/ts_range_loc_parent_no_parens.js'
- 'napi/parser/generated/deserialize/ts_range_parent.js'
- 'napi/parser/generated/deserialize/ts_range_parent_no_parens.js'
- 'napi/parser/generated/lazy/constructors.js'
- 'napi/parser/generated/lazy/types.js'
- 'napi/parser/generated/lazy/walk.js'
Expand Down
2 changes: 1 addition & 1 deletion apps/oxlint/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const parserFilePaths = [
'generated/lazy/types.js',
'generated/lazy/walk.js',
*/
'generated/deserialize/ts_range_parent_no_parens.js',
'generated/deserialize/ts_range_loc_parent_no_parens.js',
'generated/visit/keys.js',
'generated/visit/types.js',
'generated/visit/walk.js',
Expand Down
9 changes: 3 additions & 6 deletions apps/oxlint/src-js/generated/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Auto-generated code, DO NOT EDIT DIRECTLY!
// To edit this generated file you have to edit `tasks/ast_tools/src/generators/typescript.rs`.

import { Span } from '../plugins/types.ts';
export { Span };

export interface Program extends Span {
type: 'Program';
body: Array<Directive | Statement>;
Expand Down Expand Up @@ -1701,12 +1704,6 @@ export type UnaryOperator = '+' | '-' | '!' | '~' | 'typeof' | 'void' | 'delete'

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

export interface Span {
start: number;
end: number;
range: [number, number];
}

export type ModuleKind = 'script' | 'module';

export type Node =
Expand Down
1 change: 1 addition & 0 deletions apps/oxlint/src-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type {
Range,
Ranged,
RuleMeta,
Span,
Token,
Visitor,
VisitorWithHooks,
Expand Down
43 changes: 42 additions & 1 deletion apps/oxlint/src-js/plugins/location.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { initSourceText, sourceText } from './source_code.js';

import type { LineColumn } from './types.ts';
import type { LineColumn, Location, Node } from './types.ts';

const { defineProperty } = Object;

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

return getLineColumnFromOffsetUnchecked(offset);
}

/**
* Convert a source text index into a (line, column) pair without:
* 1. Checking type of `offset`, or that it's in range.
* 2. Initializing `lineStartOffsets`. Caller must do that before calling this method.
*
* @param offset - The index of a character in a file.
* @returns `{line, column}` location object with 1-indexed line and 0-indexed column.
*/
function getLineColumnFromOffsetUnchecked(offset: number): LineColumn {
// Binary search `lineStartOffsets` for the line containing `offset`
let low = 0, high = lineStartOffsets.length, mid: number;
do {
Expand Down Expand Up @@ -138,3 +152,30 @@ export function getOffsetFromLineColumn(loc: LineColumn): number {

throw new TypeError('Expected `loc` to be an object with integer `line` and `column` properties.');
}

/**
* Get the `Location` for an AST node. Used in `loc` getters on AST nodes.
*
* Overwrites the `loc` getter with the calculated `Location`, so accessing `loc` twice on same node
* results in the same object each time.
*
* For internal use only.
*
* @param node - AST node object
* @returns Location
*/
export function getNodeLoc(node: Node): Location {
// Build `lines` and `lineStartOffsets` tables if they haven't been already.
// This also decodes `sourceText` if it wasn't already.
if (lines.length === 0) initLines();

const loc = {
start: getLineColumnFromOffsetUnchecked(node.start),
end: getLineColumnFromOffsetUnchecked(node.end),
};

// Replace `loc` getter with the calculated value
defineProperty(node, 'loc', { value: loc, writable: true });

return loc;
}
17 changes: 13 additions & 4 deletions apps/oxlint/src-js/plugins/source_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@ import {
// @ts-expect-error
} from '../generated/constants.js';
// @ts-expect-error we need to generate `.d.ts` file for this module
// We use the deserializer which removes `ParenthesizedExpression`s from AST to match ESLint
import { deserializeProgramOnly } from '../../dist/generated/deserialize/ts_range_parent_no_parens.js';
import { getLineColumnFromOffset, getOffsetFromLineColumn, initLines, lines, resetLines } from './location.js';
// We use the deserializer which removes `ParenthesizedExpression`s from AST,
// and with `range`, `loc`, and `parent` properties on AST nodes, to match ESLint
import { deserializeProgramOnly } from '../../dist/generated/deserialize/ts_range_loc_parent_no_parens.js';

import {
getLineColumnFromOffset,
getNodeLoc,
getOffsetFromLineColumn,
initLines,
lines,
resetLines,
} from './location.js';

import type { Program } from '../generated/types.d.ts';
import type { Scope, ScopeManager, Variable } from './scope.ts';
Expand Down Expand Up @@ -61,7 +70,7 @@ export function initSourceText(): void {
*/
export function initAst(): void {
if (sourceText === null) initSourceText();
ast = deserializeProgramOnly(buffer, sourceText, sourceByteLen);
ast = deserializeProgramOnly(buffer, sourceText, sourceByteLen, getNodeLoc);
}

/**
Expand Down
18 changes: 9 additions & 9 deletions apps/oxlint/src-js/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ export interface VisitorWithHooks extends Visitor {
// Visit function for a specific AST node type.
export type VisitFn = (node: Node) => void;

// Range of source offsets.
export type Range = [number, number];

// Interface for any type which has `range` field
export interface Ranged {
range: Range;
}

// Internal interface for any type which has location properties.
interface Spanned extends Ranged {
// Interface for any type which has location properties.
export interface Span extends Ranged {
start: number;
end: number;
loc?: Location;
loc: Location;
}

// Range of source offsets.
export type Range = [number, number];

// Source code location.
export interface Location {
start: LineColumn;
Expand All @@ -54,10 +54,10 @@ export interface LineColumn {
}

// AST node type.
export interface Node extends Spanned {}
export interface Node extends Span {}

// AST token type.
export interface Token extends Spanned {
export interface Token extends Span {
type: string;
value: string;
}
Expand All @@ -66,7 +66,7 @@ export interface Token extends Spanned {
export type NodeOrToken = Node | Token;

// Comment.
export interface Comment extends Spanned {
export interface Comment extends Span {
type: 'Line' | 'Block';
value: string;
}
Expand Down
10 changes: 9 additions & 1 deletion apps/oxlint/test/compile-visitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ import type { EnterExit, Node, VisitFn } from '../src-js/plugins/types.ts';
const PROGRAM_TYPE_ID = NODE_TYPE_IDS_MAP.get('Program'),
EMPTY_STMT_TYPE_ID = NODE_TYPE_IDS_MAP.get('EmptyStatement');

const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
const SPAN: Node = {
start: 0,
end: 0,
range: [0, 0],
loc: {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 },
},
};

describe('compile visitor', () => {
beforeEach(initCompiledVisitor);
Expand Down
10 changes: 9 additions & 1 deletion apps/oxlint/test/fixtures/context_properties/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { sep } from 'node:path';

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

const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
const SPAN: Node = {
start: 0,
end: 0,
range: [0, 0],
loc: {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 },
},
};

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

Expand Down
10 changes: 9 additions & 1 deletion apps/oxlint/test/fixtures/createOnce/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { sep } from 'node:path';

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

const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
const SPAN: Node = {
start: 0,
end: 0,
range: [0, 0],
loc: {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 },
},
};

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

Expand Down
1 change: 0 additions & 1 deletion apps/oxlint/test/fixtures/definePlugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { definePlugin } from '../../../dist/index.js';

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

// `loc` is required for ESLint
const SPAN: Node = {
start: 0,
end: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { definePlugin, defineRule } from '../../../dist/index.js';

import type { Node } from '../../../dist/index.js';

// `loc` is required for ESLint
const SPAN: Node = {
start: 0,
end: 0,
Expand Down
1 change: 0 additions & 1 deletion apps/oxlint/test/fixtures/defineRule/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { defineRule } from '../../../dist/index.js';

import type { Node } from '../../../dist/index.js';

// `loc` is required for ESLint
const SPAN: Node = {
start: 0,
end: 0,
Expand Down
8 changes: 8 additions & 0 deletions apps/oxlint/test/fixtures/estree/output.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
x estree-check(check): program:
| start/end: [59,265]
| range: [59,265]
| loc: [{"start":{"line":5,"column":0},"end":{"line":15,"column":0}}]
,-[files/index.ts:5:1]
4 | // All `Identifier`s
5 | ,-> let a = { x: y };
Expand All @@ -67,6 +68,7 @@
x estree-check(check): ident "a":
| start/end: [63,64]
| range: [63,64]
| loc: [{"start":{"line":5,"column":4},"end":{"line":5,"column":5}}]
,-[files/index.ts:5:5]
4 | // All `Identifier`s
5 | let a = { x: y };
Expand All @@ -77,6 +79,7 @@
x estree-check(check): ident "x":
| start/end: [69,70]
| range: [69,70]
| loc: [{"start":{"line":5,"column":10},"end":{"line":5,"column":11}}]
,-[files/index.ts:5:11]
4 | // All `Identifier`s
5 | let a = { x: y };
Expand All @@ -87,6 +90,7 @@
x estree-check(check): ident "y":
| start/end: [72,73]
| range: [72,73]
| loc: [{"start":{"line":5,"column":13},"end":{"line":5,"column":14}}]
,-[files/index.ts:5:14]
4 | // All `Identifier`s
5 | let a = { x: y };
Expand All @@ -97,6 +101,7 @@
x estree-check(check): ident "b":
| start/end: [124,125]
| range: [124,125]
| loc: [{"start":{"line":8,"column":6},"end":{"line":8,"column":7}}]
,-[files/index.ts:8:7]
7 | // No `ParenthesizedExpression`s in AST
8 | const b = (x * ((('str' + ((123))))));
Expand All @@ -107,6 +112,7 @@
x estree-check(check): ident "x":
| start/end: [129,130]
| range: [129,130]
| loc: [{"start":{"line":8,"column":11},"end":{"line":8,"column":12}}]
,-[files/index.ts:8:12]
7 | // No `ParenthesizedExpression`s in AST
8 | const b = (x * ((('str' + ((123))))));
Expand All @@ -117,6 +123,7 @@
x estree-check(check): ident "T":
| start/end: [176,177]
| range: [176,177]
| loc: [{"start":{"line":11,"column":5},"end":{"line":11,"column":6}}]
,-[files/index.ts:11:6]
10 | // TS syntax
11 | type T = string;
Expand All @@ -127,6 +134,7 @@
x estree-check(check): ident "U":
| start/end: [230,231]
| range: [230,231]
| loc: [{"start":{"line":14,"column":5},"end":{"line":14,"column":6}}]
,-[files/index.ts:14:6]
13 | // No `TSParenthesizedType`s in AST
14 | type U = (((((string)) | ((number)))));
Expand Down
13 changes: 11 additions & 2 deletions apps/oxlint/test/fixtures/estree/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import assert from 'node:assert';

import type { Plugin } from '../../../dist/index.js';

const plugin: Plugin = {
Expand All @@ -16,7 +18,8 @@ const plugin: Plugin = {
context.report({
message: 'program:\n' +
`start/end: [${program.start},${program.end}]\n` +
`range: [${program.range}]`,
`range: [${program.range}]\n` +
`loc: [${JSON.stringify(program.loc)}]`,
node: program,
});
visits.push(program.type);
Expand All @@ -32,10 +35,16 @@ const plugin: Plugin = {
visits.push(`${decl.type}: (init: ${decl.init.type})`);
},
Identifier(ident) {
// Check `loc` property returns same object each time it's accessed
const { loc } = ident;
const loc2 = ident.loc;
assert(loc2 === loc);

context.report({
message: `ident "${ident.name}":\n` +
`start/end: [${ident.start},${ident.end}]\n` +
`range: [${ident.range}]`,
`range: [${ident.range}]\n` +
`loc: [${JSON.stringify(loc)}]`,
node: ident,
});
visits.push(`${ident.type}: ${ident.name}`);
Expand Down
10 changes: 9 additions & 1 deletion apps/oxlint/test/fixtures/sourceCode/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import type { ESTree, Node, Plugin, Rule } from '../../../dist/index.js';

type Program = ESTree.Program;

const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
const SPAN: Node = {
start: 0,
end: 0,
range: [0, 0],
loc: {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 },
},
};

const createRule: Rule = {
create(context) {
Expand Down
10 changes: 9 additions & 1 deletion apps/oxlint/test/fixtures/sourceCode_late_access/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import type { ESTree, Node, Plugin, Rule } from '../../../dist/index.js';

type Program = ESTree.Program;

const SPAN: Node = { start: 0, end: 0, range: [0, 0] };
const SPAN: Node = {
start: 0,
end: 0,
range: [0, 0],
loc: {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 },
},
};

// Purpose of this test fixture is to ensure that AST is not deserialized twice
// if `context.sourceCode.ast` is accessed during AST traversal.
Expand Down
Loading
Loading