diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 02432a247949b..78bca28c26b15 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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(file.patternAmbientModules, pattern && { pattern, symbol }); + file.patternAmbientModules = append(file.patternAmbientModules, pattern && !isString(pattern) ? { pattern, symbol } : undefined); } } else { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 4fec5cd18caf4..eedac5083688d 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -2533,6 +2533,7 @@ namespace ts { validatedFilesSpec: filter(filesSpecs, isString), validatedIncludeSpecs, validatedExcludeSpecs, + pathPatterns: undefined, // Initialized on first use }; } diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 22577901f20ba..897aafa261113 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -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, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { - const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName); + 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); + const matchedPattern = matchPatternOrExact(pathPatterns, moduleName); if (matchedPattern) { const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f395014baad1d..9a5cf35412582 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6195,6 +6195,7 @@ namespace ts { validatedFilesSpec: readonly string[] | undefined; validatedIncludeSpecs: readonly string[] | undefined; validatedExcludeSpecs: readonly string[] | undefined; + pathPatterns: readonly (string | Pattern)[] | undefined; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8271e6adfc22f..7a0121145241f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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 | 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); - } - else if (patternString === candidate) { - // pattern was matched as is - no need to search further - return patternString; + for (const patternOrString of patternOrStrings) { + if (patternOrString === candidate) { + return candidate; + } + + if (!isString(patternOrString)) { + patterns.push(patternOrString); } } diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 27218f3cfd154..92d205ed8bcdb 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -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; }