diff --git a/lib/util/get-try-extensions.js b/lib/util/get-try-extensions.js index ce00803a..29c16cad 100644 --- a/lib/util/get-try-extensions.js +++ b/lib/util/get-try-extensions.js @@ -4,7 +4,26 @@ */ "use strict" -const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node", ".mjs", ".cjs"]) +const { getTSConfigForContext } = require("./get-tsconfig") +const isTypescript = require("./is-typescript") + +const DEFAULT_JS_VALUE = Object.freeze([ + ".js", + ".json", + ".node", + ".mjs", + ".cjs", +]) +const DEFAULT_TS_VALUE = Object.freeze([ + ".js", + ".ts", + ".mjs", + ".mts", + ".cjs", + ".cts", + ".json", + ".node", +]) /** * Gets `tryExtensions` property from a given option object. @@ -13,7 +32,7 @@ const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node", ".mjs", ".cjs"]) * @returns {string[]|null} The `tryExtensions` value, or `null`. */ function get(option) { - if (option && option.tryExtensions && Array.isArray(option.tryExtensions)) { + if (Array.isArray(option?.tryExtensions)) { return option.tryExtensions.map(String) } return null @@ -24,19 +43,29 @@ function get(option) { * * 1. This checks `options` property, then returns it if exists. * 2. This checks `settings.n` | `settings.node` property, then returns it if exists. - * 3. This returns `[".js", ".json", ".node"]`. + * 3. This returns `[".js", ".json", ".node", ".mjs", ".cjs"]`. * * @param {RuleContext} context - The rule context. * @returns {string[]} A list of extensions. */ module.exports = function getTryExtensions(context, optionIndex = 0) { - return ( - get(context.options && context.options[optionIndex]) || - get( - context.settings && (context.settings.n || context.settings.node) - ) || - DEFAULT_VALUE - ) + const configured = + get(context.options?.[optionIndex]) ?? + get(context.settings?.n) ?? + get(context.settings?.node) + + if (configured != null) { + return configured + } + + if (isTypescript(context)) { + const tsconfig = getTSConfigForContext(context) + if (tsconfig?.config?.compilerOptions?.allowImportingTsExtensions) { + return DEFAULT_TS_VALUE + } + } + + return DEFAULT_JS_VALUE } module.exports.schema = { diff --git a/lib/util/get-tsconfig.js b/lib/util/get-tsconfig.js index 8688a822..8bb6b5f2 100644 --- a/lib/util/get-tsconfig.js +++ b/lib/util/get-tsconfig.js @@ -23,9 +23,27 @@ function getTSConfigForFile(filename) { return getTsconfig(filename, "tsconfig.json", fsCache) } +/** + * Attempts to get the ExtensionMap from the tsconfig of a given file. + * + * @param {import('eslint').Rule.RuleContext} context - The current eslint context + * @returns {import("get-tsconfig").TsConfigResult | null} + */ +function getTSConfigForContext(context) { + // TODO: remove context.get(PhysicalFilename|Filename) when dropping eslint < v10 + const filename = + context.physicalFilename ?? + context.getPhysicalFilename?.() ?? + context.filename ?? + context.getFilename?.() + + return getTSConfigForFile(filename) +} + module.exports = { getTSConfig, getTSConfigForFile, + getTSConfigForContext, } module.exports.schema = { type: "string" } diff --git a/lib/util/get-typescript-extension-map.js b/lib/util/get-typescript-extension-map.js index 917b7c39..5c6a3b1b 100644 --- a/lib/util/get-typescript-extension-map.js +++ b/lib/util/get-typescript-extension-map.js @@ -1,6 +1,6 @@ "use strict" -const { getTSConfig, getTSConfigForFile } = require("./get-tsconfig") +const { getTSConfig, getTSConfigForContext } = require("./get-tsconfig") const DEFAULT_MAPPING = normalise([ ["", ".js"], @@ -95,11 +95,11 @@ function get(option) { /** * Attempts to get the ExtensionMap from the tsconfig of a given file. * - * @param {string} filename - The filename we're getting from + * @param {import('eslint').Rule.RuleContext} context - The current file context * @returns {ExtensionMap} The `typescriptExtensionMap` value, or `null`. */ -function getFromTSConfigFromFile(filename) { - return getMappingFromTSConfig(getTSConfigForFile(filename)?.config) +function getFromTSConfigFromFile(context) { + return getMappingFromTSConfig(getTSConfigForContext(context)?.config) } /** @@ -118,15 +118,10 @@ function getFromTSConfigFromFile(filename) { * @returns {ExtensionMap} A list of extensions. */ module.exports = function getTypescriptExtensionMap(context) { - const filename = - context.physicalFilename ?? - context.getPhysicalFilename?.() ?? - context.filename ?? - context.getFilename?.() // TODO: remove context.get(PhysicalFilename|Filename) when dropping eslint < v10 return ( get(context.options?.[0]) || get(context.settings?.n ?? context.settings?.node) || - getFromTSConfigFromFile(filename) || + getFromTSConfigFromFile(context) || PRESERVE_MAPPING ) } diff --git a/lib/util/import-target.js b/lib/util/import-target.js index 72e82b45..98e9fd87 100644 --- a/lib/util/import-target.js +++ b/lib/util/import-target.js @@ -11,7 +11,7 @@ const isBuiltin = require("is-builtin-module") const resolver = require("enhanced-resolve") const isTypescript = require("./is-typescript") -const { getTSConfigForFile } = require("./get-tsconfig.js") +const { getTSConfigForContext } = require("./get-tsconfig.js") const getTypescriptExtensionMap = require("./get-typescript-extension-map") function removeTrailWildcard(input) { @@ -28,16 +28,7 @@ function removeTrailWildcard(input) { * @returns {import('enhanced-resolve').ResolveOptions['alias'] | undefined} */ function getTSConfigAliases(context) { - const tsConfig = getTSConfigForFile( - // eslint ^8 - context.physicalFilename ?? - // eslint ^7.28 (deprecated ^8) - context.getPhysicalFilename?.() ?? - // eslint ^8 (if physicalFilename undefined) - context.filename ?? - // eslint ^7 (deprecated ^8) - context.getFilename?.() - ) + const tsConfig = getTSConfigForContext(context) const paths = tsConfig?.config?.compilerOptions?.paths diff --git a/tests/fixtures/file-extension-in-import/ts-allow-extension/file.ts b/tests/fixtures/file-extension-in-import/ts-allow-extension/file.ts new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/file-extension-in-import/ts-allow-extension/tsconfig.json b/tests/fixtures/file-extension-in-import/ts-allow-extension/tsconfig.json new file mode 100644 index 00000000..32d0a2a9 --- /dev/null +++ b/tests/fixtures/file-extension-in-import/ts-allow-extension/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true, + "allowImportingTsExtensions": true + } +} diff --git a/tests/fixtures/no-missing/ts-allow-extension/file.ts b/tests/fixtures/no-missing/ts-allow-extension/file.ts new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/no-missing/ts-allow-extension/tsconfig.json b/tests/fixtures/no-missing/ts-allow-extension/tsconfig.json new file mode 100644 index 00000000..32d0a2a9 --- /dev/null +++ b/tests/fixtures/no-missing/ts-allow-extension/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "noEmit": true, + "allowImportingTsExtensions": true + } +} diff --git a/tests/lib/rules/file-extension-in-import.js b/tests/lib/rules/file-extension-in-import.js index 4e2323e7..ff2cb5fb 100644 --- a/tests/lib/rules/file-extension-in-import.js +++ b/tests/lib/rules/file-extension-in-import.js @@ -193,6 +193,17 @@ new RuleTester({ code: "require('./e.js');", settings: { node: { typescriptExtensionMap: tsReactExtensionMap } }, }, + + { + filename: fixture("ts-allow-extension/test.ts"), + code: "require('./file.js');", + env: { node: true }, + }, + { + filename: fixture("ts-allow-extension/test.ts"), + code: "require('./file.ts');", + env: { node: true }, + }, ], invalid: [ { diff --git a/tests/lib/rules/no-missing-import.js b/tests/lib/rules/no-missing-import.js index cb1f87dc..00c577f0 100644 --- a/tests/lib/rules/no-missing-import.js +++ b/tests/lib/rules/no-missing-import.js @@ -290,6 +290,17 @@ ruleTester.run("no-missing-import", rule, { env: { node: true }, }, + { + filename: fixture("ts-allow-extension/test.ts"), + code: "import './file.js';", + env: { node: true }, + }, + { + filename: fixture("ts-allow-extension/test.ts"), + code: "import './file.ts';", + env: { node: true }, + }, + // import() ...(DynamicImportSupported ? [