Skip to content

Commit

Permalink
refactor: support custom extensions on resolving (#128)
Browse files Browse the repository at this point in the history
* refactor: support custom extensions on resolving

* chore: do not try extensions which already has js extension

* feat: try extensionless file by default
  • Loading branch information
JounQin committed Jul 3, 2022
1 parent ca59b47 commit 56775b3
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-papayas-smile.md
@@ -0,0 +1,5 @@
---
"eslint-import-resolver-typescript": minor
---

refactor: support custom extensions on resolving
5 changes: 5 additions & 0 deletions .changeset/yellow-mice-build.md
@@ -0,0 +1,5 @@
---
"eslint-import-resolver-typescript": minor
---

feat: try extensionless file by default
79 changes: 44 additions & 35 deletions src/index.ts
Expand Up @@ -78,6 +78,9 @@ export interface TsResolverOptions

const fileSystem = fs as FileSystem

const JS_EXT_PATTERN = /\.(?:[cm]js|jsx?)$/
const RELATIVE_PATH_PATTERN = /^\.{1,2}(?:\/.*)?$/

let mappersBuildForOptions: TsResolverOptions
let mappers: Array<((specifier: string) => string[]) | null> | undefined
let resolver: Resolver
Expand All @@ -95,7 +98,14 @@ export function resolve(
found: boolean
path?: string | null
} {
const opts: ResolveOptions & TsResolverOptions = {
const opts: Required<
Pick<
ResolveOptions,
'conditionNames' | 'extensions' | 'mainFields' | 'useSyncFileSystemCalls'
>
> &
ResolveOptions &
TsResolverOptions = {
...options,
extensions: options?.extensions ?? defaultExtensions,
mainFields: options?.mainFields ?? defaultMainFields,
Expand All @@ -122,7 +132,7 @@ export function resolve(

initMappers(opts)

const mappedPath = getMappedPath(source, file, true)
const mappedPath = getMappedPath(source, file, opts.extensions, true)
if (mappedPath) {
log('matched ts path:', mappedPath)
}
Expand All @@ -140,7 +150,7 @@ export function resolve(
// naive attempt at @types/* resolution,
// if path is neither absolute nor relative
if (
(/\.jsx?$/.test(foundNodePath!) ||
(JS_EXT_PATTERN.test(foundNodePath!) ||
(opts.alwaysTryTypes && !foundNodePath)) &&
!/^@types[/\\]/.test(source) &&
!path.isAbsolute(source) &&
Expand Down Expand Up @@ -179,17 +189,17 @@ function resolveExtension(id: string) {
return
}

if (id.endsWith('.mjs')) {
if (id.endsWith('.cjs')) {
return {
path: idWithoutJsExt,
extensions: ['.mts', '.d.mts'],
extensions: ['.cts', '.d.cts'],
}
}

if (id.endsWith('.cjs')) {
if (id.endsWith('.mjs')) {
return {
path: idWithoutJsExt,
extensions: ['.cts', '.d.cts'],
extensions: ['.mts', '.d.mts'],
}
}

Expand All @@ -200,7 +210,7 @@ function resolveExtension(id: string) {

/**
* Like `sync` from `resolve` package, but considers that the module id
* could have a .js or .jsx extension.
* could have a .cjs, .mjs, .js or .jsx extension.
*/
function tsResolve(
source: string,
Expand Down Expand Up @@ -231,10 +241,7 @@ function removeQuerystring(id: string) {
return id
}

const JS_EXT_PATTERN = /\.(?:[cm]js|jsx?)$/
const RELATIVE_PATH_PATTERN = /^\.{1,2}(?:\/.*)?$/

/** Remove .js or .jsx extension from module id. */
/** Remove .cjs, .mjs, .js or .jsx extension from module id. */
function removeJsExtension(id: string) {
return id.replace(JS_EXT_PATTERN, '')
}
Expand All @@ -250,14 +257,19 @@ const isFile = (path?: string | undefined): path is string => {
/**
* @param {string} source the module to resolve; i.e './some-module'
* @param {string} file the importing file's full path; i.e. '/usr/local/bin/file.js'
* @param {string[]} extensions the extensions to try
* @param {boolean} retry should retry on failed to resolve
* @returns The mapped path of the module or undefined
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
function getMappedPath(
source: string,
file: string,
extensions = defaultExtensions,
retry?: boolean,
): string | undefined {
extensions = ['', ...extensions]

let paths: string[] | undefined = []

if (RELATIVE_PATH_PATTERN.test(source)) {
Expand All @@ -268,43 +280,40 @@ function getMappedPath(
} else {
paths = mappers!
.map(mapper =>
mapper?.(source).map(item =>
path.extname(item)
? item
: ['ts', 'tsx', '.d.ts', 'js'].map(ext => `${item}.${ext}`),
),
mapper?.(source).map(item => extensions.map(ext => `${item}${ext}`)),
)
.flat(2)
.filter(isFile)
}

if (retry && paths.length === 0) {
if (JS_EXT_PATTERN.test(source)) {
const isJs = JS_EXT_PATTERN.test(source)
if (isJs) {
const jsExt = path.extname(source)
const tsExt = jsExt.replace('js', 'ts')
const basename = source.replace(JS_EXT_PATTERN, '')
return (

const resolved =
getMappedPath(basename + tsExt, file) ||
getMappedPath(source + '/index.ts', file) ||
getMappedPath(source + '/index.tsx', file) ||
getMappedPath(source + '/index.d.ts', file) ||
getMappedPath(
basename + '.d' + (tsExt === '.tsx' ? '.ts' : tsExt),
file,
) ||
getMappedPath(source + '/index.js', file)
)
)

if (resolved) {
return resolved
}
}

for (const ext of extensions) {
const resolved =
(isJs ? null : getMappedPath(source + ext, file)) ||
getMappedPath(source + `/index${ext}`, file)

if (resolved) {
return resolved
}
}
return (
getMappedPath(source + '.ts', file) ||
getMappedPath(source + '.tsx', file) ||
getMappedPath(source + '.js', file) ||
getMappedPath(source + '.d.ts', file) ||
getMappedPath(source + '/index.ts', file) ||
getMappedPath(source + '/index.tsx', file) ||
getMappedPath(source + '/index.d.ts', file) ||
getMappedPath(source + '/index.js', file)
)
}

if (paths.length > 1) {
Expand Down

0 comments on commit 56775b3

Please sign in to comment.