Add tests for the tsc --watch

This commit is contained in:
Sheetal Nandi committed 2017-08-07 11:24:07 -07:00
1 parent 69e5abd5b7
commit c814d8e847
8 files changed
+1204 -125

No files matched your search

+2
View File
@@ -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",
+14 -10
View File
@@ -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) {
+3
View File
@@ -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);
+122 -102
View File
@@ -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();
}
+2
View File
@@ -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
View File
@@ -0,0 +1,1000 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\compiler\tscLib.ts" />
/// <reference path="..\virtualFileSystemWithWatch.ts" />
namespace ts.tscWatch {
export import WatchedSystem = ts.TestFSWithWatch.TestServerHost;
export type TestServerHostCreationParameters = ts.TestFSWithWatch.TestServerHostCreationParameters;
export type File = ts.TestFSWithWatch.File;
export type FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
export type Folder = ts.TestFSWithWatch.Folder;
export type FSEntry = ts.TestFSWithWatch.FSEntry;
export import createWatchedSystem = ts.TestFSWithWatch.createWatchedSystem;
export import checkFileNames = ts.TestFSWithWatch.checkFileNames;
export import libFile = ts.TestFSWithWatch.libFile;
export import checkWatchedFiles = ts.TestFSWithWatch.checkWatchedFiles;
export import checkWatchedDirectories = ts.TestFSWithWatch.checkWatchedDirectories;
export import checkOutputContains = ts.TestFSWithWatch.checkOutputContains;
export import checkOutputDoesNotContain = ts.TestFSWithWatch.checkOutputDoesNotContain;
export function checkProgramActualFiles(program: Program, expectedFiles: string[]) {
checkFileNames(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
}
export function checkProgramRootFiles(program: Program, expectedFiles: string[]) {
checkFileNames(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
}
export function createWatchWithConfig(configFilePath: string, host: WatchedSystem) {
const configFileResult = parseConfigFile(configFilePath, {}, host);
return createWatchModeWithConfigFile(configFileResult, {}, host);
}
describe("tsc-watch program updates", () => {
const commonFile1: FileOrFolder = {
path: "/a/b/commonFile1.ts",
content: "let x = 1"
};
const commonFile2: FileOrFolder = {
path: "/a/b/commonFile2.ts",
content: "let y = 1"
};
it("create watch without config file", () => {
const appFile: FileOrFolder = {
path: "/a/b/c/app.ts",
content: `
import {f} from "./module"
console.log(f)
`
};
const moduleFile: FileOrFolder = {
path: "/a/b/c/module.d.ts",
content: `export let x: number`
};
const host = createWatchedSystem([appFile, moduleFile, libFile]);
const watch = createWatchModeWithoutConfigFile([appFile.path], {}, host);
checkProgramActualFiles(watch(), [appFile.path, libFile.path, moduleFile.path]);
// TODO: Should we watch creation of config files in the root file's file hierarchy?
// const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"];
// const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]);
// checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path));
});
it("can handle tsconfig file name with difference casing", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1"
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({
include: ["app.ts"]
})
};
const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false });
const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
const watch = createWatchWithConfig(upperCaseConfigFilePath, host);
checkProgramActualFiles(watch(), [f1.path]);
});
it("create configured project without file list", () => {
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `
{
"compilerOptions": {},
"exclude": [
"e"
]
}`
};
const file1: FileOrFolder = {
path: "/a/b/c/f1.ts",
content: "let x = 1"
};
const file2: FileOrFolder = {
path: "/a/b/d/f2.ts",
content: "let y = 1"
};
const file3: FileOrFolder = {
path: "/a/b/e/f3.ts",
content: "let z = 1"
};
const host = createWatchedSystem([configFile, libFile, file1, file2, file3]);
const configFileResult = parseConfigFile(configFile.path, {}, host);
assert.equal(configFileResult.errors.length, 0, `expect no errors in config file, got ${JSON.stringify(configFileResult.errors)}`);
const watch = createWatchModeWithConfigFile(configFileResult, {}, host);
checkProgramActualFiles(watch(), [file1.path, libFile.path, file2.path]);
checkProgramRootFiles(watch(), [file1.path, file2.path]);
checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]);
checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true);
});
// TODO: if watching for config file creation
// it("add and then remove a config file in a folder with loose files", () => {
// });
it("add new files to a configured program without file list", () => {
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = createWatchedSystem([commonFile1, libFile, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true);
checkProgramRootFiles(watch(), [commonFile1.path]);
// add a new ts file
host.reloadFS([commonFile1, commonFile2, libFile, configFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]);
});
it("should ignore non-existing files specified in the config file", () => {
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {},
"files": [
"commonFile1.ts",
"commonFile3.ts"
]
}`
};
const host = createWatchedSystem([commonFile1, commonFile2, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
const commonFile3 = "/a/b/commonFile3.ts";
checkProgramRootFiles(watch(), [commonFile1.path, commonFile3]);
checkProgramActualFiles(watch(), [commonFile1.path]);
});
it("handle recreated files correctly", () => {
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = createWatchedSystem([commonFile1, commonFile2, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]);
// delete commonFile2
host.reloadFS([commonFile1, configFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramRootFiles(watch(), [commonFile1.path]);
// re-add commonFile2
host.reloadFS([commonFile1, commonFile2, configFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]);
});
it("handles the missing files - that were added to program because they were added with ///<ref", () => {
const file1: FileOrFolder = {
path: "/a/b/commonFile1.ts",
content: `/// <reference path="commonFile2.ts"/>
let x = y`
};
const host = createWatchedSystem([file1, libFile]);
const watch = createWatchModeWithoutConfigFile([file1.path], {}, host);
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, libFile.path]);
const errors = [
`a/b/commonFile1.ts(1,22): error TS6053: File '${commonFile2.path}' not found.${host.newLine}`,
`a/b/commonFile1.ts(2,29): error TS2304: Cannot find name 'y'.${host.newLine}`
];
checkOutputContains(host, errors);
host.clearOutput();
host.reloadFS([file1, commonFile2, libFile]);
host.runQueuedTimeoutCallbacks();
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, libFile.path, commonFile2.path]);
checkOutputDoesNotContain(host, errors);
});
it("should reflect change in config file", () => {
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {},
"files": ["${commonFile1.path}", "${commonFile2.path}"]
}`
};
const files = [commonFile1, commonFile2, configFile];
const host = createWatchedSystem(files);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]);
configFile.content = `{
"compilerOptions": {},
"files": ["${commonFile1.path}"]
}`;
host.reloadFS(files);
host.checkTimeoutQueueLengthAndRun(1); // reload the configured project
checkProgramRootFiles(watch(), [commonFile1.path]);
});
it("files explicitly excluded in config file", () => {
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {},
"exclude": ["/a/c"]
}`
};
const excludedFile1: FileOrFolder = {
path: "/a/c/excluedFile1.ts",
content: `let t = 1;`
};
const host = createWatchedSystem([commonFile1, commonFile2, excludedFile1, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]);
});
it("should properly handle module resolution changes in config file", () => {
const file1: FileOrFolder = {
path: "/a/b/file1.ts",
content: `import { T } from "module1";`
};
const nodeModuleFile: FileOrFolder = {
path: "/a/b/node_modules/module1.ts",
content: `export interface T {}`
};
const classicModuleFile: FileOrFolder = {
path: "/a/module1.ts",
content: `export interface T {}`
};
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {
"moduleResolution": "node"
},
"files": ["${file1.path}"]
}`
};
const files = [file1, nodeModuleFile, classicModuleFile, configFile];
const host = createWatchedSystem(files);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, nodeModuleFile.path]);
configFile.content = `{
"compilerOptions": {
"moduleResolution": "classic"
},
"files": ["${file1.path}"]
}`;
host.reloadFS(files);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, classicModuleFile.path]);
});
it("should tolerate config file errors and still try to build a project", () => {
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {
"target": "es6",
"allowAnything": true
},
"someOtherProperty": {}
}`
};
const host = createWatchedSystem([commonFile1, commonFile2, libFile, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]);
});
it("changes in files are reflected in project structure", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `export * from "./f2"`
};
const file2 = {
path: "/a/b/f2.ts",
content: `export let x = 1`
};
const file3 = {
path: "/a/c/f3.ts",
content: `export let y = 1;`
};
const host = createWatchedSystem([file1, file2, file3]);
const watch = createWatchModeWithoutConfigFile([file1.path], {}, host);
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, file2.path]);
const modifiedFile2 = {
path: file2.path,
content: `export * from "../c/f3"` // now inferred project should inclule file3
};
host.reloadFS([file1, modifiedFile2, file3]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, modifiedFile2.path, file3.path]);
});
it("deleted files affect project structure", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `export * from "./f2"`
};
const file2 = {
path: "/a/b/f2.ts",
content: `export * from "../c/f3"`
};
const file3 = {
path: "/a/c/f3.ts",
content: `export let y = 1;`
};
const host = createWatchedSystem([file1, file2, file3]);
const watch = createWatchModeWithoutConfigFile([file1.path], {}, host);
checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]);
host.reloadFS([file1, file3]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramActualFiles(watch(), [file1.path]);
});
it("deleted files affect project structure - 2", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `export * from "./f2"`
};
const file2 = {
path: "/a/b/f2.ts",
content: `export * from "../c/f3"`
};
const file3 = {
path: "/a/c/f3.ts",
content: `export let y = 1;`
};
const host = createWatchedSystem([file1, file2, file3]);
const watch = createWatchModeWithoutConfigFile([file1.path, file3.path], {}, host);
checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]);
host.reloadFS([file1, file3]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramActualFiles(watch(), [file1.path, file3.path]);
});
it("config file includes the file", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "export let x = 5"
};
const file2 = {
path: "/a/c/f2.ts",
content: `import {x} from "../b/f1"`
};
const file3 = {
path: "/a/c/f3.ts",
content: "export let y = 1"
};
const configFile = {
path: "/a/c/tsconfig.json",
content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] })
};
const host = createWatchedSystem([file1, file2, file3, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramRootFiles(watch(), [file2.path, file3.path]);
checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]);
});
it("correctly migrate files between projects", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `
export * from "../c/f2";
export * from "../d/f3";`
};
const file2 = {
path: "/a/c/f2.ts",
content: "export let x = 1;"
};
const file3 = {
path: "/a/d/f3.ts",
content: "export let y = 1;"
};
const host = createWatchedSystem([file1, file2, file3]);
const watch = createWatchModeWithoutConfigFile([file2.path, file3.path], {}, host);
checkProgramActualFiles(watch(), [file2.path, file3.path]);
const watch2 = createWatchModeWithoutConfigFile([file1.path], {}, host);
checkProgramActualFiles(watch2(), [file1.path, file2.path, file3.path]);
// Previous program shouldnt be updated
checkProgramActualFiles(watch(), [file2.path, file3.path]);
host.checkTimeoutQueueLength(0);
});
it("can correctly update configured project when set of root files has changed (new file on disk)", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x = 1"
};
const file2 = {
path: "/a/b/f2.ts",
content: "let y = 1"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: {} })
};
const host = createWatchedSystem([file1, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramActualFiles(watch(), [file1.path]);
host.reloadFS([file1, file2, configFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramActualFiles(watch(), [file1.path, file2.path]);
checkProgramRootFiles(watch(), [file1.path, file2.path]);
});
it("can correctly update configured project when set of root files has changed (new file in list of files)", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x = 1"
};
const file2 = {
path: "/a/b/f2.ts",
content: "let y = 1"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] })
};
const host = createWatchedSystem([file1, file2, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramActualFiles(watch(), [file1.path]);
const modifiedConfigFile = {
path: configFile.path,
content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })
};
host.reloadFS([file1, file2, modifiedConfigFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramRootFiles(watch(), [file1.path, file2.path]);
checkProgramActualFiles(watch(), [file1.path, file2.path]);
});
it("can update configured project when set of root files was not changed", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x = 1"
};
const file2 = {
path: "/a/b/f2.ts",
content: "let y = 1"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })
};
const host = createWatchedSystem([file1, file2, configFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramActualFiles(watch(), [file1.path, file2.path]);
const modifiedConfigFile = {
path: configFile.path,
content: JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })
};
host.reloadFS([file1, file2, modifiedConfigFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramRootFiles(watch(), [file1.path, file2.path]);
checkProgramActualFiles(watch(), [file1.path, file2.path]);
});
it("config file is deleted", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x = 1;"
};
const file2 = {
path: "/a/b/f2.ts",
content: "let y = 2;"
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: {} })
};
const host = createWatchedSystem([file1, file2, config]);
const watch = createWatchWithConfig(config.path, host);
checkProgramActualFiles(watch(), [file1.path, file2.path]);
host.clearOutput();
host.reloadFS([file1, file2]);
host.checkTimeoutQueueLengthAndRun(1);
assert.equal(host.exitCode, ExitStatus.DiagnosticsPresent_OutputsSkipped);
checkOutputContains(host, [`error TS6053: File '${config.path}' not found.${host.newLine}`]);
});
it("Proper errors: document is not contained in project", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const corruptedConfig = {
path: "/a/b/tsconfig.json",
content: "{"
};
const host = createWatchedSystem([file1, corruptedConfig]);
const watch = createWatchWithConfig(corruptedConfig.path, host);
checkProgramActualFiles(watch(), [file1.path]);
});
it("correctly handles changes in lib section of config file", () => {
const libES5 = {
path: "/compiler/lib.es5.d.ts",
content: "declare const eval: any"
};
const libES2015Promise = {
path: "/compiler/lib.es2015.promise.d.ts",
content: "declare class Promise<T> {}"
};
const app = {
path: "/src/app.ts",
content: "var x: Promise<string>;"
};
const config1 = {
path: "/src/tsconfig.json",
content: JSON.stringify(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": true,
"sourceMap": false,
"lib": [
"es5"
]
}
})
};
const config2 = {
path: config1.path,
content: JSON.stringify(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": true,
"sourceMap": false,
"lib": [
"es5",
"es2015.promise"
]
}
})
};
const host = createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" });
const watch = createWatchWithConfig(config1.path, host);
checkProgramActualFiles(watch(), [libES5.path, app.path]);
host.reloadFS([libES5, libES2015Promise, app, config2]);
host.checkTimeoutQueueLengthAndRun(1);
checkProgramActualFiles(watch(), [libES5.path, libES2015Promise.path, app.path]);
});
it("should handle non-existing directories in config file", () => {
const f = {
path: "/a/src/app.ts",
content: "let x = 1;"
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: {},
include: [
"src/**/*",
"notexistingfolder/*"
]
})
};
const host = createWatchedSystem([f, config]);
const watch = createWatchWithConfig(config.path, host);
checkProgramActualFiles(watch(), [f.path]);
});
it("rename a module file and rename back should restore the states for inferred projects", () => {
const moduleFile = {
path: "/a/b/moduleFile.ts",
content: "export function bar() { };"
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
};
const host = createWatchedSystem([moduleFile, file1, libFile]);
createWatchModeWithoutConfigFile([file1.path], {}, host);
const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n";
checkOutputDoesNotContain(host, [error]);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [error]);
host.clearOutput();
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
});
it("rename a module file and rename back should restore the states for configured projects", () => {
const moduleFile = {
path: "/a/b/moduleFile.ts",
content: "export function bar() { };"
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = createWatchedSystem([moduleFile, file1, configFile, libFile]);
createWatchWithConfig(configFile.path, host);
const error = `error TS6053: File '${moduleFile.path}' not found.${host.newLine}`;
checkOutputDoesNotContain(host, [error]);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [error]);
host.clearOutput();
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
});
it("types should load from config file path if config exists", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1"
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } })
};
const node = {
path: "/a/b/node_modules/@types/node/index.d.ts",
content: "declare var process: any"
};
const cwd = {
path: "/a/c"
};
const host = createWatchedSystem([f1, config, node, cwd], { currentDirectory: cwd.path });
const watch = createWatchWithConfig(config.path, host);
checkProgramActualFiles(watch(), [f1.path, node.path]);
});
it("add the missing module file for inferred project: should remove the `module not found` error", () => {
const moduleFile = {
path: "/a/b/moduleFile.ts",
content: "export function bar() { };"
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
};
const host = createWatchedSystem([file1, libFile]);
createWatchModeWithoutConfigFile([file1.path], {}, host);
const error = `a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.${host.newLine}`;
checkOutputContains(host, [error]);
host.clearOutput();
host.reloadFS([file1, moduleFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
});
it("Configure file diagnostics events are generated when the config file has errors", () => {
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {
"foo": "bar",
"allowJS": true
}
}`
};
const host = createWatchedSystem([file, configFile, libFile]);
createWatchWithConfig(configFile.path, host);
checkOutputContains(host, [
`a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`,
`a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}`
]);
});
it("Configure file diagnostics events are generated when the config file doesn't have errors", () => {
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {}
}`
};
const host = createWatchedSystem([file, configFile, libFile]);
createWatchWithConfig(configFile.path, host);
checkOutputDoesNotContain(host, [
`a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`,
`a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}`
]);
});
it("Configure file diagnostics events are generated when the config file changes", () => {
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {}
}`
};
const host = createWatchedSystem([file, configFile, libFile]);
createWatchWithConfig(configFile.path, host);
const error = `a/b/tsconfig.json(3,25): error TS5023: Unknown compiler option 'haha'.${host.newLine}`;
checkOutputDoesNotContain(host, [error]);
configFile.content = `{
"compilerOptions": {
"haha": 123
}
}`;
host.reloadFS([file, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [error]);
host.clearOutput();
configFile.content = `{
"compilerOptions": {}
}`;
host.reloadFS([file, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
});
it("non-existing directories listed in config file input array should be tolerated without crashing the server", () => {
const configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {},
"include": ["app/*", "test/**/*", "something"]
}`
};
const file1 = {
path: "/a/b/file1.ts",
content: "let t = 10;"
};
const host = createWatchedSystem([file1, configFile, libFile]);
const watch = createWatchWithConfig(configFile.path, host);
checkProgramActualFiles(watch(), [libFile.path]);
});
it("non-existing directories listed in config file input array should be able to handle @types if input file list is empty", () => {
const f = {
path: "/a/app.ts",
content: "let x = 1"
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compiler: {},
files: []
})
};
const t1 = {
path: "/a/node_modules/@types/typings/index.d.ts",
content: `export * from "./lib"`
};
const t2 = {
path: "/a/node_modules/@types/typings/lib.d.ts",
content: `export const x: number`
};
const host = createWatchedSystem([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) });
const watch = createWatchWithConfig(config.path, host);
checkProgramActualFiles(watch(), [t1.path, t2.path]);
});
it("should support files without extensions", () => {
const f = {
path: "/a/compile",
content: "let x = 1"
};
const host = createWatchedSystem([f, libFile]);
const watch = createWatchModeWithoutConfigFile([f.path], { allowNonTsExtensions: true }, host);
checkProgramActualFiles(watch(), [f.path, libFile.path]);
});
it("Options Diagnostic locations reported correctly with changes in configFile contents when options change", () => {
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFileContentBeforeComment = `{`;
const configFileContentComment = `
// comment
// More comment`;
const configFileContentAfterComment = `
"compilerOptions": {
"allowJs": true,
"declaration": true
}
}`;
const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment;
const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment;
const line = 5;
const errors = (line: number) => [
`a/b/tsconfig.json(${line},25): error TS5053: Option \'allowJs\' cannot be specified with option \'declaration\'.\n`,
`a/b/tsconfig.json(${line + 1},25): error TS5053: Option \'allowJs\' cannot be specified with option \'declaration\'.\n`
];
const configFile = {
path: "/a/b/tsconfig.json",
content: configFileContentWithComment
};
const host = createWatchedSystem([file, libFile, configFile]);
createWatchWithConfig(configFile.path, host);
checkOutputContains(host, errors(line));
checkOutputDoesNotContain(host, errors(line - 2));
host.clearOutput();
configFile.content = configFileContentWithoutCommentLine;
host.reloadFS([file, configFile]);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, errors(line - 2));
checkOutputDoesNotContain(host, errors(line));
});
});
describe("tsc-watch emit", () => {
it("emit with outFile or out setting projectUsesOutFile should not be returned if not set", () => {
const f1 = {
path: "/a/a.ts",
content: "let x = 1"
};
const f2 = {
path: "/a/b.ts",
content: "let y = 1"
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: { listEmittedFiles: true }
})
};
const files = [f1, f2, config, libFile];
const host = createWatchedSystem([f1, f2, config, libFile]);
createWatchWithConfig(config.path, host);
const emittedF1 = `TSFILE: ${f1.path.replace(".ts", ".js")}${host.newLine}`;
const emittedF2 = `TSFILE: ${f2.path.replace(".ts", ".js")}${host.newLine}`;
checkOutputContains(host, [emittedF1, emittedF2]);
host.clearOutput();
f1.content = "let x = 11";
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [emittedF1]);
checkOutputDoesNotContain(host, [emittedF2]);
});
it("emit with outFile or out setting projectUsesOutFile should be true if out is set", () => {
const outJS = "/a/out.js";
const f1 = {
path: "/a/a.ts",
content: "let x = 1"
};
const f2 = {
path: "/a/b.ts",
content: "let y = 1"
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: { listEmittedFiles: true, out: outJS }
})
};
const files = [f1, f2, config, libFile];
const host = createWatchedSystem([f1, f2, config, libFile]);
createWatchWithConfig(config.path, host);
const emittedF1 = `TSFILE: ${outJS}${host.newLine}`;
checkOutputContains(host, [emittedF1]);
host.clearOutput();
f1.content = "let x = 11";
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [emittedF1]);
});
it("emit with outFile or out setting projectUsesOutFile should be true if outFile is set", () => {
const outJs = "/a/out.js";
const f1 = {
path: "/a/a.ts",
content: "let x = 1"
};
const f2 = {
path: "/a/b.ts",
content: "let y = 1"
};
const config = {
path: "/a/tsconfig.json",
content: JSON.stringify({
compilerOptions: { listEmittedFiles: true, outFile: outJs }
})
};
const files = [f1, f2, config, libFile];
const host = createWatchedSystem([f1, f2, config, libFile]);
createWatchWithConfig(config.path, host);
const emittedF1 = `TSFILE: ${outJs}${host.newLine}`;
checkOutputContains(host, [emittedF1]);
host.clearOutput();
f1.content = "let x = 11";
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [emittedF1]);
});
});
}
+51 -4
View File
@@ -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;
}
}
+10 -9
View File
@@ -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) {