Skip to content

Commit fcc8883

Browse files
feat: better algorithm for custom tsconfig paths (#345)
* test(fix): tsconfig include typo * feat: better algorithm for custom tsconfig paths * perf: ignore mapper if files is empty * chore: increase size limit to 3kB * chore: changeset
1 parent c9cfe70 commit fcc8883

File tree

4 files changed

+161
-45
lines changed

4 files changed

+161
-45
lines changed

.changeset/cuddly-kiwis-care.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-import-resolver-typescript': minor
3+
---
4+
5+
Enable the mapper function just for a set of allowed files. Improves project discovery using glob and POSIX separator.

.size-limit.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
33
"path": "./lib/index.js",
4-
"limit": "2.8kB"
4+
"limit": "3kB"
55
}
66
]

src/index.ts

+154-43
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ let cachedOptions: InternalResolverOptions | undefined
111111
let prevCwd: string
112112

113113
let mappersCachedOptions: InternalResolverOptions
114-
let mappers: Array<((specifier: string) => string[]) | null> | undefined
114+
let mappers: Array<{
115+
files: Set<string>
116+
mapperFn: NonNullable<ReturnType<typeof createPathsMatcher>>
117+
}> = []
115118

116119
let resolverCachedOptions: InternalResolverOptions
117120
let cachedResolver: Resolver | undefined
@@ -159,7 +162,7 @@ export function resolve(
159162
resolver = cachedResolver
160163
}
161164

162-
log('looking for:', source)
165+
log('looking for', source, 'in', file)
163166

164167
source = removeQuerystring(source)
165168

