Make the builder state as internal and expose builder instead of builder state

This commit is contained in:
Sheetal Nandi 2017-11-14 14:47:58 -08:00
parent 6d36a3d778
commit 85ce1d0398
6 changed files with 521 additions and 319 deletions

View File

@ -1,29 +1,56 @@
/// <reference path="program.ts" />
/*@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<SourceFile>;
}
/**
* 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<true>;
function hasSameKeys<T, U>(map1: ReadonlyMap<T> | undefined, map2: ReadonlyMap<U> | 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<Readonly<FileInfo>>;
/**
* 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<ReferencedSet> | undefined;
/**
* Set of source file's paths that have been changed, either in resolution or versions
*/
changedFilesSet: ReadonlyMap<true>;
/**
* Set of cached semantic diagnostics per file
*/
semanticDiagnosticsPerFile: ReadonlyMap<ReadonlyArray<Diagnostic>>;
/**
* 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<SourceFile>;
/**
* 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<Diagnostic>;
/**
* 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<true>;
export interface BuilderOptions {
getCanonicalFileName: GetCanonicalFileName;
computeHash: (data: string) => string;
}
export function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly<BuilderState>): BuilderState {
const fileInfos = createMap<FileInfo>();
const isModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None;
const referencedMap = isModuleEmit ? createMap<ReferencedSet>() : 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<ReferencedSet> | 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<string> | undefined) => ReadonlyArray<SourceFile>;
/**
* Cache of semantic diagnostics for files with their Path being the key
*/
const semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
/** 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<true>();
const hasShapeChanged = createMap<true>();
/**
* 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<true>();
/**
* Cache of all files excluding default library file for the current program
*/
let allFilesExcludingDefaultLibraryFile: ReadonlyArray<SourceFile> | undefined;
// Iterator datas
/**
* Set of affected files being iterated
*/
let affectedFiles: ReadonlyArray<SourceFile> | 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<string>();
/**
* Already seen affected files
*/
const seenAffectedFiles = createMap<true>();
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<FileInfo>;
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<ReferencedSet>() : 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<SourceFile> {
function getFilesAffectedBy(programOfThisState: Program, path: Path, cacheToUpdateSignature?: Map<string>): ReadonlyArray<SourceFile> {
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<Diagnostic> {
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<Diagnostic> {
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<string> | 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<string> | 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<Diagnostic>;
/**
* 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<Diagnostic>;
}
/**
* 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<Diagnostic>;
}
/**
* 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);
}
}

View File

@ -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<BuilderState> | 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);

View File

@ -44,17 +44,16 @@ namespace ts {
});
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray<string>) => 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);
};

View File

@ -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;

View File

@ -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<Readonly<FileInfo>>;
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<ReferencedSet> | undefined;
getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray<Diagnostic>;
/**
* 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<true>;
/**
* Set of cached semantic diagnostics per file
*/
semanticDiagnosticsPerFile: ReadonlyMap<ReadonlyArray<Diagnostic>>;
/**
* Returns true if this state is safe to use as oldState
*/
canCreateNewStateFrom(): boolean;
getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
}
/**
* 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<Diagnostic>;
/**
* 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<true>;
interface BuilderOptions {
getCanonicalFileName: (fileName: string) => string;
computeHash: (data: string) => string;
}
function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly<BuilderState>): 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.
*/

View File

@ -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<Readonly<FileInfo>>;
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<ReferencedSet> | undefined;
getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray<Diagnostic>;
/**
* 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<true>;
/**
* Set of cached semantic diagnostics per file
*/
semanticDiagnosticsPerFile: ReadonlyMap<ReadonlyArray<Diagnostic>>;
/**
* Returns true if this state is safe to use as oldState
*/
canCreateNewStateFrom(): boolean;
getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
}
/**
* 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<Diagnostic>;
/**
* 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<true>;
interface BuilderOptions {
getCanonicalFileName: (fileName: string) => string;
computeHash: (data: string) => string;
}
function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly<BuilderState>): 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 {