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:
Sheetal Nandi
2019-03-08 17:18:19 -08:00
committed by GitHub
13 changed files with 5184 additions and 4773 deletions
+631 -541
View File
File diff suppressed because it is too large Load Diff
+793 -832
View File
File diff suppressed because it is too large Load Diff
+793 -832
View File
File diff suppressed because it is too large Load Diff
+793 -832
View File
File diff suppressed because it is too large Load Diff
+793 -832
View File
File diff suppressed because it is too large Load Diff
+790 -829
View File
File diff suppressed because it is too large Load Diff
+2 -16
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -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",
+2 -14
View File
@@ -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
});
});
});
});
}
+41
View File
@@ -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) => {