From 203535504eda82860c05414ac83386087fcde1a3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 11 Jan 2023 23:01:21 -0800 Subject: [PATCH] [Fix] `no-empty-named-blocks`: rewrite rule to only check import declarations Fixes #2666 --- CHANGELOG.md | 4 + src/rules/no-empty-named-blocks.js | 111 +++++++++++++---------- tests/src/rules/no-empty-named-blocks.js | 24 ++++- 3 files changed, 88 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d926098da..2607f85f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +### Fixed +- [`no-empty-named-blocks`]: rewrite rule to only check import declarations ([#2666]) + ## [2.27.2] - 2023-01-11 ### Fixed @@ -1366,6 +1369,7 @@ for info on changes for earlier releases. [#211]: https://github.com/import-js/eslint-plugin-import/pull/211 [#164]: https://github.com/import-js/eslint-plugin-import/pull/164 [#157]: https://github.com/import-js/eslint-plugin-import/pull/157 +[#2666]: https://github.com/import-js/eslint-plugin-import/issues/2666 [#2665]: https://github.com/import-js/eslint-plugin-import/issues/2665 [#2444]: https://github.com/import-js/eslint-plugin-import/issues/2444 [#2412]: https://github.com/import-js/eslint-plugin-import/issues/2412 diff --git a/src/rules/no-empty-named-blocks.js b/src/rules/no-empty-named-blocks.js index 114736f16..25567b08f 100644 --- a/src/rules/no-empty-named-blocks.js +++ b/src/rules/no-empty-named-blocks.js @@ -29,63 +29,78 @@ module.exports = { }, create(context) { + const importsWithoutNameds = []; + return { - Program(node) { - node.tokens.forEach((token, idx) => { - const nextToken = node.tokens[idx + 1]; + ImportDeclaration(node) { + if (!node.specifiers.some(x => x.type === 'ImportSpecifier')) { + importsWithoutNameds.push(node); + } + }, + + 'Program:exit': function (program) { + const importsTokens = importsWithoutNameds.map((node) => { + return [node, program.tokens.filter(x => x.range[0] >= node.range[0] && x.range[1] <= node.range[1])]; + }); + + importsTokens.forEach(([node, tokens]) => { + tokens.forEach((token) => { + const idx = program.tokens.indexOf(token); + const nextToken = program.tokens[idx + 1]; - if (nextToken && token.value === '{' && nextToken.value === '}') { - const hasOtherIdentifiers = node.tokens.some((token) => ( - token.type === 'Identifier' - && token.value !== 'from' - && token.value !== 'type' - && token.value !== 'typeof' - )); + if (nextToken && token.value === '{' && nextToken.value === '}') { + const hasOtherIdentifiers = tokens.some((token) => ( + token.type === 'Identifier' + && token.value !== 'from' + && token.value !== 'type' + && token.value !== 'typeof' + )); - // If it has no other identifiers it's the only thing in the import, so we can either remove the import - // completely or transform it in a side-effects only import - if (!hasOtherIdentifiers) { - context.report({ - node, - message: 'Unexpected empty named import block', - suggest: [ - { - desc: 'Remove unused import', - fix(fixer) { - // Remove the whole import - return fixer.remove(node); + // If it has no other identifiers it's the only thing in the import, so we can either remove the import + // completely or transform it in a side-effects only import + if (!hasOtherIdentifiers) { + context.report({ + node, + message: 'Unexpected empty named import block', + suggest: [ + { + desc: 'Remove unused import', + fix(fixer) { + // Remove the whole import + return fixer.remove(node); + }, }, - }, - { - desc: 'Remove empty import block', - fix(fixer) { - // Remove the empty block and the 'from' token, leaving the import only for its side - // effects, e.g. `import 'mod'` - const sourceCode = context.getSourceCode(); - const fromToken = node.tokens.find(t => t.value === 'from'); - const importToken = node.tokens.find(t => t.value === 'import'); - const hasSpaceAfterFrom = sourceCode.isSpaceBetween(fromToken, sourceCode.getTokenAfter(fromToken)); - const hasSpaceAfterImport = sourceCode.isSpaceBetween(importToken, sourceCode.getTokenAfter(fromToken)); + { + desc: 'Remove empty import block', + fix(fixer) { + // Remove the empty block and the 'from' token, leaving the import only for its side + // effects, e.g. `import 'mod'` + const sourceCode = context.getSourceCode(); + const fromToken = program.tokens.find(t => t.value === 'from'); + const importToken = program.tokens.find(t => t.value === 'import'); + const hasSpaceAfterFrom = sourceCode.isSpaceBetween(fromToken, sourceCode.getTokenAfter(fromToken)); + const hasSpaceAfterImport = sourceCode.isSpaceBetween(importToken, sourceCode.getTokenAfter(fromToken)); - const [start] = getEmptyBlockRange(node.tokens, idx); - const [, end] = fromToken.range; - const range = [start, hasSpaceAfterFrom ? end + 1 : end]; + const [start] = getEmptyBlockRange(program.tokens, idx); + const [, end] = fromToken.range; + const range = [start, hasSpaceAfterFrom ? end + 1 : end]; - return fixer.replaceTextRange(range, hasSpaceAfterImport ? '' : ' '); + return fixer.replaceTextRange(range, hasSpaceAfterImport ? '' : ' '); + }, }, + ], + }); + } else { + context.report({ + node, + message: 'Unexpected empty named import block', + fix(fixer) { + return fixer.removeRange(getEmptyBlockRange(program.tokens, idx)); }, - ], - }); - } else { - context.report({ - node, - message: 'Unexpected empty named import block', - fix(fixer) { - return fixer.removeRange(getEmptyBlockRange(node.tokens, idx)); - }, - }); + }); + } } - } + }); }); }, }; diff --git a/tests/src/rules/no-empty-named-blocks.js b/tests/src/rules/no-empty-named-blocks.js index ee21db347..87a0a3e7c 100644 --- a/tests/src/rules/no-empty-named-blocks.js +++ b/tests/src/rules/no-empty-named-blocks.js @@ -42,9 +42,27 @@ ruleTester.run('no-empty-named-blocks', rule, { ] : [], // Flow - test({ code: `import typeof Default from 'mod';`, parser: parsers.BABEL_OLD }), - test({ code: `import typeof { Named } from 'mod';`, parser: parsers.BABEL_OLD }), - test({ code: `import typeof Default, { Named } from 'mod';`, parser: parsers.BABEL_OLD }), + test({ code: `import typeof Default from 'mod'; // babel old`, parser: parsers.BABEL_OLD }), + test({ code: `import typeof { Named } from 'mod'; // babel old`, parser: parsers.BABEL_OLD }), + test({ code: `import typeof Default, { Named } from 'mod'; // babel old`, parser: parsers.BABEL_OLD }), + test({ + code: ` + module.exports = { + rules: { + 'keyword-spacing': ['error', {overrides: {}}], + } + }; + `, + }), + test({ + code: ` + import { DESCRIPTORS, NODE } from '../helpers/constants'; + // ... + import { timeLimitedPromise } from '../helpers/helpers'; + // ... + import { DESCRIPTORS2 } from '../helpers/constants'; + `, + }), ), invalid: [].concat( test({