diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts
index efa0f0a0372..8703d83aa1d 100644
--- a/src/compiler/builder.ts
+++ b/src/compiler/builder.ts
@@ -1,29 +1,56 @@
///
+/*@internal*/
namespace ts {
- export interface EmitOutput {
- outputFiles: OutputFile[];
- emitSkipped: boolean;
- }
-
- export interface OutputFile {
- name: string;
- writeByteOrderMark: boolean;
- text: string;
- }
-
- /* @internal */
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
const outputFiles: OutputFile[] = [];
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
- return { outputFiles, emitSkipped: emitResult.emitSkipped };
+ return { outputFiles, emitSkipped: emitResult.emitSkipped };
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
outputFiles.push({ name: fileName, writeByteOrderMark, text });
}
}
+ /**
+ * Internal Builder to get files affected by another file
+ */
+ export interface InternalBuilder extends BaseBuilder {
+ /**
+ * Gets the files affected by the file path
+ * This api is only for internal use
+ */
+ /*@internal*/
+ getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray;
+ }
+
+ /**
+ * Create the internal builder to get files affected by sourceFile
+ */
+ export function createInternalBuilder(options: BuilderOptions): InternalBuilder {
+ return createBuilder(options, BuilderType.InternalBuilder);
+ }
+
+ export enum BuilderType {
+ InternalBuilder,
+ SemanticDiagnosticsBuilder,
+ EmitAndSemanticDiagnosticsBuilder
+ }
+
+ /**
+ * Information about the source file: Its version and optional signature from last emit
+ */
+ interface FileInfo {
+ version: string;
+ signature?: string;
+ }
+
+ /**
+ * Referenced files with values for the keys as referenced file's path to be true
+ */
+ type ReferencedSet = ReadonlyMap;
+
function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) {
if (map1 === undefined) {
return map2 === undefined;
@@ -35,191 +62,273 @@ namespace ts {
return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key));
}
- /**
- * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object)
- * Note that it is only safe to pass BuilderState as old state when creating new state, when
- * - If iterator's next method to get next affected file is never called
- * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete
- */
- export interface BuilderState {
+ export function createBuilder(options: BuilderOptions, builderType: BuilderType.InternalBuilder): InternalBuilder;
+ export function createBuilder(options: BuilderOptions, builderType: BuilderType.SemanticDiagnosticsBuilder): SemanticDiagnosticsBuilder;
+ export function createBuilder(options: BuilderOptions, builderType: BuilderType.EmitAndSemanticDiagnosticsBuilder): EmitAndSemanticDiagnosticsBuilder;
+ export function createBuilder(options: BuilderOptions, builderType: BuilderType) {
/**
- * The map of file infos, where there is entry for each file in the program
- * The entry is signature of the file (from last emit) or empty string
+ * Information of the file eg. its version, signature etc
*/
- fileInfos: ReadonlyMap>;
-
- /**
- * Returns true if module gerneration is not ModuleKind.None
- */
- isModuleEmit: boolean;
-
- /**
- * Map of file referenced or undefined if it wasnt module emit
- * The entry is present only if file references other files
- * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set)
- */
- referencedMap: ReadonlyMap | undefined;
-
- /**
- * Set of source file's paths that have been changed, either in resolution or versions
- */
- changedFilesSet: ReadonlyMap;
-
- /**
- * Set of cached semantic diagnostics per file
- */
- semanticDiagnosticsPerFile: ReadonlyMap>;
-
- /**
- * Returns true if this state is safe to use as oldState
- */
- canCreateNewStateFrom(): boolean;
-
- /**
- * Gets the files affected by the file path
- * This api is only for internal use
- */
- /* @internal */
- getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray;
-
- /**
- * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
- */
- emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined;
-
- /**
- * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
- * The semantic diagnostics are cached and managed here
- * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files
- */
- getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray;
-
- /**
- * Get all the dependencies of the file
- */
- getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
- }
-
- /**
- * Information about the source file: Its version and optional signature from last emit
- */
- export interface FileInfo {
- version: string;
- signature: string;
- }
-
- export interface AffectedFileEmitResult extends EmitResult {
- affectedFile?: SourceFile;
- }
-
- /**
- * Referenced files with values for the keys as referenced file's path to be true
- */
- export type ReferencedSet = ReadonlyMap;
-
- export interface BuilderOptions {
- getCanonicalFileName: GetCanonicalFileName;
- computeHash: (data: string) => string;
- }
-
- export function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState {
const fileInfos = createMap();
- const isModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None;
- const referencedMap = isModuleEmit ? createMap() : undefined;
+ /**
+ * true if module emit is enabled
+ */
+ let isModuleEmit: boolean;
+
+ /**
+ * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled
+ * Otherwise undefined
+ */
+ let referencedMap: Map | undefined;
+
+ /**
+ * Get the files affected by the source file.
+ * This is dependent on whether its a module emit or not and hence function expression
+ */
+ let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) => ReadonlyArray;
+
+ /**
+ * Cache of semantic diagnostics for files with their Path being the key
+ */
const semanticDiagnosticsPerFile = createMap>();
- /** The map has key by source file's path that has been changed */
+
+ /**
+ * The map has key by source file's path that has been changed
+ */
const changedFilesSet = createMap();
- const hasShapeChanged = createMap();
+
+ /**
+ * Map of files that have already called update signature.
+ * That means hence forth these files are assumed to have
+ * no change in their signature for this version of the program
+ */
+ const hasCalledUpdateShapeSignature = createMap();
+
+ /**
+ * Cache of all files excluding default library file for the current program
+ */
let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined;
- // Iterator datas
+ /**
+ * Set of affected files being iterated
+ */
let affectedFiles: ReadonlyArray | undefined;
+ /**
+ * Current index to retrieve affected file from
+ */
let affectedFilesIndex = 0;
+ /**
+ * Current changed file for iterating over affected files
+ */
+ let currentChangedFilePath: Path | undefined;
+ /**
+ * Map of file signatures, with key being file path, calculated while getting current changed file's affected files
+ * These will be commited whenever the iteration through affected files of current changed file is complete
+ */
+ const currentAffectedFilesSignatures = createMap();
+ /**
+ * Already seen affected files
+ */
const seenAffectedFiles = createMap();
- const getEmitDependentFilesAffectedBy = isModuleEmit ?
- getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit;
- const useOldState = oldState && oldState.isModuleEmit === isModuleEmit;
- if (useOldState) {
- Debug.assert(oldState.canCreateNewStateFrom(), "Cannot use this state as old state");
- Debug.assert(!forEachEntry(oldState.changedFilesSet, (_value, path) => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files");
-
- copyEntries(oldState.changedFilesSet, changedFilesSet);
- copyEntries(oldState.semanticDiagnosticsPerFile, semanticDiagnosticsPerFile);
+ switch (builderType) {
+ case BuilderType.InternalBuilder:
+ return getInternalBuilder();
+ case BuilderType.SemanticDiagnosticsBuilder:
+ return getSemanticDiagnosticsBuilder();
+ case BuilderType.EmitAndSemanticDiagnosticsBuilder:
+ return getEmitAndSemanticDiagnosticsBuilder();
+ default:
+ notImplemented();
}
- for (const sourceFile of newProgram.getSourceFiles()) {
- const version = sourceFile.version;
- let oldInfo: Readonly;
- let oldReferences: ReferencedSet;
- const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile);
-
- // Register changed file
- // if not using old state so every file is changed
- if (!useOldState ||
- // File wasnt present earlier
- !(oldInfo = oldState.fileInfos.get(sourceFile.path)) ||
- // versions dont match
- oldInfo.version !== version ||
- // Referenced files changed
- !hasSameKeys(newReferences, (oldReferences = oldState.referencedMap && oldState.referencedMap.get(sourceFile.path))) ||
- // Referenced file was deleted
- newReferences && forEachEntry(newReferences, (_value, path) => oldState.fileInfos.has(path) && !newProgram.getSourceFileByPath(path as Path))) {
- changedFilesSet.set(sourceFile.path, true);
- // All changed files need to re-evaluate its semantic diagnostics
- semanticDiagnosticsPerFile.delete(sourceFile.path);
- }
-
- if (newReferences) {
- referencedMap.set(sourceFile.path, newReferences);
- }
- fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature });
+ function getInternalBuilder(): InternalBuilder {
+ return {
+ updateProgram,
+ getFilesAffectedBy,
+ getAllDependencies
+ };
}
- // For removed files, remove the semantic diagnostics removed files as changed
- if (useOldState) {
- oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path));
+ function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder {
+ return {
+ updateProgram,
+ getAllDependencies,
+ getSemanticDiagnosticsOfNextAffectedFile,
+ getSemanticDiagnostics
+ };
}
- // Set the old state and program to undefined to ensure we arent keeping them alive hence forward
- oldState = undefined;
- newProgram = undefined;
-
- return {
- fileInfos,
- isModuleEmit,
- referencedMap,
- changedFilesSet,
- semanticDiagnosticsPerFile,
- canCreateNewStateFrom,
- getFilesAffectedBy,
- emitNextAffectedFile,
- getSemanticDiagnostics,
- getAllDependencies
- };
+ function getEmitAndSemanticDiagnosticsBuilder(): EmitAndSemanticDiagnosticsBuilder {
+ return {
+ updateProgram,
+ getAllDependencies,
+ emitNextAffectedFile,
+ getSemanticDiagnostics
+ };
+ }
/**
- * Can use this state as old State if we have iterated through all affected files present
+ * Update current state to reflect new program
+ * Updates changed files, references, file infos etc
*/
- function canCreateNewStateFrom() {
- return !affectedFiles || affectedFiles.length <= affectedFilesIndex;
+ function updateProgram(newProgram: Program) {
+ const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None;
+ const oldReferencedMap = referencedMap;
+ if (isModuleEmit !== newProgramHasModuleEmit) {
+ // Changes in the module emit, clear out everything and initialize as if first time
+
+ // Clear file information and semantic diagnostics
+ fileInfos.clear();
+ semanticDiagnosticsPerFile.clear();
+
+ // Clear changed files and affected files information
+ changedFilesSet.clear();
+ affectedFiles = undefined;
+ currentChangedFilePath = undefined;
+ currentAffectedFilesSignatures.clear();
+
+ // Update the reference map creation
+ referencedMap = newProgramHasModuleEmit ? createMap() : undefined;
+
+ // Update the module emit
+ isModuleEmit = newProgramHasModuleEmit;
+ getEmitDependentFilesAffectedBy = isModuleEmit ?
+ getFilesAffectedByUpdatedShapeWhenModuleEmit :
+ getFilesAffectedByUpdatedShapeWhenNonModuleEmit;
+ }
+ else {
+ if (currentChangedFilePath) {
+ // Remove the diagnostics for all the affected files since we should resume the state such that
+ // the whole iteration on currentChangedFile never happened
+ affectedFiles.map(sourceFile => semanticDiagnosticsPerFile.delete(sourceFile.path));
+ affectedFiles = undefined;
+ currentAffectedFilesSignatures.clear();
+ }
+ else {
+ // Verify the sanity of old state
+ Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated");
+ }
+ Debug.assert(!forEachEntry(changedFilesSet, (_value, path) => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files");
+ }
+
+ // Clear datas that cant be retained beyond previous state
+ seenAffectedFiles.clear();
+ hasCalledUpdateShapeSignature.clear();
+ allFilesExcludingDefaultLibraryFile = undefined;
+
+ // Create the reference map and update changed files
+ for (const sourceFile of newProgram.getSourceFiles()) {
+ const version = sourceFile.version;
+ const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile);
+ const oldInfo = fileInfos.get(sourceFile.path);
+ let oldReferences: ReferencedSet;
+
+ // Register changed file if its new file or we arent reusing old state
+ if (!oldInfo) {
+ // New file: Set the file info
+ fileInfos.set(sourceFile.path, { version });
+ changedFilesSet.set(sourceFile.path, true);
+ }
+ // versions dont match
+ else if (oldInfo.version !== version ||
+ // Referenced files changed
+ !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) ||
+ // Referenced file was deleted in the new program
+ newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) {
+
+ // Changed file: Update the version, set as changed file
+ oldInfo.version = version;
+ changedFilesSet.set(sourceFile.path, true);
+
+ // All changed files need to re-evaluate its semantic diagnostics
+ semanticDiagnosticsPerFile.delete(sourceFile.path);
+ }
+
+ // Set the references
+ if (newReferences) {
+ referencedMap.set(sourceFile.path, newReferences);
+ }
+ else if (referencedMap) {
+ referencedMap.delete(sourceFile.path);
+ }
+ }
+
+ // For removed files, remove the semantic diagnostics and file info
+ if (fileInfos.size > newProgram.getSourceFiles().length) {
+ fileInfos.forEach((_value, path) => {
+ if (!newProgram.getSourceFileByPath(path as Path)) {
+ fileInfos.delete(path);
+ semanticDiagnosticsPerFile.delete(path);
+ if (referencedMap) {
+ referencedMap.delete(path);
+ }
+ }
+ });
+ }
}
/**
* Gets the files affected by the path from the program
*/
- function getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray {
+ function getFilesAffectedBy(programOfThisState: Program, path: Path, cacheToUpdateSignature?: Map): ReadonlyArray {
const sourceFile = programOfThisState.getSourceFileByPath(path);
if (!sourceFile) {
return emptyArray;
}
- if (!updateShapeSignature(programOfThisState, sourceFile)) {
+ if (!updateShapeSignature(programOfThisState, sourceFile, cacheToUpdateSignature)) {
return [sourceFile];
}
- return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile);
+ return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, cacheToUpdateSignature);
+ }
+
+ function getNextAffectedFile(programOfThisState: Program): SourceFile | Program | undefined {
+ while (true) {
+ if (affectedFiles) {
+ while (affectedFilesIndex < affectedFiles.length) {
+ const affectedFile = affectedFiles[affectedFilesIndex];
+ affectedFilesIndex++;
+ if (!seenAffectedFiles.has(affectedFile.path)) {
+ // Set the next affected file as seen and remove the cached semantic diagnostics
+ seenAffectedFiles.set(affectedFile.path, true);
+ semanticDiagnosticsPerFile.delete(affectedFile.path);
+ return affectedFile;
+ }
+ }
+
+ // Remove the changed file from the change set
+ changedFilesSet.delete(currentChangedFilePath);
+ currentChangedFilePath = undefined;
+ // Commit the changes in file signature
+ currentAffectedFilesSignatures.forEach((signature, path) => fileInfos.get(path).signature = signature);
+ currentAffectedFilesSignatures.clear();
+ affectedFiles = undefined;
+ }
+
+ // Get next changed file
+ const nextKey = changedFilesSet.keys().next();
+ if (nextKey.done) {
+ // Done
+ return undefined;
+ }
+
+ const compilerOptions = programOfThisState.getCompilerOptions();
+ // With --out or --outFile all outputs go into single file
+ // so operations are performed directly on program, return program
+ if (compilerOptions.outFile || compilerOptions.out) {
+ Debug.assert(semanticDiagnosticsPerFile.size === 0);
+ changedFilesSet.clear();
+ return programOfThisState;
+ }
+
+ // Get next batch of affected files
+ currentChangedFilePath = nextKey.value as Path;
+ affectedFilesIndex = 0;
+ affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, currentAffectedFilesSignatures);
+ }
}
/**
@@ -227,47 +336,48 @@ namespace ts {
* Returns undefined when iteration is complete
*/
function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined {
- if (affectedFiles) {
- while (affectedFilesIndex < affectedFiles.length) {
- const affectedFile = affectedFiles[affectedFilesIndex];
- affectedFilesIndex++;
- if (!seenAffectedFiles.has(affectedFile.path)) {
- seenAffectedFiles.set(affectedFile.path, true);
-
- // Emit the affected file
- const result = programOfThisState.emit(affectedFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult;
- result.affectedFile = affectedFile;
- return result;
- }
- }
-
- affectedFiles = undefined;
- }
-
- // Get next changed file
- const nextKey = changedFilesSet.keys().next();
- if (nextKey.done) {
+ const affectedFile = getNextAffectedFile(programOfThisState);
+ if (!affectedFile) {
// Done
return undefined;
}
-
- const compilerOptions = programOfThisState.getCompilerOptions();
- // With --out or --outFile all outputs go into single file, do it only once
- if (compilerOptions.outFile || compilerOptions.out) {
- Debug.assert(semanticDiagnosticsPerFile.size === 0);
- changedFilesSet.clear();
+ else if (affectedFile === programOfThisState) {
+ // When whole program is affected, do emit only once (eg when --out or --outFile is specified)
return programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers);
}
- // Get next batch of affected files
- changedFilesSet.delete(nextKey.value);
- affectedFilesIndex = 0;
- affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path);
+ // Emit the affected file
+ const targetSourceFile = affectedFile as SourceFile;
+ const result = programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult;
+ result.affectedFile = targetSourceFile;
+ return result;
+ }
- // Clear the semantic diagnostic of affected files
- affectedFiles.forEach(affectedFile => semanticDiagnosticsPerFile.delete(affectedFile.path));
+ /**
+ * Return the semantic diagnostics for the next affected file or undefined if iteration is complete
+ * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true
+ */
+ function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray {
+ while (true) {
+ const affectedFile = getNextAffectedFile(programOfThisState);
+ if (!affectedFile) {
+ // Done
+ return undefined;
+ }
+ else if (affectedFile === programOfThisState) {
+ // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified)
+ return programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken);
+ }
- return emitNextAffectedFile(programOfThisState, writeFileCallback, cancellationToken, customTransformers);
+ // Get diagnostics for the affected file if its not ignored
+ const targetSourceFile = affectedFile as SourceFile;
+ if (ignoreSourceFile && ignoreSourceFile(targetSourceFile)) {
+ // Get next affected file
+ continue;
+ }
+
+ return getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken);
+ }
}
/**
@@ -276,6 +386,7 @@ namespace ts {
* Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files
*/
function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray {
+ Debug.assert(!affectedFiles || affectedFiles[affectedFilesIndex - 1] !== sourceFile || !semanticDiagnosticsPerFile.has(sourceFile.path));
const compilerOptions = programOfThisState.getCompilerOptions();
if (compilerOptions.outFile || compilerOptions.out) {
Debug.assert(semanticDiagnosticsPerFile.size === 0);
@@ -373,19 +484,20 @@ namespace ts {
return true;
}
- /**
- * Returns if the shape of the signature has changed since last emit
- * Note that it also updates the current signature as the latest signature for the file
- */
- function updateShapeSignature(program: Program, sourceFile: SourceFile) {
+ /**
+ * Returns if the shape of the signature has changed since last emit
+ * Note that it also updates the current signature as the latest signature for the file
+ */
+ function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map | undefined) {
Debug.assert(!!sourceFile);
// If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
- if (hasShapeChanged.has(sourceFile.path)) {
+ if (hasCalledUpdateShapeSignature.has(sourceFile.path)) {
return false;
}
- hasShapeChanged.set(sourceFile.path, true);
+ Debug.assert(!cacheToUpdateSignature || !cacheToUpdateSignature.has(sourceFile.path));
+ hasCalledUpdateShapeSignature.set(sourceFile.path, true);
const info = fileInfos.get(sourceFile.path);
Debug.assert(!!info);
@@ -393,13 +505,13 @@ namespace ts {
let latestSignature: string;
if (sourceFile.isDeclarationFile) {
latestSignature = sourceFile.version;
- info.signature = latestSignature;
+ setLatestSigature();
}
else {
const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true);
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
latestSignature = options.computeHash(emitOutput.outputFiles[0].text);
- info.signature = latestSignature;
+ setLatestSigature();
}
else {
latestSignature = prevSignature;
@@ -407,6 +519,15 @@ namespace ts {
}
return !prevSignature || latestSignature !== prevSignature;
+
+ function setLatestSigature() {
+ if (cacheToUpdateSignature) {
+ cacheToUpdateSignature.set(sourceFile.path, latestSignature);
+ }
+ else {
+ info.signature = latestSignature;
+ }
+ }
}
/**
@@ -514,7 +635,7 @@ namespace ts {
/**
* When program emits modular code, gets the files affected by the sourceFile whose shape has changed
*/
- function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) {
+ function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) {
if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) {
return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape);
}
@@ -537,7 +658,7 @@ namespace ts {
if (!seenFileNamesMap.has(currentPath)) {
const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath);
seenFileNamesMap.set(currentPath, currentSourceFile);
- if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile)) {
+ if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature)) {
queue.push(...getReferencedByPaths(currentPath));
}
}
@@ -548,3 +669,93 @@ namespace ts {
}
}
}
+
+namespace ts {
+ export interface EmitOutput {
+ outputFiles: OutputFile[];
+ emitSkipped: boolean;
+ }
+
+ export interface OutputFile {
+ name: string;
+ writeByteOrderMark: boolean;
+ text: string;
+ }
+
+ export interface AffectedFileEmitResult extends EmitResult {
+ affectedFile?: SourceFile;
+ }
+
+ export interface BuilderOptions {
+ getCanonicalFileName: (fileName: string) => string;
+ computeHash: (data: string) => string;
+ }
+
+ /**
+ * Builder to manage the program state changes
+ */
+ export interface BaseBuilder {
+ /**
+ * Updates the program in the builder to represent new state
+ */
+ updateProgram(newProgram: Program): void;
+
+ /**
+ * Get all the dependencies of the file
+ */
+ getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
+ }
+
+ /**
+ * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files
+ */
+ export interface SemanticDiagnosticsBuilder extends BaseBuilder {
+ /**
+ * Gets the semantic diagnostics from the program for the next affected file and caches it
+ * Returns undefined if the iteration is complete
+ */
+ getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray;
+
+ /**
+ * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
+ * The semantic diagnostics are cached and managed here
+ * Note that it is assumed that the when asked about semantic diagnostics through this API,
+ * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
+ */
+ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray;
+ }
+
+ /**
+ * The builder that can handle the changes in program and iterate through changed file to emit the files
+ * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files
+ */
+ export interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder {
+ /**
+ * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
+ */
+ emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined;
+
+ /**
+ * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
+ * The semantic diagnostics are cached and managed here
+ * Note that it is assumed that the when asked about semantic diagnostics through this API,
+ * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
+ */
+ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray;
+ }
+
+ /**
+ * Create the builder to manage semantic diagnostics and cache them
+ */
+ export function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder {
+ return createBuilder(options, BuilderType.SemanticDiagnosticsBuilder);
+ }
+
+ /**
+ * Create the builder that can handle the changes in program and iterate through changed files
+ * to emit the those files and manage semantic diagnostics cache as well
+ */
+ export function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder {
+ return createBuilder(options, BuilderType.EmitAndSemanticDiagnosticsBuilder);
+ }
+}
diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts
index 07fb9203b6a..fcb4d98ace0 100644
--- a/src/compiler/watch.ts
+++ b/src/compiler/watch.ts
@@ -84,18 +84,17 @@ namespace ts {
}
/**
- * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter
+ * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files
*/
export function createProgramCompilerWithBuilderState(system = sys, reportDiagnostic?: DiagnosticReporter) {
reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system);
- let builderState: Readonly | undefined;
- const options: BuilderOptions = {
+ const builder = createEmitAndSemanticDiagnosticsBuilder({
getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames),
- computeHash: data => system.createHash ? system.createHash(data) : data
- };
+ computeHash: system.createHash ? system.createHash.bind(system) : identity
+ });
return (host: DirectoryStructureHost, program: Program) => {
- builderState = createBuilderState(program, options, builderState);
+ builder.updateProgram(program);
// First get and report any syntactic errors.
const diagnostics = program.getSyntacticDiagnostics().slice();
@@ -118,14 +117,14 @@ namespace ts {
let emitSkipped: boolean;
let affectedEmitResult: AffectedFileEmitResult;
- while (affectedEmitResult = builderState.emitNextAffectedFile(program, writeFile)) {
+ while (affectedEmitResult = builder.emitNextAffectedFile(program, writeFile)) {
emitSkipped = emitSkipped || affectedEmitResult.emitSkipped;
addRange(diagnostics, affectedEmitResult.diagnostics);
sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps);
}
if (reportSemanticDiagnostics) {
- addRange(diagnostics, builderState.getSemanticDiagnostics(program));
+ addRange(diagnostics, builder.getSemanticDiagnostics(program));
}
sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic);
diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts
index 89b0852bd32..a9c16c59fbd 100644
--- a/src/harness/unittests/builder.ts
+++ b/src/harness/unittests/builder.ts
@@ -44,17 +44,16 @@ namespace ts {
});
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void {
- let builderState: BuilderState;
- const builderOptions: BuilderOptions = {
+ const builder = createEmitAndSemanticDiagnosticsBuilder({
getCanonicalFileName: identity,
computeHash: identity
- };
+ });
return fileNames => {
const program = getProgram();
- builderState = createBuilderState(program, builderOptions, builderState);
+ builder.updateProgram(program);
const outputFileNames: string[] = [];
// tslint:disable-next-line no-empty
- while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) {
+ while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) {
}
assert.deepEqual(outputFileNames, fileNames);
};
diff --git a/src/server/project.ts b/src/server/project.ts
index 056fedf19af..3a5785163d2 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -139,7 +139,7 @@ namespace ts.server {
/*@internal*/
resolutionCache: ResolutionCache;
- private builderState: BuilderState;
+ private builder: InternalBuilder | undefined;
/**
* Set of files names that were updated since the last call to getChangesSinceVersion.
*/
@@ -451,11 +451,14 @@ namespace ts.server {
return [];
}
this.updateGraph();
- this.builderState = createBuilderState(this.program, {
- getCanonicalFileName: this.projectService.toCanonicalFileName,
- computeHash: data => this.projectService.host.createHash(data)
- }, this.builderState);
- return mapDefined(this.builderState.getFilesAffectedBy(this.program, scriptInfo.path),
+ if (!this.builder) {
+ this.builder = createInternalBuilder({
+ getCanonicalFileName: this.projectService.toCanonicalFileName,
+ computeHash: data => this.projectService.host.createHash(data)
+ });
+ }
+ this.builder.updateProgram(this.program);
+ return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path),
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
}
@@ -491,7 +494,7 @@ namespace ts.server {
}
this.languageService.cleanupSemanticCache();
this.languageServiceEnabled = false;
- this.builderState = undefined;
+ this.builder = undefined;
this.resolutionCache.closeTypeRootsWatch();
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
}
@@ -532,7 +535,7 @@ namespace ts.server {
this.rootFilesMap = undefined;
this.externalFiles = undefined;
this.program = undefined;
- this.builderState = undefined;
+ this.builder = undefined;
this.resolutionCache.clear();
this.resolutionCache = undefined;
this.cachedUnresolvedImportsPerFile = undefined;
diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts
index 5c306474533..e258d179310 100644
--- a/tests/baselines/reference/api/tsserverlibrary.d.ts
+++ b/tests/baselines/reference/api/tsserverlibrary.d.ts
@@ -3771,40 +3771,48 @@ declare namespace ts {
writeByteOrderMark: boolean;
text: string;
}
+ interface AffectedFileEmitResult extends EmitResult {
+ affectedFile?: SourceFile;
+ }
+ interface BuilderOptions {
+ getCanonicalFileName: (fileName: string) => string;
+ computeHash: (data: string) => string;
+ }
/**
- * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object)
- * Note that it is only safe to pass BuilderState as old state when creating new state, when
- * - If iterator's next method to get next affected file is never called
- * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete
+ * Builder to manage the program state changes
*/
- interface BuilderState {
+ interface BaseBuilder {
/**
- * The map of file infos, where there is entry for each file in the program
- * The entry is signature of the file (from last emit) or empty string
+ * Updates the program in the builder to represent new state
*/
- fileInfos: ReadonlyMap>;
+ updateProgram(newProgram: Program): void;
/**
- * Returns true if module gerneration is not ModuleKind.None
+ * Get all the dependencies of the file
*/
- isModuleEmit: boolean;
+ getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
+ }
+ /**
+ * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files
+ */
+ interface SemanticDiagnosticsBuilder extends BaseBuilder {
/**
- * Map of file referenced or undefined if it wasnt module emit
- * The entry is present only if file references other files
- * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set)
+ * Gets the semantic diagnostics from the program for the next affected file and caches it
+ * Returns undefined if the iteration is complete
*/
- referencedMap: ReadonlyMap | undefined;
+ getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray;
/**
- * Set of source file's paths that have been changed, either in resolution or versions
+ * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
+ * The semantic diagnostics are cached and managed here
+ * Note that it is assumed that the when asked about semantic diagnostics through this API,
+ * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
*/
- changedFilesSet: ReadonlyMap;
- /**
- * Set of cached semantic diagnostics per file
- */
- semanticDiagnosticsPerFile: ReadonlyMap>;
- /**
- * Returns true if this state is safe to use as oldState
- */
- canCreateNewStateFrom(): boolean;
+ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray;
+ }
+ /**
+ * The builder that can handle the changes in program and iterate through changed file to emit the files
+ * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files
+ */
+ interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder {
/**
* Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
*/
@@ -3812,33 +3820,20 @@ declare namespace ts {
/**
* Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
* The semantic diagnostics are cached and managed here
- * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files
+ * Note that it is assumed that the when asked about semantic diagnostics through this API,
+ * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
*/
getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray;
- /**
- * Get all the dependencies of the file
- */
- getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
}
/**
- * Information about the source file: Its version and optional signature from last emit
+ * Create the builder to manage semantic diagnostics and cache them
*/
- interface FileInfo {
- version: string;
- signature: string;
- }
- interface AffectedFileEmitResult extends EmitResult {
- affectedFile?: SourceFile;
- }
+ function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder;
/**
- * Referenced files with values for the keys as referenced file's path to be true
+ * Create the builder that can handle the changes in program and iterate through changed files
+ * to emit the those files and manage semantic diagnostics cache as well
*/
- type ReferencedSet = ReadonlyMap;
- interface BuilderOptions {
- getCanonicalFileName: (fileName: string) => string;
- computeHash: (data: string) => string;
- }
- function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState;
+ function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder;
}
declare namespace ts {
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
@@ -7278,7 +7273,7 @@ declare namespace ts.server {
languageServiceEnabled: boolean;
readonly trace?: (s: string) => void;
readonly realpath?: (path: string) => string;
- private builderState;
+ private builder;
/**
* Set of files names that were updated since the last call to getChangesSinceVersion.
*/
diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts
index 2212a859c0a..bac53072302 100644
--- a/tests/baselines/reference/api/typescript.d.ts
+++ b/tests/baselines/reference/api/typescript.d.ts
@@ -3718,40 +3718,48 @@ declare namespace ts {
writeByteOrderMark: boolean;
text: string;
}
+ interface AffectedFileEmitResult extends EmitResult {
+ affectedFile?: SourceFile;
+ }
+ interface BuilderOptions {
+ getCanonicalFileName: (fileName: string) => string;
+ computeHash: (data: string) => string;
+ }
/**
- * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object)
- * Note that it is only safe to pass BuilderState as old state when creating new state, when
- * - If iterator's next method to get next affected file is never called
- * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete
+ * Builder to manage the program state changes
*/
- interface BuilderState {
+ interface BaseBuilder {
/**
- * The map of file infos, where there is entry for each file in the program
- * The entry is signature of the file (from last emit) or empty string
+ * Updates the program in the builder to represent new state
*/
- fileInfos: ReadonlyMap>;
+ updateProgram(newProgram: Program): void;
/**
- * Returns true if module gerneration is not ModuleKind.None
+ * Get all the dependencies of the file
*/
- isModuleEmit: boolean;
+ getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
+ }
+ /**
+ * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files
+ */
+ interface SemanticDiagnosticsBuilder extends BaseBuilder {
/**
- * Map of file referenced or undefined if it wasnt module emit
- * The entry is present only if file references other files
- * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set)
+ * Gets the semantic diagnostics from the program for the next affected file and caches it
+ * Returns undefined if the iteration is complete
*/
- referencedMap: ReadonlyMap | undefined;
+ getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray;
/**
- * Set of source file's paths that have been changed, either in resolution or versions
+ * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
+ * The semantic diagnostics are cached and managed here
+ * Note that it is assumed that the when asked about semantic diagnostics through this API,
+ * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
*/
- changedFilesSet: ReadonlyMap;
- /**
- * Set of cached semantic diagnostics per file
- */
- semanticDiagnosticsPerFile: ReadonlyMap>;
- /**
- * Returns true if this state is safe to use as oldState
- */
- canCreateNewStateFrom(): boolean;
+ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray;
+ }
+ /**
+ * The builder that can handle the changes in program and iterate through changed file to emit the files
+ * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files
+ */
+ interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder {
/**
* Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
*/
@@ -3759,33 +3767,20 @@ declare namespace ts {
/**
* Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
* The semantic diagnostics are cached and managed here
- * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files
+ * Note that it is assumed that the when asked about semantic diagnostics through this API,
+ * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
*/
getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray;
- /**
- * Get all the dependencies of the file
- */
- getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
}
/**
- * Information about the source file: Its version and optional signature from last emit
+ * Create the builder to manage semantic diagnostics and cache them
*/
- interface FileInfo {
- version: string;
- signature: string;
- }
- interface AffectedFileEmitResult extends EmitResult {
- affectedFile?: SourceFile;
- }
+ function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder;
/**
- * Referenced files with values for the keys as referenced file's path to be true
+ * Create the builder that can handle the changes in program and iterate through changed files
+ * to emit the those files and manage semantic diagnostics cache as well
*/
- type ReferencedSet = ReadonlyMap;
- interface BuilderOptions {
- getCanonicalFileName: (fileName: string) => string;
- computeHash: (data: string) => string;
- }
- function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState;
+ function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder;
}
declare namespace ts {
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
@@ -3819,7 +3814,7 @@ declare namespace ts {
declare namespace ts {
type DiagnosticReporter = (diagnostic: Diagnostic) => void;
/**
- * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter
+ * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files
*/
function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void;
interface WatchHost {