@@ -300,34 +303,35 @@ function getMappedPath(
300303
paths = [resolved]
301304
}
302305
} else {
303-
paths = mappers!
304-
.map(mapper =>
305-
mapper?.(source).map(item => [
306-
...extensions.map(ext => `${item}${ext}`),
307-
...originalExtensions.map(ext => `${item}/index${ext}`),
308-
]),
309-
)
310-
.flat(2)
311-
.filter(mappedPath => {
312-
if (mappedPath === undefined) {
313-
return false
314-
}
315-
316-
try {
317-
const stat = fs.statSync(mappedPath, { throwIfNoEntry: false })
318-
if (stat === undefined) return false
319-
if (stat.isFile()) return true
320-
321-
// Maybe this is a module dir?
322-
if (stat.isDirectory()) {
323-
return isModule(mappedPath)
324-
}
325-
} catch {
326-
return false
306+
paths = [
307+
...new Set(
308+
mappers
309+
.filter(({ files }) => files.has(file))
310+
.map(({ mapperFn }) =>
311+
mapperFn(source).map(item => [
312+
...extensions.map(ext => `${item}${ext}`),
313+
...originalExtensions.map(ext => `${item}/index${ext}`),
314+
]),
315+
)
316+
.flat(2)
317+
.map(toNativePathSeparator),
318+
),
319+
].filter(mappedPath => {
320+
try {
321+
const stat = fs.statSync(mappedPath, { throwIfNoEntry: false })
322+
if (stat === undefined) return false
323+
if (stat.isFile()) return true
324+
325+
// Maybe this is a module dir?
326+
if (stat.isDirectory()) {
327+
return isModule(mappedPath)
327328
}
328-
329+
} catch {
329330
return false
330-
})
331+
}
332+
333+
return false
334+
})
331335
}
332336

333337
if (retry && paths.length === 0) {
@@ -367,50 +371,114 @@ function getMappedPath(
367371
return paths[0]
368372
}
369373

374+
// eslint-disable-next-line sonarjs/cognitive-complexity
370375
function initMappers(options: InternalResolverOptions) {
371376
if (
372-
mappers &&
377+
mappers.length > 0 &&
373378
mappersCachedOptions === options &&
374379
prevCwd === process.cwd()
375380
) {
376381
return
377382
}
378383
prevCwd = process.cwd()
379384

380-
const configPaths =
385+
const configPaths = (
381386
typeof options.project === 'string'
382387
? [options.project]
383388
: Array.isArray(options.project)
384389
? options.project
385390
: [process.cwd()]
391+
) // 'tinyglobby' pattern must have POSIX separator
392+
.map(config => replacePathSeparator(config, path.sep, path.posix.sep))
386393

387-
const ignore = ['!**/node_modules/**']
394+
// https://github.com/microsoft/TypeScript/blob/df342b7206cb56b56bb3b3aecbb2ee2d2ff7b217/src/compiler/commandLineParser.ts#L3006
395+
const defaultInclude = ['**/*']
396+
const defaultIgnore = ['**/node_modules/**']
388397

389-
// turn glob patterns into paths
398+
// Turn glob patterns into paths
390399
const projectPaths = [
391400
...new Set([
392401
...configPaths.filter(path => !isDynamicPattern(path)),
393402
...globSync(
394-
[...configPaths.filter(path => isDynamicPattern(path)), ...ignore],
403+
configPaths.filter(path => isDynamicPattern(path)),
395404
{
396405
expandDirectories: false,
406+
ignore: defaultIgnore,
407+
absolute: true,
397408
},
398409
),
399410
]),
400411
]
401412

402-
mappers = projectPaths.map(projectPath => {
403-
let tsconfigResult: TsConfigResult | null
413+
mappers = projectPaths
414+
.map(projectPath => {
415+
let tsconfigResult: TsConfigResult | null
404416

405-
if (isFile(projectPath)) {
406-
const { dir, base } = path.parse(projectPath)
407-
tsconfigResult = getTsconfig(dir, base)
408-
} else {
409-
tsconfigResult = getTsconfig(projectPath)
410-
}
417+
if (isFile(projectPath)) {
418+
const { dir, base } = path.parse(projectPath)
419+
tsconfigResult = getTsconfig(dir, base)
420+
} else {
421+
tsconfigResult = getTsconfig(projectPath)
422+
}
411423

412-
return tsconfigResult && createPathsMatcher(tsconfigResult)
413-
})
424+
if (!tsconfigResult) {
425+
// eslint-disable-next-line unicorn/no-useless-undefined
426+
return undefined
427+
}
428+
429+
const mapperFn = createPathsMatcher(tsconfigResult)
430+
431+
if (!mapperFn) {
432+
// eslint-disable-next-line unicorn/no-useless-undefined
433+
return undefined
434+
}
435+
436+
const files =
437+
tsconfigResult.config.files === undefined &&
438+
tsconfigResult.config.include === undefined
439+
? // Include everything if no files or include options
440+
globSync(defaultInclude, {
441+
ignore: [
442+
...(tsconfigResult.config.exclude ?? []),
443+
...defaultIgnore,
444+
],
445+
absolute: true,
446+
cwd: path.dirname(tsconfigResult.path),
447+
})
448+
: [
449+
// https://www.typescriptlang.org/tsconfig/#files
450+
...(tsconfigResult.config.files !== undefined &&
451+
tsconfigResult.config.files.length > 0
452+
? tsconfigResult.config.files.map(file =>
453+
path.normalize(
454+
path.resolve(path.dirname(tsconfigResult!.path), file),
455+
),
456+
)
457+
: []),
458+
// https://www.typescriptlang.org/tsconfig/#include
459+
...(tsconfigResult.config.include !== undefined &&
460+
tsconfigResult.config.include.length > 0
461+
? globSync(tsconfigResult.config.include, {
462+
ignore: [
463+
...(tsconfigResult.config.exclude ?? []),
464+
...defaultIgnore,
465+
],
466+
absolute: true,
467+
})
468+
: []),
469+
]
470+
471+
if (files.length === 0) {
472+
// eslint-disable-next-line unicorn/no-useless-undefined
473+
return undefined
474+
}
475+
476+
return {
477+
files: new Set(files.map(toNativePathSeparator)),
478+
mapperFn,
479+
}
480+
})
481+
.filter(isDefined)
414482

415483
mappersCachedOptions = options
416484
}
@@ -427,3 +495,46 @@ function mangleScopedPackage(moduleName: string) {
427495
}
428496
return moduleName
429497
}
498+
499+
/**
500+
* Replace path `p` from `from` to `to` separator.
501+
*
502+
* @param {string} p Path
503+
* @param {typeof path.sep} from From separator
504+
* @param {typeof path.sep} to To separator
505+
* @returns Path with `to` separator
506+
*/
507+
function replacePathSeparator(
508+
p: string,
509+
from: typeof path.sep,
510+
to: typeof path.sep,
511+
) {
512+
return from === to ? p : p.replaceAll(from, to)
513+
}
514+
515+
/**
516+
* Replace path `p` separator to its native separator.
517+
*
518+
* @param {string} p Path
519+
* @returns Path with native separator
520+
*/
521+
function toNativePathSeparator(p: string) {
522+
return replacePathSeparator(
523+
p,
524+
path[process.platform === 'win32' ? 'posix' : 'win32'].sep,
525+
path[process.platform === 'win32' ? 'win32' : 'posix'].sep,
526+
)
527+
}
528+
529+
/**
530+
* Check if value is defined.
531+
*
532+
* Helper function for TypeScript.
533+
* Should be removed when upgrading to TypeScript >= 5.5.
534+
*
535+
* @param {T | null | undefined} value Value
536+
* @returns `true` if value is defined, `false` otherwise
537+
*/
538+
function isDefined<T>(value: T | null | undefined): value is T {
539+
return value !== null && value !== undefined
540+
}

tests/withJsExtension/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"#/*": ["*"]
77
}
88
},
9-
"includes": ["./**/*"]
9+
"include": ["./**/*"]
1010
}

0 commit comments

Comments
 (0)