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
327 changes: 307 additions & 20 deletions apps/oxlint/src-js/package/compat.ts

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion apps/oxlint/test/fixtures/eslintCompat/.oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
{
"files": ["files/cfg.js"],
"rules": {
"eslint-compat-plugin/create-once-cfg": "error"
"eslint-compat-plugin/create-once-cfg": "error",
"eslint-compat-plugin/create-once-cfg2": "error"
}
}
]
Expand Down
1 change: 1 addition & 0 deletions apps/oxlint/test/fixtures/eslintCompat/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default [
},
rules: {
"eslint-compat-plugin/create-once-cfg": "error",
"eslint-compat-plugin/create-once-cfg2": "error",
},
},
];
4 changes: 3 additions & 1 deletion apps/oxlint/test/fixtures/eslintCompat/eslint.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ filename: <fixture>/files/2.js eslint-compat-plugin
<fixture>/files/cfg.js
0:1 error after hook:
filename: <fixture>/files/cfg.js eslint-compat-plugin/create-once-cfg
0:1 error after hook:
filename: <fixture>/files/cfg.js eslint-compat-plugin/create-once-cfg2

50 problems (50 errors, 0 warnings)
51 problems (51 errors, 0 warnings)
```

# stderr
Expand Down
10 changes: 9 additions & 1 deletion apps/oxlint/test/fixtures/eslintCompat/output.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,15 @@
2 | console.log(i);
`----

