Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ef5935b

Browse filesBrowse files
committed
Initial refactoring so that watch from tsc follows the tsserver projects
1 parent 94a589b commit ef5935b
Copy full SHA for ef5935b

12 files changed

+960-508Lines changed: 960 additions & 508 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/commandLineParser.ts‎

Copy file name to clipboardExpand all lines: src/compiler/commandLineParser.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1979,7 +1979,7 @@ namespace ts {
19791979
* @param host The host used to resolve files and directories.
19801980
* @param errors An array for diagnostic reporting.
19811981
*/
1982-
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo>): ExpandResult {
1982+
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo> = []): ExpandResult {
19831983
basePath = normalizePath(basePath);
19841984

19851985
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper;
Collapse file

‎src/compiler/core.ts‎

Copy file name to clipboardExpand all lines: src/compiler/core.ts
+164Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2559,4 +2559,168 @@ namespace ts {
25592559
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
25602560
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
25612561
}
2562+
2563+
export interface HostForCaching {
2564+
useCaseSensitiveFileNames: boolean;
2565+
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
2566+
fileExists(path: string): boolean;
2567+
directoryExists(path: string): boolean;
2568+
createDirectory(path: string): void;
2569+
getCurrentDirectory(): string;
2570+
getDirectories(path: string): string[];
2571+
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
2572+
}
2573+
2574+
export interface CachedHost {
2575+
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
2576+
fileExists(path: string): boolean;
2577+
directoryExists(path: string): boolean;
2578+
createDirectory(path: string): void;
2579+
getCurrentDirectory(): string;
2580+
getDirectories(path: string): string[];
2581+
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
2582+
addOrDeleteFileOrFolder(fileOrFolder: string): void;
2583+
clearCache(): void;
2584+
}
2585+
2586+
export function createCachedHost(host: HostForCaching): CachedHost {
2587+
const cachedReadDirectoryResult = createMap<FileSystemEntries>();
2588+
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
2589+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
2590+
return {
2591+
writeFile,
2592+
fileExists,
2593+
directoryExists,
2594+
createDirectory,
2595+
getCurrentDirectory,
2596+
getDirectories,
2597+
readDirectory,
2598+
addOrDeleteFileOrFolder,
2599+
clearCache
2600+
};
2601+
2602+
function toPath(fileName: string) {
2603+
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
2604+
}
2605+
2606+
function getFileSystemEntries(rootDir: string) {
2607+
const path = toPath(rootDir);
2608+
const cachedResult = cachedReadDirectoryResult.get(path);
2609+
if (cachedResult) {
2610+
return cachedResult;
2611+
}
2612+
2613+
const resultFromHost: FileSystemEntries = {
2614+
files: host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [],
2615+
directories: host.getDirectories(rootDir) || []
2616+
};
2617+
2618+
cachedReadDirectoryResult.set(path, resultFromHost);
2619+
return resultFromHost;
2620+
}
2621+
2622+
function canWorkWithCacheForDir(rootDir: string) {
2623+
// Some of the hosts might not be able to handle read directory or getDirectories
2624+
const path = toPath(rootDir);
2625+
if (cachedReadDirectoryResult.get(path)) {
2626+
return true;
2627+
}
2628+
try {
2629+
return getFileSystemEntries(rootDir);
2630+
}
2631+
catch (_e) {
2632+
return false;
2633+
}
2634+
}
2635+
2636+
function fileNameEqual(name1: string, name2: string) {
2637+
return getCanonicalFileName(name1) === getCanonicalFileName(name2);
2638+
}
2639+
2640+
function hasEntry(entries: ReadonlyArray<string>, name: string) {
2641+
return some(entries, file => fileNameEqual(file, name));
2642+
}
2643+
2644+
function updateFileSystemEntry(entries: ReadonlyArray<string>, baseName: string, isValid: boolean) {
2645+
if (hasEntry(entries, baseName)) {
2646+
if (!isValid) {
2647+
return filter(entries, entry => !fileNameEqual(entry, baseName));
2648+
}
2649+
}
2650+
else if (isValid) {
2651+
return entries.concat(baseName);
2652+
}
2653+
return entries;
2654+
}
2655+
2656+
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
2657+
const path = toPath(fileName);
2658+
const result = cachedReadDirectoryResult.get(getDirectoryPath(path));
2659+
const baseFileName = getBaseFileName(normalizePath(fileName));
2660+
if (result) {
2661+
result.files = updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true);
2662+
}
2663+
return host.writeFile(fileName, data, writeByteOrderMark);
2664+
}
2665+
2666+
function fileExists(fileName: string): boolean {
2667+
const path = toPath(fileName);
2668+
const result = cachedReadDirectoryResult.get(getDirectoryPath(path));
2669+
const baseName = getBaseFileName(normalizePath(fileName));
2670+
return (result && hasEntry(result.files, baseName)) || host.fileExists(fileName);
2671+
}
2672+
2673+
function directoryExists(dirPath: string): boolean {
2674+
const path = toPath(dirPath);
2675+
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath);
2676+
}
2677+
2678+
function createDirectory(dirPath: string) {
2679+
const path = toPath(dirPath);
2680+
const result = cachedReadDirectoryResult.get(getDirectoryPath(path));
2681+
const baseFileName = getBaseFileName(path);
2682+
if (result) {
2683+
result.directories = updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
2684+
}
2685+
host.createDirectory(dirPath);
2686+
}
2687+
2688+
function getDirectories(rootDir: string): string[] {
2689+
if (canWorkWithCacheForDir(rootDir)) {
2690+
return getFileSystemEntries(rootDir).directories.slice();
2691+
}
2692+
return host.getDirectories(rootDir);
2693+
}
2694+
function readDirectory(rootDir: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
2695+
if (canWorkWithCacheForDir(rootDir)) {
2696+
return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, path => getFileSystemEntries(path));
2697+
}
2698+
return host.readDirectory(rootDir, extensions, excludes, includes, depth);
2699+
}
2700+
2701+
function addOrDeleteFileOrFolder(fileOrFolder: string) {
2702+
const path = toPath(fileOrFolder);
2703+
const existingResult = cachedReadDirectoryResult.get(path);
2704+
if (existingResult) {
2705+
if (!host.directoryExists(fileOrFolder)) {
2706+
cachedReadDirectoryResult.delete(path);
2707+
}
2708+
}
2709+
else {
2710+
// Was this earlier file
2711+
const parentResult = cachedReadDirectoryResult.get(getDirectoryPath(path));
2712+
if (parentResult) {
2713+
const baseName = getBaseFileName(fileOrFolder);
2714+
if (parentResult) {
2715+
parentResult.files = updateFileSystemEntry(parentResult.files, baseName, host.fileExists(path));
2716+
parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(path));
2717+
}
2718+
}
2719+
}
2720+
}
2721+
2722+
function clearCache() {
2723+
cachedReadDirectoryResult.clear();
2724+
}
2725+
}
25622726
}
Collapse file

