diff --git a/apps/oxlint/conformance/snapshot.md b/apps/oxlint/conformance/snapshot.md index 08b5873d3bc58..45349e0dc41c7 100644 --- a/apps/oxlint/conformance/snapshot.md +++ b/apps/oxlint/conformance/snapshot.md @@ -7,8 +7,8 @@ | Status | Count | % | | ----------------- | ----- | ------ | | Total rules | 277 | 100.0% | -| Fully passing | 219 | 79.1% | -| Partially passing | 58 | 20.9% | +| Fully passing | 220 | 79.4% | +| Partially passing | 57 | 20.6% | | Fully failing | 0 | 0.0% | | Load errors | 0 | 0.0% | | No tests run | 0 | 0.0% | @@ -18,8 +18,8 @@ | Status | Count | % | | ----------- | ----- | ------ | | Total tests | 29765 | 100.0% | -| Passing | 28684 | 96.4% | -| Failing | 974 | 3.3% | +| Passing | 28686 | 96.4% | +| Failing | 972 | 3.3% | | Skipped | 107 | 0.4% | ## Fully Passing Rules @@ -172,6 +172,7 @@ - `no-sequences` (42 tests) - `no-spaced-func` (28 tests) - `no-sparse-arrays` (6 tests) +- `no-sync` (10 tests) - `no-tabs` (11 tests) - `no-template-curly-in-string` (22 tests) - `no-ternary` (4 tests) @@ -282,7 +283,6 @@ - `no-setter-return` - 161 / 164 (98.2%) - `no-shadow-restricted-names` - 43 / 44 (97.7%) - `no-shadow` - 300 / 308 (97.4%) -- `no-sync` - 8 / 10 (80.0%) - `no-trailing-spaces` - 53 / 54 (98.1%) - `no-undef` - 54 / 94 (57.4%) - `no-undefined` - 37 / 53 (69.8%) @@ -81358,80 +81358,6 @@ AssertionError [ERR_ASSERTION]: Should have 1 error but had 0: [] at apps/oxlint/dist/index.js -### `no-sync` - -Pass: 8 / 10 (80.0%) -Fail: 2 / 10 (20.0%) -Skip: 0 / 10 (0.0%) - -#### no-sync > invalid - -```js -function someFunction() {fs.fooSync();} -``` - -```json -{ - "options": [ - { - "allowAtRootLevel": true - } - ], - "errors": [ - { - "messageId": "noSync", - "data": { - "propertyName": "fooSync" - } - } - ] -} -``` - -AssertionError [ERR_ASSERTION]: Should have 1 error but had 0: [] - -0 !== 1 - - at assertErrorCountIsCorrect (apps/oxlint/dist/index.js) - at assertInvalidTestCasePasses (apps/oxlint/dist/index.js) - at runInvalidTestCase (apps/oxlint/dist/index.js) - at apps/oxlint/dist/index.js - - -#### no-sync > invalid - -```js -var a = function someFunction() {fs.fooSync();} -``` - -```json -{ - "options": [ - { - "allowAtRootLevel": true - } - ], - "errors": [ - { - "messageId": "noSync", - "data": { - "propertyName": "fooSync" - } - } - ] -} -``` - -AssertionError [ERR_ASSERTION]: Should have 1 error but had 0: [] - -0 !== 1 - - at assertErrorCountIsCorrect (apps/oxlint/dist/index.js) - at assertInvalidTestCasePasses (apps/oxlint/dist/index.js) - at runInvalidTestCase (apps/oxlint/dist/index.js) - at apps/oxlint/dist/index.js - - ### `no-trailing-spaces` Pass: 53 / 54 (98.1%) diff --git a/apps/oxlint/src-js/plugins/selector.ts b/apps/oxlint/src-js/plugins/selector.ts index 9c74efc589b23..4e1de27361f44 100644 --- a/apps/oxlint/src-js/plugins/selector.ts +++ b/apps/oxlint/src-js/plugins/selector.ts @@ -17,10 +17,35 @@ type NodeTypeId = number; const ESQUERY_OPTIONS: ESQueryOptions = { nodeTypeKey: "type", visitorKeys, - fallback: (node: EsqueryNode) => Object.keys(node).filter(filterKey), - matchClass: (_className: unknown, _node: EsqueryNode, _ancestors: EsqueryNode[]) => false, // TODO: Is this right? + fallback(node: EsqueryNode) { + // Our visitor keys should cover all AST node types + throw new Error(`Unknown node type: ${node.type}`); + }, + matchClass: matchesSelectorClass, }; -const filterKey = (key: string) => key !== "parent" && key !== "range" && key !== "loc"; + +/** + * Check if an AST node matches a selector class. + * @param className - Class name parsed from selector + * @param node - AST node + * @param _ancestors - AST node's ancestors + * @returns `true` if node matches class + */ +function matchesSelectorClass( + className: string, + node: EsqueryNode, + _ancestors: EsqueryNode[], +): boolean { + if (className.toLowerCase() === "function") { + const { type } = node; + return ( + type === "FunctionDeclaration" || + type === "FunctionExpression" || + type === "ArrowFunctionExpression" + ); + } + return false; +} // Specificity is a combination of: // @@ -230,7 +255,7 @@ function analyzeSelector( // TODO: Should TS function types be included in `FUNCTION_NODE_TYPE_IDS`? // This TODO comment is from ESLint's implementation. Not sure what it means! // TODO: Abstract into JSLanguage somehow. - if (esquerySelector.name === "function") return FUNCTION_NODE_TYPE_IDS; + if (esquerySelector.name.toLowerCase() === "function") return FUNCTION_NODE_TYPE_IDS; selector.isComplex = true; return null; diff --git a/apps/oxlint/test/compile_visitor.test.ts b/apps/oxlint/test/compile_visitor.test.ts index bd3beb57b71fd..f3f7932f7915c 100644 --- a/apps/oxlint/test/compile_visitor.test.ts +++ b/apps/oxlint/test/compile_visitor.test.ts @@ -578,7 +578,8 @@ describe("compile visitor", () => { const exit2 = vi.fn(() => {}); addVisitorToCompiled({ ":function": enter2, - ":function:exit": exit2, + // Check case insensitivity + ":FunCtioN:exit": exit2, }); expect(finalizeCompiledVisitor()).toBe(true); diff --git a/apps/oxlint/test/fixtures/selector/output.snap.md b/apps/oxlint/test/fixtures/selector/output.snap.md index 07310e4ff0993..0af53996de188 100644 --- a/apps/oxlint/test/fixtures/selector/output.snap.md +++ b/apps/oxlint/test/fixtures/selector/output.snap.md @@ -68,7 +68,7 @@ | *:exit: VariableDeclarator | *:exit: VariableDeclaration | *: FunctionDeclaration(foo) - | :function: FunctionDeclaration(foo) + | :FUNCTION: FunctionDeclaration(foo) | :not(Identifier): FunctionDeclaration(foo) | :matches(Identifier, FunctionDeclaration): FunctionDeclaration(foo) | Program > FunctionDeclaration: FunctionDeclaration(foo) @@ -77,6 +77,7 @@ | :matches(ObjectExpression > SpreadElement, FunctionDeclaration): FunctionDeclaration(foo) | :matches(Identifier[name=a], FunctionDeclaration[id.name=foo]): FunctionDeclaration(foo) | *: Identifier(foo) + | :function > Identifier: Identifier(foo) | Identifier: Identifier(foo) | :matches(Identifier, FunctionDeclaration): Identifier(foo) | *:exit: Identifier(foo) @@ -85,7 +86,7 @@ | *:exit: BlockStatement | *:exit: FunctionDeclaration(foo) | *: FunctionDeclaration(bar) - | :function: FunctionDeclaration(bar) + | :FUNCTION: FunctionDeclaration(bar) | :not(Identifier): FunctionDeclaration(bar) | :matches(Identifier, FunctionDeclaration): FunctionDeclaration(bar) | Program > FunctionDeclaration: FunctionDeclaration(bar) @@ -93,6 +94,7 @@ | :matches(ObjectExpression > SpreadElement, FunctionDeclaration): FunctionDeclaration(bar) | :matches(ObjectExpression > SpreadElement, FunctionDeclaration[id.name=bar]): FunctionDeclaration(bar) | *: Identifier(bar) + | :function > Identifier: Identifier(bar) | Identifier: Identifier(bar) | :matches(Identifier, FunctionDeclaration): Identifier(bar) | *:exit: Identifier(bar) @@ -103,7 +105,7 @@ | *: ExpressionStatement | :not(Identifier): ExpressionStatement | *: ArrowFunctionExpression - | :function: ArrowFunctionExpression + | :FUNCTION: ArrowFunctionExpression | :not(Identifier): ArrowFunctionExpression | *: BlockStatement | :not(Identifier): BlockStatement diff --git a/apps/oxlint/test/fixtures/selector/plugin.ts b/apps/oxlint/test/fixtures/selector/plugin.ts index aef9474c81b9b..6745afe73f594 100644 --- a/apps/oxlint/test/fixtures/selector/plugin.ts +++ b/apps/oxlint/test/fixtures/selector/plugin.ts @@ -21,7 +21,8 @@ const plugin: Plugin = { // :not ":not(Identifier)", // class - ":function", + ":FUNCTION", + ":function > Identifier", // Child "Property > Identifier", "ObjectExpression > Identifier", // does not match