mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Add tests for the tsc --watch
This commit is contained in:
1 parent
69e5abd5b7
commit
c814d8e847
8 files changed
+1204
-125
No files matched your search
@@ -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
@@ -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);
|
||||
+122
-102
@@ -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",
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user