import fs from 'node:fs'; import module from 'node:module'; import path from 'node:path'; import isNodeCoreModule from '@nolyfill/is-core-module'; import debug from 'debug'; import { createPathsMatcher, getTsconfig } from 'get-tsconfig'; import { isBunModule } from 'is-bun-module'; import { stableHash } from 'stable-hash'; import { globSync, isDynamicPattern } from 'tinyglobby'; import { ResolverFactory } from 'unrs-resolver'; const IMPORTER_NAME = 'eslint-import-resolver-typescript'; const log = debug(IMPORTER_NAME); export const defaultConditionNames = [ 'types', 'import', 'esm2020', 'es2020', 'es2015', 'require', 'node', 'node-addons', 'browser', 'default', ]; export const defaultExtensions = [ '.ts', '.tsx', '.d.ts', '.js', '.jsx', '.json', '.node', ]; export const defaultExtensionAlias = { '.js': [ '.ts', '.tsx', '.d.ts', '.js', ], '.jsx': ['.tsx', '.d.ts', '.jsx'], '.cjs': ['.cts', '.d.cts', '.cjs'], '.mjs': ['.mts', '.d.mts', '.mjs'], }; export const defaultMainFields = [ 'types', 'typings', 'fesm2020', 'fesm2015', 'esm2020', 'es2020', 'module', 'jsnext:main', 'main', ]; export const interfaceVersion = 2; const JS_EXT_PATTERN = /\.(?:[cm]js|jsx?)$/; const RELATIVE_PATH_PATTERN = /^\.{1,2}(?:\/.*)?$/; let previousOptionsHash; let optionsHash; let cachedOptions; let cachedCwd; let mappersCachedOptions; let mappers = []; let resolverCachedOptions; let cachedResolver; export function resolve(source, file, options, resolver) { if (!cachedOptions || previousOptionsHash !== (optionsHash = stableHash(options))) { previousOptionsHash = optionsHash; cachedOptions = { ...options, conditionNames: options?.conditionNames ?? defaultConditionNames, extensions: options?.extensions ?? defaultExtensions, extensionAlias: options?.extensionAlias ?? defaultExtensionAlias, mainFields: options?.mainFields ?? defaultMainFields, }; } if (!resolver) { if (!cachedResolver || resolverCachedOptions !== cachedOptions) { cachedResolver = new ResolverFactory(cachedOptions); resolverCachedOptions = cachedOptions; } resolver = cachedResolver; } log('looking for', source, 'in', file); source = removeQuerystring(source); if (isNodeCoreModule(source) || isBunModule(source)) { log('matched core:', source); return { found: true, path: null, }; } if (process.versions.pnp && source === 'pnpapi') { return { found: true, path: module.findPnpApi(file).resolveToUnqualified(source, file, { considerBuiltins: false, }), }; } initMappers(cachedOptions); let mappedPaths = getMappedPaths(source, file, cachedOptions.extensions, true); if (mappedPaths.length > 0) { log('matched ts path:', ...mappedPaths); } else { mappedPaths = [source]; } let foundNodePath; for (const mappedPath of mappedPaths) { try { const resolved = resolver.sync(path.dirname(path.resolve(file)), mappedPath); if (resolved.path) { foundNodePath = resolved.path; break; } } catch { log('failed to resolve with', mappedPath); } } if ((JS_EXT_PATTERN.test(foundNodePath) || (cachedOptions.alwaysTryTypes && !foundNodePath)) && !/^@types[/\\]/.test(source) && !path.isAbsolute(source) && !source.startsWith('.')) { const definitelyTyped = resolve('@types' + path.sep + mangleScopedPackage(source), file, options); if (definitelyTyped.found) { return definitelyTyped; } } if (foundNodePath) { log('matched node path:', foundNodePath); return { found: true, path: foundNodePath, }; } log("didn't find ", source); return { found: false, }; } export function createTypeScriptImportResolver(options) { const resolver = new ResolverFactory({ ...options, conditionNames: options?.conditionNames ?? defaultConditionNames, extensions: options?.extensions ?? defaultExtensions, extensionAlias: options?.extensionAlias ?? defaultExtensionAlias, mainFields: options?.mainFields ?? defaultMainFields, }); return { interfaceVersion: 3, name: IMPORTER_NAME, resolve(source, file) { return resolve(source, file, options, resolver); }, }; } function removeQuerystring(id) { const querystringIndex = id.lastIndexOf('?'); if (querystringIndex !== -1) { return id.slice(0, querystringIndex); } return id; } const isFile = (path) => { try { return !!(path && fs.statSync(path, { throwIfNoEntry: false })?.isFile()); } catch { return false; } }; const isModule = (modulePath) => !!modulePath && isFile(path.resolve(modulePath, 'package.json')); function getMappedPaths(source, file, extensions = defaultExtensions, retry) { const originalExtensions = extensions; extensions = ['', ...extensions]; let paths = []; if (RELATIVE_PATH_PATTERN.test(source)) { const resolved = path.resolve(path.dirname(file), source); if (isFile(resolved)) { paths = [resolved]; } } else { let mapperFns = mappers .filter(({ files }) => files.has(file)) .map(({ mapperFn }) => mapperFn); if (mapperFns.length === 0) { mapperFns = mappers .map(mapper => ({ mapperFn: mapper.mapperFn, counter: equalChars(path.dirname(file), path.dirname(mapper.path)), })) .sort((a, b) => b.counter - a.counter) .map(({ mapperFn }) => mapperFn); } paths = mapperFns .map(mapperFn => mapperFn(source).map(item => [ ...extensions.map(ext => `${item}${ext}`), ...originalExtensions.map(ext => `${item}/index${ext}`), ])) .flat(2) .map(toNativePathSeparator) .filter(mappedPath => { try { const stat = fs.statSync(mappedPath, { throwIfNoEntry: false }); if (stat === undefined) { return false; } if (stat.isFile()) { return true; } if (stat.isDirectory()) { return isModule(mappedPath); } } catch { return false; } return false; }); } if (retry && paths.length === 0) { 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, ''); let resolved = getMappedPaths(basename + tsExt, file); if (resolved.length === 0 && jsExt === '.js') { const tsxExt = jsExt.replace('js', 'tsx'); resolved = getMappedPaths(basename + tsxExt, file); } if (resolved.length === 0) { resolved = getMappedPaths(basename + '.d' + (tsExt === '.tsx' ? '.ts' : tsExt), file); } if (resolved.length > 0) { return resolved; } } for (const ext of extensions) { const mappedPaths = isJs ? [] : getMappedPaths(source + ext, file); const resolved = mappedPaths.length > 0 ? mappedPaths : getMappedPaths(source + `/index${ext}`, file); if (resolved.length > 0) { return resolved; } } } return paths; } function initMappers(options) { if (mappers.length > 0 && mappersCachedOptions === options && cachedCwd === process.cwd()) { return; } cachedCwd = process.cwd(); const configPaths = (typeof options.project === 'string' ? [options.project] : Array.isArray(options.project) ? options.project : [cachedCwd]) .map(config => replacePathSeparator(config, path.sep, path.posix.sep)); const defaultInclude = ['**/*']; const defaultIgnore = ['**/node_modules/**']; const projectPaths = [ ...new Set([ ...configPaths .filter(p => !isDynamicPattern(p)) .map(p => path.resolve(process.cwd(), p)), ...globSync(configPaths.filter(path => isDynamicPattern(path)), { absolute: true, dot: true, expandDirectories: false, ignore: defaultIgnore, }), ]), ]; mappers = projectPaths .map(projectPath => { let tsconfigResult; if (isFile(projectPath)) { const { dir, base } = path.parse(projectPath); tsconfigResult = getTsconfig(dir, base); } else { tsconfigResult = getTsconfig(projectPath); } if (!tsconfigResult) { return; } const mapperFn = createPathsMatcher(tsconfigResult); if (!mapperFn) { return; } const files = tsconfigResult.config.files == null && tsconfigResult.config.include == null ? globSync(defaultInclude, { absolute: true, cwd: path.dirname(tsconfigResult.path), dot: true, ignore: [ ...(tsconfigResult.config.exclude ?? []), ...defaultIgnore, ], }) : [ ...(tsconfigResult.config.files != null && tsconfigResult.config.files.length > 0 ? tsconfigResult.config.files.map(file => path.normalize(path.resolve(path.dirname(tsconfigResult.path), file))) : []), ...(tsconfigResult.config.include != null && tsconfigResult.config.include.length > 0 ? globSync(tsconfigResult.config.include, { absolute: true, cwd: path.dirname(tsconfigResult.path), dot: true, ignore: [ ...(tsconfigResult.config.exclude ?? []), ...defaultIgnore, ], }) : []), ]; return { path: toNativePathSeparator(tsconfigResult.path), files: new Set(files.map(toNativePathSeparator)), mapperFn, }; }) .filter(Boolean); mappersCachedOptions = options; } function mangleScopedPackage(moduleName) { if (moduleName.startsWith('@')) { const replaceSlash = moduleName.replace(path.sep, '__'); if (replaceSlash !== moduleName) { return replaceSlash.slice(1); } } return moduleName; } function replacePathSeparator(p, from, to) { return from === to ? p : p.replaceAll(from, to); } function toNativePathSeparator(p) { return replacePathSeparator(p, path[process.platform === 'win32' ? 'posix' : 'win32'].sep, path[process.platform === 'win32' ? 'win32' : 'posix'].sep); } function equalChars(a, b) { if (a.length === 0 || b.length === 0) { return 0; } let i = 0; const length = Math.min(a.length, b.length); while (i < length && a.charAt(i) === b.charAt(i)) { i += 1; } return i; } //# sourceMappingURL=index.js.map