diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 9797d9f96cc66..64b19c56efe7b 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -3071,7 +3071,6 @@ function parseJsonConfigFileContentWorker( validatedFilesSpecBeforeSubstitution, validatedIncludeSpecsBeforeSubstitution, validatedExcludeSpecsBeforeSubstitution, - pathPatterns: undefined, // Initialized on first use isDefaultIncludeSpec, }; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index fc692f1a65d2b..40b58adfdeba6 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2414,7 +2414,7 @@ export function findBestPatternMatch(values: readonly T[], getPattern: (value for (let i = 0; i < values.length; i++) { const v = values[i]; const pattern = getPattern(v); - if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { + if (pattern.prefix.length > longestMatchPrefixLength && isPatternMatch(pattern, candidate)) { longestMatchPrefixLength = pattern.prefix.length; matchedValue = v; } diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 850a8a7f381cb..d557b3e2c4a07 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -79,9 +79,9 @@ import { normalizeSlashes, PackageId, packageIdToString, + ParsedPatterns, Path, pathIsRelative, - Pattern, patternText, readJson, removeExtension, @@ -1558,7 +1558,7 @@ function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, mo } function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { - const { baseUrl, paths, configFile } = state.compilerOptions; + const { baseUrl, paths } = state.compilerOptions; if (paths && !pathIsRelative(moduleName)) { if (state.traceEnabled) { if (baseUrl) { @@ -1567,7 +1567,7 @@ function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: s trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); } const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined - const pathPatterns = configFile?.configFileSpecs ? configFile.configFileSpecs.pathPatterns ||= tryParsePatterns(paths) : undefined; + const pathPatterns = tryParsePatterns(paths); return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, pathPatterns, loader, /*onlyRecordFailures*/ false, state); } } @@ -2518,7 +2518,8 @@ function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: st if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName); } - const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, /*pathPatterns*/ undefined, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); + const pathPatterns = tryParsePatterns(versionPaths.paths); + const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, pathPatterns, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); if (result) { return removeIgnoredPackageId(result.value); } @@ -3112,7 +3113,8 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, rest); } const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); - const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, versionPaths.paths, /*pathPatterns*/ undefined, loader, !packageDirectoryExists, state); + const pathPatterns = tryParsePatterns(versionPaths.paths); + const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, versionPaths.paths, pathPatterns, loader, !packageDirectoryExists, state); if (fromPaths) { return fromPaths.value; } @@ -3120,8 +3122,7 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu return loader(extensions, candidate, !nodeModulesDirectoryExists, state); } -function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, pathPatterns: readonly (string | Pattern)[] | undefined, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { - pathPatterns ||= tryParsePatterns(paths); +function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, pathPatterns: ParsedPatterns, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { const matchedPattern = matchPatternOrExact(pathPatterns, moduleName); if (matchedPattern) { const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ec5aa60564eac..3d108ea69fa4f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7628,7 +7628,6 @@ export interface ConfigFileSpecs { validatedFilesSpecBeforeSubstitution: readonly string[] | undefined; validatedIncludeSpecsBeforeSubstitution: readonly string[] | undefined; validatedExcludeSpecsBeforeSubstitution: readonly string[] | undefined; - pathPatterns: readonly (string | Pattern)[] | undefined; isDefaultIncludeSpec: boolean; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fb4da5887538d..673c0b38c156d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -9972,8 +9972,51 @@ export function tryParsePattern(pattern: string): string | Pattern | undefined { } /** @internal */ -export function tryParsePatterns(paths: MapLike): (string | Pattern)[] { - return mapDefined(getOwnKeys(paths), path => tryParsePattern(path)); +export interface ParsedPatterns { + matchableStringSet: ReadonlySet | undefined; + patterns: (readonly Pattern[]) | undefined; +} + +const parsedPatternsCache = new WeakMap, ParsedPatterns>(); + +/** + * Divides patterns into a set of exact specifiers and patterns. + * NOTE that this function caches the result based on object identity. + * + * @internal + */ +export function tryParsePatterns(paths: MapLike): ParsedPatterns { + let result = parsedPatternsCache.get(paths); + if (result !== undefined) { + return result; + } + + let matchableStringSet: Set | undefined; + let patterns: Pattern[] | undefined; + + const pathList = getOwnKeys(paths); + for (const path of pathList) { + const patternOrStr = tryParsePattern(path); + if (patternOrStr === undefined) { + continue; + } + else if (typeof patternOrStr === "string") { + (matchableStringSet ??= new Set()).add(patternOrStr); + } + else { + (patterns ??= []).push(patternOrStr); + } + } + + parsedPatternsCache.set( + paths, + result = { + matchableStringSet, + patterns, + }, + ); + + return result; } /** @internal */ @@ -10030,22 +10073,21 @@ export const emptyFileSystemEntries: FileSystemEntries = { }; /** - * patternOrStrings contains both patterns (containing "*") and regular strings. + * `parsedPatterns` contains both patterns (containing "*") and regular strings. * Return an exact match if possible, or a pattern match, or undefined. * (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.) * * @internal */ -export function matchPatternOrExact(patternOrStrings: readonly (string | Pattern)[], candidate: string): string | Pattern | undefined { - const patterns: Pattern[] = []; - for (const patternOrString of patternOrStrings) { - if (patternOrString === candidate) { - return candidate; - } +export function matchPatternOrExact(parsedPatterns: ParsedPatterns, candidate: string): string | Pattern | undefined { + const { matchableStringSet, patterns } = parsedPatterns; - if (!isString(patternOrString)) { - patterns.push(patternOrString); - } + if (matchableStringSet?.has(candidate)) { + return candidate; + } + + if (patterns === undefined || patterns.length === 0) { + return undefined; } return findBestPatternMatch(patterns, _ => _, candidate);