Skip to content

Commit 6d99b2f

Browse files
authored
Merge pull request #8841 from Microsoft/glob2_merged
Add glob-style file include pattern support for tsconfig.json
2 parents 7b3abc6 + b49acd5 commit 6d99b2f

20 files changed

+2055
-196
lines changed

Jakefile.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ var languageServiceLibrarySources = [
121121

122122
var harnessCoreSources = [
123123
"harness.ts",
124+
"virtualFileSystem.ts",
124125
"sourceMapRecorder.ts",
125126
"harnessLanguageService.ts",
126127
"fourslash.ts",
@@ -155,7 +156,8 @@ var harnessSources = harnessCoreSources.concat([
155156
"commandLineParsing.ts",
156157
"convertCompilerOptionsFromJson.ts",
157158
"convertTypingOptionsFromJson.ts",
158-
"tsserverProjectSystem.ts"
159+
"tsserverProjectSystem.ts",
160+
"matchFiles.ts"
159161
].map(function (f) {
160162
return path.join(unittestsDirectory, f);
161163
})).concat([

src/compiler/commandLineParser.ts

Lines changed: 329 additions & 51 deletions
Large diffs are not rendered by default.

src/compiler/core.ts

Lines changed: 352 additions & 1 deletion
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,14 @@
22322232
"category": "Error",
22332233
"code": 5009
22342234
},
2235+
"File specification cannot end in a recursive directory wildcard ('**'): '{0}'.": {
2236+
"category": "Error",
2237+
"code": 5010
2238+
},
2239+
"File specification cannot contain multiple recursive directory wildcards ('**'): '{0}'.": {
2240+
"category": "Error",
2241+
"code": 5011
2242+
},
22352243
"Cannot read file '{0}': {1}": {
22362244
"category": "Error",
22372245
"code": 5012

src/compiler/sys.ts

Lines changed: 56 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace ts {
2626
getExecutingFilePath(): string;
2727
getCurrentDirectory(): string;
2828
getDirectories(path: string): string[];
29-
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
29+
readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[];
3030
getModifiedTime?(path: string): Date;
3131
createHash?(data: string): string;
3232
getMemoryUsage?(): number;
@@ -74,7 +74,7 @@ namespace ts {
7474
readFile(path: string): string;
7575
writeFile(path: string, contents: string): void;
7676
getDirectories(path: string): string[];
77-
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
77+
readDirectory(path: string, extensions?: string[], basePaths?: string[], excludeEx?: string, includeFileEx?: string, includeDirEx?: string): string[];
7878
watchFile?(path: string, callback: FileWatcherCallback): FileWatcher;
7979
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
8080
realpath(path: string): string;
@@ -85,6 +85,7 @@ namespace ts {
8585
function getWScriptSystem(): System {
8686

8787
const fso = new ActiveXObject("Scripting.FileSystemObject");
88+
const shell = new ActiveXObject("WScript.Shell");
8889

8990
const fileStream = new ActiveXObject("ADODB.Stream");
9091
fileStream.Type = 2 /*text*/;
@@ -152,10 +153,6 @@ namespace ts {
152153
}
153154
}
154155

155-
function getCanonicalPath(path: string): string {
156-
return path.toLowerCase();
157-
}
158-
159156
function getNames(collection: any): string[] {
160157
const result: string[] = [];
161158
for (let e = new Enumerator(collection); !e.atEnd(); e.moveNext()) {
@@ -169,30 +166,22 @@ namespace ts {
169166
return getNames(folder.subfolders);
170167
}
171168

172-
function readDirectory(path: string, extension?: string, exclude?: string[]): string[] {
173-
const result: string[] = [];
174-
exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s)));
175-
visitDirectory(path);
176-
return result;
177-
function visitDirectory(path: string) {
169+
function getAccessibleFileSystemEntries(path: string): FileSystemEntries {
170+
try {
178171
const folder = fso.GetFolder(path || ".");
179172
const files = getNames(folder.files);
180-
for (const current of files) {
181-
const name = combinePaths(path, current);
182-
if ((!extension || fileExtensionIs(name, extension)) && !contains(exclude, getCanonicalPath(name))) {
183-
result.push(name);
184-
}
185-
}
186-
const subfolders = getNames(folder.subfolders);
187-
for (const current of subfolders) {
188-
const name = combinePaths(path, current);
189-
if (!contains(exclude, getCanonicalPath(name))) {
190-
visitDirectory(name);
191-
}
192-
}
173+
const directories = getNames(folder.subfolders);
174+
return { files, directories };
175+
}
176+
catch (e) {
177+
return { files: [], directories: [] };
193178
}
194179
}
195180

181+
function readDirectory(path: string, extensions?: string[], excludes?: string[], includes?: string[]): string[] {
182+
return matchFiles(path, extensions, excludes, includes, /*useCaseSensitiveFileNames*/ false, shell.CurrentDirectory, getAccessibleFileSystemEntries);
183+
}
184+
196185
return {
197186
args,
198187
newLine: "\r\n",
@@ -220,7 +209,7 @@ namespace ts {
220209
return WScript.ScriptFullName;
221210
},
222211
getCurrentDirectory() {
223-
return new ActiveXObject("WScript.Shell").CurrentDirectory;
212+
return shell.CurrentDirectory;
224213
},
225214
getDirectories,
226215
readDirectory,
@@ -381,8 +370,43 @@ namespace ts {
381370
}
382371
}
383372

384-
function getCanonicalPath(path: string): string {
385-
return useCaseSensitiveFileNames ? path : path.toLowerCase();
373+
function getAccessibleFileSystemEntries(path: string): FileSystemEntries {
374+
try {
375+
const entries = _fs.readdirSync(path || ".").sort();
376+
const files: string[] = [];
377+
const directories: string[] = [];
378+
for (const entry of entries) {
379+
// This is necessary because on some file system node fails to exclude
380+
// "." and "..". See https://github.com/nodejs/node/issues/4002
381+
if (entry === "." || entry === "..") {
382+
continue;
383+
}
384+
const name = combinePaths(path, entry);
385+
386+
let stat: any;
387+
try {
388+
stat = _fs.statSync(name);
389+
}
390+
catch (e) {
391+
continue;
392+
}
393+
394+
if (stat.isFile()) {
395+
files.push(entry);
396+
}
397+
else if (stat.isDirectory()) {
398+
directories.push(entry);
399+
}
400+
}
401+
return { files, directories };
402+
}
403+
catch (e) {
404+
return { files: [], directories: [] };
405+
}
406+
}
407+
408+
function readDirectory(path: string, extensions?: string[], excludes?: string[], includes?: string[]): string[] {
409+
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), getAccessibleFileSystemEntries);
386410
}
387411

388412
const enum FileSystemEntryKind {
@@ -415,39 +439,6 @@ namespace ts {
415439
return filter<string>(_fs.readdirSync(path), p => fileSystemEntryExists(combinePaths(path, p), FileSystemEntryKind.Directory));
416440
}
417441

418-
function readDirectory(path: string, extension?: string, exclude?: string[]): string[] {
419-
const result: string[] = [];
420-
exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s)));
421-
visitDirectory(path);
422-
return result;
423-
function visitDirectory(path: string) {
424-
const files = _fs.readdirSync(path || ".").sort();
425-
const directories: string[] = [];
426-
for (const current of files) {
427-
// This is necessary because on some file system node fails to exclude
428-
// "." and "..". See https://github.com/nodejs/node/issues/4002
429-
if (current === "." || current === "..") {
430-
continue;
431-
}
432-
const name = combinePaths(path, current);
433-
if (!contains(exclude, getCanonicalPath(name))) {
434-
const stat = _fs.statSync(name);
435-
if (stat.isFile()) {
436-
if (!extension || fileExtensionIs(name, extension)) {
437-
result.push(name);
438-
}
439-
}
440-
else if (stat.isDirectory()) {
441-
directories.push(name);
442-
}
443-
}
444-
}
445-
for (const current of directories) {
446-
visitDirectory(current);
447-
}
448-
}
449-
}
450-
451442
return {
452443
args: process.argv.slice(2),
453444
newLine: _os.EOL,
@@ -586,7 +577,10 @@ namespace ts {
586577
getExecutingFilePath: () => ChakraHost.executingFile,
587578
getCurrentDirectory: () => ChakraHost.currentDirectory,
588579
getDirectories: ChakraHost.getDirectories,
589-
readDirectory: ChakraHost.readDirectory,
580+
readDirectory: (path: string, extensions?: string[], excludes?: string[], includes?: string[]) => {
581+
const pattern = getFileMatcherPatterns(path, extensions, excludes, includes, !!ChakraHost.useCaseSensitiveFileNames, ChakraHost.currentDirectory);
582+
return ChakraHost.readDirectory(path, extensions, pattern.basePaths, pattern.excludePattern, pattern.includeFilePattern, pattern.includeDirectoryPattern);
583+
},
590584
exit: ChakraHost.quit,
591585
realpath
592586
};

src/compiler/types.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,15 @@ namespace ts {
16771677
}
16781678

16791679
export interface ParseConfigHost {
1680-
readDirectory(rootDir: string, extension: string, exclude: string[]): string[];
1680+
useCaseSensitiveFileNames: boolean;
1681+
1682+
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[]): string[];
1683+
1684+
/**
1685+
* Gets a value indicating whether the specified path exists and is a file.
1686+
* @param path The path to test.
1687+
*/
1688+
fileExists(path: string): boolean;
16811689
}
16821690

16831691
export interface WriteFileCallback {
@@ -2660,6 +2668,17 @@ namespace ts {
26602668
fileNames: string[];
26612669
raw?: any;
26622670
errors: Diagnostic[];
2671+
wildcardDirectories?: Map<WatchDirectoryFlags>;
2672+
}
2673+
2674+
export const enum WatchDirectoryFlags {
2675+
None = 0,
2676+
Recursive = 1 << 0,
2677+
}
2678+
2679+
export interface ExpandResult {
2680+
fileNames: string[];
2681+
wildcardDirectories: Map<WatchDirectoryFlags>;
26632682
}
26642683

26652684
/* @internal */

src/harness/external/chai.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,13 @@ declare module chai {
167167
module assert {
168168
function equal(actual: any, expected: any, message?: string): void;
169169
function notEqual(actual: any, expected: any, message?: string): void;
170+
function deepEqual<T>(actual: T, expected: T, message?: string): void;
171+
function notDeepEqual<T>(actual: T, expected: T, message?: string): void;
172+
function lengthOf(object: any[], length: number, message?: string): void;
170173
function isTrue(value: any, message?: string): void;
171174
function isFalse(value: any, message?: string): void;
172175
function isOk(actual: any, message?: string): void;
176+
function isUndefined(value: any, message?: string): void;
177+
function isDefined(value: any, message?: string): void;
173178
}
174179
}

src/harness/harness.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
/// <reference path="external\chai.d.ts"/>
2424
/// <reference path="sourceMapRecorder.ts"/>
2525
/// <reference path="runnerbase.ts"/>
26+
/// <reference path="virtualFileSystem.ts" />
2627

2728
// Block scoped definitions work poorly for global variables, temporarily enable var
2829
/* tslint:disable:no-var-keyword */
@@ -443,7 +444,7 @@ namespace Harness {
443444
args(): string[];
444445
getExecutingFilePath(): string;
445446
exit(exitCode?: number): void;
446-
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
447+
readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]): string[];
447448
}
448449
export var IO: IO;
449450

@@ -482,7 +483,7 @@ namespace Harness {
482483
export const directoryExists: typeof IO.directoryExists = fso.FolderExists;
483484
export const fileExists: typeof IO.fileExists = fso.FileExists;
484485
export const log: typeof IO.log = global.WScript && global.WScript.StdOut.WriteLine;
485-
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude) => ts.sys.readDirectory(path, extension, exclude);
486+
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude, include) => ts.sys.readDirectory(path, extension, exclude, include);
486487

487488
export function createDirectory(path: string) {
488489
if (directoryExists(path)) {
@@ -552,7 +553,7 @@ namespace Harness {
552553
export const fileExists: typeof IO.fileExists = fs.existsSync;
553554
export const log: typeof IO.log = s => console.log(s);
554555

555-
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude) => ts.sys.readDirectory(path, extension, exclude);
556+
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude, include) => ts.sys.readDirectory(path, extension, exclude, include);
556557

557558
export function createDirectory(path: string) {
558559
if (!directoryExists(path)) {
@@ -740,8 +741,22 @@ namespace Harness {
740741
Http.writeToServerSync(serverRoot + path, "WRITE", contents);
741742
}
742743

743-
export function readDirectory(path: string, extension?: string, exclude?: string[]) {
744-
return listFiles(path).filter(f => !extension || ts.fileExtensionIs(f, extension));
744+
export function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]) {
745+
const fs = new Utils.VirtualFileSystem(path, useCaseSensitiveFileNames());
746+
for (const file in listFiles(path)) {
747+
fs.addFile(file);
748+
}
749+
return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), getCurrentDirectory(), path => {
750+
const entry = fs.traversePath(path);
751+
if (entry && entry.isDirectory()) {
752+
const directory = <Utils.VirtualDirectory>entry;
753+
return {
754+
files: ts.map(directory.getFiles(), f => f.name),
755+
directories: ts.map(directory.getDirectories(), d => d.name)
756+
};
757+
}
758+
return { files: [], directories: [] };
759+
});
745760
}
746761
}
747762
}
@@ -1531,7 +1546,9 @@ namespace Harness {
15311546

15321547
// unit tests always list files explicitly
15331548
const parseConfigHost: ts.ParseConfigHost = {
1534-
readDirectory: (name) => []
1549+
useCaseSensitiveFileNames: false,
1550+
readDirectory: (name) => [],
1551+
fileExists: (name) => true
15351552
};
15361553

15371554
// check if project has tsconfig.json in the list of files

src/harness/harnessLanguageService.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ namespace Harness.LanguageService {
288288
readDirectory(rootDir: string, extension: string): string {
289289
throw new Error("NYI");
290290
}
291+
readDirectoryNames(path: string): string {
292+
throw new Error("Not implemented.");
293+
}
294+
readFileNames(path: string): string {
295+
throw new Error("Not implemented.");
296+
}
291297
fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; }
292298
readFile(fileName: string) {
293299
const snapshot = this.nativeHost.getScriptSnapshot(fileName);
@@ -611,7 +617,7 @@ namespace Harness.LanguageService {
611617
return [];
612618
}
613619

614-
readDirectory(path: string, extension?: string): string[] {
620+
readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]): string[] {
615621
throw new Error("Not implemented Yet.");
616622
}
617623

@@ -695,4 +701,3 @@ namespace Harness.LanguageService {
695701
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
696702
}
697703
}
698-

src/harness/loggedIO.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ interface IOLog {
6161
}[];
6262
directoriesRead: {
6363
path: string,
64-
extension: string,
64+
extension: string[],
6565
exclude: string[],
66+
include: string[],
6667
result: string[]
6768
}[];
6869
}
@@ -217,9 +218,9 @@ namespace Playback {
217218
memoize(path => findResultByPath(wrapper, replayLog.filesRead, path).contents));
218219

219220
wrapper.readDirectory = recordReplay(wrapper.readDirectory, underlying)(
220-
(path, extension, exclude) => {
221-
const result = (<ts.System>underlying).readDirectory(path, extension, exclude);
222-
const logEntry = { path, extension, exclude, result };
221+
(path, extension, exclude, include) => {
222+
const result = (<ts.System>underlying).readDirectory(path, extension, exclude, include);
223+
const logEntry = { path, extension, exclude, include, result };
223224
recordLog.directoriesRead.push(logEntry);
224225
return result;
225226
},

0 commit comments

Comments
 (0)