From a8d52e4bf7878a88c7f9c76ed63c6f5f7c8d451d Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 10 Nov 2021 20:22:53 +0800 Subject: [PATCH] `prefer-export-from`: Add `ignoreUsedVariables` option (#1590) Co-authored-by: Sindre Sorhus --- docs/rules/prefer-export-from.md | 33 ++- readme.md | 2 +- rules/prefer-export-from.js | 129 ++++++---- test/prefer-export-from.mjs | 105 ++++++++ test/snapshots/prefer-export-from.mjs.md | 264 +++++++++++++++++++++ test/snapshots/prefer-export-from.mjs.snap | Bin 2625 -> 3086 bytes 6 files changed, 489 insertions(+), 44 deletions(-) diff --git a/docs/rules/prefer-export-from.md b/docs/rules/prefer-export-from.md index ec2d8b9959..c1b6f655ac 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. @@ -61,3 +61,34 @@ export { import * as namespace from './foo.js'; export default namespace; ``` + +## Options + +### ignoreUsedVariables + +Type: `boolean`\ +Default: `false` + +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 + +```js +// eslint unicorn/prefer-export-from: ["error", {"ignoreUsedVariables": false}] +import {named1, named2} from './foo.js'; + +use(named1); + +export {named1, named2}; +``` + +#### Pass + +```js +// eslint unicorn/prefer-export-from: ["error", {"ignoreUsedVariables": true}] +import {named1, named2} from './foo.js'; + +use(named1); + +export {named1, named2}; +``` 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. | ✅ | 🔧 | 💡 | diff --git a/rules/prefer-export-from.js b/rules/prefer-export-from.js index 028ff61281..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, @@ -193,28 +195,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 +226,94 @@ 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}) => 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) { + const problem = { + node: exported.node, + messageId: MESSAGE_ID_ERROR, + data: { + exported: exported.name, + }, + }; + const fix = getFixFunction({ + context, + imported, + exported, + exportDeclarations, + program, + }); + + if (shouldUseSuggestion) { + problem.suggest = [ + { + messageId: MESSAGE_ID_SUGGESTION, + fix, + }, + ]; + } else { + problem.fix = fix; + } + + yield problem; + } + } } }, }; @@ -284,6 +327,8 @@ 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 f9a552f3f1..5a963c8213 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}; + `, + ].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; + `, + 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 af4c102aae..08ebf6ebf0 100644 --- a/test/snapshots/prefer-export-from.mjs.md +++ b/test/snapshots/prefer-export-from.mjs.md @@ -1081,3 +1081,267 @@ 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\`.␊ + ` + +## 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 61201efcd9ac4f14814cff9635dd5439226cfdda..c280c8f0eec0e2972e608279bda8c0d3bea023dd 100644 GIT binary patch literal 3086 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 literal 2625 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