diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts
index 9c7707bd1d8..ff372479fad 100644
--- a/src/compiler/builder.ts
+++ b/src/compiler/builder.ts
@@ -1,101 +1,26 @@
-///
+///
/*@internal*/
namespace ts {
- 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 };
-
- 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, cancellationToken: CancellationToken): ReadonlyArray;
- }
-
- /**
- * Create the internal builder to get files affected by sourceFile
- */
- export function createInternalBuilder(host: BuilderHost): InternalBuilder {
- return createBuilder(host, BuilderKind.BuilderKindInternal);
- }
-
export enum BuilderKind {
- BuilderKindInternal,
BuilderKindSemanticDiagnostics,
BuilderKindEmitAndSemanticDiagnostics
}
- /**
- * 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;
- }
- if (map2 === undefined) {
- return map1 === undefined;
- }
- // Has same size and every key is present in both maps
- return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key));
- }
-
- export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindInternal): InternalBuilder;
export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder;
export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder;
export function createBuilder(host: BuilderHost, builderKind: BuilderKind) {
/**
- * Create the canonical file name for identity
+ * State corresponding to all the file references and shapes of the module etc
*/
- const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
- /**
- * Computing hash to for signature verification
- */
- const computeHash = host.createHash || identity;
-
- /**
- * Information of the file eg. its version, signature etc
- */
- const fileInfos = createMap();
-
- /**
- * 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, cancellationToken: CancellationToken | undefined) => ReadonlyArray;
+ const state = createBuilderState({
+ useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
+ createHash: host.createHash,
+ onUpdateProgramInitialized,
+ onSourceFileAdd: addToChangedFilesSet,
+ onSourceFileChanged: path => { addToChangedFilesSet(path); deleteSemanticDiagnostics(path); },
+ onSourceFileRemoved: deleteSemanticDiagnostics
+ });
/**
* Cache of semantic diagnostics for files with their Path being the key
@@ -107,18 +32,6 @@ namespace ts {
*/
const changedFilesSet = 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;
-
/**
* Set of affected files being iterated
*/
@@ -142,8 +55,6 @@ namespace ts {
const seenAffectedFiles = createMap();
switch (builderKind) {
- case BuilderKind.BuilderKindInternal:
- return getInternalBuilder();
case BuilderKind.BuilderKindSemanticDiagnostics:
return getSemanticDiagnosticsBuilder();
case BuilderKind.BuilderKindEmitAndSemanticDiagnostics:
@@ -152,14 +63,6 @@ namespace ts {
notImplemented();
}
- function getInternalBuilder(): InternalBuilder {
- return {
- updateProgram,
- getFilesAffectedBy,
- getAllDependencies
- };
- }
-
function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder {
return {
updateProgram,
@@ -179,17 +82,13 @@ namespace ts {
}
/**
- * Update current state to reflect new program
- * Updates changed files, references, file infos etc
+ * Initialize changedFiles, affected files set, cached diagnostics, signatures
*/
- function updateProgram(newProgram: Program) {
- const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None;
- const oldReferencedMap = referencedMap;
- if (isModuleEmit !== newProgramHasModuleEmit) {
+ function onUpdateProgramInitialized(isModuleEmitChanged: boolean) {
+ if (isModuleEmitChanged) {
// 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
@@ -197,21 +96,12 @@ namespace ts {
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.forEach(sourceFile => deleteSemanticDiagnostics(sourceFile.path));
affectedFiles = undefined;
currentAffectedFilesSignatures.clear();
}
@@ -219,100 +109,27 @@ namespace ts {
// 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);
- }
- }
- });
+ Debug.assert(!forEachKey(changedFilesSet, path => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files");
}
}
/**
- * Gets the files affected by the path from the program
+ * Add file to the changed files set
*/
- function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray {
- // Since the operation could be cancelled, the signatures are always stored in the cache
- // They will be commited once it is safe to use them
- // eg when calling this api from tsserver, if there is no cancellation of the operation
- // In the other cases the affected files signatures are commited only after the iteration through the result is complete
- const signatureCache = cacheToUpdateSignature || createMap();
- const sourceFile = programOfThisState.getSourceFileByPath(path);
- if (!sourceFile) {
- return emptyArray;
- }
+ function addToChangedFilesSet(path: Path) {
+ changedFilesSet.set(path, true);
+ }
- if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) {
- return [sourceFile];
- }
-
- const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken);
- if (!cacheToUpdateSignature) {
- // Commit all the signatures in the signature cache
- updateSignaturesFromCache(signatureCache);
- }
- return result;
+ function deleteSemanticDiagnostics(path: Path) {
+ semanticDiagnosticsPerFile.delete(path);
}
/**
- * Updates the signatures from the cache
- * This should be called whenever it is safe to commit the state of the builder
+ * Update current state to reflect new program
+ * Updates changed files, references, file infos etc which happens through the state callbacks
*/
- function updateSignaturesFromCache(signatureCache: Map) {
- signatureCache.forEach((signature, path) => {
- fileInfos.get(path).signature = signature;
- hasCalledUpdateShapeSignature.set(path, true);
- });
+ function updateProgram(newProgram: Program) {
+ state.updateProgram(newProgram);
}
/**
@@ -339,7 +156,7 @@ namespace ts {
changedFilesSet.delete(currentChangedFilePath);
currentChangedFilePath = undefined;
// Commit the changes in file signature
- updateSignaturesFromCache(currentAffectedFilesSignatures);
+ state.updateSignaturesFromCache(currentAffectedFilesSignatures);
currentAffectedFilesSignatures.clear();
affectedFiles = undefined;
}
@@ -361,7 +178,7 @@ namespace ts {
// Get next batch of affected files
currentAffectedFilesSignatures.clear();
- affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures);
+ affectedFiles = state.getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures);
currentChangedFilePath = nextKey.value as Path;
semanticDiagnosticsPerFile.delete(currentChangedFilePath);
affectedFilesIndex = 0;
@@ -500,250 +317,13 @@ namespace ts {
/**
* Get all the dependencies of the sourceFile
*/
- function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[] {
- const compilerOptions = programOfThisState.getCompilerOptions();
- // With --out or --outFile all outputs go into single file, all files depend on each other
- if (compilerOptions.outFile || compilerOptions.out) {
- return programOfThisState.getSourceFiles().map(getFileName);
- }
-
- // If this is non module emit, or its a global file, it depends on all the source files
- if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) {
- return programOfThisState.getSourceFiles().map(getFileName);
- }
-
- // Get the references, traversing deep from the referenceMap
- Debug.assert(!!referencedMap);
- const seenMap = createMap();
- const queue = [sourceFile.path];
- while (queue.length) {
- const path = queue.pop();
- if (!seenMap.has(path)) {
- seenMap.set(path, true);
- const references = referencedMap.get(path);
- if (references) {
- const iterator = references.keys();
- for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) {
- queue.push(value as Path);
- }
- }
- }
- }
-
- return flatMapIter(seenMap.keys(), path => {
- const file = programOfThisState.getSourceFileByPath(path as Path);
- if (file) {
- return file.fileName;
- }
- return path;
- });
- }
-
- function getFileName(sourceFile: SourceFile) {
- return sourceFile.fileName;
- }
-
- /**
- * For script files that contains only ambient external modules, although they are not actually external module files,
- * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
- * there are no point to rebuild all script files if these special files have changed. However, if any statement
- * in the file is not ambient external module, we treat it as a regular script file.
- */
- function containsOnlyAmbientModules(sourceFile: SourceFile) {
- for (const statement of sourceFile.statements) {
- if (!isModuleWithStringLiteralName(statement)) {
- return false;
- }
- }
- 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, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | 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 (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) {
- return false;
- }
-
- const info = fileInfos.get(sourceFile.path);
- Debug.assert(!!info);
-
- const prevSignature = info.signature;
- let latestSignature: string;
- if (sourceFile.isDeclarationFile) {
- latestSignature = sourceFile.version;
- }
- else {
- const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken);
- if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
- latestSignature = computeHash(emitOutput.outputFiles[0].text);
- }
- else {
- latestSignature = prevSignature;
- }
- }
- cacheToUpdateSignature.set(sourceFile.path, latestSignature);
-
- return !prevSignature || latestSignature !== prevSignature;
- }
-
- /**
- * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
- */
- function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined {
- let referencedFiles: Map | undefined;
-
- // We need to use a set here since the code can contain the same import twice,
- // but that will only be one dependency.
- // To avoid invernal conversion, the key of the referencedFiles map must be of type Path
- if (sourceFile.imports && sourceFile.imports.length > 0) {
- const checker: TypeChecker = program.getTypeChecker();
- for (const importName of sourceFile.imports) {
- const symbol = checker.getSymbolAtLocation(importName);
- if (symbol && symbol.declarations && symbol.declarations[0]) {
- const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]);
- if (declarationSourceFile) {
- addReferencedFile(declarationSourceFile.path);
- }
- }
- }
- }
-
- const sourceFileDirectory = getDirectoryPath(sourceFile.path);
- // Handle triple slash references
- if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
- for (const referencedFile of sourceFile.referencedFiles) {
- const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
- addReferencedFile(referencedPath);
- }
- }
-
- // Handle type reference directives
- if (sourceFile.resolvedTypeReferenceDirectiveNames) {
- sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
- if (!resolvedTypeReferenceDirective) {
- return;
- }
-
- const fileName = resolvedTypeReferenceDirective.resolvedFileName;
- const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
- addReferencedFile(typeFilePath);
- });
- }
-
- return referencedFiles;
-
- function addReferencedFile(referencedPath: Path) {
- if (!referencedFiles) {
- referencedFiles = createMap();
- }
- referencedFiles.set(referencedPath, true);
- }
- }
-
- /**
- * Gets the files referenced by the the file path
- */
- function getReferencedByPaths(referencedFilePath: Path) {
- return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) =>
- referencesInFile.has(referencedFilePath) ? filePath as Path : undefined
- );
- }
-
- /**
- * Gets all files of the program excluding the default library file
- */
- function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray {
- // Use cached result
- if (allFilesExcludingDefaultLibraryFile) {
- return allFilesExcludingDefaultLibraryFile;
- }
-
- let result: SourceFile[];
- addSourceFile(firstSourceFile);
- for (const sourceFile of program.getSourceFiles()) {
- if (sourceFile !== firstSourceFile) {
- addSourceFile(sourceFile);
- }
- }
- allFilesExcludingDefaultLibraryFile = result || emptyArray;
- return allFilesExcludingDefaultLibraryFile;
-
- function addSourceFile(sourceFile: SourceFile) {
- if (!program.isSourceFileDefaultLibrary(sourceFile)) {
- (result || (result = [])).push(sourceFile);
- }
- }
- }
-
- /**
- * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed
- */
- function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) {
- const compilerOptions = programOfThisState.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 (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) {
- return [sourceFileWithUpdatedShape];
- }
- return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape);
- }
-
- /**
- * When program emits modular code, gets the files affected by the sourceFile whose shape has changed
- */
- function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) {
- if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) {
- return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape);
- }
-
- const compilerOptions = programOfThisState.getCompilerOptions();
- if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) {
- return [sourceFileWithUpdatedShape];
- }
-
- // Now we need to if each file in the referencedBy list has a shape change as well.
- // Because if so, its own referencedBy files need to be saved as well to make the
- // emitting result consistent with files on disk.
- const seenFileNamesMap = createMap();
-
- // Start with the paths this file was referenced by
- seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape);
- const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path);
- while (queue.length > 0) {
- const currentPath = queue.pop();
- if (!seenFileNamesMap.has(currentPath)) {
- const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath);
- seenFileNamesMap.set(currentPath, currentSourceFile);
- if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) {
- queue.push(...getReferencedByPaths(currentPath));
- }
- }
- }
-
- // Return array of values that needs emit
- return flatMapIter(seenFileNamesMap.values(), value => value);
+ function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) {
+ return state.getAllDependencies(programOfThisState, sourceFile);
}
}
}
namespace ts {
- export interface EmitOutput {
- outputFiles: OutputFile[];
- emitSkipped: boolean;
- }
-
- export interface OutputFile {
- name: string;
- writeByteOrderMark: boolean;
- text: string;
- }
-
export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined;
export interface BuilderHost {
@@ -769,7 +349,7 @@ namespace ts {
/**
* Get all the dependencies of the file
*/
- getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
+ getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray;
}
/**
diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts
index f6706195a75..eb3887eed15 100644
--- a/src/compiler/builderState.ts
+++ b/src/compiler/builderState.ts
@@ -46,7 +46,76 @@ namespace ts {
return map1 === undefined;
}
// Has same size and every key is present in both maps
- return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key));
+ return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key));
+ }
+
+ /**
+ * For script files that contains only ambient external modules, although they are not actually external module files,
+ * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
+ * there are no point to rebuild all script files if these special files have changed. However, if any statement
+ * in the file is not ambient external module, we treat it as a regular script file.
+ */
+ function containsOnlyAmbientModules(sourceFile: SourceFile) {
+ for (const statement of sourceFile.statements) {
+ if (!isModuleWithStringLiteralName(statement)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
+ */
+ function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Map | undefined {
+ let referencedFiles: Map | undefined;
+
+ // We need to use a set here since the code can contain the same import twice,
+ // but that will only be one dependency.
+ // To avoid invernal conversion, the key of the referencedFiles map must be of type Path
+ if (sourceFile.imports && sourceFile.imports.length > 0) {
+ const checker: TypeChecker = program.getTypeChecker();
+ for (const importName of sourceFile.imports) {
+ const symbol = checker.getSymbolAtLocation(importName);
+ if (symbol && symbol.declarations && symbol.declarations[0]) {
+ const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]);
+ if (declarationSourceFile) {
+ addReferencedFile(declarationSourceFile.path);
+ }
+ }
+ }
+ }
+
+ const sourceFileDirectory = getDirectoryPath(sourceFile.path);
+ // Handle triple slash references
+ if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
+ for (const referencedFile of sourceFile.referencedFiles) {
+ const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
+ addReferencedFile(referencedPath);
+ }
+ }
+
+ // Handle type reference directives
+ if (sourceFile.resolvedTypeReferenceDirectiveNames) {
+ sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
+ if (!resolvedTypeReferenceDirective) {
+ return;
+ }
+
+ const fileName = resolvedTypeReferenceDirective.resolvedFileName;
+ const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
+ addReferencedFile(typeFilePath);
+ });
+ }
+
+ return referencedFiles;
+
+ function addReferencedFile(referencedPath: Path) {
+ if (!referencedFiles) {
+ referencedFiles = createMap();
+ }
+ referencedFiles.set(referencedPath, true);
+ }
}
export interface BuilderStateHost {
@@ -76,6 +145,15 @@ namespace ts {
* Gets the files affected by the file path
*/
getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken, cacheToUpdateSignature?: Map): ReadonlyArray;
+ /**
+ * Updates the signatures from the cache
+ * This should be called whenever it is safe to commit the state of the builder
+ */
+ updateSignaturesFromCache(signatureCache: Map): void;
+ /**
+ * Get all the dependencies of the sourceFile
+ */
+ getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray;
}
export function createBuilderState(host: BuilderStateHost): BuilderState {
@@ -121,11 +199,17 @@ namespace ts {
* Cache of all files excluding default library file for the current program
*/
let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined;
+ /**
+ * Cache of all the file names
+ */
+ let allFileNames: ReadonlyArray | undefined;
return {
updateProgram,
getFilesAffectedBy,
- };
+ getAllDependencies,
+ updateSignaturesFromCache
+ };
/**
* Update current state to reflect new program
@@ -155,11 +239,12 @@ namespace ts {
// Clear datas that cant be retained beyond previous state
hasCalledUpdateShapeSignature.clear();
allFilesExcludingDefaultLibraryFile = undefined;
+ allFileNames = 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 newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile, getCanonicalFileName);
const oldInfo = fileInfos.get(sourceFile.path);
let oldReferences: ReferencedSet;
@@ -174,7 +259,7 @@ namespace ts {
// 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))) {
+ newReferences && forEachKey(newReferences, path => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) {
// Changed file: Update the version, set as changed file
oldInfo.version = version;
@@ -230,6 +315,58 @@ namespace ts {
return result;
}
+ /**
+ * Get all the dependencies of the sourceFile
+ */
+ function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray {
+ const compilerOptions = programOfThisState.getCompilerOptions();
+ // With --out or --outFile all outputs go into single file, all files depend on each other
+ if (compilerOptions.outFile || compilerOptions.out) {
+ return getAllFileNames(programOfThisState);
+ }
+
+ // If this is non module emit, or its a global file, it depends on all the source files
+ if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) {
+ return getAllFileNames(programOfThisState);
+ }
+
+ // Get the references, traversing deep from the referenceMap
+ Debug.assert(!!referencedMap);
+ const seenMap = createMap();
+ const queue = [sourceFile.path];
+ while (queue.length) {
+ const path = queue.pop();
+ if (!seenMap.has(path)) {
+ seenMap.set(path, true);
+ const references = referencedMap.get(path);
+ if (references) {
+ const iterator = references.keys();
+ for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) {
+ queue.push(value as Path);
+ }
+ }
+ }
+ }
+
+ return flatMapIter(seenMap.keys(), path => {
+ const file = programOfThisState.getSourceFileByPath(path as Path);
+ if (file) {
+ return file.fileName;
+ }
+ return path;
+ });
+ }
+
+ /**
+ * Gets the names of all files from the program
+ */
+ function getAllFileNames(programOfThisState: Program): ReadonlyArray {
+ if (!allFileNames) {
+ allFileNames = programOfThisState.getSourceFiles().map(file => file.fileName);
+ }
+ return allFileNames;
+ }
+
/**
* Updates the signatures from the cache
* This should be called whenever it is safe to commit the state of the builder
@@ -241,21 +378,6 @@ namespace ts {
});
}
- /**
- * For script files that contains only ambient external modules, although they are not actually external module files,
- * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
- * there are no point to rebuild all script files if these special files have changed. However, if any statement
- * in the file is not ambient external module, we treat it as a regular script file.
- */
- function containsOnlyAmbientModules(sourceFile: SourceFile) {
- for (const statement of sourceFile.statements) {
- if (!isModuleWithStringLiteralName(statement)) {
- return false;
- }
- }
- 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
@@ -290,60 +412,6 @@ namespace ts {
return !prevSignature || latestSignature !== prevSignature;
}
- /**
- * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
- */
- function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined {
- let referencedFiles: Map | undefined;
-
- // We need to use a set here since the code can contain the same import twice,
- // but that will only be one dependency.
- // To avoid invernal conversion, the key of the referencedFiles map must be of type Path
- if (sourceFile.imports && sourceFile.imports.length > 0) {
- const checker: TypeChecker = program.getTypeChecker();
- for (const importName of sourceFile.imports) {
- const symbol = checker.getSymbolAtLocation(importName);
- if (symbol && symbol.declarations && symbol.declarations[0]) {
- const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]);
- if (declarationSourceFile) {
- addReferencedFile(declarationSourceFile.path);
- }
- }
- }
- }
-
- const sourceFileDirectory = getDirectoryPath(sourceFile.path);
- // Handle triple slash references
- if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
- for (const referencedFile of sourceFile.referencedFiles) {
- const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
- addReferencedFile(referencedPath);
- }
- }
-
- // Handle type reference directives
- if (sourceFile.resolvedTypeReferenceDirectiveNames) {
- sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
- if (!resolvedTypeReferenceDirective) {
- return;
- }
-
- const fileName = resolvedTypeReferenceDirective.resolvedFileName;
- const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
- addReferencedFile(typeFilePath);
- });
- }
-
- return referencedFiles;
-
- function addReferencedFile(referencedPath: Path) {
- if (!referencedFiles) {
- referencedFiles = createMap();
- }
- referencedFiles.set(referencedPath, true);
- }
- }
-
/**
* Gets the files referenced by the the file path
*/
diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts
index d7f3821a25d..32f4b78a8c5 100644
--- a/tests/baselines/reference/api/tsserverlibrary.d.ts
+++ b/tests/baselines/reference/api/tsserverlibrary.d.ts
@@ -3757,87 +3757,6 @@ declare namespace ts {
declare namespace ts {
function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer;
}
-declare namespace ts {
- interface EmitOutput {
- outputFiles: OutputFile[];
- emitSkipped: boolean;
- }
- interface OutputFile {
- name: string;
- writeByteOrderMark: boolean;
- text: string;
- }
- type AffectedFileResult = {
- result: T;
- affected: SourceFile | Program;
- } | undefined;
- interface BuilderHost {
- /**
- * return true if file names are treated with case sensitivity
- */
- useCaseSensitiveFileNames(): boolean;
- /**
- * If provided this would be used this hash instead of actual file shape text for detecting changes
- */
- createHash?: (data: string) => string;
- }
- /**
- * Builder to manage the program state changes
- */
- 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
- */
- 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): AffectedFileResult>;
- /**
- * 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
- */
- 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): AffectedFileResult;
- /**
- * 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
- */
- function createSemanticDiagnosticsBuilder(host: BuilderHost): 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
- */
- function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder;
-}
declare namespace ts {
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
function resolveTripleslashReference(moduleName: string, containingFile: string): string;
@@ -7233,6 +7152,17 @@ declare namespace ts.server {
onProjectClosed(project: Project): void;
}
}
+declare namespace ts {
+ interface EmitOutput {
+ outputFiles: OutputFile[];
+ emitSkipped: boolean;
+ }
+ interface OutputFile {
+ name: string;
+ writeByteOrderMark: boolean;
+ text: string;
+ }
+}
declare namespace ts.server {
enum ProjectKind {
Inferred = 0,
diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts
index 06c75e843c0..514c140f05e 100644
--- a/tests/baselines/reference/api/typescript.d.ts
+++ b/tests/baselines/reference/api/typescript.d.ts
@@ -3704,6 +3704,35 @@ declare namespace ts {
declare namespace ts {
function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer;
}
+declare namespace ts {
+ function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
+ function resolveTripleslashReference(moduleName: string, containingFile: string): string;
+ function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
+ function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
+ interface FormatDiagnosticsHost {
+ getCurrentDirectory(): string;
+ getCanonicalFileName(fileName: string): string;
+ getNewLine(): string;
+ }
+ function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string;
+ function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string;
+ function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string;
+ function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string;
+ /**
+ * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
+ * that represent a compilation unit.
+ *
+ * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and
+ * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in.
+ *
+ * @param rootNames - A set of root files.
+ * @param options - The compiler options which should be used.
+ * @param host - The host interacts with the underlying file system.
+ * @param oldProgram - Reuses an old program structure.
+ * @returns A 'Program' object.
+ */
+ function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program;
+}
declare namespace ts {
interface EmitOutput {
outputFiles: OutputFile[];
@@ -3714,6 +3743,8 @@ declare namespace ts {
writeByteOrderMark: boolean;
text: string;
}
+}
+declare namespace ts {
type AffectedFileResult = {
result: T;
affected: SourceFile | Program;
@@ -3739,7 +3770,7 @@ declare namespace ts {
/**
* Get all the dependencies of the file
*/
- getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[];
+ getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray;
}
/**
* The builder that caches the semantic diagnostics for the program and handles the changed files and affected files
@@ -3785,35 +3816,6 @@ declare namespace ts {
*/
function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder;
}
-declare namespace ts {
- function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
- function resolveTripleslashReference(moduleName: string, containingFile: string): string;
- function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
- function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
- interface FormatDiagnosticsHost {
- getCurrentDirectory(): string;
- getCanonicalFileName(fileName: string): string;
- getNewLine(): string;
- }
- function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string;
- function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string;
- function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string;
- function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string;
- /**
- * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
- * that represent a compilation unit.
- *
- * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and
- * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in.
- *
- * @param rootNames - A set of root files.
- * @param options - The compiler options which should be used.
- * @param host - The host interacts with the underlying file system.
- * @param oldProgram - Reuses an old program structure.
- * @returns A 'Program' object.
- */
- function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program;
-}
declare namespace ts {
type DiagnosticReporter = (diagnostic: Diagnostic) => void;
/**