diff --git a/apps/oxlint/conformance/snapshot.md b/apps/oxlint/conformance/snapshot.md index 4ea74194106e5..c39be979a5ecc 100644 --- a/apps/oxlint/conformance/snapshot.md +++ b/apps/oxlint/conformance/snapshot.md @@ -7,8 +7,8 @@ | Status | Count | % | | ----------------- | ----- | ------ | | Total rules | 292 | 100.0% | -| Fully passing | 279 | 95.5% | -| Partially passing | 13 | 4.5% | +| Fully passing | 280 | 95.9% | +| Partially passing | 12 | 4.1% | | Fully failing | 0 | 0.0% | | Load errors | 0 | 0.0% | | No tests run | 0 | 0.0% | @@ -18,8 +18,8 @@ | Status | Count | % | | ----------- | ----- | ------ | | Total tests | 33090 | 100.0% | -| Passing | 32761 | 99.0% | -| Failing | 58 | 0.2% | +| Passing | 32769 | 99.0% | +| Failing | 50 | 0.2% | | Skipped | 271 | 0.8% | ## Fully Passing Rules @@ -232,6 +232,7 @@ - `no-unsafe-optional-chaining` (187 tests) - `no-unused-labels` (26 tests) - `no-unused-private-class-members` (39 tests) +- `no-unused-vars` (436 tests) (21 skipped) - `no-useless-assignment` (85 tests) (2 skipped) - `no-useless-backreference` (190 tests) (1 skipped) - `no-useless-call` (44 tests) @@ -314,7 +315,6 @@ - `no-multiple-empty-lines` - 45 / 46 (97.8%) - `no-redeclare` - 68 / 75 (90.7%) - `no-unused-expressions` - 120 / 124 (96.8%) -- `no-unused-vars` - 428 / 436 (98.2%) - `no-use-before-define` - 346 / 347 (99.7%) - `prefer-object-spread` - 86 / 87 (98.9%) - `strict` - 110 / 126 (87.3%) @@ -3696,359 +3696,6 @@ AssertionError [ERR_ASSERTION]: Should have 2 errors but had 0: [] at apps/oxlint/dist/index.js -### `no-unused-vars` - -Pass: 407 / 436 (93.3%) -Fail: 8 / 436 (1.8%) -Skip: 21 / 436 (4.8%) - -#### no-unused-vars > valid - -```js -try {} catch ([firstError]) {} -``` - -```json -{ - "options": [ - { - "destructuredArrayIgnorePattern": "Error$" - } - ], - "languageOptions": { - "ecmaVersion": 2015 - } -} -``` - -AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [ - { - ruleId: 'rule-to-test/no-unused-vars', - message: "'firstError' is defined but never used.", - messageId: 'unusedVar', - severity: 1, - nodeType: 'Identifier', - line: 1, - column: 15, - endLine: 1, - endColumn: 25, - suggestions: null - } -] - -1 !== 0 - - at assertErrorCountIsCorrect (apps/oxlint/dist/index.js) - at assertValidTestCasePasses (apps/oxlint/dist/index.js) - at runValidTestCase (apps/oxlint/dist/index.js) - at apps/oxlint/dist/index.js - - -#### no-unused-vars > valid - -```js -try {} catch ({ message, stack }) {} -``` - -```json -{ - "options": [ - { - "caughtErrorsIgnorePattern": "message|stack" - } - ], - "languageOptions": { - "ecmaVersion": 2015 - } -} -``` - -AssertionError [ERR_ASSERTION]: Should have no errors but had 2: [ - { - ruleId: 'rule-to-test/no-unused-vars', - message: "'message' is defined but never used. Allowed unused caught errors must match /message|stack/u.", - messageId: 'unusedVar', - severity: 1, - nodeType: 'Identifier', - line: 1, - column: 16, - endLine: 1, - endColumn: 23, - suggestions: null - }, - { - ruleId: 'rule-to-test/no-unused-vars', - message: "'stack' is defined but never used. Allowed unused caught errors must match /message|stack/u.", - messageId: 'unusedVar', - severity: 1, - nodeType: 'Identifier', - line: 1, - column: 25, - endLine: 1, - endColumn: 30, - suggestions: null - } -] - -2 !== 0 - - at assertErrorCountIsCorrect (apps/oxlint/dist/index.js) - at assertValidTestCasePasses (apps/oxlint/dist/index.js) - at runValidTestCase (apps/oxlint/dist/index.js) - at apps/oxlint/dist/index.js - - -#### no-unused-vars > valid - -```js -try {} catch ({ errors: [firstError] }) {} -``` - -```json -{ - "options": [ - { - "caughtErrorsIgnorePattern": "Error$" - } - ], - "languageOptions": { - "ecmaVersion": 2015 - } -} -``` - -AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [ - { - ruleId: 'rule-to-test/no-unused-vars', - message: "'firstError' is defined but never used. Allowed unused caught errors must match /Error$/u.", - messageId: 'unusedVar', - severity: 1, - nodeType: 'Identifier', - line: 1, - column: 25, - endLine: 1, - endColumn: 35, - suggestions: null - } -] - -1 !== 0 - - at assertErrorCountIsCorrect (apps/oxlint/dist/index.js) - at assertValidTestCasePasses (apps/oxlint/dist/index.js) - at runValidTestCase (apps/oxlint/dist/index.js) - at apps/oxlint/dist/index.js - - -#### no-unused-vars > valid - -```js -try {} catch ({ foo, ...bar }) { console.log(bar); } -``` - -```json -{ - "options": [ - { - "ignoreRestSiblings": true - } - ], - "languageOptions": { - "ecmaVersion": 2018 - } -} -``` - -AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [ - { - ruleId: 'rule-to-test/no-unused-vars', - message: "'foo' is defined but never used.", - messageId: 'unusedVar', - severity: 1, - nodeType: 'Identifier', - line: 1, - column: 16, - endLine: 1, - endColumn: 19, - suggestions: null - } -] - -1 !== 0 - - at assertErrorCountIsCorrect (apps/oxlint/dist/index.js) - at assertValidTestCasePasses (apps/oxlint/dist/index.js) - at runValidTestCase (apps/oxlint/dist/index.js) - at apps/oxlint/dist/index.js - - -#### no-unused-vars > invalid - -```js -try {} catch ({ message }) { console.error(message); } -``` - -```json -{ - "options": [ - { - "caughtErrorsIgnorePattern": "message", - "reportUsedIgnorePattern": true - } - ], - "languageOptions": { - "ecmaVersion": 2015 - }, - "errors": [ - { - "messageId": "usedIgnoredVar", - "data": { - "varName": "message", - "additional": ". Used caught errors must not match /message/u" - } - } - ] -} -``` - -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-unused-vars > invalid - -```js -try {} catch ([_a, _b]) { doSomething(_a, _b); } -``` - -```json -{ - "options": [ - { - "caughtErrorsIgnorePattern": "^_", - "reportUsedIgnorePattern": true - } - ], - "languageOptions": { - "ecmaVersion": 6 - }, - "errors": [ - { - "messageId": "usedIgnoredVar", - "data": { - "varName": "_a", - "additional": ". Used caught errors must not match /^_/u" - } - }, - { - "messageId": "usedIgnoredVar", - "data": { - "varName": "_b", - "additional": ". Used caught errors must not match /^_/u" - } - } - ] -} -``` - -AssertionError [ERR_ASSERTION]: Should have 2 errors but had 0: [] - -0 !== 2 - - 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-unused-vars > invalid - -```js -try {} catch ([_a, _b]) { doSomething(_a, _b); } -``` - -```json -{ - "options": [ - { - "destructuredArrayIgnorePattern": "^_", - "reportUsedIgnorePattern": true - } - ], - "languageOptions": { - "ecmaVersion": 6 - }, - "errors": [ - { - "messageId": "usedIgnoredVar", - "data": { - "varName": "_a", - "additional": ". Used elements of array destructuring must not match /^_/u" - } - }, - { - "messageId": "usedIgnoredVar", - "data": { - "varName": "_b", - "additional": ". Used elements of array destructuring must not match /^_/u" - } - } - ] -} -``` - -AssertionError [ERR_ASSERTION]: Should have 2 errors but had 0: [] - -0 !== 2 - - 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-unused-vars > invalid - -```js -try {} catch ({ stack: $ }) { $ = 'Something broke: ' + $; } -``` - -```json -{ - "options": [ - { - "caughtErrorsIgnorePattern": "\\w" - } - ], - "languageOptions": { - "ecmaVersion": 2015 - }, - "errors": [ - { - "message": "'$' is assigned a value but never used. Allowed unused caught errors must match /\\w/u.", - "column": 31, - "endColumn": 32 - } - ] -} -``` - -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-use-before-define` Pass: 346 / 347 (99.7%) diff --git a/apps/oxlint/src-js/plugins/scope.ts b/apps/oxlint/src-js/plugins/scope.ts index 1d328b186e8f8..ccf5fcf9bbc7f 100644 --- a/apps/oxlint/src-js/plugins/scope.ts +++ b/apps/oxlint/src-js/plugins/scope.ts @@ -141,10 +141,50 @@ function initTsScopeManager() { // @ts-expect-error - TODO: Our types don't quite align yet tsScopeManager = analyze(ast, analyzeOptions); + fixCatchClauseDefinitions(); + // Add globals from configuration and resolve references addGlobals(); } +/** + * Fix `CatchClause` definitions to match `eslint-scope` behavior. + * + * TS-ESLint's scope manager has a bug where for destructuring patterns in `CatchClause`s + * (e.g., `catch ([a, b])` or `catch ({ message })`), the definition's `name` property is set to + * the entire pattern (`ArrayPattern` or `ObjectPattern`) instead of the individual `Identifier`. + * + * Correct this bug by setting `def.name` to the `Identifier`. + * + * @see https://github.com/typescript-eslint/typescript-eslint/issues/11981 + */ +function fixCatchClauseDefinitions(): void { + debugAssertIsNonNull(tsScopeManager); + + const { scopes } = tsScopeManager; + for (let scopeIndex = 0; scopeIndex < scopes.length; scopeIndex++) { + const scope = scopes[scopeIndex]; + if (scope.type !== "catch") continue; + + const { param } = scope.block; + if (param === null || param.type === "Identifier") continue; + + // `CatchClause` scope with an `ObjectPattern` or `ArrayPattern` parameter - fix it + const { variables } = scope; + for (let varIndex = 0; varIndex < variables.length; varIndex++) { + const variable = variables[varIndex]; + + // Variables defined in a catch clause are block-scoped, therefore can only have a single definition. + // If there were more, parser would have errored already. + debugAssert(variable.defs.length === 1, "Expected `defs.length === 1`"); + debugAssert(variable.identifiers.length === 1, "Expected `identifiers.length === 1`"); + debugAssert(variable.defs[0].name === param, "Expected `def.name === param`"); + + (variable.defs[0] as Writable<(typeof variable.defs)[number]>).name = variable.identifiers[0]; + } + } +} + /** * Add global variables from configuration and resolve references to them. *