mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-26 03:19:10 -06:00
Add tests for the tsc --watch
This commit is contained in:
parent
69e5abd5b7
commit
c814d8e847
@ -91,6 +91,7 @@ var languageServiceLibrarySources = filesFromConfig(path.join(serverDirectory, "
|
||||
var harnessCoreSources = [
|
||||
"harness.ts",
|
||||
"virtualFileSystem.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"sourceMapRecorder.ts",
|
||||
"harnessLanguageService.ts",
|
||||
"fourslash.ts",
|
||||
@ -128,6 +129,7 @@ var harnessSources = harnessCoreSources.concat([
|
||||
"convertCompilerOptionsFromJson.ts",
|
||||
"convertTypeAcquisitionFromJson.ts",
|
||||
"tsserverProjectSystem.ts",
|
||||
"tscWatchMode.ts",
|
||||
"compileOnSave.ts",
|
||||
"typingsInstaller.ts",
|
||||
"projectErrors.ts",
|
||||
|
||||
@ -387,7 +387,8 @@ namespace ts {
|
||||
allDiagnostics?: Diagnostic[];
|
||||
}
|
||||
|
||||
export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path) => string): boolean {
|
||||
export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions,
|
||||
getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean): boolean {
|
||||
// If we haven't create a program yet, then it is not up-to-date
|
||||
if (!program) {
|
||||
return false;
|
||||
@ -398,14 +399,18 @@ namespace ts {
|
||||
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))) {
|
||||
for (const file of program.getSourceFiles()) {
|
||||
if (!sourceFileUpToDate(program.getSourceFile(file.fileName))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If any of the missing file paths are now created
|
||||
if (program.getMissingFilePaths().some(missingFilePath => fileExists(missingFilePath))) {
|
||||
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)) {
|
||||
@ -445,14 +450,10 @@ namespace ts {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
closeExistingMissingFilePathFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void) {
|
||||
|
||||
const missingFilePaths = program.getMissingFilePaths();
|
||||
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
||||
@ -463,12 +464,15 @@ namespace ts {
|
||||
createMissingFileWatch,
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
closeExistingFileWatcher
|
||||
closeExistingMissingFilePathFileWatcher
|
||||
);
|
||||
}
|
||||
|
||||
export type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean };
|
||||
|
||||
/**
|
||||
* Updates the existing wild card directory watcyhes with the new set of wild card directories from the config file after new program is created
|
||||
*/
|
||||
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) {
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/// <reference path="tscLib.ts"/>
|
||||
|
||||
if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) {
|
||||
ts.sys.tryEnableSourceMapsForHost();
|
||||
}
|
||||
ts.executeCommandLine(ts.sys.args);
|
||||
|
||||
@ -12,35 +12,49 @@ namespace ts {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = {
|
||||
export interface FormatDiagnosticsHostWithWrite extends FormatDiagnosticsHost {
|
||||
write?(s: string): void;
|
||||
}
|
||||
|
||||
const defaultFormatDiagnosticsHost: FormatDiagnosticsHostWithWrite = sys ? {
|
||||
getCurrentDirectory: () => sys.getCurrentDirectory(),
|
||||
getNewLine: () => sys.newLine,
|
||||
getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames)
|
||||
};
|
||||
getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames),
|
||||
write: s => sys.write(s)
|
||||
} : undefined;
|
||||
|
||||
function getDefaultFormatDiagnosticsHost(system: System): FormatDiagnosticsHostWithWrite {
|
||||
return system === sys ? defaultFormatDiagnosticsHost : {
|
||||
getCurrentDirectory: () => system.getCurrentDirectory(),
|
||||
getNewLine: () => system.newLine,
|
||||
getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames),
|
||||
write: s => system.write(s)
|
||||
};
|
||||
}
|
||||
|
||||
let reportDiagnosticWorker = reportDiagnosticSimply;
|
||||
|
||||
function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost) {
|
||||
function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite) {
|
||||
reportDiagnosticWorker(diagnostic, host || defaultFormatDiagnosticsHost);
|
||||
}
|
||||
|
||||
function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): void {
|
||||
function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHostWithWrite): void {
|
||||
for (const diagnostic of diagnostics) {
|
||||
reportDiagnostic(diagnostic, host);
|
||||
}
|
||||
}
|
||||
|
||||
function reportEmittedFiles(files: string[]): void {
|
||||
function reportEmittedFiles(files: string[], system: System): void {
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDir = sys.getCurrentDirectory();
|
||||
const currentDir = system.getCurrentDirectory();
|
||||
|
||||
for (const file of files) {
|
||||
const filepath = getNormalizedAbsolutePath(file, currentDir);
|
||||
|
||||
sys.write(`TSFILE: ${filepath}${sys.newLine}`);
|
||||
system.write(`TSFILE: ${filepath}${system.newLine}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,15 +71,15 @@ namespace ts {
|
||||
return <string>diagnostic.messageText;
|
||||
}
|
||||
|
||||
function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
|
||||
sys.write(ts.formatDiagnostics([diagnostic], host));
|
||||
function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite): void {
|
||||
host.write(ts.formatDiagnostics([diagnostic], host));
|
||||
}
|
||||
|
||||
function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
|
||||
sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine);
|
||||
function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite): void {
|
||||
host.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine());
|
||||
}
|
||||
|
||||
function reportWatchDiagnostic(diagnostic: Diagnostic) {
|
||||
function reportWatchDiagnostic(diagnostic: Diagnostic, system: System) {
|
||||
let output = new Date().toLocaleTimeString() + " - ";
|
||||
|
||||
if (diagnostic.file) {
|
||||
@ -73,9 +87,9 @@ namespace ts {
|
||||
output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
|
||||
}
|
||||
|
||||
output += `${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine + sys.newLine + sys.newLine }`;
|
||||
output += `${ flattenDiagnosticMessageText(diagnostic.messageText, system.newLine) }${ system.newLine + system.newLine + system.newLine }`;
|
||||
|
||||
sys.write(output);
|
||||
system.write(output);
|
||||
}
|
||||
|
||||
function padLeft(s: string, length: number) {
|
||||
@ -175,7 +189,7 @@ namespace ts {
|
||||
const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys);
|
||||
if (isWatchSet(configParseResult.options)) {
|
||||
reportWatchModeWithoutSysSupport();
|
||||
createWatchModeWithConfigFile(configParseResult, commandLineOptions);
|
||||
createWatchModeWithConfigFile(configParseResult, commandLineOptions, sys);
|
||||
}
|
||||
else {
|
||||
performCompilation(configParseResult.fileNames, configParseResult.options);
|
||||
@ -184,7 +198,7 @@ namespace ts {
|
||||
else {
|
||||
if (isWatchSet(commandLine.options)) {
|
||||
reportWatchModeWithoutSysSupport();
|
||||
createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions);
|
||||
createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, sys);
|
||||
}
|
||||
else {
|
||||
performCompilation(commandLine.fileNames, commandLineOptions);
|
||||
@ -204,7 +218,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
const compilerHost = createCompilerHost(compilerOptions);
|
||||
const compileResult = compile(rootFileNames, compilerOptions, compilerHost);
|
||||
const compileResult = compile(rootFileNames, compilerOptions, compilerHost, sys);
|
||||
return sys.exit(compileResult.exitStatus);
|
||||
}
|
||||
}
|
||||
@ -216,16 +230,16 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend?: CompilerOptions) {
|
||||
return createWatchMode(configParseResult.fileNames, configParseResult.options, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend);
|
||||
export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions, system: System) {
|
||||
return createWatchMode(configParseResult.fileNames, configParseResult.options, system, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions) {
|
||||
return createWatchMode(rootFileNames, compilerOptions);
|
||||
export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions, system: System) {
|
||||
return createWatchMode(rootFileNames, compilerOptions, system);
|
||||
}
|
||||
|
||||
function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>, optionsToExtendForConfigFile?: CompilerOptions) {
|
||||
function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, system: System, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>, optionsToExtendForConfigFile?: CompilerOptions) {
|
||||
let program: Program;
|
||||
let needsReload: boolean; // true if the config file changed and needs to reload it from the disk
|
||||
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
|
||||
@ -238,11 +252,11 @@ namespace ts {
|
||||
|
||||
let host: System;
|
||||
if (configFileName) {
|
||||
host = createCachedSystem(sys);
|
||||
configFileWatcher = sys.watchFile(configFileName, onConfigFileChanged);
|
||||
host = createCachedSystem(system);
|
||||
configFileWatcher = system.watchFile(configFileName, onConfigFileChanged);
|
||||
}
|
||||
else {
|
||||
host = sys;
|
||||
host = system;
|
||||
}
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
@ -259,21 +273,23 @@ namespace ts {
|
||||
// Update the wild card directory watch
|
||||
watchConfigFileWildCardDirectories();
|
||||
|
||||
return () => program;
|
||||
|
||||
function synchronizeProgram() {
|
||||
writeLog(`Synchronizing program`);
|
||||
|
||||
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion)) {
|
||||
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the compiler host
|
||||
const compilerHost = createWatchedCompilerHost(compilerOptions);
|
||||
program = compile(rootFileNames, compilerOptions, compilerHost, program).program;
|
||||
program = compile(rootFileNames, compilerOptions, compilerHost, system, program).program;
|
||||
|
||||
// Update watches
|
||||
missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher);
|
||||
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes), system);
|
||||
}
|
||||
|
||||
function createWatchedCompilerHost(options: CompilerOptions): CompilerHost {
|
||||
@ -302,14 +318,14 @@ namespace ts {
|
||||
};
|
||||
|
||||
// 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);
|
||||
// };
|
||||
//}
|
||||
// if (host.resolveModuleNames) {
|
||||
// compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile);
|
||||
// }
|
||||
// if (host.resolveTypeReferenceDirectives) {
|
||||
// compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => {
|
||||
// return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile);
|
||||
// };
|
||||
// }
|
||||
|
||||
function ensureDirectoriesExist(directoryPath: string) {
|
||||
if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) {
|
||||
@ -342,7 +358,7 @@ namespace ts {
|
||||
const seenFiles = createMap<true>();
|
||||
|
||||
let emitSkipped: boolean;
|
||||
let diagnostics: Diagnostic[];
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined;
|
||||
let sourceMaps: SourceMapData[];
|
||||
while (filesPendingToEmit.length) {
|
||||
@ -366,7 +382,7 @@ namespace ts {
|
||||
emitSkipped = true;
|
||||
}
|
||||
|
||||
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
|
||||
diagnostics.push(...emitOutput.diagnostics);
|
||||
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
|
||||
// If it emitted more than one source files, just mark all those source files as seen
|
||||
if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) {
|
||||
@ -377,7 +393,7 @@ namespace ts {
|
||||
for (const outputFile of emitOutput.outputFiles) {
|
||||
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
|
||||
if (error) {
|
||||
(diagnostics || (diagnostics = [])).push(error);
|
||||
diagnostics.push(error);
|
||||
}
|
||||
if (emittedFiles) {
|
||||
emittedFiles.push(outputFile.name);
|
||||
@ -413,41 +429,43 @@ namespace ts {
|
||||
}
|
||||
|
||||
// Create new source file if requested or the versions dont match
|
||||
if (!hostSourceFile) {
|
||||
if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
|
||||
changedFilePaths.push(path);
|
||||
const sourceFile = getSourceFile(fileName, languageVersion, onError);
|
||||
if (sourceFile) {
|
||||
sourceFile.version = "0";
|
||||
const fileWatcher = watchSourceFileForChanges(sourceFile.path);
|
||||
sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher });
|
||||
|
||||
const sourceFile = getNewSourceFile();
|
||||
if (hostSourceFile) {
|
||||
if (shouldCreateNewSourceFile) {
|
||||
hostSourceFile.version++;
|
||||
}
|
||||
if (sourceFile) {
|
||||
hostSourceFile.sourceFile = sourceFile;
|
||||
sourceFile.version = hostSourceFile.version.toString();
|
||||
if (!hostSourceFile.fileWatcher) {
|
||||
hostSourceFile.fileWatcher = watchSourceFileForChanges(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There is no source file on host any more, close the watch, missing file paths will track it
|
||||
hostSourceFile.fileWatcher.close();
|
||||
sourceFilesCache.set(path, hostSourceFile.version.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
sourceFilesCache.set(path, "0");
|
||||
let fileWatcher: FileWatcher;
|
||||
if (sourceFile) {
|
||||
sourceFile.version = "0";
|
||||
fileWatcher = watchSourceFileForChanges(path);
|
||||
sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher });
|
||||
}
|
||||
else {
|
||||
sourceFilesCache.set(path, "0");
|
||||
}
|
||||
}
|
||||
return sourceFile;
|
||||
}
|
||||
else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
|
||||
changedFilePaths.push(path);
|
||||
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 {
|
||||
function getNewSourceFile() {
|
||||
let text: string;
|
||||
try {
|
||||
performance.mark("beforeIORead");
|
||||
@ -459,7 +477,6 @@ namespace ts {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
text = "";
|
||||
}
|
||||
|
||||
return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined;
|
||||
@ -496,14 +513,14 @@ namespace ts {
|
||||
// operations (such as saving all modified files in an editor) a chance to complete before we kick
|
||||
// off a new compilation.
|
||||
function scheduleProgramUpdate() {
|
||||
if (!sys.setTimeout || !sys.clearTimeout) {
|
||||
if (!system.setTimeout || !system.clearTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timerToUpdateProgram) {
|
||||
sys.clearTimeout(timerToUpdateProgram);
|
||||
system.clearTimeout(timerToUpdateProgram);
|
||||
}
|
||||
timerToUpdateProgram = sys.setTimeout(updateProgram, 250);
|
||||
timerToUpdateProgram = system.setTimeout(updateProgram, 250);
|
||||
}
|
||||
|
||||
function scheduleProgramReload() {
|
||||
@ -514,7 +531,7 @@ namespace ts {
|
||||
|
||||
function updateProgram() {
|
||||
timerToUpdateProgram = undefined;
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), system);
|
||||
|
||||
if (needsReload) {
|
||||
reloadConfigFile();
|
||||
@ -526,8 +543,6 @@ namespace ts {
|
||||
|
||||
function reloadConfigFile() {
|
||||
writeLog(`Reloading config file: ${configFileName}`);
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
|
||||
|
||||
needsReload = false;
|
||||
|
||||
const cachedHost = host as CachedSystem;
|
||||
@ -550,6 +565,8 @@ namespace ts {
|
||||
|
||||
function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
|
||||
writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
|
||||
|
||||
updateCachedSystem(fileName, path);
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
if (hostSourceFile) {
|
||||
// Update the cache
|
||||
@ -570,10 +587,18 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the program
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
|
||||
function updateCachedSystem(fileName: string, path: Path) {
|
||||
if (configFileName) {
|
||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(fileName, getDirectoryPath(path));
|
||||
(host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath));
|
||||
}
|
||||
}
|
||||
|
||||
function watchMissingFilePath(missingFilePath: Path) {
|
||||
return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind));
|
||||
}
|
||||
@ -588,10 +613,7 @@ namespace ts {
|
||||
closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath));
|
||||
missingFilesMap.delete(missingFilePath);
|
||||
|
||||
if (configFileName) {
|
||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
||||
(host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath));
|
||||
}
|
||||
updateCachedSystem(filename, missingFilePath);
|
||||
|
||||
// Delete the entry in the source files cache so that new source file is created
|
||||
removeSourceFile(missingFilePath);
|
||||
@ -621,10 +643,12 @@ namespace ts {
|
||||
|
||||
function onFileAddOrRemoveInWatchedDirectory(fileName: string) {
|
||||
Debug.assert(!!configFileName);
|
||||
(host as CachedSystem).addOrDeleteFileOrFolder(fileName);
|
||||
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
removeSourceFile(toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
updateCachedSystem(fileName, path);
|
||||
removeSourceFile(path);
|
||||
|
||||
// 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".
|
||||
@ -640,7 +664,7 @@ namespace ts {
|
||||
if (!needsReload) {
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtensions*/ []);
|
||||
if (!configFileSpecs.filesSpecs) {
|
||||
reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], /*host*/ undefined);
|
||||
reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], getDefaultFormatDiagnosticsHost(system));
|
||||
}
|
||||
rootFileNames = result.fileNames;
|
||||
|
||||
@ -662,7 +686,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function computeHash(data: string) {
|
||||
return sys.createHash ? sys.createHash(data) : data;
|
||||
return system.createHash ? system.createHash(data) : data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -718,35 +742,35 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: System): ParsedCommandLine {
|
||||
export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System): ParsedCommandLine {
|
||||
let configFileText: string;
|
||||
try {
|
||||
configFileText = host.readFile(configFileName);
|
||||
configFileText = system.readFile(configFileName);
|
||||
}
|
||||
catch (e) {
|
||||
const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message);
|
||||
reportWatchDiagnostic(error);
|
||||
host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
reportWatchDiagnostic(error, system);
|
||||
system.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);
|
||||
reportDiagnostics([error], getDefaultFormatDiagnosticsHost(system));
|
||||
system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = parseJsonText(configFileName, configFileText);
|
||||
reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined);
|
||||
reportDiagnostics(result.parseDiagnostics, getDefaultFormatDiagnosticsHost(system));
|
||||
|
||||
const cwd = host.getCurrentDirectory();
|
||||
const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd));
|
||||
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
|
||||
const cwd = system.getCurrentDirectory();
|
||||
const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd));
|
||||
reportDiagnostics(configParseResult.errors, getDefaultFormatDiagnosticsHost(system));
|
||||
|
||||
return configParseResult;
|
||||
}
|
||||
|
||||
function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, oldProgram?: Program) {
|
||||
function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, system: System, oldProgram?: Program) {
|
||||
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
|
||||
let statistics: Statistic[];
|
||||
if (hasDiagnostics) {
|
||||
@ -759,12 +783,12 @@ namespace ts {
|
||||
|
||||
if (compilerOptions.listFiles) {
|
||||
forEach(program.getSourceFiles(), file => {
|
||||
sys.write(file.fileName + sys.newLine);
|
||||
system.write(file.fileName + system.newLine);
|
||||
});
|
||||
}
|
||||
|
||||
if (hasDiagnostics) {
|
||||
const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1;
|
||||
const memoryUsed = system.getMemoryUsage ? system.getMemoryUsage() : -1;
|
||||
reportCountStatistic("Files", program.getSourceFiles().length);
|
||||
reportCountStatistic("Lines", countLines(program));
|
||||
reportCountStatistic("Nodes", program.getNodeCount());
|
||||
@ -830,9 +854,9 @@ namespace ts {
|
||||
}
|
||||
diagnostics = diagnostics.concat(emitOutput.diagnostics);
|
||||
|
||||
reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost);
|
||||
reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), getDefaultFormatDiagnosticsHost(system));
|
||||
|
||||
reportEmittedFiles(emitOutput.emittedFiles);
|
||||
reportEmittedFiles(emitOutput.emittedFiles, system);
|
||||
if (emitOutput.emitSkipped && diagnostics.length > 0) {
|
||||
// If the emitter didn't emit anything, then pass that value along.
|
||||
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
|
||||
@ -859,7 +883,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
for (const { name, value } of statistics) {
|
||||
sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine);
|
||||
system.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + system.newLine);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1012,7 +1036,3 @@ namespace ts {
|
||||
if (ts.Debug.isDebugging) {
|
||||
ts.Debug.enableDebugInfo();
|
||||
}
|
||||
|
||||
if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) {
|
||||
ts.sys.tryEnableSourceMapsForHost();
|
||||
}
|
||||
|
||||
@ -93,6 +93,7 @@
|
||||
"rwcRunner.ts",
|
||||
"test262Runner.ts",
|
||||
"runner.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"../server/protocol.ts",
|
||||
"../server/session.ts",
|
||||
"../server/client.ts",
|
||||
@ -116,6 +117,7 @@
|
||||
"./unittests/convertCompilerOptionsFromJson.ts",
|
||||
"./unittests/convertTypeAcquisitionFromJson.ts",
|
||||
"./unittests/tsserverProjectSystem.ts",
|
||||
"./unittests/tscWatchMode.ts",
|
||||
"./unittests/matchFiles.ts",
|
||||
"./unittests/initializeTSConfig.ts",
|
||||
"./unittests/compileOnSave.ts",
|
||||
|
||||
1000
src/harness/unittests/tscWatchMode.ts
Normal file
1000
src/harness/unittests/tscWatchMode.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -30,6 +30,19 @@ namespace ts.TestFSWithWatch {
|
||||
newLine?: string;
|
||||
}
|
||||
|
||||
export function createWatchedSystem(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost {
|
||||
if (!params) {
|
||||
params = {};
|
||||
}
|
||||
const host = new TestServerHost(/*withSafelist*/ false,
|
||||
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
|
||||
params.executingFilePath || getExecutingFilePathFromLibFile(),
|
||||
params.currentDirectory || "/",
|
||||
fileOrFolderList,
|
||||
params.newLine);
|
||||
return host;
|
||||
}
|
||||
|
||||
export function createServerHost(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost {
|
||||
if (!params) {
|
||||
params = {};
|
||||
@ -112,6 +125,23 @@ namespace ts.TestFSWithWatch {
|
||||
checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
|
||||
}
|
||||
|
||||
export function checkOutputContains(host: TestServerHost, expected: string[] | ReadonlyArray<string>) {
|
||||
const mapExpected = arrayToSet(expected);
|
||||
for (const f of host.getOutput()) {
|
||||
if (mapExpected.has(f)) {
|
||||
mapExpected.delete(f);
|
||||
}
|
||||
}
|
||||
assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`);
|
||||
}
|
||||
|
||||
export function checkOutputDoesNotContain(host: TestServerHost, expectedToBeAbsent: string[] | ReadonlyArray<string>) {
|
||||
const mapExpectedToBeAbsent = arrayToSet(expectedToBeAbsent);
|
||||
for (const f of host.getOutput()) {
|
||||
assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(host.getOutput())}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class Callbacks {
|
||||
private map: TimeOutCallback[] = [];
|
||||
private nextId = 1;
|
||||
@ -172,7 +202,7 @@ namespace ts.TestFSWithWatch {
|
||||
this.reloadFS(fileOrFolderList);
|
||||
}
|
||||
|
||||
private toFullPath(s: string) {
|
||||
toFullPath(s: string) {
|
||||
const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory);
|
||||
return this.toPath(fullPath);
|
||||
}
|
||||
@ -345,6 +375,11 @@ namespace ts.TestFSWithWatch {
|
||||
return isFile(this.fs.get(path));
|
||||
}
|
||||
|
||||
readFile(s: string) {
|
||||
const fsEntry = this.fs.get(this.toFullPath(s));
|
||||
return isFile(fsEntry) ? fsEntry.content : undefined;
|
||||
}
|
||||
|
||||
getFileSize(s: string) {
|
||||
const path = this.toFullPath(s);
|
||||
const entry = this.fs.get(path);
|
||||
@ -432,7 +467,15 @@ namespace ts.TestFSWithWatch {
|
||||
}
|
||||
|
||||
runQueuedTimeoutCallbacks() {
|
||||
this.timeoutCallbacks.invoke();
|
||||
try {
|
||||
this.timeoutCallbacks.invoke();
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message === this.existMessage) {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
runQueuedImmediateCallbacks() {
|
||||
@ -482,11 +525,15 @@ namespace ts.TestFSWithWatch {
|
||||
clear(this.output);
|
||||
}
|
||||
|
||||
readonly readFile = (s: string) => (<File>this.fs.get(this.toFullPath(s))).content;
|
||||
readonly existMessage = "System Exit";
|
||||
exitCode: number;
|
||||
readonly resolvePath = (s: string) => s;
|
||||
readonly getExecutingFilePath = () => this.executingFilePath;
|
||||
readonly getCurrentDirectory = () => this.currentDirectory;
|
||||
readonly exit = notImplemented;
|
||||
exit(exitCode?: number) {
|
||||
this.exitCode = exitCode;
|
||||
throw new Error(this.existMessage);
|
||||
}
|
||||
readonly getEnvironmentVariable = notImplemented;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1116,7 +1116,7 @@ namespace ts {
|
||||
let hostCache = new HostCache(host, getCanonicalFileName);
|
||||
const rootFileNames = hostCache.getRootFileNames();
|
||||
// If the program is already up-to-date, we can reuse it
|
||||
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), (path) => hostCache.getVersion(path))) {
|
||||
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1139,14 +1139,7 @@ namespace ts {
|
||||
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
|
||||
writeFile: noop,
|
||||
getCurrentDirectory: () => currentDirectory,
|
||||
fileExists: (fileName): boolean => {
|
||||
// stub missing host functionality
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
const entry = hostCache.getEntryByPath(path);
|
||||
return entry ?
|
||||
!isString(entry) :
|
||||
(host.fileExists && host.fileExists(fileName));
|
||||
},
|
||||
fileExists,
|
||||
readFile(fileName) {
|
||||
// stub missing host functionality
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
@ -1189,6 +1182,14 @@ namespace ts {
|
||||
program.getTypeChecker();
|
||||
return;
|
||||
|
||||
function fileExists(fileName: string) {
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
const entry = hostCache.getEntryByPath(path);
|
||||
return entry ?
|
||||
!isString(entry) :
|
||||
(host.fileExists && host.fileExists(fileName));
|
||||
}
|
||||
|
||||
// Release any files we have acquired in the old program but are
|
||||
// not part of the new program.
|
||||
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user