diff --git a/src/core/importType.js b/src/core/importType.js index 57558cbd8..8ec01c29e 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -84,6 +84,10 @@ function typeTest(name, settings, path) { return 'unknown' } +export function isScopedModule(name) { + return name.indexOf('@') === 0 +} + export default function resolveImportType(name, context) { return typeTest(name, context.settings, resolve(name, context)) } diff --git a/src/rules/extensions.js b/src/rules/extensions.js index c0213cad2..fd9d177ad 100644 --- a/src/rules/extensions.js +++ b/src/rules/extensions.js @@ -1,7 +1,7 @@ import path from 'path' import resolve from 'eslint-module-utils/resolve' -import { isBuiltIn, isExternalModule, isScoped } from '../core/importType' +import { isBuiltIn, isExternalModule, isScoped, isScopedModule } from '../core/importType' import docsUrl from '../docsUrl' const enumValues = { enum: [ 'always', 'ignorePackages', 'never' ] } @@ -126,6 +126,14 @@ module.exports = { return resolvedFileWithoutExtension === resolve(file, context) } + function isExternalRootModule(file) { + const slashCount = file.split('/').length - 1 + + if (isScopedModule(file) && slashCount <= 1) return true + if (isExternalModule(file, context, resolve(file, context)) && !slashCount) return true + return false + } + function checkFileExtension(node) { const { source } = node @@ -139,6 +147,10 @@ module.exports = { const importPath = importPathWithQueryString.replace(/\?(.*)$/, '') + // don't enforce in root external packages as they may have names with `.js`. + // Like `import Decimal from decimal.js`) + if (isExternalRootModule(importPath)) return + const resolvedPath = resolve(importPath, context) // get extension from resolved path, if possible. diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index 5f0a254f5..562802eef 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -117,6 +117,16 @@ ruleTester.run('extensions', rule, { options: [ 'never' ], }), + // Root packages should be ignored and they are names not files + test({ + code: [ + 'import lib from "pkg.js"', + 'import lib2 from "pgk/package"', + 'import lib3 from "@name/pkg.js"', + ].join('\n'), + options: [ 'never' ], + }), + // Query strings. test({ code: 'import bare from "./foo?a=True.ext"', @@ -126,6 +136,15 @@ ruleTester.run('extensions', rule, { code: 'import bare from "./foo.js?a=True"', options: [ 'always' ], }), + + test({ + code: [ + 'import lib from "pkg"', + 'import lib2 from "pgk/package.js"', + 'import lib3 from "@name/pkg"', + ].join('\n'), + options: [ 'always' ], + }), ], invalid: [ @@ -137,15 +156,6 @@ ruleTester.run('extensions', rule, { column: 15, } ], }), - test({ - code: 'import a from "a"', - options: [ 'always' ], - errors: [ { - message: 'Missing file extension "js" for "a"', - line: 1, - column: 15, - } ], - }), test({ code: 'import dot from "./file.with.dot"', options: [ 'always' ], @@ -285,11 +295,35 @@ ruleTester.run('extensions', rule, { ], }), test({ - code: 'import thing from "non-package"', + code: 'import thing from "non-package/test"', options: [ 'always' ], errors: [ { - message: 'Missing file extension for "non-package"', + message: 'Missing file extension for "non-package/test"', + line: 1, + column: 19, + }, + ], + }), + + test({ + code: 'import thing from "@name/pkg/test"', + options: [ 'always' ], + errors: [ + { + message: 'Missing file extension for "@name/pkg/test"', + line: 1, + column: 19, + }, + ], + }), + + test({ + code: 'import thing from "@name/pkg/test.js"', + options: [ 'never' ], + errors: [ + { + message: 'Unexpected use of file extension "js" for "@name/pkg/test.js"', line: 1, column: 19, },