Skip to content

Commit 8cd047b

Browse files
feat(extensions): implement import path replacement for consistent file extensions
1 parent b8e7370 commit 8cd047b

File tree

1 file changed

+54
-5
lines changed

1 file changed

+54
-5
lines changed

src/rules/extensions.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,33 @@ module.exports = {
183183
}
184184
}
185185

186+
function replaceImportPath(source, importPath) {
187+
return source.replace(
188+
/^(['"])(.+)\1$/,
189+
(_, quote) => `${quote}${importPath}${quote}`,
190+
)
191+
}
192+
193+
const parsePath = (path) => {
194+
const hashIndex = path.indexOf('#')
195+
const queryIndex = path.indexOf('?')
196+
const hasHash = hashIndex !== -1
197+
const hash = hasHash ? path.slice(hashIndex) : ''
198+
const hasQuery = queryIndex !== -1 && (!hasHash || queryIndex < hashIndex)
199+
const query = hasQuery
200+
? path.slice(queryIndex, hasHash ? hashIndex : undefined)
201+
: ''
202+
const pathname = hasQuery
203+
? path.slice(0, queryIndex)
204+
: hasHash
205+
? path.slice(0, hashIndex)
206+
: path
207+
return { pathname, query, hash }
208+
}
209+
210+
const stringifyPath = ({ pathname, query, hash }) =>
211+
pathname + query + hash
212+
186213
function checkFileExtension(source, node) {
187214
// bail if the declaration doesn't have a source, e.g. "export { foo };", or if it's only partially typed like in an editor
188215
if (!source || !source.value) { return; }
@@ -202,7 +229,11 @@ module.exports = {
202229
// don't enforce anything on builtins
203230
if (!overrideAction && isBuiltIn(importPathWithQueryString, context.settings)) { return; }
204231

205-
const importPath = importPathWithQueryString.replace(/\?(.*)$/, '');
232+
const {
233+
pathname: importPath,
234+
query,
235+
hash,
236+
} = parsePath(importPathWithQueryString)
206237

207238
// don't enforce in root external packages as they may have names with `.js`.
208239
// Like `import Decimal from decimal.js`)
@@ -227,6 +258,19 @@ module.exports = {
227258
const extensionRequired = isUseOfExtensionRequired(extension, !overrideAction && isPackage);
228259
const extensionForbidden = isUseOfExtensionForbidden(extension);
229260
if (extensionRequired && !extensionForbidden) {
261+
const fixedImportPath = stringifyPath({
262+
pathname: `${
263+
/([\\/]|[\\/]?\.?\.)$/.test(importPath)
264+
? `${
265+
importPath.endsWith('/')
266+
? importPath.slice(0, -1)
267+
: importPath
268+
}/index.${extension}`
269+
: `${importPath}.${extension}`
270+
}`,
271+
query,
272+
hash,
273+
})
230274
context.report({
231275
node: source,
232276
message:
@@ -235,7 +279,7 @@ module.exports = {
235279
fix(fixer) {
236280
return fixer.replaceText(
237281
source,
238-
JSON.stringify(`${importPathWithQueryString}.${extension}`),
282+
replaceImportPath(source.raw, fixedImportPath),
239283
);
240284
},
241285
} : {},
@@ -251,9 +295,14 @@ module.exports = {
251295
fix(fixer) {
252296
return fixer.replaceText(
253297
source,
254-
JSON.stringify(
255-
importPath.slice(0, -(extension.length + 1)),
256-
),
298+
replaceImportPath(
299+
source.raw,
300+
stringifyPath({
301+
pathname: importPath.slice(0, -(extension.length + 1)),
302+
query,
303+
hash,
304+
}),
305+
),
257306
);
258307
},
259308
} : {},

0 commit comments

Comments
 (0)