diff --git a/lib/rules/file-extension-in-import.js b/lib/rules/file-extension-in-import.js index 19acf78f..19a46c9f 100644 --- a/lib/rules/file-extension-in-import.js +++ b/lib/rules/file-extension-in-import.js @@ -7,6 +7,7 @@ const path = require("path") const fs = require("fs") const { convertTsExtensionToJs } = require("../util/map-typescript-extension") +const getTryExtensions = require("../util/get-try-extensions") const visitImport = require("../util/visit-import") /** @@ -29,6 +30,34 @@ function getExistingExtensions(filePath) { } } +/** + * Get the extension of an index file in the given directory. + * @param {string} directoryPath The directory path to check. + * @param {string[]} tryExtensions Ordered extension preferences. + * @returns {string | null} The index file extension or null if none. + */ +function getIndexExtension(directoryPath, tryExtensions) { + try { + if (fs.statSync(directoryPath).isDirectory() === false) { + return null + } + } catch { + return null + } + + const existing = getExistingExtensions(path.join(directoryPath, "index")) + if (existing.length === 0) { + return null + } + + const preferred = tryExtensions.find(ext => existing.includes(ext)) + if (preferred) { + return preferred + } + + return existing[0] || null +} + /** * @typedef {[ * ("always" | "never")?, @@ -69,6 +98,7 @@ module.exports = { } const defaultStyle = context.options[0] || "always" const overrideStyle = context.options[1] || {} + const tryExtensions = getTryExtensions(context, 1) /** * @param {import("../util/import-target.js")} target @@ -88,11 +118,24 @@ module.exports = { const actualExt = path.extname(filePath) const style = overrideStyle[actualExt] || defaultStyle - const expectedExt = convertTsExtensionToJs( + let expectedExt = convertTsExtensionToJs( context, filePath, actualExt ) + let isDirectoryImport = false + + if (currentExt === "" && actualExt === "") { + const indexExt = getIndexExtension(filePath, tryExtensions) + if (indexExt) { + isDirectoryImport = true + expectedExt = convertTsExtensionToJs( + context, + path.join(filePath, `index${indexExt}`), + indexExt + ) + } + } // Verify. if (style === "always" && currentExt !== expectedExt) { @@ -106,6 +149,13 @@ module.exports = { } const index = node.range[1] - 1 + if (isDirectoryImport) { + const needsSlash = /[/\\]$/.test(name) ? "" : "/" + return fixer.insertTextBeforeRange( + [index, index], + `${needsSlash}index${expectedExt}` + ) + } return fixer.insertTextBeforeRange( [index, index], expectedExt diff --git a/tests/fixtures/file-extension-in-import/my-folder/index.js b/tests/fixtures/file-extension-in-import/my-folder/index.js new file mode 100644 index 00000000..6990ab7c --- /dev/null +++ b/tests/fixtures/file-extension-in-import/my-folder/index.js @@ -0,0 +1 @@ +export const util = () => {} diff --git a/tests/lib/rules/file-extension-in-import.js b/tests/lib/rules/file-extension-in-import.js index e14b48f5..62171f80 100644 --- a/tests/lib/rules/file-extension-in-import.js +++ b/tests/lib/rules/file-extension-in-import.js @@ -222,6 +222,18 @@ new RuleTester({ output: "import './d.js'", errors: [{ messageId: "requireExt", data: { ext: ".js" } }], }, + { + filename: fixture("test.js"), + code: "import { util } from './my-folder'", + output: "import { util } from './my-folder/index.js'", + errors: [{ messageId: "requireExt", data: { ext: ".js" } }], + }, + { + filename: fixture("test.js"), + code: "import { util } from './my-folder/'", + output: "import { util } from './my-folder/index.js'", + errors: [{ messageId: "requireExt", data: { ext: ".js" } }], + }, { filename: fixture("test.js"), code: "import './b'",