Cache parsed path mapping patterns (#44078)

* Cache parsed path mapping patterns

If a project has many of them (e.g. 1800), parsing the patterns
repeatedly can take up a lot of time.

* Move cache to ConfigFileSpecs

* Inline constants

* Simplify cache access
This commit is contained in:
Andrew Casey 2021-05-26 09:40:42 -07:00 committed by GitHub
parent ddd3cf9406
commit 3ffa245f07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 42 additions and 31 deletions

View File

@ -1977,19 +1977,17 @@ namespace ts {
declareModuleSymbol(node);
}
else {
let pattern: Pattern | undefined;
let pattern: string | Pattern | undefined;
if (node.name.kind === SyntaxKind.StringLiteral) {
const { text } = node.name;
if (hasZeroOrOneAsteriskCharacter(text)) {
pattern = tryParsePattern(text);
}
else {
pattern = tryParsePattern(text);
if (pattern === undefined) {
errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text);
}
}
const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes)!;
file.patternAmbientModules = append<PatternAmbientModule>(file.patternAmbientModules, pattern && { pattern, symbol });
file.patternAmbientModules = append<PatternAmbientModule>(file.patternAmbientModules, pattern && !isString(pattern) ? { pattern, symbol } : undefined);
}
}
else {

View File

@ -2533,6 +2533,7 @@ namespace ts {
validatedFilesSpec: filter(filesSpecs, isString),
validatedIncludeSpecs,
validatedExcludeSpecs,
pathPatterns: undefined, // Initialized on first use
};
}

View File

@ -952,7 +952,7 @@ namespace ts {
}
function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) {
const { baseUrl, paths } = state.compilerOptions;
const { baseUrl, paths, configFile } = state.compilerOptions;
if (paths && !pathIsRelative(moduleName)) {
if (state.traceEnabled) {
if (baseUrl) {
@ -961,7 +961,8 @@ namespace ts {
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
return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, loader, /*onlyRecordFailures*/ false, state);
const pathPatterns = configFile?.configFileSpecs ? configFile.configFileSpecs.pathPatterns ||= tryParsePatterns(paths) : undefined;
return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, pathPatterns, loader, /*onlyRecordFailures*/ false, state);
}
}
@ -1400,7 +1401,7 @@ namespace ts {
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, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state);
const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, /*pathPatterns*/ undefined, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state);
if (result) {
return removeIgnoredPackageId(result.value);
}
@ -1536,7 +1537,7 @@ namespace ts {
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, packageInfo.versionPaths.version, version, rest);
}
const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host);
const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, loader, !packageDirectoryExists, state);
const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, /*pathPatterns*/ undefined, loader, !packageDirectoryExists, state);
if (fromPaths) {
return fromPaths.value;
}
@ -1546,8 +1547,9 @@ namespace ts {
return loader(extensions, candidate, !nodeModulesDirectoryExists, state);
}
function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike<string[]>, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult<Resolved> {
const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName);
function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike<string[]>, pathPatterns: readonly (string | Pattern)[] | undefined, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult<Resolved> {
pathPatterns ||= tryParsePatterns(paths);
const matchedPattern = matchPatternOrExact(pathPatterns, moduleName);
if (matchedPattern) {
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);

View File

@ -6195,6 +6195,7 @@ namespace ts {
validatedFilesSpec: readonly string[] | undefined;
validatedIncludeSpecs: readonly string[] | undefined;
validatedExcludeSpecs: readonly string[] | undefined;
pathPatterns: readonly (string | Pattern)[] | undefined;
}
/* @internal */

View File

@ -6769,14 +6769,25 @@ namespace ts {
return changeAnyExtension(path, newExtension, extensionsToRemove, /*ignoreCase*/ false) as T;
}
export function tryParsePattern(pattern: string): Pattern | undefined {
// This should be verified outside of here and a proper error thrown.
Debug.assert(hasZeroOrOneAsteriskCharacter(pattern));
/**
* Returns the input if there are no stars, a pattern if there is exactly one,
* and undefined if there are more.
*/
export function tryParsePattern(pattern: string): string | Pattern | undefined {
const indexOfStar = pattern.indexOf("*");
return indexOfStar === -1 ? undefined : {
prefix: pattern.substr(0, indexOfStar),
suffix: pattern.substr(indexOfStar + 1)
};
if (indexOfStar === -1) {
return pattern;
}
return pattern.indexOf("*", indexOfStar + 1) !== -1
? undefined
: {
prefix: pattern.substr(0, indexOfStar),
suffix: pattern.substr(indexOfStar + 1)
};
}
export function tryParsePatterns(paths: MapLike<string[]>): (string | Pattern)[] {
return mapDefined(getOwnKeys(paths), path => tryParsePattern(path));
}
export function positionIsSynthesized(pos: number): boolean {
@ -6822,21 +6833,19 @@ namespace ts {
/**
* patternStrings contains both pattern strings (containing "*") and regular strings.
* patternOrStrings 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.)
*/
export function matchPatternOrExact(patternStrings: readonly string[], candidate: string): string | Pattern | undefined {
export function matchPatternOrExact(patternOrStrings: readonly (string | Pattern)[], candidate: string): string | Pattern | undefined {
const patterns: Pattern[] = [];
for (const patternString of patternStrings) {
if (!hasZeroOrOneAsteriskCharacter(patternString)) continue;
const pattern = tryParsePattern(patternString);
if (pattern) {
patterns.push(pattern);
for (const patternOrString of patternOrStrings) {
if (patternOrString === candidate) {
return candidate;
}
else if (patternString === candidate) {
// pattern was matched as is - no need to search further
return patternString;
if (!isString(patternOrString)) {
patterns.push(patternOrString);
}
}

View File

@ -553,8 +553,8 @@ namespace ts.Completions.StringCompletions {
return undefined;
}
const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined;
if (!parsed) {
const parsed = tryParsePattern(pattern);
if (parsed === undefined || isString(parsed)) {
return undefined;
}