mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 20:25:48 -06:00
When emitting all files, emit the changed file first (#18930)
* When emitting all files, emit the changed file first * Export interface
This commit is contained in:
parent
25c3b99f29
commit
efa274f722
@ -129,6 +129,7 @@ var harnessSources = harnessCoreSources.concat([
|
||||
"textStorage.ts",
|
||||
"moduleResolution.ts",
|
||||
"tsconfigParsing.ts",
|
||||
"builder.ts",
|
||||
"commandLineParsing.ts",
|
||||
"configurationExtension.ts",
|
||||
"convertCompilerOptionsFromJson.ts",
|
||||
|
||||
@ -93,12 +93,14 @@ namespace ts {
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export function createBuilder(
|
||||
getCanonicalFileName: (fileName: string) => string,
|
||||
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed,
|
||||
computeHash: (data: string) => string,
|
||||
shouldEmitFile: (sourceFile: SourceFile) => boolean
|
||||
): Builder {
|
||||
export interface BuilderOptions {
|
||||
getCanonicalFileName: (fileName: string) => string;
|
||||
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed;
|
||||
computeHash: (data: string) => string;
|
||||
shouldEmitFile: (sourceFile: SourceFile) => boolean;
|
||||
}
|
||||
|
||||
export function createBuilder(options: BuilderOptions): Builder {
|
||||
let isModuleEmit: boolean | undefined;
|
||||
const fileInfos = createMap<FileInfo>();
|
||||
const semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
|
||||
@ -181,7 +183,7 @@ namespace ts {
|
||||
ensureProgramGraph(program);
|
||||
|
||||
const sourceFile = program.getSourceFile(path);
|
||||
const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
|
||||
const singleFileResult = sourceFile && options.shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
|
||||
const info = fileInfos.get(path);
|
||||
if (!info || !updateShapeSignature(program, sourceFile, info)) {
|
||||
return singleFileResult;
|
||||
@ -197,7 +199,7 @@ namespace ts {
|
||||
return { outputFiles: [], emitSkipped: true };
|
||||
}
|
||||
|
||||
return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
|
||||
return options.getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
|
||||
}
|
||||
|
||||
function enumerateChangedFilesSet(
|
||||
@ -220,21 +222,21 @@ namespace ts {
|
||||
onChangedFile: (fileName: string, path: Path) => void,
|
||||
onEmitOutput: (emitOutput: EmitOutputDetailed, sourceFile: SourceFile) => void
|
||||
) {
|
||||
const seenFiles = createMap<SourceFile>();
|
||||
const seenFiles = createMap<true>();
|
||||
enumerateChangedFilesSet(program, onChangedFile, (fileName, sourceFile) => {
|
||||
if (!seenFiles.has(fileName)) {
|
||||
seenFiles.set(fileName, sourceFile);
|
||||
seenFiles.set(fileName, true);
|
||||
if (sourceFile) {
|
||||
// Any affected file shouldnt have the cached diagnostics
|
||||
semanticDiagnosticsPerFile.delete(sourceFile.path);
|
||||
|
||||
const emitOutput = getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed;
|
||||
const emitOutput = options.getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed;
|
||||
onEmitOutput(emitOutput, sourceFile);
|
||||
|
||||
// mark all the emitted source files as seen
|
||||
if (emitOutput.emittedSourceFiles) {
|
||||
for (const file of emitOutput.emittedSourceFiles) {
|
||||
seenFiles.set(file.fileName, file);
|
||||
seenFiles.set(file.fileName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,13 +311,13 @@ namespace ts {
|
||||
const prevSignature = info.signature;
|
||||
let latestSignature: string;
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
latestSignature = computeHash(sourceFile.text);
|
||||
latestSignature = options.computeHash(sourceFile.text);
|
||||
info.signature = latestSignature;
|
||||
}
|
||||
else {
|
||||
const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
|
||||
const emitOutput = options.getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
|
||||
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
|
||||
latestSignature = computeHash(emitOutput.outputFiles[0].text);
|
||||
latestSignature = options.computeHash(emitOutput.outputFiles[0].text);
|
||||
info.signature = latestSignature;
|
||||
}
|
||||
else {
|
||||
@ -352,7 +354,7 @@ namespace ts {
|
||||
// Handle triple slash references
|
||||
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
|
||||
for (const referencedFile of sourceFile.referencedFiles) {
|
||||
const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
|
||||
const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, options.getCanonicalFileName);
|
||||
addReferencedFile(referencedPath);
|
||||
}
|
||||
}
|
||||
@ -365,7 +367,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
|
||||
const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
|
||||
const typeFilePath = toPath(fileName, sourceFileDirectory, options.getCanonicalFileName);
|
||||
addReferencedFile(typeFilePath);
|
||||
});
|
||||
}
|
||||
@ -381,18 +383,26 @@ namespace ts {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the emittable files from the program
|
||||
* Gets all the emittable files from the program.
|
||||
* @param firstSourceFile This one will be emitted first. See https://github.com/Microsoft/TypeScript/issues/16888
|
||||
*/
|
||||
function getAllEmittableFiles(program: Program) {
|
||||
function getAllEmittableFiles(program: Program, firstSourceFile: SourceFile): string[] {
|
||||
const defaultLibraryFileName = getDefaultLibFileName(program.getCompilerOptions());
|
||||
const sourceFiles = program.getSourceFiles();
|
||||
const result: string[] = [];
|
||||
add(firstSourceFile);
|
||||
for (const sourceFile of sourceFiles) {
|
||||
if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && shouldEmitFile(sourceFile)) {
|
||||
result.push(sourceFile.fileName);
|
||||
if (sourceFile !== firstSourceFile) {
|
||||
add(sourceFile);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
function add(sourceFile: SourceFile): void {
|
||||
if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && options.shouldEmitFile(sourceFile)) {
|
||||
result.push(sourceFile.fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getNonModuleEmitHandler(): EmitHandler {
|
||||
@ -404,14 +414,14 @@ namespace ts {
|
||||
getFilesAffectedByUpdatedShape
|
||||
};
|
||||
|
||||
function getFilesAffectedByUpdatedShape(program: Program, _sourceFile: SourceFile, singleFileResult: string[]): string[] {
|
||||
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
|
||||
const options = program.getCompilerOptions();
|
||||
// If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
|
||||
// so returning the file itself is good enough.
|
||||
if (options && (options.out || options.outFile)) {
|
||||
return singleFileResult;
|
||||
}
|
||||
return getAllEmittableFiles(program);
|
||||
return getAllEmittableFiles(program, sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,11 +494,11 @@ namespace ts {
|
||||
|
||||
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
|
||||
if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) {
|
||||
return getAllEmittableFiles(program);
|
||||
return getAllEmittableFiles(program, sourceFile);
|
||||
}
|
||||
|
||||
const options = program.getCompilerOptions();
|
||||
if (options && (options.isolatedModules || options.out || options.outFile)) {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) {
|
||||
return singleFileResult;
|
||||
}
|
||||
|
||||
@ -498,7 +508,7 @@ namespace ts {
|
||||
|
||||
const seenFileNamesMap = createMap<string>();
|
||||
const setSeenFileName = (path: Path, sourceFile: SourceFile) => {
|
||||
seenFileNamesMap.set(path, sourceFile && shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
|
||||
seenFileNamesMap.set(path, sourceFile && options.shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
|
||||
};
|
||||
|
||||
// Start with the paths this file was referenced by
|
||||
|
||||
@ -308,7 +308,7 @@ namespace ts {
|
||||
getCurrentDirectory()
|
||||
);
|
||||
// There is no extra check needed since we can just rely on the program to decide emit
|
||||
const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true);
|
||||
const builder = createBuilder({ getCanonicalFileName, getEmitOutput: getFileEmitOutput, computeHash, shouldEmitFile: () => true });
|
||||
|
||||
synchronizeProgram();
|
||||
|
||||
|
||||
@ -116,6 +116,7 @@
|
||||
"./unittests/reuseProgramStructure.ts",
|
||||
"./unittests/moduleResolution.ts",
|
||||
"./unittests/tsconfigParsing.ts",
|
||||
"./unittests/builder.ts",
|
||||
"./unittests/commandLineParsing.ts",
|
||||
"./unittests/configurationExtension.ts",
|
||||
"./unittests/convertCompilerOptionsFromJson.ts",
|
||||
|
||||
73
src/harness/unittests/builder.ts
Normal file
73
src/harness/unittests/builder.ts
Normal file
@ -0,0 +1,73 @@
|
||||
/// <reference path="reuseProgramStructure.ts" />
|
||||
|
||||
namespace ts {
|
||||
describe("builder", () => {
|
||||
it("emits dependent files", () => {
|
||||
const files: NamedSourceText[] = [
|
||||
{ name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") },
|
||||
{ name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") },
|
||||
{ name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") },
|
||||
];
|
||||
|
||||
let program = newProgram(files, ["/a.ts"], {});
|
||||
const assertChanges = makeAssertChanges(() => program);
|
||||
|
||||
assertChanges(["/c.js", "/b.js", "/a.js"]);
|
||||
|
||||
program = updateProgramFile(program, "/a.ts", "//comment");
|
||||
assertChanges(["/a.js"]);
|
||||
|
||||
program = updateProgramFile(program, "/b.ts", "export const b = c + 1;");
|
||||
assertChanges(["/b.js", "/a.js"]);
|
||||
|
||||
program = updateProgramFile(program, "/c.ts", "export const c = 1;");
|
||||
assertChanges(["/c.js", "/b.js"]);
|
||||
});
|
||||
|
||||
it("if emitting all files, emits the changed file first", () => {
|
||||
const files: NamedSourceText[] = [
|
||||
{ name: "/a.ts", text: SourceText.New("", "", "namespace A { export const x = 0; }") },
|
||||
{ name: "/b.ts", text: SourceText.New("", "", "namespace B { export const x = 0; }") },
|
||||
];
|
||||
|
||||
let program = newProgram(files, ["/a.ts", "/b.ts"], {});
|
||||
const assertChanges = makeAssertChanges(() => program);
|
||||
|
||||
assertChanges(["/a.js", "/b.js"]);
|
||||
|
||||
program = updateProgramFile(program, "/a.ts", "namespace A { export const x = 1; }");
|
||||
assertChanges(["/a.js", "/b.js"]);
|
||||
|
||||
program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }");
|
||||
assertChanges(["/b.js", "/a.js"]);
|
||||
});
|
||||
});
|
||||
|
||||
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray<string>) => void {
|
||||
const builder = createBuilder({
|
||||
getCanonicalFileName: identity,
|
||||
getEmitOutput: getFileEmitOutput,
|
||||
computeHash: identity,
|
||||
shouldEmitFile: returnTrue,
|
||||
});
|
||||
return fileNames => {
|
||||
const program = getProgram();
|
||||
builder.updateProgram(program);
|
||||
const changedFiles = builder.emitChangedFiles(program);
|
||||
assert.deepEqual(changedFileNames(changedFiles), fileNames);
|
||||
};
|
||||
}
|
||||
|
||||
function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts {
|
||||
return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => {
|
||||
updateProgramText(files, fileName, fileContent);
|
||||
});
|
||||
}
|
||||
|
||||
function changedFileNames(changedFiles: ReadonlyArray<EmitOutputDetailed>): string[] {
|
||||
return changedFiles.map(f => {
|
||||
assert.lengthOf(f.outputFiles, 1);
|
||||
return f.outputFiles[0].name;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -15,12 +15,12 @@ namespace ts {
|
||||
sourceText?: SourceText;
|
||||
}
|
||||
|
||||
interface NamedSourceText {
|
||||
export interface NamedSourceText {
|
||||
name: string;
|
||||
text: SourceText;
|
||||
}
|
||||
|
||||
interface ProgramWithSourceTexts extends Program {
|
||||
export interface ProgramWithSourceTexts extends Program {
|
||||
sourceTexts?: ReadonlyArray<NamedSourceText>;
|
||||
host: TestCompilerHost;
|
||||
}
|
||||
@ -29,7 +29,7 @@ namespace ts {
|
||||
getTrace(): string[];
|
||||
}
|
||||
|
||||
class SourceText implements IScriptSnapshot {
|
||||
export class SourceText implements IScriptSnapshot {
|
||||
private fullText: string;
|
||||
|
||||
constructor(private references: string,
|
||||
@ -103,10 +103,11 @@ namespace ts {
|
||||
function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) {
|
||||
const file = <SourceFileWithText>createSourceFile(fileName, sourceText.getFullText(), target);
|
||||
file.sourceText = sourceText;
|
||||
file.version = "" + sourceText.getVersion();
|
||||
return file;
|
||||
}
|
||||
|
||||
function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost {
|
||||
export function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost {
|
||||
const files = arrayToMap(texts, t => t.name, t => {
|
||||
if (oldProgram) {
|
||||
let oldFile = <SourceFileWithText>oldProgram.getSourceFile(t.name);
|
||||
@ -154,7 +155,7 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts {
|
||||
export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts {
|
||||
const host = createTestCompilerHost(texts, options.target);
|
||||
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host);
|
||||
program.sourceTexts = texts;
|
||||
@ -162,7 +163,7 @@ namespace ts {
|
||||
return program;
|
||||
}
|
||||
|
||||
function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) {
|
||||
export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) {
|
||||
if (!newTexts) {
|
||||
newTexts = (<ProgramWithSourceTexts>oldProgram).sourceTexts.slice(0);
|
||||
}
|
||||
@ -174,7 +175,7 @@ namespace ts {
|
||||
return program;
|
||||
}
|
||||
|
||||
function updateProgramText(files: ReadonlyArray<NamedSourceText>, fileName: string, newProgramText: string) {
|
||||
export function updateProgramText(files: ReadonlyArray<NamedSourceText>, fileName: string, newProgramText: string) {
|
||||
const file = find(files, f => f.name === fileName)!;
|
||||
file.text = file.text.updateProgram(newProgramText);
|
||||
}
|
||||
|
||||
@ -420,12 +420,15 @@ namespace ts.server {
|
||||
|
||||
private ensureBuilder() {
|
||||
if (!this.builder) {
|
||||
this.builder = createBuilder(
|
||||
this.projectService.toCanonicalFileName,
|
||||
(_program, sourceFile, emitOnlyDts, isDetailed) => this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
|
||||
data => this.projectService.host.createHash(data),
|
||||
sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent()
|
||||
);
|
||||
this.builder = createBuilder({
|
||||
getCanonicalFileName: this.projectService.toCanonicalFileName,
|
||||
getEmitOutput: (_program, sourceFile, emitOnlyDts, isDetailed) =>
|
||||
this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
|
||||
computeHash: data =>
|
||||
this.projectService.host.createHash(data),
|
||||
shouldEmitFile: sourceFile =>
|
||||
!this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user