Found 0 warnings and 46 errors.
x eslint-compat-plugin(create-once-cfg2): after hook:
| filename: <fixture>/files/cfg.js
,-[files/cfg.js:1:1]
1 | for (let i = 0; i < 3; i++) {
: ^
2 | console.log(i);
`----

Found 0 warnings and 47 errors.
Finished in Xms on 3 files with 0 rules using X threads.
```

Expand Down
172 changes: 104 additions & 68 deletions apps/oxlint/test/fixtures/eslintCompat/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,78 +201,113 @@ const createOnceSelectorRule: Rule = {
};

// This tests that `after` hook runs after all CFG event handlers.
// This rules is only run on `files/cfg.js`, to ensure CFG events handlers do not affect behavior of other rules
// These rules are only run on `files/cfg.js`, to ensure CFG events handlers do not affect behavior of other rules
// which don't use CFG event listeners.
const createOnceCfgRule: Rule = {
createOnce(context) {
// Collect all visits and check them in `after` hook
const visits: { event: string; nodeType?: string }[] = [];

return {
onCodePathStart(_codePath: unknown, node: ESTreeNode) {
visits.push({ event: "onCodePathStart", nodeType: node.type });
},
onCodePathEnd(_codePath: unknown, node: ESTreeNode) {
visits.push({ event: "onCodePathEnd", nodeType: node.type });
},
onCodePathSegmentStart(_segment: unknown, node: ESTreeNode) {
visits.push({ event: "onCodePathSegmentStart", nodeType: node.type });
},
onCodePathSegmentEnd(_segment: unknown, node: ESTreeNode) {
visits.push({ event: "onCodePathSegmentEnd", nodeType: node.type });
},
onUnreachableCodePathSegmentStart(_segment: unknown, node: ESTreeNode) {
visits.push({ event: "onUnreachableCodePathSegmentStart", nodeType: node.type });
},
onUnreachableCodePathSegmentEnd(_segment: unknown, node: ESTreeNode) {
visits.push({ event: "onUnreachableCodePathSegmentEnd", nodeType: node.type });
},
onCodePathSegmentLoop(_fromSegment: unknown, _toSegment: unknown, node: ESTreeNode) {
visits.push({ event: "onCodePathSegmentLoop", nodeType: node.type });
},
after() {
context.report({
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});

visits.push({ event: "after" });

const expectedVisits: typeof visits = [
{ event: "onCodePathStart", nodeType: "Program" },
{ event: "onCodePathSegmentStart", nodeType: "Program" },
{ event: "onCodePathSegmentEnd", nodeType: "BinaryExpression" },
{ event: "onCodePathSegmentStart", nodeType: "BinaryExpression" },
{ event: "onCodePathSegmentEnd", nodeType: "UpdateExpression" },
{ event: "onCodePathSegmentStart", nodeType: "UpdateExpression" },
{ event: "onCodePathSegmentLoop", nodeType: "BlockStatement" },
{ event: "onCodePathSegmentEnd", nodeType: "BlockStatement" },
{ event: "onCodePathSegmentStart", nodeType: "BlockStatement" },
{ event: "onCodePathSegmentEnd", nodeType: "BlockStatement" },
{ event: "onUnreachableCodePathSegmentStart", nodeType: "BlockStatement" },
{ event: "onUnreachableCodePathSegmentEnd", nodeType: "ForStatement" },
{ event: "onCodePathSegmentStart", nodeType: "ForStatement" },
{ event: "onCodePathSegmentEnd", nodeType: "Program" },
{ event: "onCodePathEnd", nodeType: "Program" },
{ event: "after" },
];

if (
visits.length !== expectedVisits.length ||
visits.some(
(v, i) =>
v.event !== expectedVisits[i].event || v.nodeType !== expectedVisits[i].nodeType,
)
) {
// Collect all visits and check them in `after` hook.
// Run 2 copies of the rule, to ensure that `after` hooks for both rules run after all visits for both rules.
const visits: { ruleNum: number; event: string; nodeType?: string }[] = [];

function createCfgRule(ruleNum: number): Rule {
function addEvent(event: string, nodeType?: string) {
visits.push({ ruleNum, event, nodeType });
}

return {
createOnce(context) {
return {
before() {
addEvent("before");
},
onCodePathStart(_codePath: unknown, node: ESTreeNode) {
addEvent("onCodePathStart", node.type);
},
onCodePathEnd(_codePath: unknown, node: ESTreeNode) {
addEvent("onCodePathEnd", node.type);
},
onCodePathSegmentStart(_segment: unknown, node: ESTreeNode) {
addEvent("onCodePathSegmentStart", node.type);
},
onCodePathSegmentEnd(_segment: unknown, node: ESTreeNode) {
addEvent("onCodePathSegmentEnd", node.type);
},
onUnreachableCodePathSegmentStart(_segment: unknown, node: ESTreeNode) {
addEvent("onUnreachableCodePathSegmentStart", node.type);
},
onUnreachableCodePathSegmentEnd(_segment: unknown, node: ESTreeNode) {
addEvent("onUnreachableCodePathSegmentEnd", node.type);
},
onCodePathSegmentLoop(_fromSegment: unknown, _toSegment: unknown, node: ESTreeNode) {
addEvent("onCodePathSegmentLoop", node.type);
},
after() {
context.report({
message: `Unexpected visits:\n${JSON.stringify(visits, null, 2)}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
}
},
} as unknown as Visitor; // TODO: Our types don't include CFG event handlers at present
},
};

addEvent("after");

if (ruleNum === 1) return;

const expectedVisits: typeof visits = [
{ ruleNum: 1, event: "before" },
{ ruleNum: 2, event: "before" },
{ ruleNum: 1, event: "onCodePathStart", nodeType: "Program" },
{ ruleNum: 2, event: "onCodePathStart", nodeType: "Program" },
{ ruleNum: 1, event: "onCodePathSegmentStart", nodeType: "Program" },
{ ruleNum: 2, event: "onCodePathSegmentStart", nodeType: "Program" },
{ ruleNum: 1, event: "onCodePathSegmentEnd", nodeType: "BinaryExpression" },
{ ruleNum: 2, event: "onCodePathSegmentEnd", nodeType: "BinaryExpression" },
{ ruleNum: 1, event: "onCodePathSegmentStart", nodeType: "BinaryExpression" },
{ ruleNum: 2, event: "onCodePathSegmentStart", nodeType: "BinaryExpression" },
{ ruleNum: 1, event: "onCodePathSegmentEnd", nodeType: "UpdateExpression" },
{ ruleNum: 2, event: "onCodePathSegmentEnd", nodeType: "UpdateExpression" },
{ ruleNum: 1, event: "onCodePathSegmentStart", nodeType: "UpdateExpression" },
{ ruleNum: 2, event: "onCodePathSegmentStart", nodeType: "UpdateExpression" },
{ ruleNum: 1, event: "onCodePathSegmentLoop", nodeType: "BlockStatement" },
{ ruleNum: 2, event: "onCodePathSegmentLoop", nodeType: "BlockStatement" },
{ ruleNum: 1, event: "onCodePathSegmentEnd", nodeType: "BlockStatement" },
{ ruleNum: 2, event: "onCodePathSegmentEnd", nodeType: "BlockStatement" },
{ ruleNum: 1, event: "onCodePathSegmentStart", nodeType: "BlockStatement" },
{ ruleNum: 2, event: "onCodePathSegmentStart", nodeType: "BlockStatement" },
{ ruleNum: 1, event: "onCodePathSegmentEnd", nodeType: "BlockStatement" },
{ ruleNum: 2, event: "onCodePathSegmentEnd", nodeType: "BlockStatement" },
{ ruleNum: 1, event: "onUnreachableCodePathSegmentStart", nodeType: "BlockStatement" },
{ ruleNum: 2, event: "onUnreachableCodePathSegmentStart", nodeType: "BlockStatement" },
{ ruleNum: 1, event: "onUnreachableCodePathSegmentEnd", nodeType: "ForStatement" },
{ ruleNum: 2, event: "onUnreachableCodePathSegmentEnd", nodeType: "ForStatement" },
{ ruleNum: 1, event: "onCodePathSegmentStart", nodeType: "ForStatement" },
{ ruleNum: 2, event: "onCodePathSegmentStart", nodeType: "ForStatement" },
{ ruleNum: 1, event: "onCodePathSegmentEnd", nodeType: "Program" },
{ ruleNum: 2, event: "onCodePathSegmentEnd", nodeType: "Program" },
{ ruleNum: 1, event: "onCodePathEnd", nodeType: "Program" },
{ ruleNum: 2, event: "onCodePathEnd", nodeType: "Program" },
{ ruleNum: 1, event: "after" },
{ ruleNum: 2, event: "after" },
];

if (
visits.length !== expectedVisits.length ||
visits.some(
(v, i) =>
v.ruleNum !== expectedVisits[i].ruleNum ||
v.event !== expectedVisits[i].event ||
v.nodeType !== expectedVisits[i].nodeType,
)
) {
context.report({
message: `Unexpected visits:\n${JSON.stringify(visits, null, 2)}`,
node: SPAN,
});
}
},
} as unknown as Visitor; // TODO: Our types don't include CFG event handlers at present
},
};
}

const createOnceCfgRule = createCfgRule(1);
const createOnceCfgRule2 = createCfgRule(2);

// Tests that `before` hook returning `false` disables visiting AST for the file.
const createOnceBeforeFalseRule: Rule = {
Expand Down Expand Up @@ -385,6 +420,7 @@ export default eslintCompatPlugin({
"create-once": createOnceRule,
"create-once-selector": createOnceSelectorRule,
"create-once-cfg": createOnceCfgRule,
"create-once-cfg2": createOnceCfgRule2,
"create-once-before-false": createOnceBeforeFalseRule,
"create-once-before-only": createOnceBeforeOnlyRule,
"create-once-after-only": createOnceAfterOnlyRule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"jsPlugins": ["./plugin.ts"],
"categories": { "correctness": "off" },
"rules": {
"eslint-compat-plugin/tracking": "error",
"eslint-compat-plugin/throw-in-after": "error",
"eslint-compat-plugin/tracking-late": "error"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import plugin from "./plugin.ts";

export default [
{
files: ["files/*.js"],
plugins: {
"eslint-compat-plugin": plugin,
},
rules: {
"eslint-compat-plugin/tracking": "error",
"eslint-compat-plugin/throw-in-after": "error",
"eslint-compat-plugin/tracking-late": "error",
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Exit code
2

# stdout
```
```

# stderr
```
filename: <fixture>/files/1.js
events:
[
"before: tracking",
"before: throw-in-after",
"before: tracking-late",
"Identifier: tracking",
"Identifier: throw-in-after",
"Identifier: tracking-late",
"Program:exit: tracking",
"Program:exit: throw-in-after",
"Program:exit: tracking-late",
"onCodePathEnd: tracking",
"onCodePathEnd: throw-in-after",
"onCodePathEnd: tracking-late",
"after: tracking",
"after: throw-in-after",
"after: tracking-late"
]

Oops! Something went wrong! :(

ESLint: 9.39.4

Error: `after` hook threw
Occurred while linting <fixture>/files/1.js
Rule: "eslint-compat-plugin/tracking-late"
at after (<fixture>/plugin.ts:51:15)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let x;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eslint": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Exit code
1

# stdout
```
x Error running JS plugin.
| File path: <fixture>/files/1.js
| Error: `after` hook threw
| at after (<fixture>/plugin.ts:51:15)

Found 0 warnings and 1 error.
Finished in Xms on 1 file with 3 rules using X threads.
```

# stderr
```
filename: <fixture>/files/1.js
events:
[
"before: tracking",
"before: throw-in-after",
"before: tracking-late",
"Identifier: tracking",
"Identifier: throw-in-after",
"Identifier: tracking-late",
"Program:exit: tracking",
"Program:exit: throw-in-after",
"Program:exit: tracking-late",
"onCodePathEnd: tracking",
"onCodePathEnd: throw-in-after",
"onCodePathEnd: tracking-late",
"after: tracking",
"after: throw-in-after",
"after: tracking-late"
]
```
Loading
Loading