mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-14 19:16:17 -06:00
Initial refactoring so that watch from tsc follows the tsserver projects
This commit is contained in:
parent
94a589b3bb
commit
ef5935b52c
@ -1979,7 +1979,7 @@ namespace ts {
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
*/
|
||||
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo>): ExpandResult {
|
||||
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo> = []): ExpandResult {
|
||||
basePath = normalizePath(basePath);
|
||||
|
||||
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper;
|
||||
|
||||
@ -2559,4 +2559,168 @@ namespace ts {
|
||||
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
|
||||
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
|
||||
}
|
||||
|
||||
export interface HostForCaching {
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
|
||||
fileExists(path: string): boolean;
|
||||
directoryExists(path: string): boolean;
|
||||
createDirectory(path: string): void;
|
||||
getCurrentDirectory(): string;
|
||||
getDirectories(path: string): string[];
|
||||
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
|
||||
}
|
||||
|
||||
export interface CachedHost {
|
||||
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
|
||||
fileExists(path: string): boolean;
|
||||
directoryExists(path: string): boolean;
|
||||
createDirectory(path: string): void;
|
||||
getCurrentDirectory(): string;
|
||||
getDirectories(path: string): string[];
|
||||
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
|
||||
addOrDeleteFileOrFolder(fileOrFolder: string): void;
|
||||
clearCache(): void;
|
||||
}
|
||||
|
||||
export function createCachedHost(host: HostForCaching): CachedHost {
|
||||
const cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
||||
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
return {
|
||||
writeFile,
|
||||
fileExists,
|
||||
directoryExists,
|
||||
createDirectory,
|
||||
getCurrentDirectory,
|
||||
getDirectories,
|
||||
readDirectory,
|
||||
addOrDeleteFileOrFolder,
|
||||
clearCache
|
||||
};
|
||||
|
||||
function toPath(fileName: string) {
|
||||
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
|
||||
}
|
||||
|
||||
function getFileSystemEntries(rootDir: string) {
|
||||
const path = toPath(rootDir);
|
||||
const cachedResult = cachedReadDirectoryResult.get(path);
|
||||
if (cachedResult) {
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
const resultFromHost: FileSystemEntries = {
|
||||
files: host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [],
|
||||
directories: host.getDirectories(rootDir) || []
|
||||
};
|
||||
|
||||
cachedReadDirectoryResult.set(path, resultFromHost);
|
||||
return resultFromHost;
|
||||
}
|
||||
|
||||
function canWorkWithCacheForDir(rootDir: string) {
|
||||
// Some of the hosts might not be able to handle read directory or getDirectories
|
||||
const path = toPath(rootDir);
|
||||
if (cachedReadDirectoryResult.get(path)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return getFileSystemEntries(rootDir);
|
||||
}
|
||||
catch (_e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function fileNameEqual(name1: string, name2: string) {
|
||||
return getCanonicalFileName(name1) === getCanonicalFileName(name2);
|
||||
}
|
||||
|
||||
function hasEntry(entries: ReadonlyArray<string>, name: string) {
|
||||
return some(entries, file => fileNameEqual(file, name));
|
||||
}
|
||||
|
||||
function updateFileSystemEntry(entries: ReadonlyArray<string>, baseName: string, isValid: boolean) {
|
||||
if (hasEntry(entries, baseName)) {
|
||||
if (!isValid) {
|
||||
return filter(entries, entry => !fileNameEqual(entry, baseName));
|
||||
}
|
||||
}
|
||||
else if (isValid) {
|
||||
return entries.concat(baseName);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
|
||||
const path = toPath(fileName);
|
||||
const result = cachedReadDirectoryResult.get(getDirectoryPath(path));
|
||||
const baseFileName = getBaseFileName(normalizePath(fileName));
|
||||
if (result) {
|
||||
result.files = updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true);
|
||||
}
|
||||
return host.writeFile(fileName, data, writeByteOrderMark);
|
||||
}
|
||||
|
||||
function fileExists(fileName: string): boolean {
|
||||
const path = toPath(fileName);
|
||||
const result = cachedReadDirectoryResult.get(getDirectoryPath(path));
|
||||
const baseName = getBaseFileName(normalizePath(fileName));
|
||||
return (result && hasEntry(result.files, baseName)) || host.fileExists(fileName);
|
||||
}
|
||||
|
||||
function directoryExists(dirPath: string): boolean {
|
||||
const path = toPath(dirPath);
|
||||
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath);
|
||||
}
|
||||
|
||||
function createDirectory(dirPath: string) {
|
||||
const path = toPath(dirPath);
|
||||
const result = cachedReadDirectoryResult.get(getDirectoryPath(path));
|
||||
const baseFileName = getBaseFileName(path);
|
||||
if (result) {
|
||||
result.directories = updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
|
||||
}
|
||||
host.createDirectory(dirPath);
|
||||
}
|
||||
|
||||
function getDirectories(rootDir: string): string[] {
|
||||
if (canWorkWithCacheForDir(rootDir)) {
|
||||
return getFileSystemEntries(rootDir).directories.slice();
|
||||
}
|
||||
return host.getDirectories(rootDir);
|
||||
}
|
||||
function readDirectory(rootDir: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
|
||||
if (canWorkWithCacheForDir(rootDir)) {
|
||||
return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, path => getFileSystemEntries(path));
|
||||
}
|
||||
return host.readDirectory(rootDir, extensions, excludes, includes, depth);
|
||||
}
|
||||
|
||||
function addOrDeleteFileOrFolder(fileOrFolder: string) {
|
||||
const path = toPath(fileOrFolder);
|
||||
const existingResult = cachedReadDirectoryResult.get(path);
|
||||
if (existingResult) {
|
||||
if (!host.directoryExists(fileOrFolder)) {
|
||||
cachedReadDirectoryResult.delete(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Was this earlier file
|
||||
const parentResult = cachedReadDirectoryResult.get(getDirectoryPath(path));
|
||||
if (parentResult) {
|
||||
const baseName = getBaseFileName(fileOrFolder);
|
||||
if (parentResult) {
|
||||
parentResult.files = updateFileSystemEntry(parentResult.files, baseName, host.fileExists(path));
|
||||
parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
cachedReadDirectoryResult.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,6 +386,114 @@ namespace ts {
|
||||
allDiagnostics?: Diagnostic[];
|
||||
}
|
||||
|
||||
export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path) => string): boolean {
|
||||
// If we haven't create a program yet, then it is not up-to-date
|
||||
if (!program) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If number of files in the program do not match, it is not up-to-date
|
||||
if (program.getRootFileNames().length !== rootFileNames.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fileNames = concatenate(rootFileNames, map(program.getSourceFiles(), sourceFile => sourceFile.fileName));
|
||||
// If any file is not up-to-date, then the whole program is not up-to-date
|
||||
for (const fileName of fileNames) {
|
||||
if (!sourceFileUpToDate(program.getSourceFile(fileName))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const currentOptions = program.getCompilerOptions();
|
||||
// If the compilation settings do no match, then the program is not up-to-date
|
||||
if (!compareDataObjects(currentOptions, newOptions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If everything matches but the text of config file is changed,
|
||||
// error locations can change for program options, so update the program
|
||||
if (currentOptions.configFile && newOptions.configFile) {
|
||||
return currentOptions.configFile.text === newOptions.configFile.text;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
|
||||
if (!sourceFile) {
|
||||
return false;
|
||||
}
|
||||
return sourceFile.version === getSourceVersion(sourceFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldProgramCreateNewSourceFiles(program: Program, newOptions: CompilerOptions) {
|
||||
// If any of these options change, we cant reuse old source file even if version match
|
||||
const oldOptions = program && program.getCompilerOptions();
|
||||
return oldOptions &&
|
||||
(oldOptions.target !== newOptions.target ||
|
||||
oldOptions.module !== newOptions.module ||
|
||||
oldOptions.moduleResolution !== newOptions.moduleResolution ||
|
||||
oldOptions.noResolve !== newOptions.noResolve ||
|
||||
oldOptions.jsx !== newOptions.jsx ||
|
||||
oldOptions.allowJs !== newOptions.allowJs ||
|
||||
oldOptions.disableSizeLimit !== newOptions.disableSizeLimit ||
|
||||
oldOptions.baseUrl !== newOptions.baseUrl ||
|
||||
!equalOwnProperties(oldOptions.paths, newOptions.paths));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the existing missing file watches with the new set of missing files after new program is created
|
||||
* @param program
|
||||
* @param existingMap
|
||||
* @param createMissingFileWatch
|
||||
* @param closeExistingFileWatcher
|
||||
*/
|
||||
export function updateMissingFilePathsWatch(program: Program, existingMap: Map<FileWatcher>,
|
||||
createMissingFileWatch: (missingFilePath: Path) => FileWatcher,
|
||||
closeExistingFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void) {
|
||||
|
||||
const missingFilePaths = program.getMissingFilePaths();
|
||||
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
||||
// Update the missing file paths watcher
|
||||
return mutateExistingMapWithNewSet(
|
||||
existingMap, newMissingFilePathMap,
|
||||
// Watch the missing files
|
||||
createMissingFileWatch,
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
closeExistingFileWatcher
|
||||
);
|
||||
}
|
||||
|
||||
export type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean };
|
||||
|
||||
export function updateWatchingWildcardDirectories(existingWatchedForWildcards: Map<WildCardDirectoryWatchers>, wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
watchDirectory: (directory: string, recursive: boolean) => FileWatcher,
|
||||
closeDirectoryWatcher: (directory: string, watcher: FileWatcher, recursive: boolean, recursiveChanged: boolean) => void) {
|
||||
return mutateExistingMap(
|
||||
existingWatchedForWildcards, wildcardDirectories,
|
||||
// Create new watch and recursive info
|
||||
(directory, flag) => {
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return {
|
||||
watcher: watchDirectory(directory, recursive),
|
||||
recursive
|
||||
};
|
||||
},
|
||||
// Close existing watch thats not needed any more
|
||||
(directory, { watcher, recursive }) => closeDirectoryWatcher(directory, watcher, recursive, /*recursiveChanged*/ false),
|
||||
// Watcher is same if the recursive flags match
|
||||
({ recursive: existingRecursive }, flag) => {
|
||||
// If the recursive dont match, it needs update
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return existingRecursive !== recursive;
|
||||
},
|
||||
// Close existing watch that doesnt match in recursive flag
|
||||
(directory, { watcher, recursive }) => closeDirectoryWatcher(directory, watcher, recursive, /*recursiveChanged*/ true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
|
||||
* that represent a compilation unit.
|
||||
@ -478,6 +586,7 @@ namespace ts {
|
||||
// used to track cases when two file names differ only in casing
|
||||
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap<SourceFile>() : undefined;
|
||||
|
||||
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
||||
const structuralIsReused = tryReuseStructureFromOldProgram();
|
||||
if (structuralIsReused !== StructureIsReused.Completely) {
|
||||
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
|
||||
@ -519,6 +628,17 @@ namespace ts {
|
||||
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
|
||||
moduleResolutionCache = undefined;
|
||||
|
||||
// Release any files we have acquired in the old program but are
|
||||
// not part of the new program.
|
||||
if (oldProgram && host.onReleaseOldSourceFile) {
|
||||
const oldSourceFiles = oldProgram.getSourceFiles();
|
||||
for (const oldSourceFile of oldSourceFiles) {
|
||||
if (!getSourceFile(oldSourceFile.path) || shouldCreateNewSourceFile) {
|
||||
host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
|
||||
oldProgram = undefined;
|
||||
|
||||
@ -783,8 +903,8 @@ namespace ts {
|
||||
|
||||
for (const oldSourceFile of oldProgram.getSourceFiles()) {
|
||||
const newSourceFile = host.getSourceFileByPath
|
||||
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target)
|
||||
: host.getSourceFile(oldSourceFile.fileName, options.target);
|
||||
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target, /*onError*/ undefined, shouldCreateNewSourceFile)
|
||||
: host.getSourceFile(oldSourceFile.fileName, options.target, /*onError*/ undefined, shouldCreateNewSourceFile);
|
||||
|
||||
if (!newSourceFile) {
|
||||
return oldProgram.structureIsReused = StructureIsReused.Not;
|
||||
@ -1593,7 +1713,7 @@ namespace ts {
|
||||
else {
|
||||
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
|
||||
}
|
||||
});
|
||||
}, shouldCreateNewSourceFile);
|
||||
|
||||
filesByName.set(path, file);
|
||||
if (file) {
|
||||
|
||||
@ -98,22 +98,9 @@ namespace ts {
|
||||
|
||||
export function executeCommandLine(args: string[]): void {
|
||||
const commandLine = parseCommandLine(args);
|
||||
let configFileName: string; // Configuration file name (if any)
|
||||
let cachedConfigFileText: string; // Cached configuration file text, used for reparsing (if any)
|
||||
let configFileWatcher: FileWatcher; // Configuration file watcher
|
||||
let directoryWatcher: FileWatcher; // Directory watcher to monitor source file addition/removal
|
||||
let cachedProgram: Program; // Program cached from last compilation
|
||||
let rootFileNames: string[]; // Root fileNames for compilation
|
||||
let compilerOptions: CompilerOptions; // Compiler options for compilation
|
||||
let compilerHost: CompilerHost; // Compiler host
|
||||
let hostGetSourceFile: typeof compilerHost.getSourceFile; // getSourceFile method from default host
|
||||
let timerHandleForRecompilation: any; // Handle for 0.25s wait timer to trigger recompilation
|
||||
let timerHandleForDirectoryChanges: any; // Handle for 0.25s wait timer to trigger directory change handler
|
||||
|
||||
// This map stores and reuses results of fileExists check that happen inside 'createProgram'
|
||||
// This allows to save time in module resolution heavy scenarios when existence of the same file might be checked multiple times.
|
||||
let cachedExistingFiles: Map<boolean>;
|
||||
let hostFileExists: typeof compilerHost.fileExists;
|
||||
// Configuration file name (if any)
|
||||
let configFileName: string;
|
||||
|
||||
if (commandLine.options.locale) {
|
||||
if (!isJSONSupported()) {
|
||||
@ -126,7 +113,7 @@ namespace ts {
|
||||
// If there are any errors due to command line parsing and/or
|
||||
// setting up localization, report them and quit.
|
||||
if (commandLine.errors.length > 0) {
|
||||
reportDiagnostics(commandLine.errors, compilerHost);
|
||||
reportDiagnostics(commandLine.errors, /*host*/ undefined);
|
||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
}
|
||||
|
||||
@ -183,232 +170,570 @@ namespace ts {
|
||||
return sys.exit(ExitStatus.Success);
|
||||
}
|
||||
|
||||
if (isWatchSet(commandLine.options)) {
|
||||
if (!sys.watchFile) {
|
||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
|
||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
}
|
||||
if (configFileName) {
|
||||
configFileWatcher = sys.watchFile(configFileName, configFileChanged);
|
||||
}
|
||||
if (sys.watchDirectory && configFileName) {
|
||||
const directory = ts.getDirectoryPath(configFileName);
|
||||
directoryWatcher = sys.watchDirectory(
|
||||
// When the configFileName is just "tsconfig.json", the watched directory should be
|
||||
// the current directory; if there is a given "project" parameter, then the configFileName
|
||||
// is an absolute file name.
|
||||
directory === "" ? "." : directory,
|
||||
watchedDirectoryChanged, /*recursive*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
performCompilation();
|
||||
|
||||
function parseConfigFile(): ParsedCommandLine {
|
||||
if (!cachedConfigFileText) {
|
||||
try {
|
||||
cachedConfigFileText = sys.readFile(configFileName);
|
||||
}
|
||||
catch (e) {
|
||||
const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message);
|
||||
reportWatchDiagnostic(error);
|
||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!cachedConfigFileText) {
|
||||
const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName);
|
||||
reportDiagnostics([error], /* compilerHost */ undefined);
|
||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = parseJsonText(configFileName, cachedConfigFileText);
|
||||
reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined);
|
||||
|
||||
const cwd = sys.getCurrentDirectory();
|
||||
const configParseResult = parseJsonSourceFileConfigFileContent(result, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd));
|
||||
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
|
||||
|
||||
if (configFileName) {
|
||||
const configParseResult = parseConfigFile(configFileName, commandLine, sys);
|
||||
const { fileNames, options } = configParseResult;
|
||||
if (isWatchSet(configParseResult.options)) {
|
||||
if (!sys.watchFile) {
|
||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
|
||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
}
|
||||
|
||||
if (!directoryWatcher && sys.watchDirectory && configFileName) {
|
||||
const directory = ts.getDirectoryPath(configFileName);
|
||||
directoryWatcher = sys.watchDirectory(
|
||||
// When the configFileName is just "tsconfig.json", the watched directory should be
|
||||
// the current directory; if there is a given "project" parameter, then the configFileName
|
||||
// is an absolute file name.
|
||||
directory === "" ? "." : directory,
|
||||
watchedDirectoryChanged, /*recursive*/ true);
|
||||
}
|
||||
reportWatchModeWithoutSysSupport();
|
||||
createWatchMode(commandLine, configFileName, fileNames, options, configParseResult.configFileSpecs, configParseResult.wildcardDirectories);
|
||||
}
|
||||
return configParseResult;
|
||||
else {
|
||||
performCompilation(fileNames, options);
|
||||
}
|
||||
}
|
||||
else if (isWatchSet(commandLine.options)) {
|
||||
reportWatchModeWithoutSysSupport();
|
||||
createWatchMode(commandLine);
|
||||
}
|
||||
else {
|
||||
performCompilation(commandLine.fileNames, commandLine.options);
|
||||
}
|
||||
|
||||
// Invoked to perform initial compilation or re-compilation in watch mode
|
||||
function performCompilation() {
|
||||
|
||||
if (!cachedProgram) {
|
||||
if (configFileName) {
|
||||
const configParseResult = parseConfigFile();
|
||||
rootFileNames = configParseResult.fileNames;
|
||||
compilerOptions = configParseResult.options;
|
||||
}
|
||||
else {
|
||||
rootFileNames = commandLine.fileNames;
|
||||
compilerOptions = commandLine.options;
|
||||
}
|
||||
compilerHost = createCompilerHost(compilerOptions);
|
||||
hostGetSourceFile = compilerHost.getSourceFile;
|
||||
compilerHost.getSourceFile = getSourceFile;
|
||||
|
||||
hostFileExists = compilerHost.fileExists;
|
||||
compilerHost.fileExists = cachedFileExists;
|
||||
function reportWatchModeWithoutSysSupport() {
|
||||
if (!sys.watchFile || !sys.watchDirectory) {
|
||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
|
||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
}
|
||||
}
|
||||
|
||||
function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) {
|
||||
if (compilerOptions.pretty) {
|
||||
reportDiagnosticWorker = reportDiagnosticWithColorAndContext;
|
||||
}
|
||||
|
||||
// reset the cache of existing files
|
||||
cachedExistingFiles = createMap<boolean>();
|
||||
|
||||
const compilerHost = createCompilerHost(compilerOptions);
|
||||
const compileResult = compile(rootFileNames, compilerOptions, compilerHost);
|
||||
return sys.exit(compileResult.exitStatus);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isWatchSet(compilerOptions)) {
|
||||
return sys.exit(compileResult.exitStatus);
|
||||
interface HostFileInfo {
|
||||
version: number;
|
||||
sourceFile: SourceFile;
|
||||
fileWatcher: FileWatcher;
|
||||
}
|
||||
|
||||
function createWatchMode(commandLine: ParsedCommandLine, configFileName?: string, configFileRootFiles?: string[], configFileOptions?: CompilerOptions, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>) {
|
||||
let program: Program;
|
||||
let needsReload: boolean;
|
||||
let missingFilesMap: Map<FileWatcher>;
|
||||
let configFileWatcher: FileWatcher;
|
||||
let watchedWildCardDirectories: Map<WildCardDirectoryWatchers>;
|
||||
let timerToUpdateProgram: any;
|
||||
|
||||
let compilerOptions: CompilerOptions;
|
||||
let rootFileNames: string[];
|
||||
|
||||
const sourceFilesCache = createMap<HostFileInfo | string>();
|
||||
|
||||
let host: System;
|
||||
if (configFileName) {
|
||||
rootFileNames = configFileRootFiles;
|
||||
compilerOptions = configFileOptions;
|
||||
host = createCachedSystem(sys);
|
||||
configFileWatcher = sys.watchFile(configFileName, onConfigFileChanged);
|
||||
}
|
||||
else {
|
||||
rootFileNames = commandLine.fileNames;
|
||||
compilerOptions = commandLine.options;
|
||||
host = sys;
|
||||
}
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
|
||||
if (compilerOptions.pretty) {
|
||||
reportDiagnosticWorker = reportDiagnosticWithColorAndContext;
|
||||
}
|
||||
|
||||
synchronizeProgram();
|
||||
|
||||
// Update the wild card directory watch
|
||||
watchConfigFileWildCardDirectories();
|
||||
|
||||
function synchronizeProgram() {
|
||||
writeLog(`Synchronizing program`);
|
||||
|
||||
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCachedProgram(compileResult.program);
|
||||
// Create the compiler host
|
||||
const compilerHost = createWatchedCompilerHost(compilerOptions);
|
||||
program = compile(rootFileNames, compilerOptions, compilerHost, program).program;
|
||||
|
||||
// Update watches
|
||||
missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher);
|
||||
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
|
||||
|
||||
const missingPaths = compileResult.program.getMissingFilePaths();
|
||||
missingPaths.forEach(path => {
|
||||
const fileWatcher = sys.watchFile(path, (_fileName, eventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created) {
|
||||
fileWatcher.close();
|
||||
startTimerForRecompilation();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function cachedFileExists(fileName: string): boolean {
|
||||
let fileExists = cachedExistingFiles.get(fileName);
|
||||
if (fileExists === undefined) {
|
||||
cachedExistingFiles.set(fileName, fileExists = hostFileExists(fileName));
|
||||
function createWatchedCompilerHost(options: CompilerOptions): CompilerHost {
|
||||
const existingDirectories = createMap<boolean>();
|
||||
function directoryExists(directoryPath: string): boolean {
|
||||
if (existingDirectories.has(directoryPath)) {
|
||||
return true;
|
||||
}
|
||||
if (host.directoryExists(directoryPath)) {
|
||||
existingDirectories.set(directoryPath, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return fileExists;
|
||||
}
|
||||
|
||||
function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) {
|
||||
// Return existing SourceFile object if one is available
|
||||
if (cachedProgram) {
|
||||
const sourceFile = cachedProgram.getSourceFile(fileName);
|
||||
// A modified source file has no watcher and should not be reused
|
||||
if (sourceFile && sourceFile.fileWatcher) {
|
||||
return sourceFile;
|
||||
function ensureDirectoriesExist(directoryPath: string) {
|
||||
if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
|
||||
const parentDirectory = getDirectoryPath(directoryPath);
|
||||
ensureDirectoriesExist(parentDirectory);
|
||||
host.createDirectory(directoryPath);
|
||||
}
|
||||
}
|
||||
// Use default host function
|
||||
const sourceFile = hostGetSourceFile(fileName, languageVersion, onError);
|
||||
if (sourceFile && isWatchSet(compilerOptions) && sys.watchFile) {
|
||||
// Attach a file watcher
|
||||
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName, eventKind) => sourceFileChanged(sourceFile, eventKind));
|
||||
}
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
// Change cached program to the given program
|
||||
function setCachedProgram(program: Program) {
|
||||
if (cachedProgram) {
|
||||
const newSourceFiles = program ? program.getSourceFiles() : undefined;
|
||||
forEach(cachedProgram.getSourceFiles(), sourceFile => {
|
||||
if (!(newSourceFiles && contains(newSourceFiles, sourceFile))) {
|
||||
if (sourceFile.fileWatcher) {
|
||||
sourceFile.fileWatcher.close();
|
||||
sourceFile.fileWatcher = undefined;
|
||||
}
|
||||
type OutputFingerprint = {
|
||||
hash: string;
|
||||
byteOrderMark: boolean;
|
||||
mtime: Date;
|
||||
};
|
||||
let outputFingerprints: Map<OutputFingerprint>;
|
||||
|
||||
function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void {
|
||||
if (!outputFingerprints) {
|
||||
outputFingerprints = createMap<OutputFingerprint>();
|
||||
}
|
||||
|
||||
const hash = host.createHash(data);
|
||||
const mtimeBefore = host.getModifiedTime(fileName);
|
||||
|
||||
if (mtimeBefore) {
|
||||
const fingerprint = outputFingerprints.get(fileName);
|
||||
// If output has not been changed, and the file has no external modification
|
||||
if (fingerprint &&
|
||||
fingerprint.byteOrderMark === writeByteOrderMark &&
|
||||
fingerprint.hash === hash &&
|
||||
fingerprint.mtime.getTime() === mtimeBefore.getTime()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
host.writeFile(fileName, data, writeByteOrderMark);
|
||||
|
||||
const mtimeAfter = host.getModifiedTime(fileName);
|
||||
|
||||
outputFingerprints.set(fileName, {
|
||||
hash,
|
||||
byteOrderMark: writeByteOrderMark,
|
||||
mtime: mtimeAfter
|
||||
});
|
||||
}
|
||||
cachedProgram = program;
|
||||
}
|
||||
|
||||
// If a source file changes, mark it as unwatched and start the recompilation timer
|
||||
function sourceFileChanged(sourceFile: SourceFile, eventKind: FileWatcherEventKind) {
|
||||
sourceFile.fileWatcher.close();
|
||||
sourceFile.fileWatcher = undefined;
|
||||
if (eventKind === FileWatcherEventKind.Deleted) {
|
||||
unorderedRemoveItem(rootFileNames, sourceFile.fileName);
|
||||
}
|
||||
startTimerForRecompilation();
|
||||
}
|
||||
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) {
|
||||
try {
|
||||
performance.mark("beforeIOWrite");
|
||||
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
|
||||
|
||||
// If the configuration file changes, forget cached program and start the recompilation timer
|
||||
function configFileChanged() {
|
||||
setCachedProgram(undefined);
|
||||
cachedConfigFileText = undefined;
|
||||
startTimerForRecompilation();
|
||||
}
|
||||
//if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) {
|
||||
writeFileIfUpdated(fileName, data, writeByteOrderMark);
|
||||
//}
|
||||
//else {
|
||||
//host.writeFile(fileName, data, writeByteOrderMark);
|
||||
//}
|
||||
|
||||
function watchedDirectoryChanged(fileName: string) {
|
||||
if (fileName && !ts.isSupportedSourceFileName(fileName, compilerOptions)) {
|
||||
return;
|
||||
performance.mark("afterIOWrite");
|
||||
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
|
||||
}
|
||||
catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startTimerForHandlingDirectoryChanges();
|
||||
const newLine = getNewLineCharacter(options);
|
||||
const realpath = host.realpath && ((path: string) => host.realpath(path));
|
||||
|
||||
return {
|
||||
getSourceFile: getVersionedSourceFile,
|
||||
getSourceFileByPath: getVersionedSourceFileByPath,
|
||||
getDefaultLibLocation,
|
||||
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
|
||||
writeFile,
|
||||
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
|
||||
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames,
|
||||
getCanonicalFileName,
|
||||
getNewLine: () => newLine,
|
||||
fileExists,
|
||||
readFile: fileName => host.readFile(fileName),
|
||||
trace: (s: string) => host.write(s + newLine),
|
||||
directoryExists: directoryName => host.directoryExists(directoryName),
|
||||
getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "",
|
||||
getDirectories: (path: string) => host.getDirectories(path),
|
||||
realpath,
|
||||
onReleaseOldSourceFile
|
||||
};
|
||||
|
||||
// TODO: cache module resolution
|
||||
//if (host.resolveModuleNames) {
|
||||
// compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile);
|
||||
//}
|
||||
//if (host.resolveTypeReferenceDirectives) {
|
||||
// compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => {
|
||||
// return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile);
|
||||
// };
|
||||
//}
|
||||
}
|
||||
|
||||
function startTimerForHandlingDirectoryChanges() {
|
||||
if (!sys.setTimeout || !sys.clearTimeout) {
|
||||
return;
|
||||
function fileExists(fileName: string) {
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
const hostSourceFileInfo = sourceFilesCache.get(path);
|
||||
if (hostSourceFileInfo !== undefined) {
|
||||
return !isString(hostSourceFileInfo);
|
||||
}
|
||||
|
||||
if (timerHandleForDirectoryChanges) {
|
||||
sys.clearTimeout(timerHandleForDirectoryChanges);
|
||||
}
|
||||
timerHandleForDirectoryChanges = sys.setTimeout(directoryChangeHandler, 250);
|
||||
return host.fileExists(fileName);
|
||||
}
|
||||
|
||||
function directoryChangeHandler() {
|
||||
const parsedCommandLine = parseConfigFile();
|
||||
const newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName);
|
||||
const canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName);
|
||||
function getDefaultLibLocation(): string {
|
||||
return getDirectoryPath(normalizePath(host.getExecutingFilePath()));
|
||||
}
|
||||
|
||||
// We check if the project file list has changed. If so, we just throw away the old program and start fresh.
|
||||
if (!arrayIsEqualTo(newFileNames && newFileNames.sort(), canonicalRootFileNames && canonicalRootFileNames.sort())) {
|
||||
setCachedProgram(undefined);
|
||||
startTimerForRecompilation();
|
||||
function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||
return getVersionedSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile);
|
||||
}
|
||||
|
||||
function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
// No source file on the host
|
||||
if (isString(hostSourceFile)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create new source file if requested or the versions dont match
|
||||
if (!hostSourceFile) {
|
||||
const sourceFile = getSourceFile(fileName, languageVersion, onError);
|
||||
if (sourceFile) {
|
||||
sourceFile.version = "0";
|
||||
const fileWatcher = watchSourceFileForChanges(sourceFile.path);
|
||||
sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher });
|
||||
}
|
||||
else {
|
||||
sourceFilesCache.set(path, "0");
|
||||
}
|
||||
return sourceFile;
|
||||
}
|
||||
else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
|
||||
if (shouldCreateNewSourceFile) {
|
||||
hostSourceFile.version++;
|
||||
}
|
||||
const newSourceFile = getSourceFile(fileName, languageVersion, onError);
|
||||
if (newSourceFile) {
|
||||
newSourceFile.version = hostSourceFile.version.toString();
|
||||
hostSourceFile.sourceFile = newSourceFile;
|
||||
}
|
||||
else {
|
||||
// File doesnt exist any more
|
||||
hostSourceFile.fileWatcher.close();
|
||||
sourceFilesCache.set(path, hostSourceFile.version.toString());
|
||||
}
|
||||
|
||||
return newSourceFile;
|
||||
}
|
||||
|
||||
return hostSourceFile.sourceFile;
|
||||
|
||||
function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile {
|
||||
let text: string;
|
||||
try {
|
||||
performance.mark("beforeIORead");
|
||||
text = host.readFile(fileName, compilerOptions.charset);
|
||||
performance.mark("afterIORead");
|
||||
performance.measure("I/O Read", "beforeIORead", "afterIORead");
|
||||
}
|
||||
catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
text = "";
|
||||
}
|
||||
|
||||
return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function removeSourceFile(path: Path) {
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
if (hostSourceFile !== undefined) {
|
||||
if (!isString(hostSourceFile)) {
|
||||
hostSourceFile.fileWatcher.close();
|
||||
}
|
||||
sourceFilesCache.delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceVersion(path: Path): string {
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString();
|
||||
}
|
||||
|
||||
function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) {
|
||||
const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path);
|
||||
// If this is the source file thats in the cache and new program doesnt need it,
|
||||
// remove the cached entry.
|
||||
// Note we arent deleting entry if file became missing in new program or
|
||||
// there was version update and new source file was created.
|
||||
if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) {
|
||||
sourceFilesCache.delete(oldSourceFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
// Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
|
||||
// operations (such as saving all modified files in an editor) a chance to complete before we kick
|
||||
// off a new compilation.
|
||||
function startTimerForRecompilation() {
|
||||
function scheduleProgramUpdate() {
|
||||
if (!sys.setTimeout || !sys.clearTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timerHandleForRecompilation) {
|
||||
sys.clearTimeout(timerHandleForRecompilation);
|
||||
if (timerToUpdateProgram) {
|
||||
sys.clearTimeout(timerToUpdateProgram);
|
||||
}
|
||||
timerHandleForRecompilation = sys.setTimeout(recompile, 250);
|
||||
timerToUpdateProgram = sys.setTimeout(updateProgram, 250);
|
||||
}
|
||||
|
||||
function recompile() {
|
||||
timerHandleForRecompilation = undefined;
|
||||
function scheduleProgramReload() {
|
||||
Debug.assert(!!configFileName);
|
||||
needsReload = true;
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
|
||||
function updateProgram() {
|
||||
timerToUpdateProgram = undefined;
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
|
||||
performCompilation();
|
||||
|
||||
if (needsReload) {
|
||||
reloadConfigFile();
|
||||
}
|
||||
else {
|
||||
synchronizeProgram();
|
||||
}
|
||||
}
|
||||
|
||||
function reloadConfigFile() {
|
||||
writeLog(`Reloading config file: ${configFileName}`);
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
|
||||
|
||||
needsReload = false;
|
||||
|
||||
const cachedHost = host as CachedSystem;
|
||||
cachedHost.clearCache();
|
||||
const configParseResult = parseConfigFile(configFileName, commandLine, cachedHost);
|
||||
rootFileNames = configParseResult.fileNames;
|
||||
compilerOptions = configParseResult.options;
|
||||
configFileSpecs = configParseResult.configFileSpecs;
|
||||
configFileWildCardDirectories = configParseResult.wildcardDirectories;
|
||||
|
||||
synchronizeProgram();
|
||||
|
||||
// Update the wild card directory watch
|
||||
watchConfigFileWildCardDirectories();
|
||||
}
|
||||
|
||||
function watchSourceFileForChanges(path: Path) {
|
||||
return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind));
|
||||
}
|
||||
|
||||
function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
|
||||
writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
if (hostSourceFile) {
|
||||
// Update the cache
|
||||
if (eventKind === FileWatcherEventKind.Deleted) {
|
||||
if (!isString(hostSourceFile)) {
|
||||
hostSourceFile.fileWatcher.close();
|
||||
sourceFilesCache.set(path, (hostSourceFile.version++).toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Deleted file created
|
||||
if (isString(hostSourceFile)) {
|
||||
sourceFilesCache.delete(path);
|
||||
}
|
||||
else {
|
||||
// file changed - just update the version
|
||||
hostSourceFile.version++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the program
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
|
||||
function watchMissingFilePath(missingFilePath: Path) {
|
||||
return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind));
|
||||
}
|
||||
|
||||
function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) {
|
||||
fileWatcher.close();
|
||||
}
|
||||
|
||||
function onMissingFileChange(filename: string, missingFilePath: Path, eventKind: FileWatcherEventKind) {
|
||||
writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${filename}`);
|
||||
if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) {
|
||||
closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath));
|
||||
missingFilesMap.delete(missingFilePath);
|
||||
|
||||
if (configFileName) {
|
||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
||||
(host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath));
|
||||
}
|
||||
|
||||
// Delete the entry in the source files cache so that new source file is created
|
||||
removeSourceFile(missingFilePath);
|
||||
|
||||
// When a missing file is created, we should update the graph.
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
function watchConfigFileWildCardDirectories() {
|
||||
const wildcards = createMapFromTemplate(configFileWildCardDirectories);
|
||||
watchedWildCardDirectories = updateWatchingWildcardDirectories(
|
||||
watchedWildCardDirectories, wildcards,
|
||||
watchWildCardDirectory, stopWatchingWildCardDirectory
|
||||
);
|
||||
}
|
||||
|
||||
function watchWildCardDirectory(directory: string, recursive: boolean) {
|
||||
return host.watchDirectory(directory, fileName =>
|
||||
onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileName, directory)),
|
||||
recursive);
|
||||
}
|
||||
|
||||
function stopWatchingWildCardDirectory(_directory: string, fileWatcher: FileWatcher, _recursive: boolean, _recursiveChanged: boolean) {
|
||||
fileWatcher.close();
|
||||
}
|
||||
|
||||
function onFileAddOrRemoveInWatchedDirectory(fileName: string) {
|
||||
Debug.assert(!!configFileName);
|
||||
(host as CachedSystem).addOrDeleteFileOrFolder(fileName);
|
||||
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
removeSourceFile(toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
|
||||
// If a change was made inside "folder/file", node will trigger the callback twice:
|
||||
// one with the fileName being "folder/file", and the other one with "folder".
|
||||
// We don't respond to the second one.
|
||||
if (fileName && !isSupportedSourceFileName(fileName, compilerOptions)) {
|
||||
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`);
|
||||
|
||||
// Reload is pending, do the reload
|
||||
if (!needsReload) {
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtensions*/ []);
|
||||
if (!configFileSpecs.filesSpecs) {
|
||||
reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], /*host*/ undefined);
|
||||
}
|
||||
rootFileNames = result.fileNames;
|
||||
|
||||
// Schedule Update the program
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) {
|
||||
writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
|
||||
scheduleProgramReload();
|
||||
}
|
||||
|
||||
function writeLog(s: string) {
|
||||
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
|
||||
if (hasDiagnostics) {
|
||||
host.write(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) {
|
||||
interface CachedSystem extends System {
|
||||
addOrDeleteFileOrFolder(fileOrFolder: string): void;
|
||||
clearCache(): void;
|
||||
}
|
||||
|
||||
function createCachedSystem(host: System): CachedSystem {
|
||||
const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined;
|
||||
const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined;
|
||||
const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined;
|
||||
const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined;
|
||||
const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined;
|
||||
const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined;
|
||||
const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined;
|
||||
const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined;
|
||||
const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined;
|
||||
const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined;
|
||||
|
||||
const cachedHost = createCachedHost(host);
|
||||
return {
|
||||
args: host.args,
|
||||
newLine: host.newLine,
|
||||
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
|
||||
write: s => host.write(s),
|
||||
readFile: (path, encoding?) => host.readFile(path, encoding),
|
||||
getFileSize,
|
||||
writeFile: (fileName, data, writeByteOrderMark?) => cachedHost.writeFile(fileName, data, writeByteOrderMark),
|
||||
watchFile,
|
||||
watchDirectory,
|
||||
resolvePath: path => host.resolvePath(path),
|
||||
fileExists: fileName => cachedHost.fileExists(fileName),
|
||||
directoryExists: dir => cachedHost.directoryExists(dir),
|
||||
createDirectory: dir => cachedHost.createDirectory(dir),
|
||||
getExecutingFilePath: () => host.getExecutingFilePath(),
|
||||
getCurrentDirectory: () => cachedHost.getCurrentDirectory(),
|
||||
getDirectories: dir => cachedHost.getDirectories(dir),
|
||||
readDirectory: (path, extensions, excludes, includes, depth) => cachedHost.readDirectory(path, extensions, excludes, includes, depth),
|
||||
getModifiedTime,
|
||||
createHash,
|
||||
getMemoryUsage,
|
||||
exit: exitCode => host.exit(exitCode),
|
||||
realpath,
|
||||
getEnvironmentVariable: name => host.getEnvironmentVariable(name),
|
||||
tryEnableSourceMapsForHost,
|
||||
debugMode: host.debugMode,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
addOrDeleteFileOrFolder: fileOrFolder => cachedHost.addOrDeleteFileOrFolder(fileOrFolder),
|
||||
clearCache: () => cachedHost.clearCache()
|
||||
};
|
||||
}
|
||||
|
||||
function parseConfigFile(configFileName: string, commandLine: ParsedCommandLine, host: System): ParsedCommandLine {
|
||||
let configFileText: string;
|
||||
try {
|
||||
configFileText = host.readFile(configFileName);
|
||||
}
|
||||
catch (e) {
|
||||
const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message);
|
||||
reportWatchDiagnostic(error);
|
||||
host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
return;
|
||||
}
|
||||
if (!configFileText) {
|
||||
const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName);
|
||||
reportDiagnostics([error], /* compilerHost */ undefined);
|
||||
host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = parseJsonText(configFileName, configFileText);
|
||||
reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined);
|
||||
|
||||
const cwd = host.getCurrentDirectory();
|
||||
const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd));
|
||||
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
|
||||
|
||||
return configParseResult;
|
||||
}
|
||||
|
||||
function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, oldProgram?: Program) {
|
||||
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
|
||||
let statistics: Statistic[];
|
||||
if (hasDiagnostics) {
|
||||
@ -416,7 +741,7 @@ namespace ts {
|
||||
statistics = [];
|
||||
}
|
||||
|
||||
const program = createProgram(fileNames, compilerOptions, compilerHost);
|
||||
const program = createProgram(fileNames, compilerOptions, compilerHost, oldProgram);
|
||||
const exitStatus = compileProgram();
|
||||
|
||||
if (compilerOptions.listFiles) {
|
||||
@ -481,6 +806,8 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: in watch mode to emit only affected files
|
||||
|
||||
// Otherwise, emit and report any errors we ran into.
|
||||
const emitOutput = program.emit();
|
||||
diagnostics = diagnostics.concat(emitOutput.diagnostics);
|
||||
|
||||
@ -2326,6 +2326,7 @@ namespace ts {
|
||||
/* @internal */ patternAmbientModules?: PatternAmbientModule[];
|
||||
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
|
||||
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
|
||||
/* @internal */ version: string;
|
||||
}
|
||||
|
||||
export interface Bundle extends Node {
|
||||
@ -3952,8 +3953,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
export interface CompilerHost extends ModuleResolutionHost {
|
||||
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
|
||||
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
|
||||
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile;
|
||||
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile;
|
||||
getCancellationToken?(): CancellationToken;
|
||||
getDefaultLibFileName(options: CompilerOptions): string;
|
||||
getDefaultLibLocation?(): string;
|
||||
@ -3977,6 +3978,7 @@ namespace ts {
|
||||
*/
|
||||
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
getEnvironmentVariable?(name: string): string;
|
||||
onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@ -3611,6 +3611,99 @@ namespace ts {
|
||||
export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags {
|
||||
return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags;
|
||||
}
|
||||
|
||||
export function compareDataObjects(dst: any, src: any): boolean {
|
||||
if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const e in dst) {
|
||||
if (typeof dst[e] === "object") {
|
||||
if (!compareDataObjects(dst[e], src[e])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (typeof dst[e] !== "function") {
|
||||
if (dst[e] !== src[e]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function cleanExistingMap<T>(
|
||||
existingMap: Map<T>,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void) {
|
||||
if (existingMap) {
|
||||
// Remove all
|
||||
existingMap.forEach((existingValue, key) => {
|
||||
existingMap.delete(key);
|
||||
onDeleteExistingValue(key, existingValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function mutateExistingMapWithNewSet<T>(
|
||||
existingMap: Map<T>, newMap: Map<true>,
|
||||
createNewValue: (key: string) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void
|
||||
): Map<T> {
|
||||
return mutateExistingMap(
|
||||
existingMap, newMap,
|
||||
/*createNewValue*/(key, _valueInNewMap) => createNewValue(key),
|
||||
onDeleteExistingValue,
|
||||
);
|
||||
}
|
||||
|
||||
export function mutateExistingMap<T, U>(
|
||||
existingMap: Map<T>, newMap: Map<U>,
|
||||
createNewValue: (key: string, valueInNewMap: U) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void,
|
||||
isSameValue?: (existingValue: T, valueInNewMap: U) => boolean,
|
||||
OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void,
|
||||
onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void
|
||||
): Map<T> {
|
||||
// If there are new values update them
|
||||
if (newMap) {
|
||||
if (existingMap) {
|
||||
// Needs update
|
||||
existingMap.forEach((existingValue, key) => {
|
||||
const valueInNewMap = newMap.get(key);
|
||||
// Existing value - remove it
|
||||
if (valueInNewMap === undefined) {
|
||||
existingMap.delete(key);
|
||||
onDeleteExistingValue(key, existingValue);
|
||||
}
|
||||
// different value - remove it
|
||||
else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) {
|
||||
existingMap.delete(key);
|
||||
OnDeleteExistingMismatchValue(key, existingValue);
|
||||
}
|
||||
else if (onSameExistingValue) {
|
||||
onSameExistingValue(existingValue, valueInNewMap);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Create new
|
||||
existingMap = createMap<T>();
|
||||
}
|
||||
|
||||
// Add new values that are not already present
|
||||
newMap.forEach((valueInNewMap, key) => {
|
||||
if (!existingMap.has(key)) {
|
||||
// New values
|
||||
existingMap.set(key, createNewValue(key, valueInNewMap));
|
||||
}
|
||||
});
|
||||
|
||||
return existingMap;
|
||||
}
|
||||
|
||||
cleanExistingMap(existingMap, onDeleteExistingValue);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
|
||||
@ -1350,7 +1350,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) {
|
||||
const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName);
|
||||
const cachedServerHost = new CachedServerHost(this.host);
|
||||
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost);
|
||||
this.logger.info(`Opened configuration file ${configFileName}`);
|
||||
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName);
|
||||
|
||||
@ -8,13 +8,12 @@ namespace ts.server {
|
||||
newLine: string;
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
|
||||
private readonly cachedHost: CachedHost;
|
||||
|
||||
readonly trace: (s: string) => void;
|
||||
readonly realpath?: (path: string) => string;
|
||||
|
||||
private cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
||||
private readonly currentDirectory: string;
|
||||
|
||||
constructor(private readonly host: ServerHost, private getCanonicalFileName: (fileName: string) => string) {
|
||||
constructor(private readonly host: ServerHost) {
|
||||
this.args = host.args;
|
||||
this.newLine = host.newLine;
|
||||
this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames;
|
||||
@ -24,41 +23,7 @@ namespace ts.server {
|
||||
if (this.host.realpath) {
|
||||
this.realpath = path => this.host.realpath(path);
|
||||
}
|
||||
this.currentDirectory = this.host.getCurrentDirectory();
|
||||
}
|
||||
|
||||
private toPath(fileName: string) {
|
||||
return toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
|
||||
}
|
||||
|
||||
private getFileSystemEntries(rootDir: string) {
|
||||
const path = this.toPath(rootDir);
|
||||
const cachedResult = this.cachedReadDirectoryResult.get(path);
|
||||
if (cachedResult) {
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
const resultFromHost: FileSystemEntries = {
|
||||
files: this.host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [],
|
||||
directories: this.host.getDirectories(rootDir) || []
|
||||
};
|
||||
|
||||
this.cachedReadDirectoryResult.set(path, resultFromHost);
|
||||
return resultFromHost;
|
||||
}
|
||||
|
||||
private canWorkWithCacheForDir(rootDir: string) {
|
||||
// Some of the hosts might not be able to handle read directory or getDirectories
|
||||
const path = this.toPath(rootDir);
|
||||
if (this.cachedReadDirectoryResult.get(path)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return this.getFileSystemEntries(rootDir);
|
||||
}
|
||||
catch (_e) {
|
||||
return false;
|
||||
}
|
||||
this.cachedHost = createCachedHost(host);
|
||||
}
|
||||
|
||||
write(s: string) {
|
||||
@ -66,13 +31,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) {
|
||||
const path = this.toPath(fileName);
|
||||
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
|
||||
const baseFileName = getBaseFileName(toNormalizedPath(fileName));
|
||||
if (result) {
|
||||
result.files = this.updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true);
|
||||
}
|
||||
return this.host.writeFile(fileName, data, writeByteOrderMark);
|
||||
this.cachedHost.writeFile(fileName, data, writeByteOrderMark);
|
||||
}
|
||||
|
||||
resolvePath(path: string) {
|
||||
@ -88,7 +47,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getCurrentDirectory() {
|
||||
return this.currentDirectory;
|
||||
return this.cachedHost.getCurrentDirectory();
|
||||
}
|
||||
|
||||
exit(exitCode?: number) {
|
||||
@ -101,78 +60,32 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getDirectories(rootDir: string) {
|
||||
if (this.canWorkWithCacheForDir(rootDir)) {
|
||||
return this.getFileSystemEntries(rootDir).directories.slice();
|
||||
}
|
||||
return this.host.getDirectories(rootDir);
|
||||
return this.cachedHost.getDirectories(rootDir);
|
||||
}
|
||||
|
||||
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
|
||||
if (this.canWorkWithCacheForDir(rootDir)) {
|
||||
return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, path => this.getFileSystemEntries(path));
|
||||
}
|
||||
return this.host.readDirectory(rootDir, extensions, excludes, includes, depth);
|
||||
return this.cachedHost.readDirectory(rootDir, extensions, excludes, includes, depth);
|
||||
}
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
const path = this.toPath(fileName);
|
||||
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
|
||||
const baseName = getBaseFileName(toNormalizedPath(fileName));
|
||||
return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName);
|
||||
return this.cachedHost.fileExists(fileName);
|
||||
}
|
||||
|
||||
directoryExists(dirPath: string) {
|
||||
const path = this.toPath(dirPath);
|
||||
return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath);
|
||||
return this.cachedHost.directoryExists(dirPath);
|
||||
}
|
||||
|
||||
readFile(path: string, encoding?: string): string {
|
||||
return this.host.readFile(path, encoding);
|
||||
}
|
||||
|
||||
private fileNameEqual(name1: string, name2: string) {
|
||||
return this.getCanonicalFileName(name1) === this.getCanonicalFileName(name2);
|
||||
}
|
||||
|
||||
private hasEntry(entries: ReadonlyArray<string>, name: string) {
|
||||
return some(entries, file => this.fileNameEqual(file, name));
|
||||
}
|
||||
|
||||
private updateFileSystemEntry(entries: ReadonlyArray<string>, baseName: string, isValid: boolean) {
|
||||
if (this.hasEntry(entries, baseName)) {
|
||||
if (!isValid) {
|
||||
return filter(entries, entry => !this.fileNameEqual(entry, baseName));
|
||||
}
|
||||
}
|
||||
else if (isValid) {
|
||||
return entries.concat(baseName);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) {
|
||||
const path = this.toPath(fileOrFolder);
|
||||
const existingResult = this.cachedReadDirectoryResult.get(path);
|
||||
if (existingResult) {
|
||||
if (!this.host.directoryExists(fileOrFolder)) {
|
||||
this.cachedReadDirectoryResult.delete(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Was this earlier file
|
||||
const parentResult = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
|
||||
if (parentResult) {
|
||||
const baseName = getBaseFileName(fileOrFolder);
|
||||
if (parentResult) {
|
||||
parentResult.files = this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path));
|
||||
parentResult.directories = this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.cachedHost.addOrDeleteFileOrFolder(fileOrFolder);
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
||||
return this.cachedHost.clearCache();
|
||||
}
|
||||
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
|
||||
|
||||
@ -639,38 +639,13 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
const missingFilePaths = this.program.getMissingFilePaths();
|
||||
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
||||
// Update the missing file paths watcher
|
||||
this.missingFilesMap = mutateExistingMapWithNewSet(
|
||||
this.missingFilesMap, newMissingFilePathMap,
|
||||
this.missingFilesMap = updateMissingFilePathsWatch(this.program, this.missingFilesMap,
|
||||
// Watch the missing files
|
||||
missingFilePath => {
|
||||
const fileWatcher = this.projectService.addFileWatcher(
|
||||
WatchType.MissingFilePath, this, missingFilePath,
|
||||
(filename, eventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
||||
this.missingFilesMap.delete(missingFilePath);
|
||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
|
||||
|
||||
if (this.projectKind === ProjectKind.Configured) {
|
||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
||||
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
|
||||
}
|
||||
|
||||
// When a missing file is created, we should update the graph.
|
||||
this.markAsDirty();
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
}
|
||||
}
|
||||
);
|
||||
return fileWatcher;
|
||||
},
|
||||
missingFilePath => this.addMissingFileWatcher(missingFilePath),
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
(missingFilePath, fileWatcher) => {
|
||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded);
|
||||
}
|
||||
(missingFilePath, fileWatcher) => this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded)
|
||||
);
|
||||
}
|
||||
|
||||
@ -694,6 +669,32 @@ namespace ts.server {
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
private addMissingFileWatcher(missingFilePath: Path) {
|
||||
const fileWatcher = this.projectService.addFileWatcher(
|
||||
WatchType.MissingFilePath, this, missingFilePath,
|
||||
(filename, eventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
||||
this.missingFilesMap.delete(missingFilePath);
|
||||
this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
|
||||
|
||||
if (this.projectKind === ProjectKind.Configured) {
|
||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
||||
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
|
||||
}
|
||||
|
||||
// When a missing file is created, we should update the graph.
|
||||
this.markAsDirty();
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
}
|
||||
}
|
||||
);
|
||||
return fileWatcher;
|
||||
}
|
||||
|
||||
private closeMissingFileWatcher(missingFilePath: Path, fileWatcher: FileWatcher, reason: WatcherCloseReason) {
|
||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, reason);
|
||||
}
|
||||
|
||||
isWatchedMissingFile(path: Path) {
|
||||
return this.missingFilesMap && this.missingFilesMap.has(path);
|
||||
}
|
||||
@ -1135,33 +1136,18 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
|
||||
this.directoriesWatchedForWildcards = mutateExistingMap(
|
||||
this.directoriesWatchedForWildcards, wildcardDirectories,
|
||||
// Create new watch and recursive info
|
||||
(directory, flag) => {
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return {
|
||||
watcher: this.projectService.addDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory,
|
||||
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
|
||||
recursive
|
||||
),
|
||||
recursive
|
||||
};
|
||||
},
|
||||
// Close existing watch thats not needed any more
|
||||
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded
|
||||
this.directoriesWatchedForWildcards = updateWatchingWildcardDirectories(this.directoriesWatchedForWildcards,
|
||||
wildcardDirectories,
|
||||
// Create new directory watcher
|
||||
(directory, recursive) => this.projectService.addDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory,
|
||||
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
|
||||
recursive
|
||||
),
|
||||
// Watcher is same if the recursive flags match
|
||||
({ recursive: existingRecursive }, flag) => {
|
||||
// If the recursive dont match, it needs update
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return existingRecursive !== recursive;
|
||||
},
|
||||
// Close existing watch that doesnt match in recursive flag
|
||||
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged
|
||||
// Close directory watcher
|
||||
(directory, watcher, recursive, recursiveChanged) => this.projectService.closeDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory, watcher, recursive,
|
||||
recursiveChanged ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -292,77 +292,4 @@ namespace ts.server {
|
||||
deleted(oldItems[oldIndex++]);
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanExistingMap<T>(
|
||||
existingMap: Map<T>,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void) {
|
||||
if (existingMap) {
|
||||
// Remove all
|
||||
existingMap.forEach((existingValue, key) => {
|
||||
existingMap.delete(key);
|
||||
onDeleteExistingValue(key, existingValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function mutateExistingMapWithNewSet<T>(
|
||||
existingMap: Map<T>, newMap: Map<true>,
|
||||
createNewValue: (key: string) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void
|
||||
): Map<T> {
|
||||
return mutateExistingMap(
|
||||
existingMap, newMap,
|
||||
/*createNewValue*/(key, _valueInNewMap) => createNewValue(key),
|
||||
onDeleteExistingValue,
|
||||
);
|
||||
}
|
||||
|
||||
export function mutateExistingMap<T, U>(
|
||||
existingMap: Map<T>, newMap: Map<U>,
|
||||
createNewValue: (key: string, valueInNewMap: U) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void,
|
||||
isSameValue?: (existingValue: T, valueInNewMap: U) => boolean,
|
||||
OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void,
|
||||
onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void
|
||||
): Map<T> {
|
||||
// If there are new values update them
|
||||
if (newMap) {
|
||||
if (existingMap) {
|
||||
// Needs update
|
||||
existingMap.forEach((existingValue, key) => {
|
||||
const valueInNewMap = newMap.get(key);
|
||||
// Existing value - remove it
|
||||
if (valueInNewMap === undefined) {
|
||||
existingMap.delete(key);
|
||||
onDeleteExistingValue(key, existingValue);
|
||||
}
|
||||
// different value - remove it
|
||||
else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) {
|
||||
existingMap.delete(key);
|
||||
OnDeleteExistingMismatchValue(key, existingValue);
|
||||
}
|
||||
else if (onSameExistingValue) {
|
||||
onSameExistingValue(existingValue, valueInNewMap);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Create new
|
||||
existingMap = createMap<T>();
|
||||
}
|
||||
|
||||
// Add new values that are not already present
|
||||
newMap.forEach((valueInNewMap, key) => {
|
||||
if (!existingMap.has(key)) {
|
||||
// New values
|
||||
existingMap.set(key, createNewValue(key, valueInNewMap));
|
||||
}
|
||||
});
|
||||
|
||||
return existingMap;
|
||||
}
|
||||
|
||||
cleanExistingMap(existingMap, onDeleteExistingValue);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1114,9 +1114,9 @@ namespace ts {
|
||||
|
||||
// Get a fresh cache of the host information
|
||||
let hostCache = new HostCache(host, getCanonicalFileName);
|
||||
|
||||
const rootFileNames = hostCache.getRootFileNames();
|
||||
// If the program is already up-to-date, we can reuse it
|
||||
if (programUpToDate()) {
|
||||
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), (path) => hostCache.getVersion(path))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1126,18 +1126,7 @@ namespace ts {
|
||||
// the program points to old source files that have been invalidated because of
|
||||
// incremental parsing.
|
||||
|
||||
const oldSettings = program && program.getCompilerOptions();
|
||||
const newSettings = hostCache.compilationSettings();
|
||||
const shouldCreateNewSourceFiles = oldSettings &&
|
||||
(oldSettings.target !== newSettings.target ||
|
||||
oldSettings.module !== newSettings.module ||
|
||||
oldSettings.moduleResolution !== newSettings.moduleResolution ||
|
||||
oldSettings.noResolve !== newSettings.noResolve ||
|
||||
oldSettings.jsx !== newSettings.jsx ||
|
||||
oldSettings.allowJs !== newSettings.allowJs ||
|
||||
oldSettings.disableSizeLimit !== oldSettings.disableSizeLimit ||
|
||||
oldSettings.baseUrl !== newSettings.baseUrl ||
|
||||
!equalOwnProperties(oldSettings.paths, newSettings.paths));
|
||||
|
||||
// Now create a new compiler
|
||||
const compilerHost: CompilerHost = {
|
||||
@ -1172,7 +1161,8 @@ namespace ts {
|
||||
},
|
||||
getDirectories: path => {
|
||||
return host.getDirectories ? host.getDirectories(path) : [];
|
||||
}
|
||||
},
|
||||
onReleaseOldSourceFile
|
||||
};
|
||||
if (host.trace) {
|
||||
compilerHost.trace = message => host.trace(message);
|
||||
@ -1188,36 +1178,29 @@ namespace ts {
|
||||
}
|
||||
|
||||
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
|
||||
const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program);
|
||||
|
||||
// Release any files we have acquired in the old program but are
|
||||
// not part of the new program.
|
||||
if (program) {
|
||||
const oldSourceFiles = program.getSourceFiles();
|
||||
const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldSettings);
|
||||
for (const oldSourceFile of oldSourceFiles) {
|
||||
if (!newProgram.getSourceFile(oldSourceFile.fileName) || shouldCreateNewSourceFiles) {
|
||||
documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
program = createProgram(rootFileNames, newSettings, compilerHost, program);
|
||||
|
||||
// hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point.
|
||||
// It needs to be cleared to allow all collected snapshots to be released
|
||||
hostCache = undefined;
|
||||
|
||||
program = newProgram;
|
||||
|
||||
// Make sure all the nodes in the program are both bound, and have their parent
|
||||
// pointers set property.
|
||||
program.getTypeChecker();
|
||||
return;
|
||||
|
||||
function getOrCreateSourceFile(fileName: string): SourceFile {
|
||||
return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
// Release any files we have acquired in the old program but are
|
||||
// not part of the new program.
|
||||
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
|
||||
const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions);
|
||||
documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey);
|
||||
}
|
||||
|
||||
function getOrCreateSourceFileByPath(fileName: string, path: Path): SourceFile {
|
||||
function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||
return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile);
|
||||
}
|
||||
|
||||
function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||
Debug.assert(hostCache !== undefined);
|
||||
// The program is asking for this file, check first if the host can locate it.
|
||||
// If the host can not locate the file, then it does not exist. return undefined
|
||||
@ -1230,7 +1213,7 @@ namespace ts {
|
||||
// Check if the language version has changed since we last created a program; if they are the same,
|
||||
// it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile
|
||||
// can not be reused. we have to dump all syntax trees and create new ones.
|
||||
if (!shouldCreateNewSourceFiles) {
|
||||
if (!shouldCreateNewSourceFile) {
|
||||
// Check if the old program had this file already
|
||||
const oldSourceFile = program && program.getSourceFileByPath(path);
|
||||
if (oldSourceFile) {
|
||||
@ -1270,49 +1253,6 @@ namespace ts {
|
||||
// Could not find this file in the old program, create a new SourceFile for it.
|
||||
return documentRegistry.acquireDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
|
||||
}
|
||||
|
||||
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
|
||||
if (!sourceFile) {
|
||||
return false;
|
||||
}
|
||||
const path = sourceFile.path || toPath(sourceFile.fileName, currentDirectory, getCanonicalFileName);
|
||||
return sourceFile.version === hostCache.getVersion(path);
|
||||
}
|
||||
|
||||
function programUpToDate(): boolean {
|
||||
// If we haven't create a program yet, then it is not up-to-date
|
||||
if (!program) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If number of files in the program do not match, it is not up-to-date
|
||||
const rootFileNames = hostCache.getRootFileNames();
|
||||
if (program.getSourceFiles().length !== rootFileNames.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If any file is not up-to-date, then the whole program is not up-to-date
|
||||
for (const fileName of rootFileNames) {
|
||||
if (!sourceFileUpToDate(program.getSourceFile(fileName))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const currentOptions = program.getCompilerOptions();
|
||||
const newOptions = hostCache.compilationSettings();
|
||||
// If the compilation settings do no match, then the program is not up-to-date
|
||||
if (!compareDataObjects(currentOptions, newOptions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If everything matches but the text of config file is changed,
|
||||
// error locations can change for program options, so update the program
|
||||
if (currentOptions.configFile && newOptions.configFile) {
|
||||
return currentOptions.configFile.text === newOptions.configFile.text;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getProgram(): Program {
|
||||
|
||||
@ -994,26 +994,6 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function compareDataObjects(dst: any, src: any): boolean {
|
||||
if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const e in dst) {
|
||||
if (typeof dst[e] === "object") {
|
||||
if (!compareDataObjects(dst[e], src[e])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (typeof dst[e] !== "function") {
|
||||
if (dst[e] !== src[e]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) {
|
||||
if (node.kind === SyntaxKind.ArrayLiteralExpression ||
|
||||
node.kind === SyntaxKind.ObjectLiteralExpression) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user