diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index ff21d76ad8e76..b7f01b838fdbf 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -230,7 +230,7 @@ namespace ts.BuilderState { // Create the reference map, and set the file infos for (const sourceFile of newProgram.getSourceFiles()) { - const version = sourceFile.version; + const version = Debug.assertDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); const oldInfo = useOldState ? oldState!.fileInfos.get(sourceFile.path) : undefined; if (referencedMap) { const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 65e8fc81d9f00..b188e708a623f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -74,12 +74,7 @@ namespace ts { // TODO(shkamat): update this after reworking ts build API export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { const existingDirectories = createMap(); - function getCanonicalFileName(fileName: string): string { - // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. - // otherwise use toLowerCase as a canonical form. - return system.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); - } - + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { let text: string | undefined; try { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 8a94393e2cebd..9b08c54222f63 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -275,7 +275,7 @@ namespace ts { } } - export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost, host: ProgramHost) { + export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost, host: { createHash?(data: string): string; }) { const originalGetSourceFile = compilerHost.getSourceFile; const computeHash = host.createHash || generateDjb2Hash; compilerHost.getSourceFile = (...args) => { @@ -383,6 +383,56 @@ namespace ts { if (!buildInfo.program) return undefined; return createBuildProgramUsingProgramBuildInfo(buildInfo.program); } + + export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost { + const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system); + host.createHash = maybeBind(system, system.createHash); + setGetSourceFileAsHashVersioned(host, system); + changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName)); + return host; + } + + interface IncrementalProgramOptions { + rootNames: ReadonlyArray; + options: CompilerOptions; + configFileParsingDiagnostics?: ReadonlyArray; + projectReferences?: ReadonlyArray; + host?: CompilerHost; + createProgram?: CreateProgram; + } + function createIncrementalProgram({ + rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram + }: IncrementalProgramOptions): T { + host = host || createIncrementalCompilerHost(options); + createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram; + const oldProgram = readBuilderProgram(options, path => host!.readFile(path)) as any as T; + return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); + } + + export interface IncrementalCompilationOptions { + rootNames: ReadonlyArray; + options: CompilerOptions; + configFileParsingDiagnostics?: ReadonlyArray; + projectReferences?: ReadonlyArray; + host?: CompilerHost; + reportDiagnostic?: DiagnosticReporter; + reportErrorSummary?: ReportEmitErrorSummary; + afterProgramEmitAndDiagnostics?(program: EmitAndSemanticDiagnosticsBuilderProgram): void; + system?: System; + } + export function performIncrementalCompilation(input: IncrementalCompilationOptions) { + const system = input.system || sys; + const host = input.host || (input.host = createIncrementalCompilerHost(input.options, system)); + const builderProgram = createIncrementalProgram(input); + const exitStatus = emitFilesAndReportErrors( + builderProgram, + input.reportDiagnostic || createDiagnosticReporter(system), + s => host.trace && host.trace(s), + input.reportErrorSummary || input.options.pretty ? errorCount => system.write(getErrorSummaryText(errorCount, system.newLine)) : undefined + ); + if (input.afterProgramEmitAndDiagnostics) input.afterProgramEmitAndDiagnostics(builderProgram); + return exitStatus; + } } namespace ts { diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index 98a2d4ffa4b0c..f8647393cb118 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -57,20 +57,23 @@ namespace ts.tscWatch { function checkOutputErrors( host: WatchedSystem, logsBeforeWatchDiagnostic: string[] | undefined, - preErrorsWatchDiagnostic: Diagnostic, + preErrorsWatchDiagnostic: Diagnostic | undefined, logsBeforeErrors: string[] | undefined, errors: ReadonlyArray | ReadonlyArray, disableConsoleClears?: boolean | undefined, - ...postErrorsWatchDiagnostics: Diagnostic[] + ...postErrorsWatchDiagnostics: Diagnostic[] | string[] ) { let screenClears = 0; const outputs = host.getOutput(); - const expectedOutputCount = 1 + errors.length + postErrorsWatchDiagnostics.length + - (logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + (logsBeforeErrors ? logsBeforeErrors.length : 0); + const expectedOutputCount = (preErrorsWatchDiagnostic ? 1 : 0) + + errors.length + + postErrorsWatchDiagnostics.length + + (logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + + (logsBeforeErrors ? logsBeforeErrors.length : 0); assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs)); let index = 0; forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log)); - assertWatchDiagnostic(preErrorsWatchDiagnostic); + if (preErrorsWatchDiagnostic) assertWatchDiagnostic(preErrorsWatchDiagnostic); forEach(logsBeforeErrors, log => assertLog("logBeforeError", log)); // Verify errors forEach(errors, assertDiagnostic); @@ -98,13 +101,18 @@ namespace ts.tscWatch { index++; } - function assertWatchDiagnostic(diagnostic: Diagnostic) { - const expected = getWatchDiagnosticWithoutDate(diagnostic); - if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) { - assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`); - screenClears++; + function assertWatchDiagnostic(diagnostic: Diagnostic | string) { + if (isString(diagnostic)) { + assert.equal(outputs[index], diagnostic, getOutputAtFailedMessage("Diagnostic", diagnostic)); + } + else { + const expected = getWatchDiagnosticWithoutDate(diagnostic); + if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) { + assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`); + screenClears++; + } + assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected)); } - assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected)); index++; } @@ -159,6 +167,18 @@ namespace ts.tscWatch { assert.equal(host.exitCode, expectedExitCode); } + export function checkNormalBuildErrors(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, reportErrorSummary?: boolean) { + checkOutputErrors( + host, + /*logsBeforeWatchDiagnostic*/ undefined, + /*preErrorsWatchDiagnostic*/ undefined, + /*logsBeforeErrors*/ undefined, + errors, + /*disableConsoleClears*/ undefined, + ...(reportErrorSummary ? [getErrorSummaryText(errors.length, host.newLine)] : emptyArray) + ); + } + function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain { return !!(message as DiagnosticMessageChain).messageText; } diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index f21f994914ccd..35ec5bff55552 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -15,9 +15,51 @@ namespace ts.tscWatch { expectedIncrementalEmit?: ReadonlyArray; expectedIncrementalErrors?: ReadonlyArray; } - function verifyIncrementalWatchEmit({ - files, expectedInitialEmit, expectedInitialErrors, modifyFs, expectedIncrementalEmit, expectedIncrementalErrors - }: VerifyIncrementalWatchEmitInput) { + function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) { + it("with tsc --w", () => { + verifyIncrementalWatchEmitWorker({ + input, + emitAndReportErrors: createWatchOfConfigFile, + verifyErrors: checkOutputErrorsInitial + }); + }); + it("with tsc", () => { + verifyIncrementalWatchEmitWorker({ + input, + emitAndReportErrors: incrementalBuild, + verifyErrors: checkNormalBuildErrors + }); + }); + } + + function incrementalBuild(configFile: string, host: WatchedSystem) { + const reportDiagnostic = createDiagnosticReporter(host); + const config = parseConfigFileWithSystem(configFile, {}, host, reportDiagnostic); + if (config) { + performIncrementalCompilation({ + rootNames: config.fileNames, + options: config.options, + projectReferences: config.projectReferences, + configFileParsingDiagnostics: getConfigFileParsingDiagnostics(config), + reportDiagnostic, + system: host + }); + } + return { close: noop }; + } + + interface VerifyIncrementalWatchEmitWorkerInput { + input: VerifyIncrementalWatchEmitInput; + emitAndReportErrors: (configFile: string, host: WatchedSystem) => { close(): void; }; + verifyErrors: (host: WatchedSystem, errors: ReadonlyArray) => void; + } + function verifyIncrementalWatchEmitWorker({ + input: { + files, expectedInitialEmit, expectedInitialErrors, modifyFs, expectedIncrementalEmit, expectedIncrementalErrors + }, + emitAndReportErrors, + verifyErrors + }: VerifyIncrementalWatchEmitWorkerInput) { const host = createWatchedSystem(files, { currentDirectory: project }); const originalWriteFile = host.writeFile; const writtenFiles = createMap(); @@ -26,19 +68,41 @@ namespace ts.tscWatch { writtenFiles.set(path, content); originalWriteFile.call(host, path, content); }; - verifyWatch(host, writtenFiles, expectedInitialEmit, expectedInitialErrors); + verifyBuild({ + host, + writtenFiles, + emitAndReportErrors, + verifyErrors, + expectedEmit: expectedInitialEmit, + expectedErrors: expectedInitialErrors + }); if (modifyFs) { modifyFs(host); - verifyWatch(host, writtenFiles, Debug.assertDefined(expectedIncrementalEmit), Debug.assertDefined(expectedIncrementalErrors)); + verifyBuild({ + host, + writtenFiles, + emitAndReportErrors, + verifyErrors, + expectedEmit: Debug.assertDefined(expectedIncrementalEmit), + expectedErrors: Debug.assertDefined(expectedIncrementalErrors) + }); } } - function verifyWatch(host: WatchedSystem, writtenFiles: Map, expectedEmit: ReadonlyArray, expectedErrors: ReadonlyArray) { + interface VerifyBuildWorker { + host: WatchedSystem; + writtenFiles: Map; + emitAndReportErrors: VerifyIncrementalWatchEmitWorkerInput["emitAndReportErrors"]; + verifyErrors: VerifyIncrementalWatchEmitWorkerInput["verifyErrors"]; + expectedEmit: ReadonlyArray; + expectedErrors: ReadonlyArray; + } + function verifyBuild({ host, writtenFiles, emitAndReportErrors, verifyErrors, expectedEmit, expectedErrors }: VerifyBuildWorker) { writtenFiles.clear(); - const watch = createWatchOfConfigFile("tsconfig.json", host); + const result = emitAndReportErrors("tsconfig.json", host); checkFileEmit(writtenFiles, expectedEmit); - checkOutputErrorsInitial(host, expectedErrors); - watch.close(); + verifyErrors(host, expectedErrors); + result.close(); } function checkFileEmit(actual: Map, expected: ReadonlyArray) { @@ -73,7 +137,7 @@ namespace ts.tscWatch { content: "var y = 20;\n" }; - it("own file emit without errors", () => { + describe("own file emit without errors", () => { const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10"); verifyIncrementalWatchEmit({ files: [libFile, file1, file2, configFile], @@ -125,7 +189,7 @@ namespace ts.tscWatch { }); }); - it("own file emit with errors", () => { + describe("own file emit with errors", () => { const fileModified: File = { path: file2.path, content: `const y: string = 20;` @@ -208,7 +272,7 @@ namespace ts.tscWatch { }); }); - it("with --out", () => { + describe("with --out", () => { const config: File = { path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } }) @@ -279,7 +343,7 @@ namespace ts.tscWatch { content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } }) }; - it("own file emit without errors", () => { + describe("own file emit without errors", () => { const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10"); verifyIncrementalWatchEmit({ files: [libFile, file1, file2, config], @@ -330,7 +394,7 @@ namespace ts.tscWatch { }); }); - it("own file emit with errors", () => { + describe("own file emit with errors", () => { const fileModified: File = { path: file2.path, content: `export const y: string = 20;` @@ -412,7 +476,7 @@ namespace ts.tscWatch { }); }); - it("with --out", () => { + describe("with --out", () => { const config: File = { path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } }) diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index 499848fdf923a..ccc04d38088d5 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -260,40 +260,16 @@ namespace ts { 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 = { + return sys.exit(ts.performIncrementalCompilation({ 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, + projectReferences, reportDiagnostic, - s => sys.write(s + sys.newLine), - createReportErrorSummary(options) - ); - reportStatistics(program); - return sys.exit(exitStatus); + reportErrorSummary: createReportErrorSummary(options), + afterProgramEmitAndDiagnostics: builderProgram => reportStatistics(builderProgram.getProgram()) + })); } function updateCreateProgram(host: { createProgram: CreateProgram; }) {