Skip to content

Commit 6997e9b

Browse files
authored
Merge pull request #17269 from Microsoft/watchImprovements
Watch improvements in tsserver
2 parents 7f7d0c6 + 8ac01d7 commit 6997e9b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+8737
-3279
lines changed

Jakefile.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ var typesMapOutputPath = path.join(builtLocalDirectory, 'typesMap.json');
9494
var harnessCoreSources = [
9595
"harness.ts",
9696
"virtualFileSystem.ts",
97+
"virtualFileSystemWithWatch.ts",
9798
"sourceMapRecorder.ts",
9899
"harnessLanguageService.ts",
99100
"fourslash.ts",
@@ -126,14 +127,14 @@ var harnessSources = harnessCoreSources.concat([
126127
"transpile.ts",
127128
"reuseProgramStructure.ts",
128129
"textStorage.ts",
129-
"cachingInServerLSHost.ts",
130130
"moduleResolution.ts",
131131
"tsconfigParsing.ts",
132132
"commandLineParsing.ts",
133133
"configurationExtension.ts",
134134
"convertCompilerOptionsFromJson.ts",
135135
"convertTypeAcquisitionFromJson.ts",
136136
"tsserverProjectSystem.ts",
137+
"tscWatchMode.ts",
137138
"compileOnSave.ts",
138139
"typingsInstaller.ts",
139140
"projectErrors.ts",
@@ -159,7 +160,6 @@ var harnessSources = harnessCoreSources.concat([
159160
"utilities.ts",
160161
"scriptVersionCache.ts",
161162
"scriptInfo.ts",
162-
"lsHost.ts",
163163
"project.ts",
164164
"typingsCache.ts",
165165
"editorServices.ts",

src/compiler/builder.ts

Lines changed: 524 additions & 0 deletions
Large diffs are not rendered by default.

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1209,7 +1209,7 @@ namespace ts {
12091209
}
12101210

12111211
function diagnosticName(nameArg: __String | Identifier) {
1212-
return typeof nameArg === "string" ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
1212+
return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
12131213
}
12141214

12151215
function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) {

src/compiler/commandLineParser.ts

Lines changed: 87 additions & 55 deletions
Large diffs are not rendered by default.

src/compiler/core.ts

Lines changed: 245 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -467,13 +467,20 @@ namespace ts {
467467
return result;
468468
}
469469

470-
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U[] | undefined): U[] {
470+
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U | U[] | undefined): U[] {
471471
const result: U[] = [];
472472
while (true) {
473473
const { value, done } = iter.next();
474474
if (done) break;
475475
const res = mapfn(value);
476-
if (res) result.push(...res);
476+
if (res) {
477+
if (isArray(res)) {
478+
result.push(...res);
479+
}
480+
else {
481+
result.push(res);
482+
}
483+
}
477484
}
478485
return result;
479486
}
@@ -523,6 +530,19 @@ namespace ts {
523530
return result;
524531
}
525532

533+
export function mapDefinedIter<T, U>(iter: Iterator<T>, mapFn: (x: T) => U | undefined): U[] {
534+
const result: U[] = [];
535+
while (true) {
536+
const { value, done } = iter.next();
537+
if (done) break;
538+
const res = mapFn(value);
539+
if (res !== undefined) {
540+
result.push(res);
541+
}
542+
}
543+
return result;
544+
}
545+
526546
/**
527547
* Computes the first matching span of elements and returns a tuple of the first span
528548
* and the remaining elements.
@@ -766,7 +786,7 @@ namespace ts {
766786
* @param end The offset in `from` at which to stop copying values (non-inclusive).
767787
*/
768788
export function addRange<T>(to: T[] | undefined, from: ReadonlyArray<T> | undefined, start?: number, end?: number): T[] | undefined {
769-
if (from === undefined) return to;
789+
if (from === undefined || from.length === 0) return to;
770790
if (to === undefined) return from.slice(start, end);
771791
start = start === undefined ? 0 : toOffset(from, start);
772792
end = end === undefined ? from.length : toOffset(from, end);
@@ -1222,6 +1242,13 @@ namespace ts {
12221242
return Array.isArray ? Array.isArray(value) : value instanceof Array;
12231243
}
12241244

1245+
/**
1246+
* Tests whether a value is string
1247+
*/
1248+
export function isString(text: any): text is string {
1249+
return typeof text === "string";
1250+
}
1251+
12251252
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
12261253
return value !== undefined && test(value) ? value : undefined;
12271254
}
@@ -1232,7 +1259,13 @@ namespace ts {
12321259
}
12331260

12341261
/** Does nothing. */
1235-
export function noop(): void {}
1262+
export function noop(): void { }
1263+
1264+
/** Do nothing and return false */
1265+
export function returnFalse(): false { return false; }
1266+
1267+
/** Do nothing and return true */
1268+
export function returnTrue(): true { return true; }
12361269

12371270
/** Returns its argument. */
12381271
export function identity<T>(x: T) { return x; }
@@ -1476,16 +1509,16 @@ namespace ts {
14761509
function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison {
14771510
while (text1 && text2) {
14781511
// We still have both chains.
1479-
const string1 = typeof text1 === "string" ? text1 : text1.messageText;
1480-
const string2 = typeof text2 === "string" ? text2 : text2.messageText;
1512+
const string1 = isString(text1) ? text1 : text1.messageText;
1513+
const string2 = isString(text2) ? text2 : text2.messageText;
14811514

14821515
const res = compareValues(string1, string2);
14831516
if (res) {
14841517
return res;
14851518
}
14861519

1487-
text1 = typeof text1 === "string" ? undefined : text1.next;
1488-
text2 = typeof text2 === "string" ? undefined : text2.next;
1520+
text1 = isString(text1) ? undefined : text1.next;
1521+
text2 = isString(text2) ? undefined : text2.next;
14891522
}
14901523

14911524
if (!text1 && !text2) {
@@ -1811,6 +1844,8 @@ namespace ts {
18111844
* Removes a trailing directory separator from a path.
18121845
* @param path The path.
18131846
*/
1847+
export function removeTrailingDirectorySeparator(path: Path): Path;
1848+
export function removeTrailingDirectorySeparator(path: string): string;
18141849
export function removeTrailingDirectorySeparator(path: string) {
18151850
if (path.charAt(path.length - 1) === directorySeparator) {
18161851
return path.substr(0, path.length - 1);
@@ -2073,8 +2108,8 @@ namespace ts {
20732108
}
20742109

20752110
export interface FileSystemEntries {
2076-
files: ReadonlyArray<string>;
2077-
directories: ReadonlyArray<string>;
2111+
readonly files: ReadonlyArray<string>;
2112+
readonly directories: ReadonlyArray<string>;
20782113
}
20792114

20802115
export interface FileMatcherPatterns {
@@ -2227,7 +2262,7 @@ namespace ts {
22272262
return ScriptKind.TS;
22282263
case Extension.Tsx:
22292264
return ScriptKind.TSX;
2230-
case ".json":
2265+
case Extension.Json:
22312266
return ScriptKind.JSON;
22322267
default:
22332268
return ScriptKind.Unknown;
@@ -2631,5 +2666,203 @@ namespace ts {
26312666
return (arg: T) => f(arg) && g(arg);
26322667
}
26332668

2634-
export function assertTypeIsNever(_: never): void {}
2669+
export function assertTypeIsNever(_: never): void { }
2670+
2671+
export interface CachedDirectoryStructureHost extends DirectoryStructureHost {
2672+
addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): void;
2673+
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
2674+
clearCache(): void;
2675+
}
2676+
2677+
interface MutableFileSystemEntries {
2678+
readonly files: string[];
2679+
readonly directories: string[];
2680+
}
2681+
2682+
export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost {
2683+
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
2684+
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
2685+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
2686+
return {
2687+
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
2688+
newLine: host.newLine,
2689+
readFile: (path, encoding) => host.readFile(path, encoding),
2690+
write: s => host.write(s),
2691+
writeFile,
2692+
fileExists,
2693+
directoryExists,
2694+
createDirectory,
2695+
getCurrentDirectory,
2696+
getDirectories,
2697+
readDirectory,
2698+
addOrDeleteFileOrDirectory,
2699+
addOrDeleteFile,
2700+
clearCache,
2701+
exit: code => host.exit(code)
2702+
};
2703+
2704+
function toPath(fileName: string) {
2705+
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
2706+
}
2707+
2708+
function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined {
2709+
return cachedReadDirectoryResult.get(rootDirPath);
2710+
}
2711+
2712+
function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined {
2713+
return getCachedFileSystemEntries(getDirectoryPath(path));
2714+
}
2715+
2716+
function getBaseNameOfFileName(fileName: string) {
2717+
return getBaseFileName(normalizePath(fileName));
2718+
}
2719+
2720+
function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) {
2721+
const resultFromHost: MutableFileSystemEntries = {
2722+
files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [],
2723+
directories: host.getDirectories(rootDir) || []
2724+
};
2725+
2726+
cachedReadDirectoryResult.set(rootDirPath, resultFromHost);
2727+
return resultFromHost;
2728+
}
2729+
2730+
/**
2731+
* If the readDirectory result was already cached, it returns that
2732+
* Otherwise gets result from host and caches it.
2733+
* The host request is done under try catch block to avoid caching incorrect result
2734+
*/
2735+
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined {
2736+
const cachedResult = getCachedFileSystemEntries(rootDirPath);
2737+
if (cachedResult) {
2738+
return cachedResult;
2739+
}
2740+
2741+
try {
2742+
return createCachedFileSystemEntries(rootDir, rootDirPath);
2743+
}
2744+
catch (_e) {
2745+
// If there is exception to read directories, dont cache the result and direct the calls to host
2746+
Debug.assert(!cachedReadDirectoryResult.has(rootDirPath));
2747+
return undefined;
2748+
}
2749+
}
2750+
2751+
function fileNameEqual(name1: string, name2: string) {
2752+
return getCanonicalFileName(name1) === getCanonicalFileName(name2);
2753+
}
2754+
2755+
function hasEntry(entries: ReadonlyArray<string>, name: string) {
2756+
return some(entries, file => fileNameEqual(file, name));
2757+
}
2758+
2759+
function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) {
2760+
if (hasEntry(entries, baseName)) {
2761+
if (!isValid) {
2762+
return filterMutate(entries, entry => !fileNameEqual(entry, baseName));
2763+
}
2764+
}
2765+
else if (isValid) {
2766+
return entries.push(baseName);
2767+
}
2768+
}
2769+
2770+
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
2771+
const path = toPath(fileName);
2772+
const result = getCachedFileSystemEntriesForBaseDir(path);
2773+
if (result) {
2774+
updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true);
2775+
}
2776+
return host.writeFile(fileName, data, writeByteOrderMark);
2777+
}
2778+
2779+
function fileExists(fileName: string): boolean {
2780+
const path = toPath(fileName);
2781+
const result = getCachedFileSystemEntriesForBaseDir(path);
2782+
return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) ||
2783+
host.fileExists(fileName);
2784+
}
2785+
2786+
function directoryExists(dirPath: string): boolean {
2787+
const path = toPath(dirPath);
2788+
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath);
2789+
}
2790+
2791+
function createDirectory(dirPath: string) {
2792+
const path = toPath(dirPath);
2793+
const result = getCachedFileSystemEntriesForBaseDir(path);
2794+
const baseFileName = getBaseNameOfFileName(dirPath);
2795+
if (result) {
2796+
updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
2797+
}
2798+
host.createDirectory(dirPath);
2799+
}
2800+
2801+
function getDirectories(rootDir: string): string[] {
2802+
const rootDirPath = toPath(rootDir);
2803+
const result = tryReadDirectory(rootDir, rootDirPath);
2804+
if (result) {
2805+
return result.directories.slice();
2806+
}
2807+
return host.getDirectories(rootDir);
2808+
}
2809+
2810+
function readDirectory(rootDir: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
2811+
const rootDirPath = toPath(rootDir);
2812+
const result = tryReadDirectory(rootDir, rootDirPath);
2813+
if (result) {
2814+
return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries);
2815+
}
2816+
return host.readDirectory(rootDir, extensions, excludes, includes, depth);
2817+
2818+
function getFileSystemEntries(dir: string) {
2819+
const path = toPath(dir);
2820+
if (path === rootDirPath) {
2821+
return result;
2822+
}
2823+
return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path);
2824+
}
2825+
}
2826+
2827+
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
2828+
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
2829+
if (existingResult) {
2830+
// This was a folder already present, remove it if this doesnt exist any more
2831+
if (!host.directoryExists(fileOrDirectory)) {
2832+
cachedReadDirectoryResult.delete(fileOrDirectoryPath);
2833+
}
2834+
}
2835+
else {
2836+
// This was earlier a file (hence not in cached directory contents)
2837+
// or we never cached the directory containing it
2838+
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath);
2839+
if (parentResult) {
2840+
const baseName = getBaseNameOfFileName(fileOrDirectory);
2841+
if (parentResult) {
2842+
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrDirectoryPath));
2843+
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrDirectoryPath));
2844+
}
2845+
}
2846+
}
2847+
}
2848+
2849+
function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) {
2850+
if (eventKind === FileWatcherEventKind.Changed) {
2851+
return;
2852+
}
2853+
2854+
const parentResult = getCachedFileSystemEntriesForBaseDir(filePath);
2855+
if (parentResult) {
2856+
updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created);
2857+
}
2858+
}
2859+
2860+
function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) {
2861+
updateFileSystemEntry(parentResult.files, baseName, fileExists);
2862+
}
2863+
2864+
function clearCache() {
2865+
cachedReadDirectoryResult.clear();
2866+
}
2867+
}
26352868
}

src/compiler/diagnosticMessages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3090,10 +3090,6 @@
30903090
"category": "Message",
30913091
"code": 6128
30923092
},
3093-
"The config file '{0}' found doesn't contain any source files.": {
3094-
"category": "Error",
3095-
"code": 6129
3096-
},
30973093
"Resolving real path for '{0}', result '{1}'.": {
30983094
"category": "Message",
30993095
"code": 6130

0 commit comments

Comments
 (0)