‎src/compiler/program.ts‎

Copy file name to clipboardExpand all lines: src/compiler/program.ts
+123-3Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,114 @@ namespace ts {
386386
allDiagnostics?: Diagnostic[];
387387
}
388388

389+
export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path) => string): boolean {
390+
// If we haven't create a program yet, then it is not up-to-date
391+
if (!program) {
392+
return false;
393+
}
394+
395+
// If number of files in the program do not match, it is not up-to-date
396+
if (program.getRootFileNames().length !== rootFileNames.length) {
397+
return false;
398+
}
399+
400+
const fileNames = concatenate(rootFileNames, map(program.getSourceFiles(), sourceFile => sourceFile.fileName));
401+
// If any file is not up-to-date, then the whole program is not up-to-date
402+
for (const fileName of fileNames) {
403+
if (!sourceFileUpToDate(program.getSourceFile(fileName))) {
404+
return false;
405+
}
406+
}
407+
408+
const currentOptions = program.getCompilerOptions();
409+
// If the compilation settings do no match, then the program is not up-to-date
410+
if (!compareDataObjects(currentOptions, newOptions)) {
411+
return false;
412+
}
413+
414+
// If everything matches but the text of config file is changed,
415+
// error locations can change for program options, so update the program
416+
if (currentOptions.configFile && newOptions.configFile) {
417+
return currentOptions.configFile.text === newOptions.configFile.text;
418+
}
419+
420+
return true;
421+
422+
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
423+
if (!sourceFile) {
424+
return false;
425+
}
426+
return sourceFile.version === getSourceVersion(sourceFile.path);
427+
}
428+
}
429+
430+
function shouldProgramCreateNewSourceFiles(program: Program, newOptions: CompilerOptions) {
431+
// If any of these options change, we cant reuse old source file even if version match
432+
const oldOptions = program && program.getCompilerOptions();
433+
return oldOptions &&
434+
(oldOptions.target !== newOptions.target ||
435+
oldOptions.module !== newOptions.module ||
436+
oldOptions.moduleResolution !== newOptions.moduleResolution ||
437+
oldOptions.noResolve !== newOptions.noResolve ||
438+
oldOptions.jsx !== newOptions.jsx ||
439+
oldOptions.allowJs !== newOptions.allowJs ||
440+
oldOptions.disableSizeLimit !== newOptions.disableSizeLimit ||
441+
oldOptions.baseUrl !== newOptions.baseUrl ||
442+
!equalOwnProperties(oldOptions.paths, newOptions.paths));
443+
}
444+
445+
/**
446+
* Updates the existing missing file watches with the new set of missing files after new program is created
447+
* @param program
448+
* @param existingMap
449+
* @param createMissingFileWatch
450+
* @param closeExistingFileWatcher
451+
*/
452+
export function updateMissingFilePathsWatch(program: Program, existingMap: Map<FileWatcher>,
453+
createMissingFileWatch: (missingFilePath: Path) => FileWatcher,
454+
closeExistingFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void) {
455+
456+
const missingFilePaths = program.getMissingFilePaths();
457+
const newMissingFilePathMap = arrayToSet(missingFilePaths);
458+
// Update the missing file paths watcher
459+
return mutateExistingMapWithNewSet(
460+
existingMap, newMissingFilePathMap,
461+
// Watch the missing files
462+
createMissingFileWatch,
463+
// Files that are no longer missing (e.g. because they are no longer required)
464+
// should no longer be watched.
465+
closeExistingFileWatcher
466+
);
467+
}
468+
469+
export type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean };
470+
471+
export function updateWatchingWildcardDirectories(existingWatchedForWildcards: Map<WildCardDirectoryWatchers>, wildcardDirectories: Map<WatchDirectoryFlags>,
472+
watchDirectory: (directory: string, recursive: boolean) => FileWatcher,
473+
closeDirectoryWatcher: (directory: string, watcher: FileWatcher, recursive: boolean, recursiveChanged: boolean) => void) {
474+
return mutateExistingMap(
475+
existingWatchedForWildcards, wildcardDirectories,
476+
// Create new watch and recursive info
477+
(directory, flag) => {
478+
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
479+
return {
480+
watcher: watchDirectory(directory, recursive),
481+
recursive
482+
};
483+
},
484+
// Close existing watch thats not needed any more
485+
(directory, { watcher, recursive }) => closeDirectoryWatcher(directory, watcher, recursive, /*recursiveChanged*/ false),
486+
// Watcher is same if the recursive flags match
487+
({ recursive: existingRecursive }, flag) => {
488+
// If the recursive dont match, it needs update
489+
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
490+
return existingRecursive !== recursive;
491+
},
492+
// Close existing watch that doesnt match in recursive flag
493+
(directory, { watcher, recursive }) => closeDirectoryWatcher(directory, watcher, recursive, /*recursiveChanged*/ true)
494+
);
495+
}
496+
389497
/**
390498
* Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
391499
* that represent a compilation unit.
@@ -478,6 +586,7 @@ namespace ts {
478586
// used to track cases when two file names differ only in casing
479587
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap<SourceFile>() : undefined;
480588

589+
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
481590
const structuralIsReused = tryReuseStructureFromOldProgram();
482591
if (structuralIsReused !== StructureIsReused.Completely) {
483592
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
@@ -519,6 +628,17 @@ namespace ts {
519628
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
520629
moduleResolutionCache = undefined;
521630

631+
// Release any files we have acquired in the old program but are
632+
// not part of the new program.
633+
if (oldProgram && host.onReleaseOldSourceFile) {
634+
const oldSourceFiles = oldProgram.getSourceFiles();
635+
for (const oldSourceFile of oldSourceFiles) {
636+
if (!getSourceFile(oldSourceFile.path) || shouldCreateNewSourceFile) {
637+
host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions());
638+
}
639+
}
640+
}
641+
522642
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
523643
oldProgram = undefined;
524644

@@ -783,8 +903,8 @@ namespace ts {
783903

784904
for (const oldSourceFile of oldProgram.getSourceFiles()) {
785905
const newSourceFile = host.getSourceFileByPath
786-
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target)
787-
: host.getSourceFile(oldSourceFile.fileName, options.target);
906+
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target, /*onError*/ undefined, shouldCreateNewSourceFile)
907+
: host.getSourceFile(oldSourceFile.fileName, options.target, /*onError*/ undefined, shouldCreateNewSourceFile);
788908

789909
if (!newSourceFile) {
790910
return oldProgram.structureIsReused = StructureIsReused.Not;
@@ -1593,7 +1713,7 @@ namespace ts {
15931713
else {
15941714
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
15951715
}
1596-
});
1716+
}, shouldCreateNewSourceFile);
15971717

15981718
filesByName.set(path, file);
15991719
if (file) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.