From 24a558c926ac68e2abf800549f87569ff88fd9e7 Mon Sep 17 00:00:00 2001 From: MichaelMitchell-at <=> Date: Mon, 3 Feb 2025 01:13:00 -0500 Subject: [PATCH] Do not merge file patterns into a single regular expression --- src/compiler/commandLineParser.ts | 36 +++++++++++------------ src/compiler/utilities.ts | 42 +++++++++------------------ src/services/getEditsForFileRename.ts | 5 ++-- 3 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8248a04194666..db7f27eb9a730 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -56,7 +56,6 @@ import { getNormalizedAbsolutePath, getOwnKeys, getRegexFromPattern, - getRegularExpressionForWildcard, getRegularExpressionsForWildcards, getRelativePathFromFile, getSpellingSuggestion, @@ -2689,16 +2688,17 @@ function filterSameAsDefaultInclude(specs: readonly string[] | undefined) { function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean { if (!includeSpecs) return returnTrue; const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory()); - const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames); - const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames); - if (includeRe) { - if (excludeRe) { - return path => !(includeRe.test(path) && !excludeRe.test(path)); + const excludeRegexes = patterns.excludePatterns && patterns.excludePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)); + const includeRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)); + if (includeRegexes) { + if (excludeRegexes) { + return path => !(includeRegexes.some(regex => regex.test(path)) && !excludeRegexes.some(regex => regex.test(path))); } - return path => !includeRe.test(path); + return path => !includeRegexes.some(regex => regex.test(path)); } - if (excludeRe) { - return path => excludeRe.test(path); + + if (excludeRegexes) { + return path => excludeRegexes.some(regex => regex.test(path)); } return returnTrue; } @@ -3931,7 +3931,7 @@ export function getFileNamesFromConfigSpecs( // Valid only if *.json specified if (!jsonOnlyIncludeRegexes) { const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json)); - const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`); + const includeFilePatterns = getRegularExpressionsForWildcards(includes, basePath, "files"); jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray; } const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file)); @@ -4032,11 +4032,11 @@ export function matchesExcludeWorker( currentDirectory: string, basePath?: string, ): boolean { - const excludePattern = getRegularExpressionForWildcard(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude"); - const excludeRegex = excludePattern && getRegexFromPattern(excludePattern, useCaseSensitiveFileNames); - if (!excludeRegex) return false; - if (excludeRegex.test(pathToCheck)) return true; - return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck)); + const excludePatterns = getRegularExpressionsForWildcards(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude"); + const excludeRegexes = excludePatterns && excludePatterns.map(pattern => getRegexFromPattern(pattern, useCaseSensitiveFileNames)); + if (!excludeRegexes) return false; + if (excludeRegexes.some(regex => regex.test(pathToCheck))) return true; + return !hasExtension(pathToCheck) && excludeRegexes.some(regex => regex.test(ensureTrailingDirectorySeparator(pathToCheck))); } function validateSpecs(specs: readonly string[], errors: Diagnostic[], disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { @@ -4081,15 +4081,15 @@ function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExclu // // /a/b/* - Watch /a/b directly to catch any new file // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z - const rawExcludeRegex = getRegularExpressionForWildcard(exclude, basePath, "exclude"); - const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); + const rawExcludeRegexes = getRegularExpressionsForWildcards(exclude, basePath, "exclude"); + const excludeRegexes = rawExcludeRegexes && rawExcludeRegexes.map(regex => new RegExp(regex, useCaseSensitiveFileNames ? "" : "i")); const wildcardDirectories: MapLike = {}; const wildCardKeyToPath = new Map(); if (include !== undefined) { const recursiveKeys: CanonicalKey[] = []; for (const file of include) { const spec = normalizePath(combinePaths(basePath, file)); - if (excludeRegex && excludeRegex.test(spec)) { + if (some(excludeRegexes, excludeRegex => excludeRegex.test(spec))) { continue; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5755369134d8a..122d997404b34 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -416,7 +416,6 @@ import { LogicalOperator, LogicalOrCoalescingAssignmentOperator, mangleScopedPackageName, - map, mapDefined, MapLike, MemberName, @@ -9552,26 +9551,15 @@ const wildcardMatchers = { exclude: excludeMatcher, }; -/** @internal */ -export function getRegularExpressionForWildcard(specs: readonly string[] | undefined, basePath: string, usage: "files" | "directories" | "exclude"): string | undefined { - const patterns = getRegularExpressionsForWildcards(specs, basePath, usage); - if (!patterns || !patterns.length) { - return undefined; - } - - const pattern = patterns.map(pattern => `(${pattern})`).join("|"); - // If excluding, match "foo/bar/baz...", but if including, only allow "foo". - const terminator = usage === "exclude" ? "($|/)" : "$"; - return `^(${pattern})${terminator}`; -} - /** @internal */ export function getRegularExpressionsForWildcards(specs: readonly string[] | undefined, basePath: string, usage: "files" | "directories" | "exclude"): readonly string[] | undefined { if (specs === undefined || specs.length === 0) { return undefined; } - return flatMap(specs, spec => spec && getSubPatternFromSpec(spec, basePath, usage, wildcardMatchers[usage])); + // If excluding, match "foo/bar/baz...", but if including, only allow "foo". + const terminator = usage === "exclude" ? "($|/)" : "$"; + return flatMap(specs, spec => spec && `^${getSubPatternFromSpec(spec, basePath, usage, wildcardMatchers[usage])}${terminator}`); } /** @@ -9684,12 +9672,9 @@ export interface FileSystemEntries { /** @internal */ export interface FileMatcherPatterns { - /** One pattern for each "include" spec. */ includeFilePatterns: readonly string[] | undefined; - /** One pattern matching one of any of the "include" specs. */ - includeFilePattern: string | undefined; - includeDirectoryPattern: string | undefined; - excludePattern: string | undefined; + includeDirectoryPatterns: readonly string[] | undefined; + excludePatterns: readonly string[] | undefined; basePaths: readonly string[]; } @@ -9704,10 +9689,9 @@ export function getFileMatcherPatterns(path: string, excludes: readonly string[] const absolutePath = combinePaths(currentDirectory, path); return { - includeFilePatterns: map(getRegularExpressionsForWildcards(includes, absolutePath, "files"), pattern => `^${pattern}$`), - includeFilePattern: getRegularExpressionForWildcard(includes, absolutePath, "files"), - includeDirectoryPattern: getRegularExpressionForWildcard(includes, absolutePath, "directories"), - excludePattern: getRegularExpressionForWildcard(excludes, absolutePath, "exclude"), + includeFilePatterns: getRegularExpressionsForWildcards(includes, absolutePath, "files"), + includeDirectoryPatterns: getRegularExpressionsForWildcards(includes, absolutePath, "directories"), + excludePatterns: getRegularExpressionsForWildcards(excludes, absolutePath, "exclude"), basePaths: getBasePaths(path, includes, useCaseSensitiveFileNames), }; } @@ -9729,8 +9713,8 @@ export function matchFiles(path: string, extensions: readonly string[] | undefin const patterns = getFileMatcherPatterns(path, excludes, includes, useCaseSensitiveFileNames, currentDirectory); const includeFileRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => getRegexFromPattern(pattern, useCaseSensitiveFileNames)); - const includeDirectoryRegex = patterns.includeDirectoryPattern && getRegexFromPattern(patterns.includeDirectoryPattern, useCaseSensitiveFileNames); - const excludeRegex = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, useCaseSensitiveFileNames); + const includeDirectoryRegexes = patterns.includeDirectoryPatterns && patterns.includeDirectoryPatterns.map(pattern => getRegexFromPattern(pattern, useCaseSensitiveFileNames)); + const excludeRegexes = patterns.excludePatterns && patterns.excludePatterns.map(pattern => getRegexFromPattern(pattern, useCaseSensitiveFileNames)); // Associate an array of results with each include regex. This keeps results in order of the "include" order. // If there are no "includes", then just put everything in results[0]. @@ -9753,7 +9737,7 @@ export function matchFiles(path: string, extensions: readonly string[] | undefin const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if (extensions && !fileExtensionIsOneOf(name, extensions)) continue; - if (excludeRegex && excludeRegex.test(absoluteName)) continue; + if (excludeRegexes && excludeRegexes.some(regex => regex.test(absoluteName))) continue; if (!includeFileRegexes) { results[0].push(name); } @@ -9776,8 +9760,8 @@ export function matchFiles(path: string, extensions: readonly string[] | undefin const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if ( - (!includeDirectoryRegex || includeDirectoryRegex.test(absoluteName)) && - (!excludeRegex || !excludeRegex.test(absoluteName)) + (!includeDirectoryRegexes || includeDirectoryRegexes.some(regex => regex.test(absoluteName))) && + (!excludeRegexes || !excludeRegexes.some(regex => regex.test(absoluteName))) ) { visitDirectory(name, absoluteName, depth); } diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index cffc15aeeabb3..1839d0f284539 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -119,10 +119,11 @@ function updateTsconfigFiles(program: Program, changeTracker: textChanges.Change const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined); if (includes.length === 0) return; const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory); + const includeRegexes = Debug.checkDefined(matchers.includeFilePatterns).map(pattern => getRegexFromPattern(pattern, useCaseSensitiveFileNames)); // If there isn't some include for this, add a new one. if ( - getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(oldFileOrDirPath) && - !getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath) + includeRegexes.some(regex => regex.test(oldFileOrDirPath)) && + !includeRegexes.some(regex => regex.test(newFileOrDirPath)) ) { changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), factory.createStringLiteral(relativePath(newFileOrDirPath))); }