8000 Merge pull request #17669 from Microsoft/builder · microsoft/TypeScript@9e570c3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9e570c3

Browse files
authored
Merge pull request #17669 from Microsoft/builder
Improvements to tsc --watch
2 parents 84b2e23 + ea95f3b commit 9e570c3

Some content is hidden

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

44 files changed

+5754
-1986
lines changed

Jakefile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ var languageServiceLibrarySources = filesFromConfig(path.join(serverDirectory, "
9191
var harnessCoreSources = [
9292
"harness.ts",
9393
"virtualFileSystem.ts",
94+
"virtualFileSystemWithWatch.ts",
9495
"sourceMapRecorder.ts",
9596
"harnessLanguageService.ts",
9697
"fourslash.ts",
@@ -128,6 +129,7 @@ var harnessSources = harnessCoreSources.concat([
128129
"convertCompilerOptionsFromJson.ts",
129130
"convertTypeAcquisitionFromJson.ts",
130131
"tsserverProjectSystem.ts",
132+
"tscWatchMode.ts",
131133
"compileOnSave.ts",
132134
"typingsInstaller.ts",
133135
"projectErrors.ts",
8000

src/compiler/builder.ts

Lines changed: 492 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
@@ -1166,7 +1166,7 @@ namespace ts {
11661166
}
11671167

11681168
function diagnosticName(nameArg: __String | Identifier) {
1169-
return typeof nameArg === "string" ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
1169+
return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
11701170
}
11711171

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

src/compiler/commandLineParser.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ namespace ts {
885885
*/
886886
export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } {
887887
const textOrDiagnostic = tryReadFile(fileName, readFile);
888-
return typeof textOrDiagnostic === "string" ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
888+
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
889889
}
890890

891891
/**
@@ -907,7 +907,7 @@ namespace ts {
907907
*/
908908
export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile {
909909
const textOrDiagnostic = tryReadFile(fileName, readFile);
910-
return typeof textOrDiagnostic === "string" ? parseJsonText(fileName, textOrDiagnostic) : <JsonSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
910+
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : <JsonSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
911911
}
912912

913913
function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic {
@@ -1111,9 +1111,9 @@ namespace ts {
11111111
if (!isDoubleQuotedString(valueExpression)) {
11121112
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected));
11131113
}
1114-
reportInvalidOptionValue(option && (typeof option.type === "string" && option.type !== "string"));
1114+
reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string"));
11151115
const text = (<StringLiteral>valueExpression).text;
1116-
if (option && typeof option.type !== "string") {
1116+
if (option && !isString(option.type)) {
11171117
const customOption = <CommandLineOptionOfCustomType>option;
11181118
// Validate custom option type
11191119
if (!customOption.type.has(text.toLowerCase())) {
@@ -1184,15 +1184,15 @@ namespace ts {
11841184
function getCompilerOptionValueTypeString(option: CommandLineOption) {
11851185
return option.type === "list" ?
11861186
"Array" :
1187-
typeof option.type === "string" ? option.type : "string";
1187+
isString(option.type) ? option.type : "string";
11881188
}
11891189

11901190
function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue {
11911191
if (option) {
11921192
if (option.type === "list") {
11931193
return isArray(value);
11941194
}
1195-
const expectedType = typeof option.type === "string" ? option.type : "string";
1195+
const expectedType = isString(option.type) ? option.type : "string";
11961196
return typeof value === expectedType;
11971197
}
11981198
}
@@ -1578,7 +1578,7 @@ namespace ts {
15781578
let extendedConfigPath: Path;
15791579

15801580
if (json.extends) {
1581-
if (typeof json.extends !== "string") {
1581+
if (!isString(json.extends)) {
15821582
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
15831583
}
15841584
else {
@@ -1803,7 +1803,7 @@ namespace ts {
18031803
if (optType === "list" && isArray(value)) {
18041804
return convertJsonOptionOfListType(<CommandLineOptionOfListType>opt, value, basePath, errors);
18051805
}
1806-
else if (typeof optType !== "string") {
1806+
else if (!isString(optType)) {
18071807
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
18081808
}
18091809
return normalizeNonListOptionValue(opt, basePath, value);
@@ -1816,13 +1816,13 @@ namespace ts {
18161816
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
18171817
if (option.type === "list") {
18181818
const listOption = <CommandLineOptionOfListType>option;
1819-
if (listOption.element.isFilePath || typeof listOption.element.type !== "string") {
1819+
if (listOption.element.isFilePath || !isString(listOption.element.type)) {
18201820
return <CompilerOptionsValue>filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v);
18211821
}
18221822
return value;
18231823
}
1824-
else if (typeof option.type !== "string") {
1825-
return option.type.get(typeof value === "string" ? value.toLowerCase() : value);
1824+
else if (!isString(option.type)) {
1825+
return option.type.get(isString(value) ? value.toLowerCase() : value);
18261826
}
18271827
return normalizeNonListOptionValue(option, basePath, value);
18281828
}
@@ -1984,7 +1984,7 @@ namespace ts {
19841984
* @param host The host used to resolve files and directories.
19851985
* @param extraFileExtensions optionaly file extra file extension information from host
19861986
*/
1987-
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo>): ExpandResult {
1987+
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo> = []): ExpandResult {
19881988
basePath = normalizePath(basePath);
19891989

19901990
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper;

src/compiler/core.ts

Lines changed: 217 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,13 @@ namespace ts {
12021202
return Array.isArray ? Array.isArray(value) : value instanceof Array;
12031203
}
12041204

1205+
/**
1206+
* Tests whether a value is string
1207+
*/
1208+
export function isString(text: any): text is string {
1209+
return typeof text === "string";
1210+
}
1211+
12051212
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
12061213
return value !== undefined && test(value) ? value : undefined;
12071214
}
@@ -1212,7 +1219,10 @@ namespace ts {
12121219
}
12131220

12141221
/** Does nothing. */
1215-
export function noop(): void {}
1222+
export function noop(): void { }
1223+
1224+
/** Do nothing and return false */
1225+
export function returnFalse(): false { return false; }
12161226

12171227
/** Throws an error because a function is not implemented. */
12181228
export function notImplemented(): never {
@@ -1455,16 +1465,16 @@ namespace ts {
14551465
function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison {
14561466
while (text1 && text2) {
14571467
// We still have both chains.
1458-
const string1 = typeof text1 === "string" ? text1 : text1.messageText;
1459-
const string2 = typeof text2 === "string" ? text2 : text2.messageText;
1468+
const string1 = isString(text1) ? text1 : text1.messageText;
1469+
const string2 = isString(text2) ? text2 : text2.messageText;
14601470

14611471
const res = compareValues(string1, string2);
14621472
if (res) {
14631473
return res;
14641474
}
14651475

1466-
text1 = typeof text1 === "string" ? undefined : text1.next;
1467-
text2 = typeof text2 === "string" ? undefined : text2.next;
1476+
text1 = isString(text1) ? undefined : text1.next;
1477+
text2 = isString(text2) ? undefined : text2.next;
14681478
}
14691479

14701480
if (!text1 && !text2) {
@@ -2066,8 +2076,8 @@ namespace ts {
20662076
}
20672077

20682078
export interface FileSystemEntries {
2069-
files: ReadonlyArray<string>;
2070-
directories: ReadonlyArray<string>;
2079+
readonly files: ReadonlyArray<string>;
2080+
readonly directories: ReadonlyArray<string>;
20712081
}
20722082

20732083
export interface FileMatcherPatterns {
@@ -2620,4 +2630,204 @@ namespace ts {
26202630
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
26212631
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
26222632
}
2633+
2634+
export interface HostForCaching extends PartialSystem {
2635+
useCaseSensitiveFileNames: boolean;
2636+
}
2637+
2638+
export interface CachedHost {
2639+
addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void;
2640+
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
2641+
clearCache(): void;
2642+
}
2643+
2644+
export interface CachedPartialSystem extends PartialSystem, CachedHost {
2645+
}
2646+
2647+
interface MutableFileSystemEntries {
2648+
readonly files: string[];
2649+
readonly directories: string[];
2650+
}
2651+
2652+
export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem {
2653+
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
2654+
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
2655+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
2656+
return {
2657+
writeFile,
2658+
fileExists,
2659+
directoryExists,
2660+
createDirectory,
2661+
getCurrentDirectory,
2662+
getDirectories,
2663+
readDirectory,
2664+
addOrDeleteFileOrFolder,
2665+
addOrDeleteFile,
2666+
clearCache
2667+
};
2668+
2669+
function toPath(fileName: string) {
2670+
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
2671+
}
2672+
2673+
function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined {
2674+
return cachedReadDirectoryResult.get(rootDirPath);
2675+
}
2676+
2677+
function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined {
2678+
return getCachedFileSystemEntries(getDirectoryPath(path));
2679+
}
2680+
2681+
function getBaseNameOfFileName(fileName: string) {
2682+
return getBaseFileName(normalizePath(fileName));
2683+
}
2684+
2685+
function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) {
2686+
const resultFromHost: MutableFileSystemEntries = {
2687+
files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [],
2688+
directories: host.getDirectories(rootDir) || []
2689+
};
2690+
2691+
cachedReadDirectoryResult.set(rootDirPath, resultFromHost);
2692+
return resultFromHost;
2693+
}
2694+
2695+
/**
2696+
* If the readDirectory result was already cached, it returns that
2697+
* Otherwise gets result from host and caches it.
2698+
* The host request is done under try catch block to avoid caching incorrect result
2699+
*/
2700+
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined {
2701+
const cachedResult = getCachedFileSystemEntries(rootDirPath);
2702+
if (cachedResult) {
2703+
return cachedResult;
2704+
}
2705+
2706+
try {
2707+
return createCachedFileSystemEntries(rootDir, rootDirPath);
2708+
}
2709+
catch (_e) {
2710+
// If there is exception to read directories, dont cache the result and direct the calls to host
2711+
Debug.assert(!cachedReadDirectoryResult.has(rootDirPath));
2712+
return undefined;
2713+
}
2714+
}
2715+
2716+
function fileNameEqual(name1: string, name2: string) {
2717+
return getCanonicalFileName(name1) === getCanonicalFileName(name2);
2718+
}
2719+
2720+ 10000
function hasEntry(entries: ReadonlyArray<string>, name: string) {
2721+
return some(entries, file => fileNameEqual(file, name));
2722+
}
2723+
2724+
function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) {
2725+
if (hasEntry(entries, baseName)) {
2726+
if (!isValid) {
2727+
return filterMutate(entries, entry => !fileNameEqual(entry, baseName));
2728+
}
2729+
}
2730+
else if (isValid) {
2731+
return entries.push(baseName);
2732+
}
2733+
}
2734+
2735+
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
2736+
const path = toPath(fileName);
2737+
const result = getCachedFileSystemEntriesForBaseDir(path);
2738+
if (result) {
2739+
updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true);
2740+
}
2741+
return host.writeFile(fileName, data, writeByteOrderMark);
2742+
}
2743+
2744+
function fileExists(fileName: string): boolean {
2745+
const path = toPath(fileName);
2746+
const result = getCachedFileSystemEntriesForBaseDir(path);
2747+
return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) ||
2748+
host.fileExists(fileName);
2749+
}
2750+
2751+
function directoryExists(dirPath: string): boolean {
2752+
const path = toPath(dirPath);
2753+
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath);
2754+
}
2755+
2756+
function createDirectory(dirPath: string) {
2757+
const path = toPath(dirPath);
2758+
const result = getCachedFileSystemEntriesForBaseDir(path);
2759+
const baseFileName = getBaseNameOfFileName(dirPath);
2760+
if (result) {
2761+
updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
2762+
}
2763+
host.createDirectory(dirPath);
2764+
}
2765+
2766+
function getDirectories(rootDir: string): string[] {
2767+
const rootDirPath = toPath(rootDir);
2768+
const result = tryReadDirectory(rootDir, rootDirPath);
2769+
if (result) {
2770+
return result.directories.slice();
2771+
}
2772+
return host.getDirectories(rootDir);
2773+
}
2774+
2775+
function readDirectory(rootDir: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
2776+
const rootDirPath = toPath(rootDir);
2777+
const result = tryReadDirectory(rootDir, rootDirPath);
2778+
if (result) {
2779+
return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries);
2780+
}
2781+
return host.readDirectory(rootDir, extensions, excludes, includes, depth);
2782+
2783+
function getFileSystemEntries(dir: string) {
2784+
const path = toPath(dir);
2785+
if (path === rootDirPath) {
2786+
return result;
2787+
}
2788+
return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path);
2789+
}
2790+
}
2791+
2792+
function addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path) {
2793+
const existingResult = getCachedFileSystemEntries(fileOrFolderPath);
2794+
if (existingResult) {
2795+
// This was a folder already present, remove it if this doesnt exist any more
2796+
if (!host.directoryExists(fileOrFolder)) {
2797+
cachedReadDirectoryResult.delete(fileOrFolderPath);
2798+
}
2799+
}
2800+
else {
2801+
// This was earlier a file (hence not in cached directory contents)
2802+
// or we never cached the directory containing it
2803+
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrFolderPath);
2804+
if (parentResult) {
2805+
const baseName = getBaseNameOfFileName(fileOrFolder);
2806+
if (parentResult) {
2807+
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath));
2808+
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath));
2809+
}
2810+
}
2811+
}
2812+
}
2813+
2814+
function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) {
2815+
if (eventKind === FileWatcherEventKind.Changed) {
2816+
return;
2817+
}
2818+
2819+
const parentResult = getCachedFileSystemEntriesForBaseDir(filePath);
2820+
if (parentResult) {
2821+
updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created);
2822+
}
2823+
}
2824+
2825+
function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) {
2826+
updateFileSystemEntry(parentResult.files, baseName, fileExists);
2827+
}
2828+
2829+
function clearCache() {
2830+
cachedReadDirectoryResult.clear();
2831+
}
2832+
}
26232833
}

0 commit comments

Comments
 (0)
0