Skip to content

Commit e6f6a5e

Browse files
author
Andy
authored
Merge pull request #11495 from Microsoft/includes_glob
Implicitly consider an extensionless file in "includes" to be a recursive directory glob
2 parents 8984e43 + 040942f commit e6f6a5e

File tree

5 files changed

+196
-236
lines changed

5 files changed

+196
-236
lines changed

src/compiler/commandLineParser.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,9 +1008,7 @@ namespace ts {
10081008
function convertTypingOptionsFromJsonWorker(jsonOptions: any,
10091009
basePath: string, errors: Diagnostic[], configFileName?: string): TypingOptions {
10101010

1011-
const options: TypingOptions = getBaseFileName(configFileName) === "jsconfig.json"
1012-
? { enableAutoDiscovery: true, include: [], exclude: [] }
1013-
: { enableAutoDiscovery: false, include: [], exclude: [] };
1011+
const options: TypingOptions = { enableAutoDiscovery: getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] };
10141012
convertOptionsFromJson(typingOptionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_typing_option_0, errors);
10151013
return options;
10161014
}
@@ -1263,12 +1261,13 @@ namespace ts {
12631261
/**
12641262
* Gets directories in a set of include patterns that should be watched for changes.
12651263
*/
1266-
function getWildcardDirectories(include: string[], exclude: string[], path: string, useCaseSensitiveFileNames: boolean) {
1264+
function getWildcardDirectories(include: string[], exclude: string[], path: string, useCaseSensitiveFileNames: boolean): Map<WatchDirectoryFlags> {
12671265
// We watch a directory recursively if it contains a wildcard anywhere in a directory segment
12681266
// of the pattern:
12691267
//
12701268
// /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively
12711269
// /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added
1270+
// /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler
12721271
//
12731272
// We watch a directory without recursion if it contains a wildcard in the file segment of
12741273
// the pattern:
@@ -1281,15 +1280,14 @@ namespace ts {
12811280
if (include !== undefined) {
12821281
const recursiveKeys: string[] = [];
12831282
for (const file of include) {
1284-
const name = normalizePath(combinePaths(path, file));
1285-
if (excludeRegex && excludeRegex.test(name)) {
1283+
const spec = normalizePath(combinePaths(path, file));
1284+
if (excludeRegex && excludeRegex.test(spec)) {
12861285
continue;
12871286
}
12881287

1289-
const match = wildcardDirectoryPattern.exec(name);
1288+
const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames);
12901289
if (match) {
1291-
const key = useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase();
1292-
const flags = watchRecursivePattern.test(name) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None;
1290+
const { key, flags } = match;
12931291
const existingFlags = wildcardDirectories[key];
12941292
if (existingFlags === undefined || existingFlags < flags) {
12951293
wildcardDirectories[key] = flags;
@@ -1313,6 +1311,20 @@ namespace ts {
13131311
return wildcardDirectories;
13141312
}
13151313

1314+
function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: string, flags: WatchDirectoryFlags } | undefined {
1315+
const match = wildcardDirectoryPattern.exec(spec);
1316+
if (match) {
1317+
return {
1318+
key: useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(),
1319+
flags: watchRecursivePattern.test(spec) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None
1320+
};
1321+
}
1322+
if (isImplicitGlob(spec)) {
1323+
return { key: spec, flags: WatchDirectoryFlags.Recursive };
1324+
}
1325+
return undefined;
1326+
}
1327+
13161328
/**
13171329
* Determines whether a literal or wildcard file has already been included that has a higher
13181330
* extension priority.

src/compiler/core.ts

Lines changed: 99 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,7 +1312,7 @@ namespace ts {
13121312
*/
13131313
export function getDirectoryPath(path: Path): Path;
13141314
export function getDirectoryPath(path: string): string;
1315-
export function getDirectoryPath(path: string): any {
1315+
export function getDirectoryPath(path: string): string {
13161316
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf(directorySeparator)));
13171317
}
13181318

@@ -1572,6 +1572,10 @@ namespace ts {
15721572
return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos;
15731573
}
15741574

1575+
export function hasExtension(fileName: string): boolean {
1576+
return getBaseFileName(fileName).indexOf(".") >= 0;
1577+
}
1578+
15751579
export function fileExtensionIs(path: string, extension: string): boolean {
15761580
return path.length > extension.length && endsWith(path, extension);
15771581
}
@@ -1617,81 +1621,105 @@ namespace ts {
16171621

16181622
let pattern = "";
16191623
let hasWrittenSubpattern = false;
1620-
spec: for (const spec of specs) {
1624+
for (const spec of specs) {
16211625
if (!spec) {
16221626
continue;
16231627
}
16241628

1625-
let subpattern = "";
1626-
let hasRecursiveDirectoryWildcard = false;
1627-
let hasWrittenComponent = false;
1628-
const components = getNormalizedPathComponents(spec, basePath);
1629-
if (usage !== "exclude" && components[components.length - 1] === "**") {
1630-
continue spec;
1629+
const subPattern = getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter);
1630+
if (subPattern === undefined) {
1631+
continue;
16311632
}
16321633

1633-
// getNormalizedPathComponents includes the separator for the root component.
1634-
// We need to remove to create our regex correctly.
1635-
components[0] = removeTrailingDirectorySeparator(components[0]);
1634+
if (hasWrittenSubpattern) {
1635+
pattern += "|";
1636+
}
16361637

1637-
let optionalCount = 0;
1638-
for (let component of components) {
1639-
if (component === "**") {
1640-
if (hasRecursiveDirectoryWildcard) {
1641-
continue spec;
1642-
}
1638+
pattern += "(" + subPattern + ")";
1639+
hasWrittenSubpattern = true;
1640+
}
16431641

1644-
subpattern += doubleAsteriskRegexFragment;
1645-
hasRecursiveDirectoryWildcard = true;
1646-
hasWrittenComponent = true;
1647-
}
1648-
else {
1649-
if (usage === "directories") {
1650-
subpattern += "(";
1651-
optionalCount++;
1652-
}
1642+
if (!pattern) {
1643+
return undefined;
1644+
}
16531645

1654-
if (hasWrittenComponent) {
1655-
subpattern += directorySeparator;
1656-
}
1646+
// If excluding, match "foo/bar/baz...", but if including, only allow "foo".
1647+
const terminator = usage === "exclude" ? "($|/)" : "$";
1648+
return `^(${pattern})${terminator}`;
1649+
}
16571650

1658-
if (usage !== "exclude") {
1659-
// The * and ? wildcards should not match directories or files that start with . if they
1660-
// appear first in a component. Dotted directories and files can be included explicitly
1661-
// like so: **/.*/.*
1662-
if (component.charCodeAt(0) === CharacterCodes.asterisk) {
1663-
subpattern += "([^./]" + singleAsteriskRegexFragment + ")?";
1664-
component = component.substr(1);
1665-
}
1666-
else if (component.charCodeAt(0) === CharacterCodes.question) {
1667-
subpattern += "[^./]";
1668-
component = component.substr(1);
1669-
}
1670-
}
1651+
/**
1652+
* An "includes" path "foo" is implicitly a glob "foo/** /*" (without the space) if its last component has no extension,
1653+
* and does not contain any glob characters itself.
1654+
*/
1655+
export function isImplicitGlob(lastPathComponent: string): boolean {
1656+
return !/[.*?]/.test(lastPathComponent);
1657+
}
1658+
1659+
function getSubPatternFromSpec(spec: string, basePath: string, usage: "files" | "directories" | "exclude", singleAsteriskRegexFragment: string, doubleAsteriskRegexFragment: string, replaceWildcardCharacter: (match: string) => string): string | undefined {
1660+
let subpattern = "";
1661+
let hasRecursiveDirectoryWildcard = false;
1662+
let hasWrittenComponent = false;
1663+
const components = getNormalizedPathComponents(spec, basePath);
1664+
const lastComponent = lastOrUndefined(components);
1665+
if (usage !== "exclude" && lastComponent === "**") {
1666+
return undefined;
1667+
}
1668+
1669+
// getNormalizedPathComponents includes the separator for the root component.
1670+
// We need to remove to create our regex correctly.
1671+
components[0] = removeTrailingDirectorySeparator(components[0]);
16711672

1672-
subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
1673-
hasWrittenComponent = true;
1673+
if (isImplicitGlob(lastComponent)) {
1674+
components.push("**", "*");
1675+
}
1676+
1677+
let optionalCount = 0;
1678+
for (let component of components) {
1679+
if (component === "**") {
1680+
if (hasRecursiveDirectoryWildcard) {
1681+
return undefined;
16741682
}
1675-
}
16761683

1677-
while (optionalCount > 0) {
1678-
subpattern += ")?";
1679-
optionalCount--;
1684+
subpattern += doubleAsteriskRegexFragment;
1685+
hasRecursiveDirectoryWildcard = true;
16801686
}
1687+
else {
1688+
if (usage === "directories") {
1689+
subpattern += "(";
1690+
optionalCount++;
1691+
}
16811692

1682-
if (hasWrittenSubpattern) {
1683-
pattern += "|";
1693+
if (hasWrittenComponent) {
1694+
subpattern += directorySeparator;
1695+
}
1696+
1697+
if (usage !== "exclude") {
1698+
// The * and ? wildcards should not match directories or files that start with . if they
1699+
// appear first in a component. Dotted directories and files can be included explicitly
1700+
// like so: **/.*/.*
1701+
if (component.charCodeAt(0) === CharacterCodes.asterisk) {
1702+
subpattern += "([^./]" + singleAsteriskRegexFragment + ")?";
1703+
component = component.substr(1);
1704+
}
1705+
else if (component.charCodeAt(0) === CharacterCodes.question) {
1706+
subpattern += "[^./]";
1707+
component = component.substr(1);
1708+
}
1709+
}
1710+
1711+
subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
16841712
}
16851713

1686-
pattern += "(" + subpattern + ")";
1687-
hasWrittenSubpattern = true;
1714+
hasWrittenComponent = true;
16881715
}
16891716

1690-
if (!pattern) {
1691-
return undefined;
1717+
while (optionalCount > 0) {
1718+
subpattern += ")?";
1719+
optionalCount--;
16921720
}
16931721

1694-
return "^(" + pattern + (usage === "exclude" ? ")($|/)" : ")$");
1722+
return subpattern;
16951723
}
16961724

16971725
function replaceWildCardCharacterFiles(match: string) {
@@ -1778,43 +1806,44 @@ namespace ts {
17781806
function getBasePaths(path: string, includes: string[], useCaseSensitiveFileNames: boolean) {
17791807
// Storage for our results in the form of literal paths (e.g. the paths as written by the user).
17801808
const basePaths: string[] = [path];
1809+
17811810
if (includes) {
17821811
// Storage for literal base paths amongst the include patterns.
17831812
const includeBasePaths: string[] = [];
17841813
for (const include of includes) {
17851814
// We also need to check the relative paths by converting them to absolute and normalizing
17861815
// in case they escape the base path (e.g "..\somedirectory")
17871816
const absolute: string = isRootedDiskPath(include) ? include : normalizePath(combinePaths(path, include));
1788-
1789-
const wildcardOffset = indexOfAnyCharCode(absolute, wildcardCharCodes);
1790-
const includeBasePath = wildcardOffset < 0
1791-
? removeTrailingDirectorySeparator(getDirectoryPath(absolute))
1792-
: absolute.substring(0, absolute.lastIndexOf(directorySeparator, wildcardOffset));
1793-
17941817
// Append the literal and canonical candidate base paths.
1795-
includeBasePaths.push(includeBasePath);
1818+
includeBasePaths.push(getIncludeBasePath(absolute));
17961819
}
17971820

17981821
// Sort the offsets array using either the literal or canonical path representations.
17991822
includeBasePaths.sort(useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive);
18001823

18011824
// Iterate over each include base path and include unique base paths that are not a
18021825
// subpath of an existing base path
1803-
include: for (let i = 0; i < includeBasePaths.length; i++) {
1804-
const includeBasePath = includeBasePaths[i];
1805-
for (let j = 0; j < basePaths.length; j++) {
1806-
if (containsPath(basePaths[j], includeBasePath, path, !useCaseSensitiveFileNames)) {
1807-
continue include;
1808-
}
1826+
for (const includeBasePath of includeBasePaths) {
1827+
if (ts.every(basePaths, basePath => !containsPath(basePath, includeBasePath, path, !useCaseSensitiveFileNames))) {
1828+
basePaths.push(includeBasePath);
18091829
}
1810-
1811-
basePaths.push(includeBasePath);
18121830
}
18131831
}
18141832

18151833
return basePaths;
18161834
}
18171835

1836+
function getIncludeBasePath(absolute: string): string {
1837+
const wildcardOffset = indexOfAnyCharCode(absolute, wildcardCharCodes);
1838+
if (wildcardOffset < 0) {
1839+
// No "*" or "?" in the path
1840+
return !hasExtension(absolute)
1841+
? absolute
1842+
: removeTrailingDirectorySeparator(getDirectoryPath(absolute));
1843+
}
1844+
return absolute.substring(0, absolute.lastIndexOf(directorySeparator, wildcardOffset));
1845+
}
1846+
18181847
export function ensureScriptKind(fileName: string, scriptKind?: ScriptKind): ScriptKind {
18191848
// Using scriptKind as a condition handles both:
18201849
// - 'scriptKind' is unspecified and thus it is `undefined`

src/compiler/program.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -971,10 +971,6 @@ namespace ts {
971971
return sortAndDeduplicateDiagnostics(allDiagnostics);
972972
}
973973

974-
function hasExtension(fileName: string): boolean {
975-
return getBaseFileName(fileName).indexOf(".") >= 0;
976-
}
977-
978974
function processRootFile(fileName: string, isDefaultLib: boolean) {
979975
processSourceFile(normalizePath(fileName), isDefaultLib);
980976
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3145,6 +3145,7 @@ namespace ts {
31453145
Pretty,
31463146
}
31473147

3148+
/** Either a parsed command line or a parsed tsconfig.json */
31483149
export interface ParsedCommandLine {
31493150
options: CompilerOptions;
31503151
typingOptions?: TypingOptions;

0 commit comments

Comments
 (0)