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
41 changes: 40 additions & 1 deletion apps/oxlint/src-js/plugins/source_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const require = createRequire(import.meta.url);

const { max } = Math;

// Pattern for splitting source text into lines
const LINE_BREAK_PATTERN = /\r\n|[\r\n\u2028\u2029]/gu;

// Text decoder, for decoding source text from buffer
const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true });

Expand All @@ -31,6 +34,10 @@ let sourceText: string | null = null;
let sourceByteLen: number = 0;
let ast: Program | null = null;

// Lazily populated when `SOURCE_CODE.lines` is accessed.
const lines: string[] = [],
lineStartOffsets: number[] = [];

// Lazily populated when `SOURCE_CODE.visitorKeys` is accessed.
let visitorKeys: { [key: string]: string[] } | null = null;

Expand Down Expand Up @@ -75,6 +82,35 @@ export function getAst(): Program {
return ast;
}

/**
* Split source text into lines.
*/
function initLines(): void {
if (sourceText === null) initSourceText();

// This implementation is based on the one in ESLint.
// TODO: Investigate if using `String.prototype.matchAll` is faster.
// This comment is above ESLint's implementation:
/*
* Previously, this was implemented using a regex that
* matched a sequence of non-linebreak characters followed by a
* linebreak, then adding the lengths of the matches. However,
* this caused a catastrophic backtracking issue when the end
* of a file contained a large number of non-newline characters.
* To avoid this, the current implementation just matches newlines
* and uses match.index to get the correct line start indices.
*/

lineStartOffsets.push(0);
let lastOffset = 0, offset, match;
while ((match = LINE_BREAK_PATTERN.exec(sourceText))) {
offset = match.index;
lines.push(sourceText.slice(lastOffset, offset));
lineStartOffsets.push(lastOffset = offset + match[0].length);
}
lines.push(sourceText.slice(lastOffset));
}

/**
* Reset source after file has been linted, to free memory.
*
Expand All @@ -89,6 +125,8 @@ export function resetSource(): void {
buffer = null;
sourceText = null;
ast = null;
lines.length = 0;
lineStartOffsets.length = 0;
}

// `SourceCode` object.
Expand Down Expand Up @@ -136,7 +174,8 @@ export const SOURCE_CODE = Object.freeze({

// Get source text as array of lines, split according to specification's definition of line breaks.
get lines(): string[] {
throw new Error('`sourceCode.lines` not implemented yet'); // TODO
if (lines.length === 0) initLines();
return lines;
},

/**
Expand Down
3 changes: 3 additions & 0 deletions apps/oxlint/test/fixtures/sourceCode/files/1.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
let foo, bar;

// x
// y
23 changes: 18 additions & 5 deletions apps/oxlint/test/fixtures/sourceCode/output.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,51 @@
# stdout
```
x source-code-plugin(create): create:
| text: "let foo, bar;\n"
| getText(): "let foo, bar;\n"
| text: "let foo, bar;\n\n// x\n// y\n"
| getText(): "let foo, bar;\n\n// x\n// y\n"
| lines: ["let foo, bar;","","// x","// y",""]
| ast: "foo"
| visitorKeys: left, right
,-[files/1.js:1:1]
1 | let foo, bar;
: ^
2 |
`----

x source-code-plugin(create-once): after:
| source: "let foo, bar;\n"
| source: "let foo, bar;\n\n// x\n// y\n"
,-[files/1.js:1:1]
1 | let foo, bar;
: ^
2 |
`----

x source-code-plugin(create-once): before:
| text: "let foo, bar;\n"
| getText(): "let foo, bar;\n"
| text: "let foo, bar;\n\n// x\n// y\n"
| getText(): "let foo, bar;\n\n// x\n// y\n"
| lines: ["let foo, bar;","","// x","// y",""]
| ast: "foo"
| visitorKeys: left, right
,-[files/1.js:1:1]
1 | let foo, bar;
: ^
2 |
`----

x source-code-plugin(create): var decl:
| source: "let foo, bar;"
,-[files/1.js:1:1]
1 | let foo, bar;
: ^^^^^^^^^^^^^
2 |
`----

x source-code-plugin(create-once): var decl:
| source: "let foo, bar;"
,-[files/1.js:1:1]
1 | let foo, bar;
: ^^^^^^^^^^^^^
2 |
`----

x source-code-plugin(create): ident "foo":
Expand All @@ -52,6 +59,7 @@
,-[files/1.js:1:5]
1 | let foo, bar;
: ^^^
2 |
`----

x source-code-plugin(create-once): ident "foo":
Expand All @@ -62,6 +70,7 @@
,-[files/1.js:1:5]
1 | let foo, bar;
: ^^^
2 |
`----

x source-code-plugin(create): ident "bar":
Expand All @@ -72,6 +81,7 @@
,-[files/1.js:1:10]
1 | let foo, bar;
: ^^^
2 |
`----

x source-code-plugin(create-once): ident "bar":
Expand All @@ -82,11 +92,13 @@
,-[files/1.js:1:10]
1 | let foo, bar;
: ^^^
2 |
`----

x source-code-plugin(create): create:
| text: "let qux;\n"
| getText(): "let qux;\n"
| lines: ["let qux;",""]
| ast: "qux"
| visitorKeys: left, right
,-[files/2.js:1:1]
Expand All @@ -104,6 +116,7 @@
x source-code-plugin(create-once): before:
| text: "let qux;\n"
| getText(): "let qux;\n"
| lines: ["let qux;",""]
| ast: "qux"
| visitorKeys: left, right
,-[files/2.js:1:1]
Expand Down
2 changes: 2 additions & 0 deletions apps/oxlint/test/fixtures/sourceCode/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const createRule: Rule = {
message: 'create:\n' +
`text: ${JSON.stringify(context.sourceCode.text)}\n` +
`getText(): ${JSON.stringify(context.sourceCode.getText())}\n` +
`lines: ${JSON.stringify(context.sourceCode.lines)}\n` +
// @ts-ignore
`ast: "${ast.body[0].declarations[0].id.name}"\n` +
`visitorKeys: ${context.sourceCode.visitorKeys.BinaryExpression.join(', ')}`,
Expand Down Expand Up @@ -55,6 +56,7 @@ const createOnceRule: Rule = {
message: 'before:\n' +
`text: ${JSON.stringify(context.sourceCode.text)}\n` +
`getText(): ${JSON.stringify(context.sourceCode.getText())}\n` +
`lines: ${JSON.stringify(context.sourceCode.lines)}\n` +
// @ts-ignore
`ast: "${ast.body[0].declarations[0].id.name}"\n` +
`visitorKeys: ${context.sourceCode.visitorKeys.BinaryExpression.join(', ')}`,
Expand Down
Loading