From 6ab06c56d6a2c213d054581e8ea1820a067967e1 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 9 Nov 2021 09:49:41 +0800 Subject: [PATCH 1/6] `prefer-export-from`: Add `ignoreUsedVariables` option --- docs/rules/prefer-export-from.md | 38 ++++ rules/prefer-export-from.js | 107 ++++++---- test/prefer-export-from.mjs | 105 +++++++++ test/snapshots/prefer-export-from.mjs.md | 236 +++++++++++++++++++++ test/snapshots/prefer-export-from.mjs.snap | Bin 2625 -> 2984 bytes 5 files changed, 447 insertions(+), 39 deletions(-) diff --git a/docs/rules/prefer-export-from.md b/docs/rules/prefer-export-from.md index ec2d8b9959..ad126639bb 100644 --- a/docs/rules/prefer-export-from.md +++ b/docs/rules/prefer-export-from.md @@ -61,3 +61,41 @@ export { import * as namespace from './foo.js'; export default namespace; ``` + +## Options + +### ignoreUsedVariables + +Type: `boolean`\ +Default: `false` + +When pass `"ignoreUsedVariables": true`, if any variable is used more than just re-export or not exported, all variables in the import declaration will be ignored. + +#### Fail + +```js +// eslint unicorn/import-index: ["error", {"ignoreUsedVariables": false}] +import {named1, named2} from './foo.js'; + +use(named1); + +export {named1, named2}; +``` + +#### Pass + +```js +// eslint unicorn/import-index: ["error", {"ignoreUsedVariables": true}] +import {named1, named2} from './foo.js'; + +use(named1); + +export {named1, named2}; +``` + +```js +// eslint unicorn/import-index: ["error", {"ignoreUsedVariables": true}] +import {named1, named2} from './foo.js'; + +export {named1}; +``` diff --git a/rules/prefer-export-from.js b/rules/prefer-export-from.js index 028ff61281..14f5f65a52 100644 --- a/rules/prefer-export-from.js +++ b/rules/prefer-export-from.js @@ -193,28 +193,19 @@ function isVariableUnused(node, context) { && references[0].identifier === node.id; } -function * getProblems({ - context, - variable, - program, - exportDeclarations, -}) { - const {identifiers, references} = variable; - - if (identifiers.length !== 1 || references.length === 0) { - return; - } - - const specifier = identifiers[0].parent; - - const imported = { +function getImported(variable) { + const specifier = variable.identifiers[0].parent; + return { name: getImportedName(specifier), node: specifier, declaration: specifier.parent, variable, }; +} - for (const {identifier} of references) { +function getExports(imported, context) { + const exports = []; + for (const {identifier} of imported.variable.references) { const exported = getExported(identifier, context); if (!exported) { @@ -233,44 +224,81 @@ function * getProblems({ continue; } - yield { - node: exported.node, - messageId: MESSAGE_ID, - data: { - exported: exported.name, - }, - fix: fix({ - context, - imported, - exported, - exportDeclarations, - program, - }), - }; + exports.push(exported); } + + return exports; } +const schema = [ + { + type: 'object', + additionalProperties: false, + properties: { + ignoreUsedVariables: { + type: 'boolean', + default: false, + }, + }, + }, +]; + /** @param {import('eslint').Rule.RuleContext} context */ function create(context) { - const variables = []; + const {ignoreUsedVariables} = {ignoreUsedVariables: false, ...context.options[0]}; + const importDeclarations = new Set(); const exportDeclarations = []; return { 'ImportDeclaration[specifiers.length>0]'(node) { - variables.push(...context.getDeclaredVariables(node)); + importDeclarations.add(node); }, // `ExportAllDeclaration` and `ExportDefaultDeclaration` can't be reused 'ExportNamedDeclaration[source.type="Literal"]'(node) { exportDeclarations.push(node); }, * 'Program:exit'(program) { - for (const variable of variables) { - yield * getProblems({ - context, - variable, - exportDeclarations, - program, - }); + for (const importDeclaration of importDeclarations) { + const variables = context.getDeclaredVariables(importDeclaration) + .map(variable => { + const imported = getImported(variable); + const exports = getExports(imported, context); + + return { + variable, + imported, + exports, + }; + }); + + if ( + ignoreUsedVariables + && variables.some(({variable, exports}) => { + const {references} = variable; + return references.length === 0 || references.length !== exports.length; + }) + ) { + continue; + } + + for (const {imported, exports} of variables) { + for (const exported of exports) { + yield { + node: exported.node, + messageId: MESSAGE_ID, + data: { + exported: exported.name, + }, + fix: fix({ + context, + imported, + exported, + exportDeclarations, + program, + }), + }; + } + } } }, }; @@ -284,6 +312,7 @@ module.exports = { description: 'Prefer `export…from` when re-exporting.', }, fixable: 'code', + schema, messages, }, }; diff --git a/test/prefer-export-from.mjs b/test/prefer-export-from.mjs index f9a552f3f1..4a54479dd5 100644 --- a/test/prefer-export-from.mjs +++ b/test/prefer-export-from.mjs @@ -289,3 +289,108 @@ test.typescript({ ], invalid: [], }); + +// `ignoreUsedVariables` +test.snapshot({ + valid: [ + outdent` + import defaultExport from 'foo'; + use(defaultExport); + export default defaultExport; + `, + outdent` + import defaultExport from 'foo'; + use(defaultExport); + export {defaultExport}; + `, + outdent` + import {named} from 'foo'; + use(named); + export {named}; + `, + outdent` + import {named} from 'foo'; + use(named); + export default named; + `, + outdent` + import * as namespace from 'foo'; + use(namespace); + export {namespace}; + `, + outdent` + import * as namespace from 'foo'; + use(namespace); + export default namespace; + `, + outdent` + import * as namespace from 'foo'; + export {namespace as default}; + export {namespace as named}; + `, + outdent` + import * as namespace from 'foo'; + export default namespace; + export {namespace as named}; + `, + outdent` + import defaultExport, {named} from 'foo'; + use(defaultExport); + export {named}; + `, + outdent` + import defaultExport, {named} from 'foo'; + use(named); + export {defaultExport}; + `, + outdent` + import {named1, named2} from 'foo'; + use(named1); + export {named2}; + `, + outdent` + import defaultExport, {named1, named2} from 'foo'; + use(defaultExport); + export {named1, named2}; + `, + outdent` + import defaultExport, {named1, named2} from 'foo'; + use(named1); + export {defaultExport, named2}; + `, + outdent` + import {notUsedNotExported, exported} from 'foo'; + export {exported}; + `, + ].map(code => ({code, options: [{ignoreUsedVariables: true}]})), + invalid: [ + outdent` + import defaultExport from 'foo'; + export {defaultExport as default}; + export {defaultExport as named}; + `, + outdent` + import {named} from 'foo'; + export {named as default}; + export {named as named}; + `, + outdent` + import {named} from 'foo'; + export default named; + export {named as named}; + `, + outdent` + import defaultExport, {named} from 'foo'; + export default defaultExport; + export {named}; + `, + outdent` + import defaultExport, {named} from 'foo'; + export {defaultExport as default, named}; + `, + outdent` + import defaultExport from 'foo'; + export const variable = defaultExport; + `, + ].map(code => ({code, options: [{ignoreUsedVariables: true}]})), +}); diff --git a/test/snapshots/prefer-export-from.mjs.md b/test/snapshots/prefer-export-from.mjs.md index af4c102aae..1b664d3f83 100644 --- a/test/snapshots/prefer-export-from.mjs.md +++ b/test/snapshots/prefer-export-from.mjs.md @@ -1081,3 +1081,239 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^ Use \`export…from\` to re-export \`namespace\`.␊ 3 | export default namespace;␊ ` + +## Invalid #1 + 1 | import defaultExport from 'foo'; + 2 | export {defaultExport as default}; + 3 | export {defaultExport as named}; + +> Options + + `␊ + [␊ + {␊ + "ignoreUsedVariables": true␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 |␊ + 2 |␊ + 3 |␊ + 4 | export {default, default as named} from 'foo';␊ + ` + +> Error 1/2 + + `␊ + 1 | import defaultExport from 'foo';␊ + > 2 | export {defaultExport as default};␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`default\`.␊ + 3 | export {defaultExport as named};␊ + ` + +> Error 2/2 + + `␊ + 1 | import defaultExport from 'foo';␊ + 2 | export {defaultExport as default};␊ + > 3 | export {defaultExport as named};␊ + | ^^^^^^^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`named\`.␊ + ` + +## Invalid #2 + 1 | import {named} from 'foo'; + 2 | export {named as default}; + 3 | export {named as named}; + +> Options + + `␊ + [␊ + {␊ + "ignoreUsedVariables": true␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 |␊ + 2 |␊ + 3 |␊ + 4 | export {named as default, named} from 'foo';␊ + ` + +> Error 1/2 + + `␊ + 1 | import {named} from 'foo';␊ + > 2 | export {named as default};␊ + | ^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`default\`.␊ + 3 | export {named as named};␊ + ` + +> Error 2/2 + + `␊ + 1 | import {named} from 'foo';␊ + 2 | export {named as default};␊ + > 3 | export {named as named};␊ + | ^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`named\`.␊ + ` + +## Invalid #3 + 1 | import {named} from 'foo'; + 2 | export default named; + 3 | export {named as named}; + +> Options + + `␊ + [␊ + {␊ + "ignoreUsedVariables": true␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 |␊ + 2 |␊ + 3 |␊ + 4 | export {named as default, named} from 'foo';␊ + ` + +> Error 1/2 + + `␊ + 1 | import {named} from 'foo';␊ + > 2 | export default named;␊ + | ^^^^^^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`default\`.␊ + 3 | export {named as named};␊ + ` + +> Error 2/2 + + `␊ + 1 | import {named} from 'foo';␊ + 2 | export default named;␊ + > 3 | export {named as named};␊ + | ^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`named\`.␊ + ` + +## Invalid #4 + 1 | import defaultExport, {named} from 'foo'; + 2 | export default defaultExport; + 3 | export {named}; + +> Options + + `␊ + [␊ + {␊ + "ignoreUsedVariables": true␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 |␊ + 2 |␊ + 3 |␊ + 4 | export {default, named} from 'foo';␊ + ` + +> Error 1/2 + + `␊ + 1 | import defaultExport, {named} from 'foo';␊ + > 2 | export default defaultExport;␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`default\`.␊ + 3 | export {named};␊ + ` + +> Error 2/2 + + `␊ + 1 | import defaultExport, {named} from 'foo';␊ + 2 | export default defaultExport;␊ + > 3 | export {named};␊ + | ^^^^^ Use \`export…from\` to re-export \`named\`.␊ + ` + +## Invalid #5 + 1 | import defaultExport, {named} from 'foo'; + 2 | export {defaultExport as default, named}; + +> Options + + `␊ + [␊ + {␊ + "ignoreUsedVariables": true␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 |␊ + 2 |␊ + 3 | export {default, named} from 'foo';␊ + ` + +> Error 1/2 + + `␊ + 1 | import defaultExport, {named} from 'foo';␊ + > 2 | export {defaultExport as default, named};␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`default\`.␊ + ` + +> Error 2/2 + + `␊ + 1 | import defaultExport, {named} from 'foo';␊ + > 2 | export {defaultExport as default, named};␊ + | ^^^^^ Use \`export…from\` to re-export \`named\`.␊ + ` + +## Invalid #6 + 1 | import defaultExport from 'foo'; + 2 | export const variable = defaultExport; + +> Options + + `␊ + [␊ + {␊ + "ignoreUsedVariables": true␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 |␊ + 2 |␊ + 3 | export {default as variable} from 'foo';␊ + ` + +> Error 1/1 + + `␊ + 1 | import defaultExport from 'foo';␊ + > 2 | export const variable = defaultExport;␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`variable\`.␊ + ` diff --git a/test/snapshots/prefer-export-from.mjs.snap b/test/snapshots/prefer-export-from.mjs.snap index 61201efcd9ac4f14814cff9635dd5439226cfdda..4ed007bf229f5c036550af3c50be8a7c3458098d 100644 GIT binary patch literal 2984 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 e932|+bWKmcHZ9fHRTBekZ~q5L1ou)v=H literal 2625 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 From a58be750ceffd0528db2140d45a5e563f6b8bb13 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 10 Nov 2021 18:07:19 +0800 Subject: [PATCH 2/6] Use suggestion api when unused --- docs/rules/prefer-export-from.md | 9 +--- rules/prefer-export-from.js | 48 ++++++++++++++------- test/prefer-export-from.mjs | 8 ++-- test/snapshots/prefer-export-from.mjs.md | 28 ++++++++++++ test/snapshots/prefer-export-from.mjs.snap | Bin 2984 -> 3086 bytes 5 files changed, 65 insertions(+), 28 deletions(-) diff --git a/docs/rules/prefer-export-from.md b/docs/rules/prefer-export-from.md index ad126639bb..4578cc6845 100644 --- a/docs/rules/prefer-export-from.md +++ b/docs/rules/prefer-export-from.md @@ -69,7 +69,7 @@ export default namespace; Type: `boolean`\ Default: `false` -When pass `"ignoreUsedVariables": true`, if any variable is used more than just re-export or not exported, all variables in the import declaration will be ignored. +When `true`, if an import is used in other places than just re-export, all variables in the import declaration will be ignored. #### Fail @@ -92,10 +92,3 @@ use(named1); export {named1, named2}; ``` - -```js -// eslint unicorn/import-index: ["error", {"ignoreUsedVariables": true}] -import {named1, named2} from './foo.js'; - -export {named1}; -``` diff --git a/rules/prefer-export-from.js b/rules/prefer-export-from.js index 14f5f65a52..347d40f95d 100644 --- a/rules/prefer-export-from.js +++ b/rules/prefer-export-from.js @@ -5,9 +5,11 @@ const { isClosingBraceToken, } = require('eslint-utils'); -const MESSAGE_ID = 'prefer-export-from'; +const MESSAGE_ID_ERROR = 'error'; +const MESSAGE_ID_SUGGESTION = 'suggestion'; const messages = { - [MESSAGE_ID]: 'Use `export…from` to re-export `{{exported}}`.', + [MESSAGE_ID_ERROR]: 'Use `export…from` to re-export `{{exported}}`.', + [MESSAGE_ID_SUGGESTION]: 'Switch to `export…from`.', }; function * removeSpecifier(node, fixer, sourceCode) { @@ -72,7 +74,7 @@ function * removeImportOrExport(node, fixer, sourceCode) { } } -function fix({ +function getFixFunction({ context, imported, exported, @@ -273,30 +275,43 @@ function create(context) { if ( ignoreUsedVariables - && variables.some(({variable, exports}) => { - const {references} = variable; - return references.length === 0 || references.length !== exports.length; - }) + && variables.some(({variable, exports}) => variable.references.length !== exports.length) ) { continue; } + const shouldUseSuggestion = ignoreUsedVariables + && variables.some(({variable}) => variable.references.length === 0); + for (const {imported, exports} of variables) { for (const exported of exports) { - yield { + const problem = { node: exported.node, - messageId: MESSAGE_ID, + messageId: MESSAGE_ID_ERROR, data: { exported: exported.name, }, - fix: fix({ - context, - imported, - exported, - exportDeclarations, - program, - }), }; + const fix = getFixFunction({ + context, + imported, + exported, + exportDeclarations, + program, + }); + + if (shouldUseSuggestion) { + problem.suggest = [ + { + messageId: MESSAGE_ID_SUGGESTION, + fix, + }, + ]; + } else { + problem.fix = fix; + } + + yield problem; } } } @@ -312,6 +327,7 @@ module.exports = { description: 'Prefer `export…from` when re-exporting.', }, fixable: 'code', + hasSuggestions: true, schema, messages, }, diff --git a/test/prefer-export-from.mjs b/test/prefer-export-from.mjs index 4a54479dd5..5a963c8213 100644 --- a/test/prefer-export-from.mjs +++ b/test/prefer-export-from.mjs @@ -358,10 +358,6 @@ test.snapshot({ use(named1); export {defaultExport, named2}; `, - outdent` - import {notUsedNotExported, exported} from 'foo'; - export {exported}; - `, ].map(code => ({code, options: [{ignoreUsedVariables: true}]})), invalid: [ outdent` @@ -392,5 +388,9 @@ test.snapshot({ import defaultExport from 'foo'; export const variable = defaultExport; `, + outdent` + import {notUsedNotExported, exported} from 'foo'; + export {exported}; + `, ].map(code => ({code, options: [{ignoreUsedVariables: true}]})), }); diff --git a/test/snapshots/prefer-export-from.mjs.md b/test/snapshots/prefer-export-from.mjs.md index 1b664d3f83..08ebf6ebf0 100644 --- a/test/snapshots/prefer-export-from.mjs.md +++ b/test/snapshots/prefer-export-from.mjs.md @@ -1317,3 +1317,31 @@ Generated by [AVA](https://avajs.dev). > 2 | export const variable = defaultExport;␊ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use \`export…from\` to re-export \`variable\`.␊ ` + +## Invalid #7 + 1 | import {notUsedNotExported, exported} from 'foo'; + 2 | export {exported}; + +> Options + + `␊ + [␊ + {␊ + "ignoreUsedVariables": true␊ + }␊ + ]␊ + ` + +> Error 1/1 + + `␊ + 1 | import {notUsedNotExported, exported} from 'foo';␊ + > 2 | export {exported};␊ + | ^^^^^^^^ Use \`export…from\` to re-export \`exported\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`export…from\`.␊ + 1 | import {notUsedNotExported, } from 'foo';␊ + 2 |␊ + 3 | export {exported} from 'foo';␊ + ` diff --git a/test/snapshots/prefer-export-from.mjs.snap b/test/snapshots/prefer-export-from.mjs.snap index 4ed007bf229f5c036550af3c50be8a7c3458098d..c280c8f0eec0e2972e608279bda8c0d3bea023dd 100644 GIT binary patch literal 3086 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 literal 2984 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 e932|+bWKmcHZ9fHRTBekZ~q5L1ou)v=H From 7ca97f30218e90830bc86fc323e3352157028f96 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 10 Nov 2021 18:11:46 +0800 Subject: [PATCH 3/6] Update notice --- docs/rules/prefer-export-from.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/prefer-export-from.md b/docs/rules/prefer-export-from.md index 4578cc6845..b70bb03efb 100644 --- a/docs/rules/prefer-export-from.md +++ b/docs/rules/prefer-export-from.md @@ -2,7 +2,7 @@ ✅ *This rule is part of the [recommended](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config) config.* -🔧 *This rule is [auto-fixable](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems).* +🔧💡 *This rule is [auto-fixable](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) and provides [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).* When re-exporting from a module, it's unnecessary to import and then export. It can be done in a single `export…from` declaration. From 02cac7fa5574a37e3721415178f6bbe535aa04c4 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 10 Nov 2021 18:16:30 +0800 Subject: [PATCH 4/6] Fix docs --- docs/rules/prefer-export-from.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rules/prefer-export-from.md b/docs/rules/prefer-export-from.md index b70bb03efb..0358f6cae9 100644 --- a/docs/rules/prefer-export-from.md +++ b/docs/rules/prefer-export-from.md @@ -74,7 +74,7 @@ When `true`, if an import is used in other places than just re-export, all varia #### Fail ```js -// eslint unicorn/import-index: ["error", {"ignoreUsedVariables": false}] +// eslint unicorn/prefer-export-from: ["error", {"ignoreUsedVariables": false}] import {named1, named2} from './foo.js'; use(named1); @@ -85,7 +85,7 @@ export {named1, named2}; #### Pass ```js -// eslint unicorn/import-index: ["error", {"ignoreUsedVariables": true}] +// eslint unicorn/prefer-export-from: ["error", {"ignoreUsedVariables": true}] import {named1, named2} from './foo.js'; use(named1); From 9d8e6a848ed723ce36836ea7f2057bd449e21141 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 10 Nov 2021 17:38:43 +0700 Subject: [PATCH 5/6] Update prefer-export-from.md --- docs/rules/prefer-export-from.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/prefer-export-from.md b/docs/rules/prefer-export-from.md index 0358f6cae9..c1b6f655ac 100644 --- a/docs/rules/prefer-export-from.md +++ b/docs/rules/prefer-export-from.md @@ -69,7 +69,7 @@ export default namespace; Type: `boolean`\ Default: `false` -When `true`, if an import is used in other places than just re-export, all variables in the import declaration will be ignored. +When `true`, if an import is used in other places than just a re-export, all variables in the import declaration will be ignored. #### Fail From 777079b476888770b80783bbb3147623282160be Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 10 Nov 2021 18:41:01 +0800 Subject: [PATCH 6/6] Update table --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 559a14351d..2cda9839d1 100644 --- a/readme.md +++ b/readme.md @@ -212,7 +212,7 @@ Each rule has emojis denoting: | [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. | ✅ | 🔧 | | | [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) | Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. | ✅ | 🔧 | 💡 | | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | 💡 | -| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | 🔧 | | +| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | 🔧 | 💡 | | [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 | | [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | | | [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | ✅ | 🔧 | 💡 |