diff --git a/CHANGELOG.md b/CHANGELOG.md index bd878a8b1..63de41cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +### Added +- [`no-named-default`, `no-default-export`, `prefer-default-export`, `no-named-export`, `export`, `named`, `namespace`, `no-unused-modules`]: support arbitrary module namespace names ([#2358], thanks [@sosukesuzuki]) + ### Changed - [Tests] `no-nodejs-modules`: add tests for node protocol URL ([#2367], thanks [@sosukesuzuki]) +- [Tests] `default`, `no-anonymous-default-export`, `no-mutable-exports`, `no-named-as-default-member`, `no-named-as-default`: add tests for arbitrary module namespace names ([#2358], thanks [@sosukesuzuki]) ## [2.25.4] - 2022-01-02 @@ -959,6 +963,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md [#2367]: https://github.com/import-js/eslint-plugin-import/pull/2367 +[#2358]: https://github.com/import-js/eslint-plugin-import/pull/2358 [#2341]: https://github.com/import-js/eslint-plugin-import/pull/2341 [#2334]: https://github.com/import-js/eslint-plugin-import/pull/2334 [#2305]: https://github.com/import-js/eslint-plugin-import/pull/2305 diff --git a/src/ExportMap.js b/src/ExportMap.js index 564b5d63d..7425dce44 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -479,11 +479,11 @@ ExportMap.parse = function (path, content, context) { })); return; case 'ExportAllDeclaration': - m.namespace.set(s.exported.name, addNamespace(exportMeta, s.source.value)); + m.namespace.set(s.exported.name || s.exported.value, addNamespace(exportMeta, s.source.value)); return; case 'ExportSpecifier': if (!n.source) { - m.namespace.set(s.exported.name, addNamespace(exportMeta, s.local)); + m.namespace.set(s.exported.name || s.exported.value, addNamespace(exportMeta, s.local)); return; } // else falls through @@ -593,7 +593,7 @@ ExportMap.parse = function (path, content, context) { importedSpecifiers.add(specifier.type); } if (specifier.type === 'ImportSpecifier') { - importedSpecifiers.add(specifier.imported.name); + importedSpecifiers.add(specifier.imported.name || specifier.imported.value); } // import { type Foo } (Flow) diff --git a/src/rules/export.js b/src/rules/export.js index e0b2c5785..b9378f091 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -88,7 +88,7 @@ module.exports = { 'ExportDefaultDeclaration': (node) => addNamed('default', node, getParent(node)), 'ExportSpecifier': (node) => addNamed( - node.exported.name, + node.exported.name || node.exported.value, node.exported, getParent(node.parent), ), diff --git a/src/rules/named.js b/src/rules/named.js index a529c295b..ad1b5e172 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -58,7 +58,9 @@ module.exports = { return; } - const deepLookup = imports.hasDeep(im[key].name); + const name = im[key].name || im[key].value; + + const deepLookup = imports.hasDeep(name); if (!deepLookup.found) { if (deepLookup.path.length > 1) { @@ -66,9 +68,9 @@ module.exports = { .map(i => path.relative(path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()), i.path)) .join(' -> '); - context.report(im[key], `${im[key].name} not found via ${deepPath}`); + context.report(im[key], `${name} not found via ${deepPath}`); } else { - context.report(im[key], im[key].name + ' not found in \'' + node.source.value + '\''); + context.report(im[key], name + ' not found in \'' + node.source.value + '\''); } } }); diff --git a/src/rules/namespace.js b/src/rules/namespace.js index 6325b88ba..8a7099df6 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -69,7 +69,7 @@ module.exports = { case 'ImportSpecifier': { const meta = imports.get( // default to 'default' for default https://i.imgur.com/nj6qAWy.jpg - specifier.imported ? specifier.imported.name : 'default', + specifier.imported ? (specifier.imported.name || specifier.imported.value) : 'default', ); if (!meta || !meta.namespace) { break; } namespaces.set(specifier.local.name, meta.namespace); diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js index a17428c56..ed1aaf8db 100644 --- a/src/rules/no-default-export.js +++ b/src/rules/no-default-export.js @@ -25,7 +25,7 @@ module.exports = { }, ExportNamedDeclaration(node) { - node.specifiers.filter(specifier => specifier.exported.name === 'default').forEach(specifier => { + node.specifiers.filter(specifier => (specifier.exported.name || specifier.exported.value) === 'default').forEach(specifier => { const { loc } = context.getSourceCode().getFirstTokens(node)[1] || {}; if (specifier.type === 'ExportDefaultSpecifier') { context.report({ node, message: preferNamed, loc }); diff --git a/src/rules/no-named-default.js b/src/rules/no-named-default.js index 116c89cf5..6a5c1db70 100644 --- a/src/rules/no-named-default.js +++ b/src/rules/no-named-default.js @@ -17,7 +17,7 @@ module.exports = { return; } - if (im.type === 'ImportSpecifier' && im.imported.name === 'default') { + if (im.type === 'ImportSpecifier' && (im.imported.name || im.imported.value) === 'default') { context.report({ node: im.local, message: `Use default import syntax to import '${im.local.name}'.` }); diff --git a/src/rules/no-named-export.js b/src/rules/no-named-export.js index bb586ead0..6c92ad9ca 100644 --- a/src/rules/no-named-export.js +++ b/src/rules/no-named-export.js @@ -25,7 +25,7 @@ module.exports = { return context.report({ node, message }); } - const someNamed = node.specifiers.some(specifier => specifier.exported.name !== 'default'); + const someNamed = node.specifiers.some(specifier => (specifier.exported.name || specifier.exported.value) !== 'default'); if (someNamed) { context.report({ node, message }); } diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 9891d4097..efc216994 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -604,7 +604,7 @@ module.exports = { if (specifiers.length > 0) { specifiers.forEach(specifier => { if (specifier.exported) { - newExportIdentifiers.add(specifier.exported.name); + newExportIdentifiers.add(specifier.exported.name || specifier.exported.value); } }); } @@ -715,8 +715,8 @@ module.exports = { if (astNode.source) { resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context); astNode.specifiers.forEach(specifier => { - const name = specifier.local.name; - if (specifier.local.name === DEFAULT) { + const name = specifier.local.name || specifier.local.value; + if (name === DEFAULT) { newDefaultImports.add(resolvedPath); } else { newImports.set(name, resolvedPath); @@ -753,7 +753,7 @@ module.exports = { specifier.type === IMPORT_NAMESPACE_SPECIFIER) { return; } - newImports.set(specifier.imported.name, resolvedPath); + newImports.set(specifier.imported.name || specifier.imported.value, resolvedPath); }); } }); @@ -942,7 +942,7 @@ module.exports = { }, 'ExportNamedDeclaration': node => { node.specifiers.forEach(specifier => { - checkUsage(node, specifier.exported.name); + checkUsage(node, specifier.exported.name || specifier.exported.value); }); forEachDeclarationIdentifier(node.declaration, (name) => { checkUsage(node, name); diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js index a8c52bb1a..230efad12 100644 --- a/src/rules/prefer-default-export.js +++ b/src/rules/prefer-default-export.js @@ -40,7 +40,7 @@ module.exports = { }, 'ExportSpecifier': function (node) { - if (node.exported.name === 'default') { + if ((node.exported.name || node.exported.value) === 'default') { hasDefaultExport = true; } else { specifierExportCount++; diff --git a/tests/files/.eslintrc b/tests/files/.eslintrc.js similarity index 85% rename from tests/files/.eslintrc rename to tests/files/.eslintrc.js index 6d36c133b..6f2529801 100644 --- a/tests/files/.eslintrc +++ b/tests/files/.eslintrc.js @@ -1,4 +1,9 @@ -{ +const eslintPkg = require('eslint/package.json'); +const semver = require('semver'); + +const supportsArbitraryModuleNamespaceIdentifierNames = semver.satisfies(eslintPkg.version, '>= 8.7'); + +const config = { "parser": "babel-eslint", "parserOptions": { "sourceType": "module", @@ -281,5 +286,36 @@ "import/no-duplicates": 0, "import/no-extraneous-dependencies": 0, "import/unambiguous": 0 - } + }, + ignorePatterns: [ + "default-export-namespace-string.js", + "default-export-string.js", + "export-default-string-and-named.js", + "no-unused-modules/arbitrary-module-namespace-identifier-name-a.js", + "no-unused-modules/arbitrary-module-namespace-identifier-name-b.js", + "no-unused-modules/arbitrary-module-namespace-identifier-name-c.js" + ], +} + +if (supportsArbitraryModuleNamespaceIdentifierNames) { + config.ignorePatterns = []; + config.overrides = [ + // For parsing arbitrary module namespace names + { + "files": [ + "default-export-namespace-string.js", + "default-export-string.js", + "export-default-string-and-named.js", + "no-unused-modules/arbitrary-module-namespace-identifier-name-a.js", + "no-unused-modules/arbitrary-module-namespace-identifier-name-b.js", + "no-unused-modules/arbitrary-module-namespace-identifier-name-c.js" + ], + "parser": "espree", + "parserOptions": { + "ecmaVersion": 2022 + } + } + ]; } + +module.exports = config; diff --git a/tests/files/default-export-namespace-string.js b/tests/files/default-export-namespace-string.js new file mode 100644 index 000000000..5b4a01ab7 --- /dev/null +++ b/tests/files/default-export-namespace-string.js @@ -0,0 +1 @@ +export * as "default" from "./named-exports"; diff --git a/tests/files/default-export-string.js b/tests/files/default-export-string.js new file mode 100644 index 000000000..4f68b517e --- /dev/null +++ b/tests/files/default-export-string.js @@ -0,0 +1,3 @@ +function foo() { return 'bar' } + +export { foo as "default" } diff --git a/tests/files/export-default-string-and-named.js b/tests/files/export-default-string-and-named.js new file mode 100644 index 000000000..62c7d13c4 --- /dev/null +++ b/tests/files/export-default-string-and-named.js @@ -0,0 +1,4 @@ +const bar = "bar"; +export function foo() {} + +export { bar as "default" } diff --git a/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-a.js b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-a.js new file mode 100644 index 000000000..7ad810de8 --- /dev/null +++ b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-a.js @@ -0,0 +1,2 @@ +const foo = 333 +export { foo as "foo" } diff --git a/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-b.js b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-b.js new file mode 100644 index 000000000..fa7652725 --- /dev/null +++ b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-b.js @@ -0,0 +1 @@ +import { "foo" as foo } from "./arbitrary-module-namespace-identifier-name-a.js" diff --git a/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-c.js b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-c.js new file mode 100644 index 000000000..7ad810de8 --- /dev/null +++ b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-c.js @@ -0,0 +1,2 @@ +const foo = 333 +export { foo as "foo" } diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index 0274e4374..c173f8354 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -1,5 +1,5 @@ import path from 'path'; -import { test, SYNTAX_CASES, getTSParsers } from '../utils'; +import { test, testVersion, SYNTAX_CASES, getTSParsers } from '../utils'; import { RuleTester } from 'eslint'; import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; @@ -8,7 +8,7 @@ const ruleTester = new RuleTester(); const rule = require('rules/default'); ruleTester.run('default', rule, { - valid: [ + valid: [].concat( test({ code: 'import "./malformed.js"' }), test({ code: 'import foo from "./empty-folder";' }), @@ -92,8 +92,16 @@ ruleTester.run('default', rule, { parser: require.resolve('babel-eslint'), }), + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'export { "default" as bar } from "./bar"', + parserOptions: { + ecmaVersion: 2022, + }, + })), + ...SYNTAX_CASES, - ], + ), invalid: [ test({ diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 29fae4101..d075aea6b 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -46,7 +46,7 @@ ruleTester.run('export', rule, { })) || [], ), - invalid: [ + invalid: [].concat( // multiple defaults // test({ // code: 'export default foo; export default bar', @@ -122,7 +122,19 @@ ruleTester.run('export', rule, { code: 'export * from "./default-export"', errors: [`No named exports found in module './default-export'.`], }), - ], + + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'let foo; export { foo as "foo" }; export * from "./export-all"', + errors: [ + 'Multiple exports of name \'foo\'.', + 'Multiple exports of name \'foo\'.', + ], + parserOptions: { + ecmaVersion: 2022, + }, + })), + ), }); diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 10ba76b79..af2f9ce18 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -7,9 +7,8 @@ import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; const ruleTester = new RuleTester(); const rule = require('rules/named'); -function error(name, module) { - return { message: name + ' not found in \'' + module + '\'', - type: 'Identifier' }; +function error(name, module, type = 'Identifier') { + return { message: name + ' not found in \'' + module + '\'', type }; } ruleTester.run('named', rule, { @@ -199,10 +198,16 @@ ruleTester.run('named', rule, { testVersion('>=7.8.0', () => ({ code: 'import { something } from "./dynamic-import-in-commonjs"', parserOptions: { ecmaVersion: 2021 } })), + + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'import { "foo" as foo } from "./bar"', parserOptions: { ecmaVersion: 2022 } })), + testVersion('>= 8.7', () => ({ + code: 'import { "foo" as foo } from "./empty-module"', parserOptions: { ecmaVersion: 2022 } })), ), ], - invalid: [ + invalid: [].concat( test({ code: 'import { somethingElse } from "./test-module"', errors: [ error('somethingElse', './test-module') ] }), @@ -323,7 +328,24 @@ ruleTester.run('named', rule, { code: 'import { default as barDefault } from "./re-export"', errors: [`default not found in './re-export'`], }), - ], + + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'import { "somethingElse" as somethingElse } from "./test-module"', + errors: [ error('somethingElse', './test-module', 'Literal') ], + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: 'import { "baz" as baz, "bop" as bop } from "./bar"', + errors: [error('baz', './bar', 'Literal'), error('bop', './bar', 'Literal')], + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: 'import { "default" as barDefault } from "./re-export"', + errors: [`default not found in './re-export'`], + parserOptions: { ecmaVersion: 2022 }, + })), + ), }); // #311: import of mismatched case diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 826637b97..15ab133b1 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -183,10 +183,35 @@ const valid = [ parserOptions: { ecmaVersion: 2020, }, - })) || []), + })), + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: "import * as names from './default-export-string';", + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: "import * as names from './default-export-string'; console.log(names.default)", + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: "import * as names from './default-export-namespace-string';", + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: "import * as names from './default-export-namespace-string'; console.log(names.default)", + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: `import { "b" as b } from "./deep/a"; console.log(b.c.d.e)`, + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: `import { "b" as b } from "./deep/a"; var {c:{d:{e}}} = b`, + parserOptions: { ecmaVersion: 2022 }, + }))), ]; -const invalid = [ +const invalid = [].concat( test({ code: "import * as names from './named-exports'; " + ' console.log(names.c);', errors: [error('c', 'names')] }), @@ -275,7 +300,18 @@ const invalid = [ }, }), -] + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: `import { "b" as b } from "./deep/a"; console.log(b.e)`, + errors: [ "'e' not found in imported namespace 'b'." ], + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: `import { "b" as b } from "./deep/a"; console.log(b.c.e)`, + errors: [ "'e' not found in deeply imported namespace 'b.c'." ], + parserOptions: { ecmaVersion: 2022 }, + })), +) /////////////////////// // deep dereferences // diff --git a/tests/src/rules/no-anonymous-default-export.js b/tests/src/rules/no-anonymous-default-export.js index 231f1b667..0428ee1b9 100644 --- a/tests/src/rules/no-anonymous-default-export.js +++ b/tests/src/rules/no-anonymous-default-export.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES } from '../utils'; +import { test, testVersion, SYNTAX_CASES } from '../utils'; import { RuleTester } from 'eslint'; @@ -6,7 +6,7 @@ const ruleTester = new RuleTester(); const rule = require('rules/no-anonymous-default-export'); ruleTester.run('no-anonymous-default-export', rule, { - valid: [ + valid: [].concat( // Exports with identifiers are valid test({ code: 'const foo = 123\nexport default foo' }), test({ code: 'export default function foo() {}' }), @@ -31,12 +31,17 @@ ruleTester.run('no-anonymous-default-export', rule, { test({ code: 'export * from \'foo\'' }), test({ code: 'const foo = 123\nexport { foo }' }), test({ code: 'const foo = 123\nexport { foo as default }' }), + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'const foo = 123\nexport { foo as "default" }', + parserOptions: { ecmaVersion: 2022 }, + })), // Allow call expressions by default for backwards compatibility test({ code: 'export default foo(bar)' }), ...SYNTAX_CASES, - ], + ), invalid: [ test({ code: 'export default []', errors: [{ message: 'Assign array to a variable before exporting as module default' }] }), diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index 61e55d959..1d149421f 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -162,5 +162,16 @@ ruleTester.run('no-default-export', rule, { }, ], }), + // es2022: Arbitrary module namespae identifier names + testVersion('>= 8.7', () => ({ + code: 'let foo; export { foo as "default" }', + errors: [ + { + type: 'ExportNamedDeclaration', + message: 'Do not alias `foo` as `default`. Just export `foo` itself instead.', + }, + ], + parserOptions: { ecmaVersion: 2022 }, + })), ), }); diff --git a/tests/src/rules/no-mutable-exports.js b/tests/src/rules/no-mutable-exports.js index 2ecae48cd..e96b841b5 100644 --- a/tests/src/rules/no-mutable-exports.js +++ b/tests/src/rules/no-mutable-exports.js @@ -1,11 +1,11 @@ -import { test } from '../utils'; +import { test, testVersion } from '../utils'; import { RuleTester } from 'eslint'; import rule from 'rules/no-mutable-exports'; const ruleTester = new RuleTester(); ruleTester.run('no-mutable-exports', rule, { - valid: [ + valid: [].concat( test({ code: 'export const count = 1' }), test({ code: 'export function getCount() {}' }), test({ code: 'export class Counter {}' }), @@ -32,8 +32,12 @@ ruleTester.run('no-mutable-exports', rule, { parser: require.resolve('babel-eslint'), code: 'type Foo = {}\nexport type {Foo}', }), - ], - invalid: [ + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'const count = 1\nexport { count as "counter" }', parserOptions: { ecmaVersion: 2022 }, + })), + ), + invalid: [].concat( test({ code: 'export let count = 1', errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'], @@ -66,6 +70,12 @@ ruleTester.run('no-mutable-exports', rule, { code: 'var count = 1\nexport default count', errors: ['Exporting mutable \'var\' binding, use \'const\' instead.'], }), + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'let count = 1\nexport { count as "counter" }', + errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'], + parserOptions: { ecmaVersion: 2022 }, + })), // todo: undeclared globals // test({ @@ -76,5 +86,5 @@ ruleTester.run('no-mutable-exports', rule, { // code: 'count = 1\nexport default count', // errors: ['Exporting mutable global binding, use \'const\' instead.'], // }), - ], + ), }); diff --git a/tests/src/rules/no-named-as-default-member.js b/tests/src/rules/no-named-as-default-member.js index b4f3cf589..53cba230b 100644 --- a/tests/src/rules/no-named-as-default-member.js +++ b/tests/src/rules/no-named-as-default-member.js @@ -1,21 +1,27 @@ -import { test, SYNTAX_CASES } from '../utils'; +import { test, testVersion, SYNTAX_CASES } from '../utils'; import { RuleTester } from 'eslint'; import rule from 'rules/no-named-as-default-member'; const ruleTester = new RuleTester(); ruleTester.run('no-named-as-default-member', rule, { - valid: [ + valid: [].concat( test({ code: 'import bar, {foo} from "./bar";' }), test({ code: 'import bar from "./bar"; const baz = bar.baz' }), test({ code: 'import {foo} from "./bar"; const baz = foo.baz;' }), test({ code: 'import * as named from "./named-exports"; const a = named.a' }), test({ code: 'import foo from "./default-export-default-property"; const a = foo.default' }), + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'import bar, { foo } from "./export-default-string-and-named"', + parserOptions: { ecmaVersion: 2022 }, + })), + ...SYNTAX_CASES, - ], + ), - invalid: [ + invalid: [].concat( test({ code: 'import bar from "./bar"; const foo = bar.foo;', errors: [{ @@ -56,5 +62,17 @@ ruleTester.run('no-named-as-default-member', rule, { type: 'Identifier', }], }), - ], + // es2022: Arbitrary module namespace identifier names + testVersion('>= 8.7', () => ({ + code: 'import bar from "./export-default-string-and-named"; const foo = bar.foo;', + errors: [{ + message: ( + 'Caution: `bar` also has a named export `foo`. ' + + 'Check if you meant to write `import {foo} from \'./export-default-string-and-named\'` instead.' + ), + type: 'MemberExpression', + }], + parserOptions: { ecmaVersion: 2022 }, + })), + ), }); diff --git a/tests/src/rules/no-named-as-default.js b/tests/src/rules/no-named-as-default.js index 57b2f53bd..129419350 100644 --- a/tests/src/rules/no-named-as-default.js +++ b/tests/src/rules/no-named-as-default.js @@ -1,11 +1,11 @@ -import { test, SYNTAX_CASES } from '../utils'; +import { test, testVersion, SYNTAX_CASES } from '../utils'; import { RuleTester } from 'eslint'; const ruleTester = new RuleTester(); const rule = require('rules/no-named-as-default'); ruleTester.run('no-named-as-default', rule, { - valid: [ + valid: [].concat( test({ code: 'import "./malformed.js"' }), test({ code: 'import bar, { foo } from "./bar";' }), @@ -21,10 +21,16 @@ ruleTester.run('no-named-as-default', rule, { test({ code: 'export default from "./bar";', parser: require.resolve('babel-eslint') }), + // es2022: Arbitrary module namespae identifier names + testVersion('>= 8.7', () => ({ + code: 'import bar, { foo } from "./export-default-string-and-named"', + parserOptions: { ecmaVersion: 2022 }, + })), + ...SYNTAX_CASES, - ], + ), - invalid: [ + invalid: [].concat( test({ code: 'import foo from "./bar";', errors: [ { @@ -57,5 +63,23 @@ ruleTester.run('no-named-as-default', rule, { type: 'Literal', }], }), - ], + + // es2022: Arbitrary module namespae identifier names + testVersion('>= 8.7', () => ({ + code: 'import foo from "./export-default-string-and-named"', + errors: [{ + message: 'Using exported name \'foo\' as identifier for default export.', + type: 'ImportDefaultSpecifier', + }], + parserOptions: { ecmaVersion: 2022 }, + })), + testVersion('>= 8.7', () => ({ + code: 'import foo, { foo as bar } from "./export-default-string-and-named"', + errors: [{ + message: 'Using exported name \'foo\' as identifier for default export.', + type: 'ImportDefaultSpecifier', + }], + parserOptions: { ecmaVersion: 2022 }, + })), + ), }); diff --git a/tests/src/rules/no-named-default.js b/tests/src/rules/no-named-default.js index 56470f2ba..1ed150287 100644 --- a/tests/src/rules/no-named-default.js +++ b/tests/src/rules/no-named-default.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES } from '../utils'; +import { test, testVersion, SYNTAX_CASES } from '../utils'; import { RuleTester } from 'eslint'; const ruleTester = new RuleTester(); @@ -22,7 +22,7 @@ ruleTester.run('no-named-default', rule, { ...SYNTAX_CASES, ], - invalid: [ + invalid: [].concat( /*test({ code: 'import { default } from "./bar";', errors: [{ @@ -45,5 +45,17 @@ ruleTester.run('no-named-default', rule, { type: 'Identifier', }], }), - ], + + // es2022: Arbitrary module namespae identifier names + testVersion('>= 8.7', () => ({ + code: 'import { "default" as bar } from "./bar";', + errors: [{ + message: 'Use default import syntax to import \'bar\'.', + type: 'Identifier', + }], + parserOptions: { + ecmaVersion: 2022, + }, + })) || [], + ), }); diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index 41d0fcd7c..4d8978b35 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -1,11 +1,11 @@ import { RuleTester } from 'eslint'; -import { test } from '../utils'; +import { test, testVersion } from '../utils'; const ruleTester = new RuleTester(); const rule = require('rules/no-named-export'); ruleTester.run('no-named-export', rule, { - valid: [ + valid: [].concat( test({ code: 'export default function bar() {};', }), @@ -27,7 +27,13 @@ ruleTester.run('no-named-export', rule, { test({ code: `import {default as foo} from './foo';`, }), - ], + + // es2022: Arbitrary module namespae identifier names + testVersion('>= 8.7', () => ({ + code: 'let foo; export { foo as "default" }', + parserOptions: { ecmaVersion: 2022 }, + })), + ), invalid: [ test({ code: ` diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 6f87058c4..8610ff4aa 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -1,4 +1,4 @@ -import { test, testFilePath, getTSParsers } from '../utils'; +import { test, testVersion, testFilePath, getTSParsers } from '../utils'; import jsxConfig from '../../../config/react'; import typescriptConfig from '../../../config/typescript'; @@ -1261,3 +1261,33 @@ describe('support (nested) destructuring assignment', () => { invalid: [], }); }); + +describe('support ES2022 Arbitrary module namespace identifier names', () => { + ruleTester.run('no-unused-module', rule, { + valid: [].concat( + testVersion('>= 8.7', () => ({ + options: unusedExportsOptions, + code: `import { "foo" as foo } from "./arbitrary-module-namespace-identifier-name-a"`, + parserOptions: { ecmaVersion: 2022 }, + filename: testFilePath('./no-unused-modules/arbitrary-module-namespace-identifier-name-b.js'), + })), + testVersion('>= 8.7', () => ({ + options: unusedExportsOptions, + code: 'const foo = 333;\nexport { foo as "foo" }', + parserOptions: { ecmaVersion: 2022 }, + filename: testFilePath('./no-unused-modules/arbitrary-module-namespace-identifier-name-a.js'), + })), + ), + invalid: [].concat( + testVersion('>= 8.7', () => ({ + options: unusedExportsOptions, + code: 'const foo = 333\nexport { foo as "foo" }', + parserOptions: { ecmaVersion: 2022 }, + filename: testFilePath('./no-unused-modules/arbitrary-module-namespace-identifier-name-c.js'), + errors: [ + error(`exported declaration 'foo' not used within other modules`), + ], + })), + ), + }); +}); diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index 36205b193..d00a99668 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -1,4 +1,4 @@ -import { test, getNonDefaultParsers } from '../utils'; +import { test, testVersion, getNonDefaultParsers } from '../utils'; import { RuleTester } from 'eslint'; @@ -6,7 +6,7 @@ const ruleTester = new RuleTester(); const rule = require('../../../src/rules/prefer-default-export'); ruleTester.run('prefer-default-export', rule, { - valid: [ + valid: [].concat( test({ code: ` export const foo = 'foo'; @@ -94,7 +94,12 @@ ruleTester.run('prefer-default-export', rule, { `, parser: require.resolve('babel-eslint'), }), - ], + // es2022: Arbitrary module namespae identifier names + testVersion('>= 8.7', () => ({ + code: 'let foo; export { foo as "default" };', + parserOptions: { ecmaVersion: 2022 }, + })), + ), invalid: [ test({ code: `