Download web/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js from Amitkumar001/Law_Slm: direct link, hf CLI and curl.
- Browser
- Download file 28.3 kB
-
https://huggingface.co/Amitkumar001/Law_Slm/resolve/main/web/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js
- Command line
-
hf download hf://Amitkumar001/Law_Slm/web/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js
-
curl -L -o TsconfigPathsPlugin.js https://huggingface.co/Amitkumar001/Law_Slm/resolve/main/web/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js
28.3 kB
| /* | |
| MIT License http://www.opensource.org/licenses/mit-license.php | |
| Author Natsu @xiaoxiaojx | |
| */ | |
| ; | |
| const { aliasResolveHandler, compileAliasOptions } = require("./AliasUtils"); | |
| const { modulesResolveHandler } = require("./ModulesUtils"); | |
| const { readJson } = require("./util/fs"); | |
| const { PathType: _PathType, isSubPath, normalize } = require("./util/path"); | |
| /** @typedef {import("./Resolver")} Resolver */ | |
| /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ | |
| /** @typedef {import("./AliasUtils").AliasOption} AliasOption */ | |
| /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ | |
| /** @typedef {import("./Resolver").ResolveContext} ResolveContext */ | |
| /** @typedef {import("./Resolver").FileSystem} FileSystem */ | |
| /** @typedef {import("./Resolver").TsconfigPathsData} TsconfigPathsData */ | |
| /** @typedef {import("./Resolver").TsconfigPathsMap} TsconfigPathsMap */ | |
| /** @typedef {import("./ResolverFactory").TsconfigOptions} TsconfigOptions */ | |
| // Sentinel stored in `_contextSelectionCache` for `requestPath`s whose | |
| // scan returned `null` ("no context matched"). Using a non-null marker | |
| // lets the cache-hit path be a single `Map.get()` — we distinguish | |
| // "cached null" from "not cached yet" without a second `has` lookup. | |
| const NULL_CONTEXT = Symbol("NULL_CONTEXT"); | |
| /** | |
| * Per-`TsconfigPathsMap` memoization of `_selectPathsDataForContext`. | |
| * | |
| * Real-world builds resolve hundreds of requests per source file (every | |
| * import in the file), and webpack-style resolvers walk the same | |
| * `requestPath` (= source-file directory) for each one. Without this | |
| * cache every resolve re-scans the full `contextList` even though the | |
| * answer is invariant for a given `(map, requestPath)` pair. | |
| * | |
| * The outer key is the `TsconfigPathsMap` itself — rebuilt on every | |
| * tsconfig change — so a `WeakMap` lets the inner map be collected | |
| * automatically once the map goes away. The inner Map is keyed by | |
| * `requestPath` (string); a `Symbol` sentinel stands in for "no | |
| * matching context" so `Map.get` alone distinguishes the three states | |
| * (cached data / cached null / not cached). | |
| * @type {WeakMap<TsconfigPathsMap, Map<string, TsconfigPathsData | typeof NULL_CONTEXT>>} | |
| */ | |
| const _contextSelectionCache = new WeakMap(); | |
| /** | |
| * @typedef {object} TsconfigCompilerOptions | |
| * @property {string=} baseUrl Base URL for resolving paths | |
| * @property {{ [key: string]: string[] }=} paths TypeScript paths mapping | |
| */ | |
| /** | |
| * @typedef {object} TsconfigReference | |
| * @property {string} path Path to the referenced project | |
| */ | |
| /** | |
| * @typedef {object} Tsconfig | |
| * @property {TsconfigCompilerOptions=} compilerOptions Compiler options | |
| * @property {string | string[]=} extends Extended configuration paths | |
| * @property {TsconfigReference[]=} references Project references | |
| */ | |
| const DEFAULT_CONFIG_FILE = "tsconfig.json"; | |
| const READ_JSON_OPTIONS = { stripComments: true }; | |
| // Trailing `/*` or `\*` segment of a tsconfig `paths` mapping (e.g. | |
| // `./src/*` → `./src`). Hoisted so we don't allocate a fresh regex per | |
| // path entry on every tsconfig load — and so the same regex object can be | |
| // reused for the matching `test` + `replace` pair below. | |
| const WILDCARD_TAIL_RE = /[/\\]\*$/; | |
| /** | |
| * @param {string} pattern Path pattern | |
| * @returns {number} Length of the prefix | |
| */ | |
| function getPrefixLength(pattern) { | |
| const prefixLength = pattern.indexOf("*"); | |
| if (prefixLength === -1) { | |
| return pattern.length; | |
| } | |
| return prefixLength; | |
| } | |
| /** | |
| * Sort path patterns. | |
| * If a module name can be matched with multiple patterns then pattern with the longest prefix will be picked. | |
| * @param {string[]} arr Array of path patterns | |
| * @returns {string[]} Array of path patterns sorted by longest prefix | |
| */ | |
| function sortByLongestPrefix(arr) { | |
| return [...arr].sort((a, b) => getPrefixLength(b) - getPrefixLength(a)); | |
| } | |
| /** | |
| * Merge two tsconfig objects | |
| * @param {Tsconfig | null} base base config | |
| * @param {Tsconfig | null} config config to merge | |
| * @returns {Tsconfig} merged config | |
| */ | |
| function mergeTsconfigs(base, config) { | |
| base = base || {}; | |
| config = config || {}; | |
| return { | |
| ...base, | |
| ...config, | |
| compilerOptions: { | |
| .../** @type {TsconfigCompilerOptions} */ (base.compilerOptions), | |
| .../** @type {TsconfigCompilerOptions} */ (config.compilerOptions), | |
| }, | |
| }; | |
| } | |
| /** | |
| * Substitute ${configDir} template variable in path | |
| * @param {string} pathValue the path value | |
| * @param {string} configDir the config directory | |
| * @returns {string} the path with substituted template | |
| */ | |
| function substituteConfigDir(pathValue, configDir) { | |
| // eslint-disable-next-line no-template-curly-in-string | |
| if (!pathValue.includes("${configDir}")) return pathValue; | |
| return pathValue.replace(/\$\{configDir\}/g, configDir); | |
| } | |
| /** | |
| * Convert tsconfig paths to resolver options | |
| * @param {string} configDir Config file directory | |
| * @param {{ [key: string]: string[] }} paths TypeScript paths mapping | |
| * @param {Resolver} resolver resolver instance | |
| * @param {string=} baseUrl Base URL for resolving paths (relative to configDir) | |
| * @returns {TsconfigPathsData} the resolver options | |
| */ | |
| function tsconfigPathsToResolveOptions(configDir, paths, resolver, baseUrl) { | |
| // Calculate absolute base URL | |
| const absoluteBaseUrl = !baseUrl | |
| ? configDir | |
| : resolver.join(configDir, baseUrl); | |
| /** @type {string[]} */ | |
| const sortedKeys = sortByLongestPrefix(Object.keys(paths)); | |
| /** @type {AliasOption[]} */ | |
| const alias = []; | |
| /** @type {string[]} */ | |
| const modules = []; | |
| for (const pattern of sortedKeys) { | |
| const mappings = paths[pattern]; | |
| // Substitute ${configDir} in path mappings | |
| const absolutePaths = mappings.map((mapping) => { | |
| const substituted = substituteConfigDir(mapping, configDir); | |
| return resolver.join(absoluteBaseUrl, substituted); | |
| }); | |
| if (absolutePaths.length > 0) { | |
| if (pattern === "*") { | |
| // Pull `dir/*` entries directly into `modules` with their | |
| // trailing wildcard stripped, skipping anything else. The | |
| // previous `.map(...).filter(Boolean)` form allocated two | |
| // throwaway arrays plus a spread iterator per `*` mapping. | |
| for (let j = 0; j < absolutePaths.length; j++) { | |
| const dir = absolutePaths[j]; | |
| if (WILDCARD_TAIL_RE.test(dir)) { | |
| modules.push(dir.replace(WILDCARD_TAIL_RE, "")); | |
| } | |
| } | |
| } else { | |
| alias.push({ name: pattern, alias: absolutePaths }); | |
| } | |
| } | |
| } | |
| if (baseUrl && absoluteBaseUrl && !modules.includes(absoluteBaseUrl)) { | |
| modules.push(absoluteBaseUrl); | |
| } | |
| return { | |
| alias: compileAliasOptions(resolver, alias), | |
| modules, | |
| }; | |
| } | |
| /** | |
| * Get the base context for the current project | |
| * @param {string} context the context | |
| * @param {Resolver} resolver resolver instance | |
| * @param {string=} baseUrl base URL for resolving paths | |
| * @returns {string} the base context | |
| */ | |
| function getAbsoluteBaseUrl(context, resolver, baseUrl) { | |
| return !baseUrl ? context : resolver.join(context, baseUrl); | |
| } | |
| /** | |
| * @param {TsconfigPathsData} main main paths data | |
| * @param {string} mainContext main context | |
| * @param {{ [baseUrl: string]: TsconfigPathsData }} refs references map | |
| * @param {Set<string>} fileDependencies file dependencies | |
| * @returns {TsconfigPathsMap} the tsconfig paths map | |
| */ | |
| function buildTsconfigPathsMap(main, mainContext, refs, fileDependencies) { | |
| const allContexts = /** @type {{ [context: string]: TsconfigPathsData }} */ ({ | |
| [mainContext]: main, | |
| ...refs, | |
| }); | |
| // Precompute the key list once per tsconfig load. `_selectPathsDataForContext` | |
| // runs per resolve and otherwise would call `Object.entries(allContexts)` | |
| // each time, allocating a fresh [key, value][] array. | |
| const contextList = Object.keys(allContexts); | |
| return { | |
| main, | |
| mainContext, | |
| refs, | |
| allContexts, | |
| contextList, | |
| fileDependencies, | |
| }; | |
| } | |
| module.exports = class TsconfigPathsPlugin { | |
| /** | |
| * @param {true | string | TsconfigOptions} configFileOrOptions tsconfig file path or options object | |
| */ | |
| constructor(configFileOrOptions) { | |
| if ( | |
| typeof configFileOrOptions === "object" && | |
| configFileOrOptions !== null | |
| ) { | |
| // Options object format | |
| const { configFile } = configFileOrOptions; | |
| /** @type {boolean} */ | |
| this.isAutoConfigFile = typeof configFile !== "string"; | |
| /** @type {string} */ | |
| this.configFile = this.isAutoConfigFile | |
| ? DEFAULT_CONFIG_FILE | |
| : /** @type {string} */ (configFile); | |
| /** @type {string[] | "auto"} */ | |
| if (Array.isArray(configFileOrOptions.references)) { | |
| /** @type {TsconfigReference[] | "auto"} */ | |
| this.references = configFileOrOptions.references.map((ref) => ({ | |
| path: ref, | |
| })); | |
| } else if (configFileOrOptions.references === "auto") { | |
| this.references = "auto"; | |
| } else { | |
| this.references = []; | |
| } | |
| /** @type {string | undefined} */ | |
| this.baseUrl = configFileOrOptions.baseUrl; | |
| } else { | |
| /** @type {boolean} */ | |
| this.isAutoConfigFile = configFileOrOptions === true; | |
| /** @type {string} */ | |
| this.configFile = this.isAutoConfigFile | |
| ? DEFAULT_CONFIG_FILE | |
| : /** @type {string} */ (configFileOrOptions); | |
| /** @type {TsconfigReference[] | "auto"} */ | |
| this.references = []; | |
| /** @type {string | undefined} */ | |
| this.baseUrl = undefined; | |
| } | |
| } | |
| /** | |
| * @param {Resolver} resolver the resolver | |
| * @returns {void} | |
| */ | |
| apply(resolver) { | |
| const aliasTarget = resolver.ensureHook("internal-resolve"); | |
| const moduleTarget = resolver.ensureHook("module"); | |
| resolver | |
| .getHook("raw-resolve") | |
| .tapAsync("TsconfigPathsPlugin", (request, resolveContext, callback) => { | |
| this._getTsconfigPathsMap( | |
| resolver, | |
| request, | |
| resolveContext, | |
| (err, tsconfigPathsMap) => { | |
| if (err) return callback(err); | |
| if (!tsconfigPathsMap) return callback(); | |
| const selectedData = this._selectPathsDataForContext( | |
| request.path, | |
| tsconfigPathsMap, | |
| ); | |
| if (!selectedData) return callback(); | |
| aliasResolveHandler( | |
| resolver, | |
| selectedData.alias, | |
| aliasTarget, | |
| request, | |
| resolveContext, | |
| (err, result) => { | |
| if (err) return callback(err); | |
| if (result) return callback(null, result); | |
| // https://github.com/webpack/webpack/issues/20944 | |
| // Unlike resolve.alias, tsconfig paths should fall through | |
| // when a pattern matches but the mapped path does not exist | |
| // (matching TypeScript's native resolution behavior). | |
| return callback(); | |
| }, | |
| ); | |
| }, | |
| ); | |
| }); | |
| resolver | |
| .getHook("raw-module") | |
| .tapAsync("TsconfigPathsPlugin", (request, resolveContext, callback) => { | |
| this._getTsconfigPathsMap( | |
| resolver, | |
| request, | |
| resolveContext, | |
| (err, tsconfigPathsMap) => { | |
| if (err) return callback(err); | |
| if (!tsconfigPathsMap) return callback(); | |
| const selectedData = this._selectPathsDataForContext( | |
| request.path, | |
| tsconfigPathsMap, | |
| ); | |
| if (!selectedData) return callback(); | |
| modulesResolveHandler( | |
| resolver, | |
| selectedData.modules, | |
| moduleTarget, | |
| request, | |
| resolveContext, | |
| callback, | |
| ); | |
| }, | |
| ); | |
| }); | |
| } | |
| /** | |
| * Get TsconfigPathsMap for the request (with caching) | |
| * @param {Resolver} resolver the resolver | |
| * @param {ResolveRequest} request the request | |
| * @param {ResolveContext} resolveContext the resolve context | |
| * @param {(err: Error | null, result?: TsconfigPathsMap | null) => void} callback the callback | |
| * @returns {void} | |
| */ | |
| _getTsconfigPathsMap(resolver, request, resolveContext, callback) { | |
| if (typeof request.tsconfigPathsMap !== "undefined") { | |
| const cached = request.tsconfigPathsMap; | |
| if (!cached) return callback(null, null); | |
| if (resolveContext.fileDependencies) { | |
| for (const fileDependency of cached.fileDependencies) { | |
| resolveContext.fileDependencies.add(fileDependency); | |
| } | |
| } | |
| return callback(null, cached); | |
| } | |
| if (this.isAutoConfigFile) { | |
| this._findTsconfigUpward( | |
| resolver, | |
| request.path || process.cwd(), | |
| (err, result) => { | |
| if (err) { | |
| request.tsconfigPathsMap = null; | |
| return callback(err); | |
| } | |
| if (!result) { | |
| request.tsconfigPathsMap = null; | |
| return callback(null, null); | |
| } | |
| const map = /** @type {TsconfigPathsMap} */ (result); | |
| request.tsconfigPathsMap = map; | |
| if (resolveContext.fileDependencies) { | |
| for (const fileDependency of map.fileDependencies) { | |
| resolveContext.fileDependencies.add(fileDependency); | |
| } | |
| } | |
| callback(null, map); | |
| }, | |
| ); | |
| return; | |
| } | |
| const absTsconfigPath = resolver.join( | |
| request.path || process.cwd(), | |
| this.configFile, | |
| ); | |
| this._loadTsconfigPathsMap(resolver, absTsconfigPath, (err, result) => { | |
| if (err) { | |
| request.tsconfigPathsMap = null; | |
| return callback(err); | |
| } | |
| const map = /** @type {TsconfigPathsMap} */ (result); | |
| request.tsconfigPathsMap = map; | |
| if (resolveContext.fileDependencies) { | |
| for (const fileDependency of map.fileDependencies) { | |
| resolveContext.fileDependencies.add(fileDependency); | |
| } | |
| } | |
| callback(null, map); | |
| }); | |
| } | |
| /** | |
| * Walk up from startDir to the filesystem root looking for tsconfig.json. | |
| * Like TypeScript's own `findConfigFile` / `forEachAncestorDirectory`. | |
| * @param {Resolver} resolver the resolver | |
| * @param {string} startDir the directory to start searching from | |
| * @param {(err: Error | null, result?: TsconfigPathsMap | null) => void} callback the callback | |
| * @returns {void} | |
| */ | |
| _findTsconfigUpward(resolver, startDir, callback) { | |
| const { fileSystem } = resolver; | |
| const configFileName = this.configFile; | |
| /** | |
| * @param {string} dir current directory | |
| * @returns {void} | |
| */ | |
| const check = (dir) => { | |
| const candidate = resolver.join(dir, configFileName); | |
| fileSystem.stat(candidate, (statErr) => { | |
| if (!statErr) { | |
| // Found — load it | |
| this._loadTsconfigPathsMap(resolver, candidate, (loadErr, result) => { | |
| if (loadErr) { | |
| // Auto mode: soft-fail silently. | |
| return callback(null, null); | |
| } | |
| callback(null, result); | |
| }); | |
| return; | |
| } | |
| // Not found — move to parent | |
| const parentDir = resolver.dirname(dir); | |
| if (parentDir === dir) { | |
| // Reached filesystem root, no tsconfig.json found | |
| return callback(null, null); | |
| } | |
| check(parentDir); | |
| }); | |
| }; | |
| check(startDir); | |
| } | |
| /** | |
| * Load tsconfig.json and build complete TsconfigPathsMap | |
| * Includes main project paths and all referenced projects | |
| * @param {Resolver} resolver the resolver | |
| * @param {string} absTsconfigPath absolute path to tsconfig.json | |
| * @param {(err: Error | null, result?: TsconfigPathsMap) => void} callback the callback | |
| * @returns {void} | |
| */ | |
| _loadTsconfigPathsMap(resolver, absTsconfigPath, callback) { | |
| /** @type {Set<string>} */ | |
| const fileDependencies = new Set(); | |
| this._loadTsconfig( | |
| resolver, | |
| absTsconfigPath, | |
| fileDependencies, | |
| undefined, | |
| (err, config) => { | |
| if (err) return callback(err); | |
| const cfg = /** @type {Tsconfig} */ (config); | |
| const compilerOptions = cfg.compilerOptions || {}; | |
| const mainContext = resolver.dirname(absTsconfigPath); | |
| const baseUrl = | |
| this.baseUrl !== undefined ? this.baseUrl : compilerOptions.baseUrl; | |
| const main = tsconfigPathsToResolveOptions( | |
| mainContext, | |
| compilerOptions.paths || {}, | |
| resolver, | |
| baseUrl, | |
| ); | |
| /** @type {{ [baseUrl: string]: TsconfigPathsData }} */ | |
| const refs = {}; | |
| let referencesToUse = null; | |
| if (this.references === "auto") { | |
| referencesToUse = cfg.references; | |
| } else if (Array.isArray(this.references)) { | |
| referencesToUse = this.references; | |
| } | |
| if (!Array.isArray(referencesToUse)) { | |
| return callback( | |
| null, | |
| buildTsconfigPathsMap(main, mainContext, refs, fileDependencies), | |
| ); | |
| } | |
| this._loadTsconfigReferences( | |
| resolver, | |
| mainContext, | |
| referencesToUse, | |
| fileDependencies, | |
| refs, | |
| (refErr) => { | |
| if (refErr) return callback(refErr); | |
| callback( | |
| null, | |
| buildTsconfigPathsMap(main, mainContext, refs, fileDependencies), | |
| ); | |
| }, | |
| ); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Select the correct TsconfigPathsData based on request.path (context-aware) | |
| * Matches the behavior of tsconfig-paths-webpack-plugin | |
| * @param {string | false} requestPath the request path | |
| * @param {TsconfigPathsMap} tsconfigPathsMap the tsconfig paths map | |
| * @returns {TsconfigPathsData | null} the selected paths data | |
| */ | |
| _selectPathsDataForContext(requestPath, tsconfigPathsMap) { | |
| const { main, allContexts, contextList } = tsconfigPathsMap; | |
| if (!requestPath) { | |
| return main; | |
| } | |
| // Single-context tsconfigs (no project references) hit the loop | |
| // below at most once; in that case the cache lookup costs more | |
| // than the loop itself. Only memoize when there are 2+ contexts | |
| // — that's the monorepo / project-references shape where the | |
| // scan actually walks multiple entries per resolve and the | |
| // `(map, requestPath)` answer can be reused. | |
| /** @type {Map<string, TsconfigPathsData | typeof NULL_CONTEXT> | undefined} */ | |
| let perMap; | |
| if (contextList.length >= 2) { | |
| perMap = _contextSelectionCache.get(tsconfigPathsMap); | |
| if (perMap !== undefined) { | |
| const cached = perMap.get(requestPath); | |
| if (cached !== undefined) { | |
| return cached === NULL_CONTEXT | |
| ? null | |
| : /** @type {TsconfigPathsData} */ (cached); | |
| } | |
| } else { | |
| perMap = new Map(); | |
| _contextSelectionCache.set(tsconfigPathsMap, perMap); | |
| } | |
| } | |
| let longestMatchContext = null; | |
| let longestMatchLength = 0; | |
| // Iterate the pre-computed key list (the previous | |
| // `Object.entries(allContexts)` form allocated a fresh | |
| // `[key, value][]` per resolve). Defer the `allContexts[context]` | |
| // lookup to after we know the context actually matches — non-matches | |
| // are the common case and don't need the property access. | |
| for (let i = 0; i < contextList.length; i++) { | |
| const context = contextList[i]; | |
| if (context === requestPath) { | |
| const exact = allContexts[context]; | |
| if (perMap !== undefined) perMap.set(requestPath, exact); | |
| return exact; | |
| } | |
| // Cheap integer-compare gate first: a context can only beat the | |
| // current longest match if its own length is strictly greater. | |
| // Skipping `isSubPath` (a `startsWith` + char-code probe) when the | |
| // length already disqualifies the candidate avoids the per-resolve | |
| // scan over every shorter context. | |
| if ( | |
| context.length > longestMatchLength && | |
| isSubPath(context, requestPath) | |
| ) { | |
| longestMatchContext = context; | |
| longestMatchLength = context.length; | |
| } | |
| } | |
| const result = | |
| longestMatchContext === null ? null : allContexts[longestMatchContext]; | |
| if (perMap !== undefined) { | |
| perMap.set(requestPath, result === null ? NULL_CONTEXT : result); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Load tsconfig from extends path | |
| * @param {Resolver} resolver the resolver | |
| * @param {string} configFilePath current config file path | |
| * @param {string} extendedConfigValue extends value | |
| * @param {Set<string>} fileDependencies the file dependencies | |
| * @param {Set<string>} visitedConfigPaths config paths being loaded (for circular extends detection) | |
| * @param {(err: Error | null, result?: Tsconfig) => void} callback callback | |
| * @returns {void} | |
| */ | |
| _loadTsconfigFromExtends( | |
| resolver, | |
| configFilePath, | |
| extendedConfigValue, | |
| fileDependencies, | |
| visitedConfigPaths, | |
| callback, | |
| ) { | |
| const { fileSystem } = resolver; | |
| const currentDir = resolver.dirname(configFilePath); | |
| // Substitute ${configDir} in extends path | |
| extendedConfigValue = substituteConfigDir(extendedConfigValue, currentDir); | |
| // Remember the original value before potentially appending .json | |
| const originalExtendedConfigValue = extendedConfigValue; | |
| if ( | |
| typeof extendedConfigValue === "string" && | |
| !extendedConfigValue.includes(".json") | |
| ) { | |
| extendedConfigValue += ".json"; | |
| } | |
| const initialExtendedConfigPath = resolver.join( | |
| currentDir, | |
| extendedConfigValue, | |
| ); | |
| /** | |
| * @param {string} extendedConfigPath resolved config path to load | |
| */ | |
| const loadExtended = (extendedConfigPath) => { | |
| this._loadTsconfig( | |
| resolver, | |
| extendedConfigPath, | |
| fileDependencies, | |
| visitedConfigPaths, | |
| (err, config) => { | |
| if (err) return callback(err); | |
| const cfg = /** @type {Tsconfig} */ (config); | |
| const compilerOptions = cfg.compilerOptions || { | |
| baseUrl: undefined, | |
| }; | |
| if (compilerOptions.baseUrl) { | |
| const extendedConfigDir = resolver.dirname(extendedConfigPath); | |
| compilerOptions.baseUrl = getAbsoluteBaseUrl( | |
| extendedConfigDir, | |
| resolver, | |
| compilerOptions.baseUrl, | |
| ); | |
| } | |
| delete cfg.references; | |
| callback(null, cfg); | |
| }, | |
| ); | |
| }; | |
| fileSystem.stat(initialExtendedConfigPath, (existsErr) => { | |
| if (!existsErr) return loadExtended(initialExtendedConfigPath); | |
| // The relative form does not exist — treat the value as a package | |
| // specifier and compute its `node_modules/<...>` sub-path. | |
| let nodeModulesSubPath = null; | |
| if ( | |
| typeof originalExtendedConfigValue === "string" && | |
| originalExtendedConfigValue.startsWith("@") && | |
| originalExtendedConfigValue.split("/").length === 2 | |
| ) { | |
| // Scoped package, no sub-path ("@scope/name") → | |
| // node_modules/@scope/name/tsconfig.json (not @scope/name.json). | |
| // See: test/fixtures/tsconfig-paths/extends-pkg-entry/ | |
| nodeModulesSubPath = `${originalExtendedConfigValue}/${DEFAULT_CONFIG_FILE}`; | |
| } else if ( | |
| extendedConfigValue.includes("/") && | |
| !originalExtendedConfigValue.startsWith(".") && | |
| !originalExtendedConfigValue.startsWith("/") | |
| ) { | |
| // Package sub-path ("react/tsconfig", "@scope/name/tsconfig") → | |
| // node_modules/react/tsconfig.json. | |
| // See: test/fixtures/tsconfig-paths/extends-npm/ | |
| nodeModulesSubPath = extendedConfigValue; | |
| } else if ( | |
| !originalExtendedConfigValue.startsWith(".") && | |
| !originalExtendedConfigValue.startsWith("/") | |
| ) { | |
| // Unscoped package, no sub-path ("my-base-config") → | |
| // node_modules/my-base-config/tsconfig.json. | |
| nodeModulesSubPath = `${originalExtendedConfigValue}/${DEFAULT_CONFIG_FILE}`; | |
| } | |
| // Not a package specifier (relative/absolute) — load the missing | |
| // path anyway so the read surfaces its own ENOENT. | |
| if (nodeModulesSubPath === null) { | |
| return loadExtended(initialExtendedConfigPath); | |
| } | |
| // Walk ancestor node_modules like Node.js/TypeScript so a package | |
| // hoisted to a parent workspace directory is found. #21457 | |
| const subPath = normalize(`node_modules/${nodeModulesSubPath}`); | |
| this._findExtendsInNodeModules(resolver, currentDir, subPath, (found) => { | |
| loadExtended(found || resolver.join(currentDir, subPath)); | |
| }); | |
| }); | |
| } | |
| /** | |
| * Walk up from startDir looking for `<dir>/<subPath>` (a | |
| * `node_modules/...` sub-path), matching Node.js module resolution so a | |
| * package hoisted to a parent workspace's node_modules is found. | |
| * @param {Resolver} resolver the resolver | |
| * @param {string} startDir directory to start searching from | |
| * @param {string} subPath node_modules-relative sub-path to look for | |
| * @param {(found: string | null) => void} callback receives the found path or null | |
| * @returns {void} | |
| */ | |
| _findExtendsInNodeModules(resolver, startDir, subPath, callback) { | |
| const { fileSystem } = resolver; | |
| /** | |
| * @param {string} dir current directory | |
| */ | |
| const check = (dir) => { | |
| const candidate = resolver.join(dir, subPath); | |
| fileSystem.stat(candidate, (statErr) => { | |
| if (!statErr) return callback(candidate); | |
| const parentDir = resolver.dirname(dir); | |
| if (parentDir === dir) return callback(null); | |
| check(parentDir); | |
| }); | |
| }; | |
| check(startDir); | |
| } | |
| /** | |
| * Load referenced tsconfig projects and store in referenceMatchMap | |
| * Simple implementation matching tsconfig-paths-webpack-plugin: | |
| * Just load each reference and store independently | |
| * @param {Resolver} resolver the resolver | |
| * @param {string} context the context | |
| * @param {TsconfigReference[]} references array of references | |
| * @param {Set<string>} fileDependencies the file dependencies | |
| * @param {{ [baseUrl: string]: TsconfigPathsData }} referenceMatchMap the map to populate | |
| * @param {(err: Error | null) => void} callback callback | |
| * @param {Set<string>=} visitedRefPaths visited reference config paths (for circular reference detection) | |
| * @returns {void} | |
| */ | |
| _loadTsconfigReferences( | |
| resolver, | |
| context, | |
| references, | |
| fileDependencies, | |
| referenceMatchMap, | |
| callback, | |
| visitedRefPaths, | |
| ) { | |
| if (references.length === 0) return callback(null); | |
| const visited = visitedRefPaths || new Set(); | |
| let pending = references.length; | |
| const finishOne = () => { | |
| if (--pending === 0) callback(null); | |
| }; | |
| for (const ref of references) { | |
| const refPath = substituteConfigDir(ref.path, context); | |
| const refConfigPath = resolver.join( | |
| resolver.join(context, refPath), | |
| DEFAULT_CONFIG_FILE, | |
| ); | |
| if (visited.has(refConfigPath)) { | |
| finishOne(); | |
| continue; | |
| } | |
| visited.add(refConfigPath); | |
| this._loadTsconfig( | |
| resolver, | |
| refConfigPath, | |
| fileDependencies, | |
| undefined, | |
| (err, refConfig) => { | |
| // Failures are swallowed to match tsconfig-paths-webpack-plugin: | |
| // a broken reference must not abort the main project's resolution. | |
| if (err) return finishOne(); | |
| const cfg = /** @type {Tsconfig} */ (refConfig); | |
| if (cfg.compilerOptions && cfg.compilerOptions.paths) { | |
| const refContext = resolver.dirname(refConfigPath); | |
| referenceMatchMap[refContext] = tsconfigPathsToResolveOptions( | |
| refContext, | |
| cfg.compilerOptions.paths || {}, | |
| resolver, | |
| cfg.compilerOptions.baseUrl, | |
| ); | |
| } | |
| if (this.references === "auto" && Array.isArray(cfg.references)) { | |
| this._loadTsconfigReferences( | |
| resolver, | |
| resolver.dirname(refConfigPath), | |
| cfg.references, | |
| fileDependencies, | |
| referenceMatchMap, | |
| finishOne, | |
| visited, | |
| ); | |
| } else { | |
| finishOne(); | |
| } | |
| }, | |
| ); | |
| } | |
| } | |
| /** | |
| * Load tsconfig.json with extends support | |
| * @param {Resolver} resolver the resolver | |
| * @param {string} configFilePath absolute path to tsconfig.json | |
| * @param {Set<string>} fileDependencies the file dependencies | |
| * @param {Set<string> | undefined} visitedConfigPaths config paths being loaded (for circular extends detection) | |
| * @param {(err: Error | null, result?: Tsconfig) => void} callback callback | |
| * @returns {void} | |
| */ | |
| _loadTsconfig( | |
| resolver, | |
| configFilePath, | |
| fileDependencies, | |
| visitedConfigPaths, | |
| callback, | |
| ) { | |
| const visited = visitedConfigPaths || new Set(); | |
| if (visited.has(configFilePath)) { | |
| return callback(null, /** @type {Tsconfig} */ ({})); | |
| } | |
| visited.add(configFilePath); | |
| readJson( | |
| resolver.fileSystem, | |
| configFilePath, | |
| READ_JSON_OPTIONS, | |
| (err, parsed) => { | |
| if (err) return callback(/** @type {Error} */ (err)); | |
| const config = /** @type {Tsconfig} */ (parsed); | |
| fileDependencies.add(configFilePath); | |
| const extendedConfig = config.extends; | |
| if (!extendedConfig) return callback(null, config); | |
| if (!Array.isArray(extendedConfig)) { | |
| this._loadTsconfigFromExtends( | |
| resolver, | |
| configFilePath, | |
| extendedConfig, | |
| fileDependencies, | |
| visited, | |
| (extErr, extendedTsconfig) => { | |
| if (extErr) return callback(extErr); | |
| callback( | |
| null, | |
| mergeTsconfigs( | |
| /** @type {Tsconfig} */ (extendedTsconfig), | |
| config, | |
| ), | |
| ); | |
| }, | |
| ); | |
| return; | |
| } | |
| /** @type {Tsconfig} */ | |
| let base = {}; | |
| let i = 0; | |
| const next = () => { | |
| if (i >= extendedConfig.length) { | |
| return callback(null, mergeTsconfigs(base, config)); | |
| } | |
| this._loadTsconfigFromExtends( | |
| resolver, | |
| configFilePath, | |
| extendedConfig[i++], | |
| fileDependencies, | |
| visited, | |
| (extErr, extendedTsconfig) => { | |
| if (extErr) return callback(extErr); | |
| base = mergeTsconfigs( | |
| base, | |
| /** @type {Tsconfig} */ (extendedTsconfig), | |
| ); | |
| next(); | |
| }, | |
| ); | |
| }; | |
| next(); | |
| }, | |
| ); | |
| } | |
| }; | |