mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #30232 from Microsoft/incrementalWithNormalTsc
Use .tsbuildinfo to build with tsc and tsc --w when not using build mode
This commit is contained in:
+631
-541
File diff suppressed because it is too large
Load Diff
+793
-832
File diff suppressed because it is too large
Load Diff
+793
-832
File diff suppressed because it is too large
Load Diff
+793
-832
File diff suppressed because it is too large
Load Diff
+793
-832
File diff suppressed because it is too large
Load Diff
+790
-829
File diff suppressed because it is too large
Load Diff
+2
-16
@@ -451,15 +451,7 @@ namespace ts {
|
||||
let readFileWithCache = (f: string) => host.readFile(f);
|
||||
let projectCompilerOptions = baseCompilerOptions;
|
||||
const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions);
|
||||
const originalGetSourceFile = compilerHost.getSourceFile;
|
||||
const computeHash = host.createHash || generateDjb2Hash;
|
||||
compilerHost.getSourceFile = (...args) => {
|
||||
const result = originalGetSourceFile.call(compilerHost, ...args);
|
||||
if (result) {
|
||||
result.version = computeHash.call(host, result.text);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
setGetSourceFileAsHashVersioned(compilerHost, host);
|
||||
|
||||
const buildInfoChecked = createFileMap<true>(toPath);
|
||||
|
||||
@@ -1241,13 +1233,7 @@ namespace ts {
|
||||
function getOldProgram(proj: ResolvedConfigFileName, parsed: ParsedCommandLine) {
|
||||
const value = builderPrograms.getValue(proj);
|
||||
if (value) return value;
|
||||
const buildInfoPath = getOutputPathForBuildInfo(parsed.options);
|
||||
if (!buildInfoPath) return undefined;
|
||||
const content = readFileWithCache(buildInfoPath);
|
||||
if (!content) return undefined;
|
||||
const buildInfo = getBuildInfo(content);
|
||||
if (buildInfo.version !== version) return undefined;
|
||||
return buildInfo.program && createBuildProgramUsingProgramBuildInfo(buildInfo.program) as any as T;
|
||||
return readBuilderProgram(parsed.options, readFileWithCache) as any as T;
|
||||
}
|
||||
|
||||
function updateBundle(proj: ResolvedConfigFileName): BuildResultFlags {
|
||||
|
||||
+86
-45
@@ -129,7 +129,6 @@ namespace ts {
|
||||
const diagnostics = program.getConfigFileParsingDiagnostics().slice();
|
||||
const configFileParsingDiagnosticsLength = diagnostics.length;
|
||||
addRange(diagnostics, program.getSyntacticDiagnostics());
|
||||
let reportSemanticDiagnostics = false;
|
||||
|
||||
// If we didn't have any syntactic errors, then also try getting the global and
|
||||
// semantic errors.
|
||||
@@ -138,7 +137,7 @@ namespace ts {
|
||||
addRange(diagnostics, program.getGlobalDiagnostics());
|
||||
|
||||
if (diagnostics.length === configFileParsingDiagnosticsLength) {
|
||||
reportSemanticDiagnostics = true;
|
||||
addRange(diagnostics, program.getSemanticDiagnostics());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,10 +145,6 @@ namespace ts {
|
||||
const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile);
|
||||
addRange(diagnostics, emitDiagnostics);
|
||||
|
||||
if (reportSemanticDiagnostics) {
|
||||
addRange(diagnostics, program.getSemanticDiagnostics());
|
||||
}
|
||||
|
||||
sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic);
|
||||
if (writeFileName) {
|
||||
const currentDir = program.getCurrentDirectory();
|
||||
@@ -280,6 +275,18 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost, host: ProgramHost<any>) {
|
||||
const originalGetSourceFile = compilerHost.getSourceFile;
|
||||
const computeHash = host.createHash || generateDjb2Hash;
|
||||
compilerHost.getSourceFile = (...args) => {
|
||||
const result = originalGetSourceFile.call(compilerHost, ...args);
|
||||
if (result) {
|
||||
result.version = computeHash.call(host, result.text);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the watch compiler host that can be extended with config file or root file names and options host
|
||||
*/
|
||||
@@ -364,6 +371,18 @@ namespace ts {
|
||||
host.projectReferences = projectReferences;
|
||||
return host;
|
||||
}
|
||||
|
||||
export function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined) {
|
||||
if (compilerOptions.out || compilerOptions.outFile) return undefined;
|
||||
const buildInfoPath = getOutputPathForBuildInfo(compilerOptions);
|
||||
if (!buildInfoPath) return undefined;
|
||||
const content = readFile(buildInfoPath);
|
||||
if (!content) return undefined;
|
||||
const buildInfo = getBuildInfo(content);
|
||||
if (buildInfo.version !== version) return undefined;
|
||||
if (!buildInfo.program) return undefined;
|
||||
return createBuildProgramUsingProgramBuildInfo(buildInfo.program);
|
||||
}
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
@@ -494,6 +513,9 @@ namespace ts {
|
||||
/** Gets the existing program without synchronizing with changes on host */
|
||||
/*@internal*/
|
||||
getCurrentProgram(): T;
|
||||
/** Closes the watch */
|
||||
/*@internal*/
|
||||
close(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,8 +546,6 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const initialVersion = 1;
|
||||
|
||||
/**
|
||||
* Creates the watch from the host for root files and compiler options
|
||||
*/
|
||||
@@ -536,13 +556,14 @@ namespace ts {
|
||||
export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfConfigFile<T>): WatchOfConfigFile<T>;
|
||||
export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T> & WatchCompilerHostOfConfigFile<T>): WatchOfFilesAndCompilerOptions<T> | WatchOfConfigFile<T> {
|
||||
interface FilePresentOnHost {
|
||||
version: number;
|
||||
version: string;
|
||||
sourceFile: SourceFile;
|
||||
fileWatcher: FileWatcher;
|
||||
}
|
||||
type FileMissingOnHost = number;
|
||||
type FileMissingOnHost = false;
|
||||
interface FilePresenceUnknownOnHost {
|
||||
version: number;
|
||||
version: false;
|
||||
fileWatcher?: FileWatcher;
|
||||
}
|
||||
type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost;
|
||||
type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost;
|
||||
@@ -592,11 +613,13 @@ namespace ts {
|
||||
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
|
||||
writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
|
||||
let configFileWatcher: FileWatcher | undefined;
|
||||
if (configFileName) {
|
||||
watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile);
|
||||
configFileWatcher = watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile);
|
||||
}
|
||||
|
||||
const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost;
|
||||
setGetSourceFileAsHashVersioned(compilerHost, host);
|
||||
// Members for CompilerHost
|
||||
const getNewSourceFile = compilerHost.getSourceFile;
|
||||
compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args);
|
||||
@@ -634,27 +657,50 @@ namespace ts {
|
||||
((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference));
|
||||
const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives;
|
||||
|
||||
builderProgram = readBuilderProgram(compilerOptions, path => compilerHost.readFile(path)) as any as T;
|
||||
synchronizeProgram();
|
||||
|
||||
// Update the wild card directory watch
|
||||
watchConfigFileWildCardDirectories();
|
||||
|
||||
return configFileName ?
|
||||
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram } :
|
||||
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram, updateRootFileNames };
|
||||
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram, close } :
|
||||
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram, updateRootFileNames, close };
|
||||
|
||||
function close() {
|
||||
resolutionCache.clear();
|
||||
clearMap(sourceFilesCache, value => {
|
||||
if (value && value.fileWatcher) {
|
||||
value.fileWatcher.close();
|
||||
value.fileWatcher = undefined;
|
||||
}
|
||||
});
|
||||
if (configFileWatcher) {
|
||||
configFileWatcher.close();
|
||||
configFileWatcher = undefined;
|
||||
}
|
||||
if (watchedWildcardDirectories) {
|
||||
clearMap(watchedWildcardDirectories, closeFileWatcherOf);
|
||||
watchedWildcardDirectories = undefined!;
|
||||
}
|
||||
if (missingFilesMap) {
|
||||
clearMap(missingFilesMap, closeFileWatcher);
|
||||
missingFilesMap = undefined!;
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentBuilderProgram() {
|
||||
return builderProgram;
|
||||
}
|
||||
|
||||
function getCurrentProgram() {
|
||||
return builderProgram && builderProgram.getProgram();
|
||||
return builderProgram && builderProgram.getProgramOrUndefined();
|
||||
}
|
||||
|
||||
function synchronizeProgram() {
|
||||
writeLog(`Synchronizing program`);
|
||||
|
||||
const program = getCurrentProgram();
|
||||
const program = getCurrentBuilderProgram();
|
||||
if (hasChangedCompilerOptions) {
|
||||
newLine = updateNewLine();
|
||||
if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) {
|
||||
@@ -671,7 +717,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else {
|
||||
createNewProgram(program, hasInvalidatedResolution);
|
||||
createNewProgram(hasInvalidatedResolution);
|
||||
}
|
||||
|
||||
if (host.afterProgramCreate) {
|
||||
@@ -681,13 +727,13 @@ namespace ts {
|
||||
return builderProgram;
|
||||
}
|
||||
|
||||
function createNewProgram(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||
function createNewProgram(hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||
// Compile the program
|
||||
writeLog("CreatingProgramWith::");
|
||||
writeLog(` roots: ${JSON.stringify(rootFileNames)}`);
|
||||
writeLog(` options: ${JSON.stringify(compilerOptions)}`);
|
||||
|
||||
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
|
||||
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !getCurrentProgram();
|
||||
hasChangedCompilerOptions = false;
|
||||
hasChangedConfigFileParsingErrors = false;
|
||||
resolutionCache.startCachingPerDirectoryResolution();
|
||||
@@ -731,19 +777,19 @@ namespace ts {
|
||||
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
}
|
||||
|
||||
function isFileMissingOnHost(hostSourceFile: HostFileInfo): hostSourceFile is FileMissingOnHost {
|
||||
return typeof hostSourceFile === "number";
|
||||
function isFileMissingOnHost(hostSourceFile: HostFileInfo | undefined): hostSourceFile is FileMissingOnHost {
|
||||
return typeof hostSourceFile === "boolean";
|
||||
}
|
||||
|
||||
function isFilePresentOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresentOnHost {
|
||||
return !!(hostSourceFile as FilePresentOnHost).sourceFile;
|
||||
function isFilePresenceUnknownOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresenceUnknownOnHost {
|
||||
return typeof (hostSourceFile as FilePresenceUnknownOnHost).version === "boolean";
|
||||
}
|
||||
|
||||
function fileExists(fileName: string) {
|
||||
const path = toPath(fileName);
|
||||
// If file is missing on host from cache, we can definitely say file doesnt exist
|
||||
// otherwise we need to ensure from the disk
|
||||
if (isFileMissingOnHost(sourceFilesCache.get(path)!)) {
|
||||
if (isFileMissingOnHost(sourceFilesCache.get(path))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -751,44 +797,39 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
|
||||
const hostSourceFile = sourceFilesCache.get(path)!;
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
// No source file on the host
|
||||
if (isFileMissingOnHost(hostSourceFile)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create new source file if requested or the versions dont match
|
||||
if (!hostSourceFile || shouldCreateNewSourceFile || !isFilePresentOnHost(hostSourceFile) || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
|
||||
if (hostSourceFile === undefined || shouldCreateNewSourceFile || isFilePresenceUnknownOnHost(hostSourceFile)) {
|
||||
const sourceFile = getNewSourceFile(fileName, languageVersion, onError);
|
||||
if (hostSourceFile) {
|
||||
if (shouldCreateNewSourceFile) {
|
||||
hostSourceFile.version++;
|
||||
}
|
||||
|
||||
if (sourceFile) {
|
||||
// Set the source file and create file watcher now that file was present on the disk
|
||||
(hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
|
||||
sourceFile.version = hostSourceFile.version.toString();
|
||||
if (!(hostSourceFile as FilePresentOnHost).fileWatcher) {
|
||||
(hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile);
|
||||
hostSourceFile.version = sourceFile.version;
|
||||
if (!hostSourceFile.fileWatcher) {
|
||||
hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There is no source file on host any more, close the watch, missing file paths will track it
|
||||
if (isFilePresentOnHost(hostSourceFile)) {
|
||||
if (hostSourceFile.fileWatcher) {
|
||||
hostSourceFile.fileWatcher.close();
|
||||
}
|
||||
sourceFilesCache.set(path, hostSourceFile.version);
|
||||
sourceFilesCache.set(path, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (sourceFile) {
|
||||
sourceFile.version = initialVersion.toString();
|
||||
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile);
|
||||
sourceFilesCache.set(path, { sourceFile, version: initialVersion, fileWatcher });
|
||||
sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher });
|
||||
}
|
||||
else {
|
||||
sourceFilesCache.set(path, initialVersion);
|
||||
sourceFilesCache.set(path, false);
|
||||
}
|
||||
}
|
||||
return sourceFile;
|
||||
@@ -801,17 +842,17 @@ namespace ts {
|
||||
if (hostSourceFile !== undefined) {
|
||||
if (isFileMissingOnHost(hostSourceFile)) {
|
||||
// The next version, lets set it as presence unknown file
|
||||
sourceFilesCache.set(path, { version: Number(hostSourceFile) + 1 });
|
||||
sourceFilesCache.set(path, { version: false });
|
||||
}
|
||||
else {
|
||||
hostSourceFile.version++;
|
||||
(hostSourceFile as FilePresenceUnknownOnHost).version = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceVersion(path: Path): string | undefined {
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
return !hostSourceFile || isFileMissingOnHost(hostSourceFile) ? undefined : hostSourceFile.version.toString();
|
||||
return !hostSourceFile || !hostSourceFile.version ? undefined : hostSourceFile.version;
|
||||
}
|
||||
|
||||
function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) {
|
||||
@@ -820,14 +861,14 @@ namespace ts {
|
||||
// remove the cached entry.
|
||||
// Note we arent deleting entry if file became missing in new program or
|
||||
// there was version update and new source file was created.
|
||||
if (hostSourceFileInfo) {
|
||||
if (hostSourceFileInfo !== undefined) {
|
||||
// record the missing file paths so they can be removed later if watchers arent tracking them
|
||||
if (isFileMissingOnHost(hostSourceFileInfo)) {
|
||||
(missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path);
|
||||
}
|
||||
else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) {
|
||||
if ((hostSourceFileInfo as FilePresentOnHost).fileWatcher) {
|
||||
(hostSourceFileInfo as FilePresentOnHost).fileWatcher.close();
|
||||
if (hostSourceFileInfo.fileWatcher) {
|
||||
hostSourceFileInfo.fileWatcher.close();
|
||||
}
|
||||
sourceFilesCache.delete(oldSourceFile.resolvedPath);
|
||||
if (!hasSourceFileByPath) {
|
||||
@@ -924,7 +965,7 @@ namespace ts {
|
||||
updateCachedSystemWithFile(fileName, path, eventKind);
|
||||
|
||||
// Update the source file cache
|
||||
if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.get(path)) {
|
||||
if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.has(path)) {
|
||||
resolutionCache.invalidateResolutionOfFile(path);
|
||||
}
|
||||
resolutionCache.removeResolutionsFromProjectReferenceRedirects(path);
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"unittests/tscWatch/consoleClearing.ts",
|
||||
"unittests/tscWatch/emit.ts",
|
||||
"unittests/tscWatch/emitAndErrorUpdates.ts",
|
||||
"unittests/tscWatch/incremental.ts",
|
||||
"unittests/tscWatch/programUpdates.ts",
|
||||
"unittests/tscWatch/resolutionCache.ts",
|
||||
"unittests/tscWatch/watchEnvironment.ts",
|
||||
|
||||
@@ -99,13 +99,7 @@ namespace ts {
|
||||
// outputs
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
|
||||
// build info
|
||||
outputFiles[project.third][ext.buildinfo],
|
||||
],
|
||||
// These are first not present and later read new contents to generate third output
|
||||
outputFiles[project.first][ext.buildinfo],
|
||||
outputFiles[project.second][ext.buildinfo]
|
||||
]
|
||||
);
|
||||
|
||||
let dtsChangedExpectedDiagnostics: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
@@ -131,12 +125,8 @@ namespace ts {
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
outputFiles[project.third][ext.dts],
|
||||
|
||||
// build info
|
||||
outputFiles[project.third][ext.buildinfo],
|
||||
],
|
||||
outputFiles[project.first][ext.dts], // dts changes so once read old content, and once new (to emit third)
|
||||
outputFiles[project.first][ext.buildinfo], // since first build info changes
|
||||
);
|
||||
|
||||
let dtsChangedExpectedDiagnosticsDependOrdered: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
@@ -173,8 +163,7 @@ namespace ts {
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
...outputFiles[project.third],
|
||||
],
|
||||
outputFiles[project.first][ext.buildinfo], // since first build info changes
|
||||
]
|
||||
);
|
||||
|
||||
let dtsUnchangedExpectedDiagnosticsDependOrdered: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
@@ -227,7 +216,6 @@ namespace ts {
|
||||
value.set(path, 1);
|
||||
}
|
||||
value.set(outputFiles[project.second][ext.dts], 2); // dts changes so once read old content, and once new (to emit third)
|
||||
value.set(outputFiles[project.second][ext.buildinfo], 2); // since first build info changes
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace ts.tscWatch {
|
||||
export interface Watch {
|
||||
(): Program;
|
||||
getBuilderProgram(): EmitAndSemanticDiagnosticsBuilderProgram;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
@@ -40,6 +41,7 @@ namespace ts.tscWatch {
|
||||
const watch = createWatchProgram(compilerHost);
|
||||
const result = (() => watch.getCurrentProgram().getProgram()) as Watch;
|
||||
result.getBuilderProgram = () => watch.getCurrentProgram();
|
||||
result.close = () => watch.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
namespace ts.tscWatch {
|
||||
describe("unittests:: tsc-watch:: emit file --incremental", () => {
|
||||
const project = "/users/username/projects/project";
|
||||
|
||||
const configFile: File = {
|
||||
path: `${project}/tsconfig.json`,
|
||||
content: JSON.stringify({ compilerOptions: { incremental: true } })
|
||||
};
|
||||
|
||||
interface VerifyIncrementalWatchEmitInput {
|
||||
files: ReadonlyArray<File>;
|
||||
expectedInitialEmit: ReadonlyArray<File>;
|
||||
expectedInitialErrors: ReadonlyArray<string>;
|
||||
modifyFs?: (host: WatchedSystem) => void;
|
||||
expectedIncrementalEmit?: ReadonlyArray<File>;
|
||||
expectedIncrementalErrors?: ReadonlyArray<string>;
|
||||
}
|
||||
function verifyIncrementalWatchEmit({
|
||||
files, expectedInitialEmit, expectedInitialErrors, modifyFs, expectedIncrementalEmit, expectedIncrementalErrors
|
||||
}: VerifyIncrementalWatchEmitInput) {
|
||||
const host = createWatchedSystem(files, { currentDirectory: project });
|
||||
const originalWriteFile = host.writeFile;
|
||||
const writtenFiles = createMap<string>();
|
||||
host.writeFile = (path, content) => {
|
||||
assert.isFalse(writtenFiles.has(path));
|
||||
writtenFiles.set(path, content);
|
||||
originalWriteFile.call(host, path, content);
|
||||
};
|
||||
verifyWatch(host, writtenFiles, expectedInitialEmit, expectedInitialErrors);
|
||||
if (modifyFs) {
|
||||
modifyFs(host);
|
||||
verifyWatch(host, writtenFiles, Debug.assertDefined(expectedIncrementalEmit), Debug.assertDefined(expectedIncrementalErrors));
|
||||
}
|
||||
}
|
||||
|
||||
function verifyWatch(host: WatchedSystem, writtenFiles: Map<string>, expectedEmit: ReadonlyArray<File>, expectedErrors: ReadonlyArray<string>) {
|
||||
writtenFiles.clear();
|
||||
const watch = createWatchOfConfigFile("tsconfig.json", host);
|
||||
checkFileEmit(writtenFiles, expectedEmit);
|
||||
checkOutputErrorsInitial(host, expectedErrors);
|
||||
watch.close();
|
||||
}
|
||||
|
||||
function checkFileEmit(actual: Map<string>, expected: ReadonlyArray<File>) {
|
||||
assert.equal(actual.size, expected.length, `Actual: ${JSON.stringify(arrayFrom(actual.entries()), /*replacer*/ undefined, " ")}\nExpected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}`);
|
||||
expected.forEach(file => assert.equal(actual.get(file.path), file.content, `Emit for ${file.path}`));
|
||||
}
|
||||
|
||||
const libFileInfo: BuilderState.FileInfo = {
|
||||
version: Harness.mockHash(libFile.content),
|
||||
signature: Harness.mockHash(libFile.content)
|
||||
};
|
||||
|
||||
describe("non module compilation", () => {
|
||||
function getFileInfo(content: string): BuilderState.FileInfo {
|
||||
return { version: Harness.mockHash(content), signature: Harness.mockHash(`declare ${content}\n`) };
|
||||
}
|
||||
|
||||
const file1: File = {
|
||||
path: `${project}/file1.ts`,
|
||||
content: "const x = 10;"
|
||||
};
|
||||
const file2: File = {
|
||||
path: `${project}/file2.ts`,
|
||||
content: "const y = 20;"
|
||||
};
|
||||
const file1Js: File = {
|
||||
path: `${project}/file1.js`,
|
||||
content: "var x = 10;\n"
|
||||
};
|
||||
const file2Js: File = {
|
||||
path: `${project}/file2.js`,
|
||||
content: "var y = 20;\n"
|
||||
};
|
||||
|
||||
it("own file emit without errors", () => {
|
||||
const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10");
|
||||
verifyIncrementalWatchEmit({
|
||||
files: [libFile, file1, file2, configFile],
|
||||
expectedInitialEmit: [
|
||||
file1Js,
|
||||
file2Js,
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(file1.content),
|
||||
[file2.path]: getFileInfo(file2.content)
|
||||
},
|
||||
options: { incremental: true, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [libFile.path, file1.path, file2.path]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedInitialErrors: emptyArray,
|
||||
modifyFs: host => host.writeFile(file2.path, modifiedFile2Content),
|
||||
expectedIncrementalEmit: [
|
||||
file1Js,
|
||||
{ path: file2Js.path, content: file2Js.content.replace("y", "z").replace("20", "10") },
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(file1.content),
|
||||
[file2.path]: getFileInfo(modifiedFile2Content)
|
||||
},
|
||||
options: { incremental: true, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [libFile.path, file1.path, file2.path]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedIncrementalErrors: emptyArray,
|
||||
});
|
||||
});
|
||||
|
||||
it("own file emit with errors", () => {
|
||||
const fileModified: File = {
|
||||
path: file2.path,
|
||||
content: `const y: string = 20;`
|
||||
};
|
||||
const file2FileInfo: BuilderState.FileInfo = {
|
||||
version: Harness.mockHash(fileModified.content),
|
||||
signature: Harness.mockHash("declare const y: string;\n")
|
||||
};
|
||||
const file2ReuasableError: ProgramBuildInfoDiagnostic = [
|
||||
file2.path, [
|
||||
{
|
||||
file: file2.path,
|
||||
start: 6,
|
||||
length: 1,
|
||||
code: Diagnostics.Type_0_is_not_assignable_to_type_1.code,
|
||||
category: Diagnostics.Type_0_is_not_assignable_to_type_1.category,
|
||||
messageText: "Type '20' is not assignable to type 'string'."
|
||||
}
|
||||
] as ReusableDiagnostic[]
|
||||
];
|
||||
const file2Errors = [
|
||||
"file2.ts(1,7): error TS2322: Type '20' is not assignable to type 'string'.\n"
|
||||
];
|
||||
const modifiedFile1Content = file1.content.replace("x", "z");
|
||||
verifyIncrementalWatchEmit({
|
||||
files: [libFile, file1, fileModified, configFile],
|
||||
expectedInitialEmit: [
|
||||
file1Js,
|
||||
file2Js,
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(file1.content),
|
||||
[file2.path]: file2FileInfo
|
||||
},
|
||||
options: { incremental: true, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [
|
||||
libFile.path,
|
||||
file1.path,
|
||||
file2ReuasableError
|
||||
]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedInitialErrors: file2Errors,
|
||||
modifyFs: host => host.writeFile(file1.path, modifiedFile1Content),
|
||||
expectedIncrementalEmit: [
|
||||
{ path: file1Js.path, content: file1Js.content.replace("x", "z") },
|
||||
file2Js,
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(modifiedFile1Content),
|
||||
[file2.path]: file2FileInfo
|
||||
},
|
||||
options: { incremental: true, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [
|
||||
libFile.path,
|
||||
file1.path,
|
||||
file2ReuasableError
|
||||
]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedIncrementalErrors: file2Errors,
|
||||
});
|
||||
});
|
||||
|
||||
it("with --out", () => {
|
||||
const config: File = {
|
||||
path: configFile.path,
|
||||
content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } })
|
||||
};
|
||||
const outFile: File = {
|
||||
path: `${project}/out.js`,
|
||||
content: "var x = 10;\nvar y = 20;\n"
|
||||
};
|
||||
verifyIncrementalWatchEmit({
|
||||
files: [libFile, file1, file2, config],
|
||||
expectedInitialEmit: [
|
||||
outFile,
|
||||
{
|
||||
path: `${project}/out.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
bundle: {
|
||||
commonSourceDirectory: `${project}/`,
|
||||
sourceFiles: [file1.path, file2.path],
|
||||
js: {
|
||||
sections: [
|
||||
{ pos: 0, end: outFile.content.length, kind: BundleFileSectionKind.Text }
|
||||
]
|
||||
},
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedInitialErrors: emptyArray
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("module compilation", () => {
|
||||
function getFileInfo(content: string): BuilderState.FileInfo {
|
||||
return {
|
||||
version: Harness.mockHash(content),
|
||||
signature: Harness.mockHash(`${content.replace("export ", "export declare ")}\n`)
|
||||
};
|
||||
}
|
||||
|
||||
function getEmitContent(varName: string, value: string) {
|
||||
return `define(["require", "exports"], function (require, exports) {
|
||||
"use strict";
|
||||
exports.__esModule = true;
|
||||
exports.${varName} = ${value};
|
||||
});
|
||||
`;
|
||||
}
|
||||
const file1: File = {
|
||||
path: `${project}/file1.ts`,
|
||||
content: "export const x = 10;"
|
||||
};
|
||||
const file2: File = {
|
||||
path: `${project}/file2.ts`,
|
||||
content: "export const y = 20;"
|
||||
};
|
||||
const file1Js: File = {
|
||||
path: `${project}/file1.js`,
|
||||
content: getEmitContent("x", "10")
|
||||
};
|
||||
const file2Js: File = {
|
||||
path: `${project}/file2.js`,
|
||||
content: getEmitContent("y", "20")
|
||||
};
|
||||
const config: File = {
|
||||
path: configFile.path,
|
||||
content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } })
|
||||
};
|
||||
|
||||
it("own file emit without errors", () => {
|
||||
const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10");
|
||||
verifyIncrementalWatchEmit({
|
||||
files: [libFile, file1, file2, config],
|
||||
expectedInitialEmit: [
|
||||
file1Js,
|
||||
file2Js,
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(file1.content),
|
||||
[file2.path]: getFileInfo(file2.content)
|
||||
},
|
||||
options: { incremental: true, module: ModuleKind.AMD, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [libFile.path, file1.path, file2.path]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedInitialErrors: emptyArray,
|
||||
modifyFs: host => host.writeFile(file2.path, modifiedFile2Content),
|
||||
expectedIncrementalEmit: [
|
||||
{ path: `${project}/file2.js`, content: getEmitContent("z", "10") },
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(file1.content),
|
||||
[file2.path]: getFileInfo(modifiedFile2Content)
|
||||
},
|
||||
options: { incremental: true, module: ModuleKind.AMD, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [libFile.path, file1.path, file2.path]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedIncrementalErrors: emptyArray,
|
||||
});
|
||||
});
|
||||
|
||||
it("own file emit with errors", () => {
|
||||
const fileModified: File = {
|
||||
path: file2.path,
|
||||
content: `export const y: string = 20;`
|
||||
};
|
||||
const file2FileInfo: BuilderState.FileInfo = {
|
||||
version: Harness.mockHash(fileModified.content),
|
||||
signature: Harness.mockHash("export declare const y: string;\n")
|
||||
};
|
||||
const file2ReuasableError: ProgramBuildInfoDiagnostic = [
|
||||
file2.path, [
|
||||
{
|
||||
file: file2.path,
|
||||
start: 13,
|
||||
length: 1,
|
||||
code: Diagnostics.Type_0_is_not_assignable_to_type_1.code,
|
||||
category: Diagnostics.Type_0_is_not_assignable_to_type_1.category,
|
||||
messageText: "Type '20' is not assignable to type 'string'."
|
||||
}
|
||||
] as ReusableDiagnostic[]
|
||||
];
|
||||
const file2Errors = [
|
||||
"file2.ts(1,14): error TS2322: Type '20' is not assignable to type 'string'.\n"
|
||||
];
|
||||
const modifiedFile1Content = file1.content.replace("x = 10", "z = 10");
|
||||
verifyIncrementalWatchEmit({
|
||||
files: [libFile, file1, fileModified, config],
|
||||
expectedInitialEmit: [
|
||||
file1Js,
|
||||
file2Js,
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(file1.content),
|
||||
[file2.path]: file2FileInfo
|
||||
},
|
||||
options: { incremental: true, module: ModuleKind.AMD, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [
|
||||
libFile.path,
|
||||
file1.path,
|
||||
file2ReuasableError
|
||||
]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedInitialErrors: file2Errors,
|
||||
modifyFs: host => host.writeFile(file1.path, modifiedFile1Content),
|
||||
expectedIncrementalEmit: [
|
||||
{ path: file1Js.path, content: file1Js.content.replace("x = 10", "z = 10") },
|
||||
{
|
||||
path: `${project}/tsconfig.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
program: {
|
||||
fileInfos: {
|
||||
[libFile.path]: libFileInfo,
|
||||
[file1.path]: getFileInfo(modifiedFile1Content),
|
||||
[file2.path]: file2FileInfo
|
||||
},
|
||||
options: { incremental: true, module: ModuleKind.AMD, configFilePath: configFile.path },
|
||||
referencedMap: {},
|
||||
exportedModulesMap: {},
|
||||
semanticDiagnosticsPerFile: [
|
||||
libFile.path,
|
||||
file2ReuasableError,
|
||||
file1.path
|
||||
]
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedIncrementalErrors: file2Errors,
|
||||
});
|
||||
});
|
||||
|
||||
it("with --out", () => {
|
||||
const config: File = {
|
||||
path: configFile.path,
|
||||
content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } })
|
||||
};
|
||||
const outFile: File = {
|
||||
path: `${project}/out.js`,
|
||||
content: `${getEmitContent("file1", "x", "10")}${getEmitContent("file2", "y", "20")}`
|
||||
};
|
||||
function getEmitContent(file: string, varName: string, value: string) {
|
||||
return `define("${file}", ["require", "exports"], function (require, exports) {
|
||||
"use strict";
|
||||
exports.__esModule = true;
|
||||
exports.${varName} = ${value};
|
||||
});
|
||||
`;
|
||||
}
|
||||
verifyIncrementalWatchEmit({
|
||||
files: [libFile, file1, file2, config],
|
||||
expectedInitialEmit: [
|
||||
outFile,
|
||||
{
|
||||
path: `${project}/out.tsbuildinfo`,
|
||||
content: getBuildInfoText({
|
||||
bundle: {
|
||||
commonSourceDirectory: `${project}/`,
|
||||
sourceFiles: [file1.path, file2.path],
|
||||
js: {
|
||||
sections: [
|
||||
{ pos: 0, end: outFile.content.length, kind: BundleFileSectionKind.Text }
|
||||
]
|
||||
},
|
||||
},
|
||||
version
|
||||
})
|
||||
}
|
||||
],
|
||||
expectedInitialErrors: emptyArray
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -147,6 +147,9 @@ namespace ts {
|
||||
reportWatchModeWithoutSysSupport();
|
||||
createWatchOfConfigFile(configParseResult, commandLineOptions);
|
||||
}
|
||||
else if (isIncrementalCompilation(configParseResult.options)) {
|
||||
performIncrementalCompilation(configParseResult);
|
||||
}
|
||||
else {
|
||||
performCompilation(configParseResult.fileNames, configParseResult.projectReferences, configParseResult.options, getConfigFileParsingDiagnostics(configParseResult));
|
||||
}
|
||||
@@ -254,6 +257,44 @@ namespace ts {
|
||||
return sys.exit(exitStatus);
|
||||
}
|
||||
|
||||
function performIncrementalCompilation(config: ParsedCommandLine) {
|
||||
const { options, fileNames, projectReferences } = config;
|
||||
const host = createCompilerHost(options);
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
|
||||
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
enableStatistics(options);
|
||||
const oldProgram = readBuilderProgram(options, path => host.readFile(path));
|
||||
const configFileParsingDiagnostics = getConfigFileParsingDiagnostics(config);
|
||||
const programOptions: CreateProgramOptions = {
|
||||
rootNames: fileNames,
|
||||
options,
|
||||
projectReferences,
|
||||
host,
|
||||
configFileParsingDiagnostics: getConfigFileParsingDiagnostics(config),
|
||||
};
|
||||
const program = createProgram(programOptions);
|
||||
const builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(
|
||||
program,
|
||||
{
|
||||
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
|
||||
createHash: maybeBind(sys, sys.createHash),
|
||||
writeFile: (path, data, writeByteOrderMark) => sys.writeFile(path, data, writeByteOrderMark)
|
||||
},
|
||||
oldProgram,
|
||||
configFileParsingDiagnostics
|
||||
);
|
||||
|
||||
const exitStatus = emitFilesAndReportErrors(
|
||||
builderProgram,
|
||||
reportDiagnostic,
|
||||
s => sys.write(s + sys.newLine),
|
||||
createReportErrorSummary(options)
|
||||
);
|
||||
reportStatistics(program);
|
||||
return sys.exit(exitStatus);
|
||||
}
|
||||
|
||||
function updateCreateProgram<T extends BuilderProgram>(host: { createProgram: CreateProgram<T>; }) {
|
||||
const compileUsingBuilder = host.createProgram;
|
||||
host.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
|
||||
|
||||
Reference in New Issue
Block a user