Convert builder state to mutable data, so that later we can create builder Program out of this
This commit is contained in:
parent
2586bb303c
commit
bb0fc0d2bc
|
@ -13,7 +13,7 @@ namespace ts {
|
|||
/**
|
||||
* State corresponding to all the file references and shapes of the module etc
|
||||
*/
|
||||
const state = createBuilderState({
|
||||
const state = createBuilderStateOld({
|
||||
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
|
||||
createHash: host.createHash,
|
||||
onUpdateProgramInitialized,
|
||||
|
|
|
@ -136,7 +136,7 @@ namespace ts {
|
|||
onSourceFileRemoved(path: Path): void;
|
||||
}
|
||||
|
||||
export interface BuilderState {
|
||||
export interface BuilderStateOld {
|
||||
/**
|
||||
* Updates the program in the builder to represent new state
|
||||
*/
|
||||
|
@ -156,7 +156,65 @@ namespace ts {
|
|||
getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export function createBuilderState(host: BuilderStateHost): BuilderState {
|
||||
export interface BuilderState {
|
||||
/**
|
||||
* Information of the file eg. its version, signature etc
|
||||
*/
|
||||
fileInfos: Map<FileInfo>;
|
||||
/**
|
||||
* Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled
|
||||
* Otherwise undefined
|
||||
* Thus non undefined value indicates, module emit
|
||||
*/
|
||||
readonly referencedMap: ReadonlyMap<ReferencedSet> | undefined;
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
hasCalledUpdateShapeSignature: Map<true>;
|
||||
/**
|
||||
* Cache of all files excluding default library file for the current program
|
||||
*/
|
||||
allFilesExcludingDefaultLibraryFile: ReadonlyArray<SourceFile> | undefined;
|
||||
/**
|
||||
* Cache of all the file names
|
||||
*/
|
||||
allFileNames: ReadonlyArray<string> | undefined;
|
||||
}
|
||||
|
||||
export function createBuilderState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: BuilderState): BuilderState {
|
||||
const fileInfos = createMap<FileInfo>();
|
||||
const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap<ReferencedSet>() : undefined;
|
||||
const hasCalledUpdateShapeSignature = createMap<true>();
|
||||
const useOldState = oldState && !!oldState.referencedMap !== !!referencedMap;
|
||||
|
||||
// Create the reference map, and set the file infos
|
||||
for (const sourceFile of newProgram.getSourceFiles()) {
|
||||
const version = sourceFile.version;
|
||||
const oldInfo = useOldState && oldState.fileInfos.get(sourceFile.path);
|
||||
if (referencedMap) {
|
||||
const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName);
|
||||
if (newReferences) {
|
||||
referencedMap.set(sourceFile.path, newReferences);
|
||||
}
|
||||
}
|
||||
fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature });
|
||||
}
|
||||
|
||||
oldState = undefined;
|
||||
newProgram = undefined;
|
||||
|
||||
return {
|
||||
fileInfos,
|
||||
referencedMap,
|
||||
hasCalledUpdateShapeSignature,
|
||||
allFilesExcludingDefaultLibraryFile: undefined,
|
||||
allFileNames: undefined
|
||||
};
|
||||
}
|
||||
|
||||
export function createBuilderStateOld(host: BuilderStateHost): BuilderStateOld {
|
||||
/**
|
||||
* Create the canonical file name for identity
|
||||
*/
|
||||
|
@ -171,11 +229,6 @@ namespace ts {
|
|||
*/
|
||||
const fileInfos = createMap<FileInfo>();
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -218,7 +271,7 @@ namespace ts {
|
|||
function updateProgram(newProgram: Program) {
|
||||
const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None;
|
||||
const oldReferencedMap = referencedMap;
|
||||
const isModuleEmitChanged = isModuleEmit !== newProgramHasModuleEmit;
|
||||
const isModuleEmitChanged = !!referencedMap !== newProgramHasModuleEmit;
|
||||
if (isModuleEmitChanged) {
|
||||
// Changes in the module emit, clear out everything and initialize as if first time
|
||||
|
||||
|
@ -229,8 +282,7 @@ namespace ts {
|
|||
referencedMap = newProgramHasModuleEmit ? createMap<ReferencedSet>() : undefined;
|
||||
|
||||
// Update the module emit
|
||||
isModuleEmit = newProgramHasModuleEmit;
|
||||
getEmitDependentFilesAffectedBy = isModuleEmit ?
|
||||
getEmitDependentFilesAffectedBy = newProgramHasModuleEmit ?
|
||||
getFilesAffectedByUpdatedShapeWhenModuleEmit :
|
||||
getFilesAffectedByUpdatedShapeWhenNonModuleEmit;
|
||||
}
|
||||
|
@ -326,12 +378,11 @@ namespace ts {
|
|||
}
|
||||
|
||||
// If this is non module emit, or its a global file, it depends on all the source files
|
||||
if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) {
|
||||
if (!referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) {
|
||||
return getAllFileNames(programOfThisState);
|
||||
}
|
||||
|
||||
// Get the references, traversing deep from the referenceMap
|
||||
Debug.assert(!!referencedMap);
|
||||
const seenMap = createMap<true>();
|
||||
const queue = [sourceFile.path];
|
||||
while (queue.length) {
|
||||
|
@ -497,3 +548,177 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
namespace ts.BuilderState {
|
||||
type ComputeHash = (data: string) => string;
|
||||
|
||||
/**
|
||||
* Gets the files affected by the path from the program
|
||||
*/
|
||||
export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash?: ComputeHash, cacheToUpdateSignature?: Map<string>): ReadonlyArray<SourceFile> {
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash)) {
|
||||
return [sourceFile];
|
||||
}
|
||||
|
||||
const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash);
|
||||
if (!cacheToUpdateSignature) {
|
||||
// Commit all the signatures in the signature cache
|
||||
updateSignaturesFromCache(state, signatureCache);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the signatures from the cache into state's fileinfo signatures
|
||||
* This should be called whenever it is safe to commit the state of the builder
|
||||
*/
|
||||
export function updateSignaturesFromCache(state: BuilderState, signatureCache: Map<string>) {
|
||||
signatureCache.forEach((signature, path) => {
|
||||
state.fileInfos.get(path).signature = signature;
|
||||
state.hasCalledUpdateShapeSignature.set(path, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the shape of the signature has changed since last emit
|
||||
*/
|
||||
function updateShapeSignature(state: Readonly<BuilderState>, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map<string>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | 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 (state.hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const info = state.fileInfos.get(sourceFile.path);
|
||||
Debug.assert(!!info);
|
||||
|
||||
const prevSignature = info.signature;
|
||||
let latestSignature: string;
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
latestSignature = sourceFile.version;
|
||||
}
|
||||
else {
|
||||
const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken);
|
||||
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
|
||||
latestSignature = (computeHash || identity)(emitOutput.outputFiles[0].text);
|
||||
}
|
||||
else {
|
||||
latestSignature = prevSignature;
|
||||
}
|
||||
}
|
||||
cacheToUpdateSignature.set(sourceFile.path, latestSignature);
|
||||
|
||||
return !prevSignature || latestSignature !== prevSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the files referenced by the the file path
|
||||
*/
|
||||
function getReferencedByPaths(state: Readonly<BuilderState>, referencedFilePath: Path) {
|
||||
return mapDefinedIter(state.referencedMap.entries(), ([filePath, referencesInFile]) =>
|
||||
referencesInFile.has(referencedFilePath) ? filePath as Path : undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 all files of the program excluding the default library file
|
||||
*/
|
||||
function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile): ReadonlyArray<SourceFile> {
|
||||
// Use cached result
|
||||
if (state.allFilesExcludingDefaultLibraryFile) {
|
||||
return state.allFilesExcludingDefaultLibraryFile;
|
||||
}
|
||||
|
||||
let result: SourceFile[];
|
||||
addSourceFile(firstSourceFile);
|
||||
for (const sourceFile of programOfThisState.getSourceFiles()) {
|
||||
if (sourceFile !== firstSourceFile) {
|
||||
addSourceFile(sourceFile);
|
||||
}
|
||||
}
|
||||
state.allFilesExcludingDefaultLibraryFile = result || emptyArray;
|
||||
return state.allFilesExcludingDefaultLibraryFile;
|
||||
|
||||
function addSourceFile(sourceFile: SourceFile) {
|
||||
if (!programOfThisState.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(state: BuilderState, 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(state, programOfThisState, sourceFileWithUpdatedShape);
|
||||
}
|
||||
|
||||
/**
|
||||
* When program emits modular code, gets the files affected by the sourceFile whose shape has changed
|
||||
*/
|
||||
function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map<string>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined) {
|
||||
if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) {
|
||||
return getAllFilesExcludingDefaultLibraryFile(state, 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<SourceFile>();
|
||||
|
||||
// Start with the paths this file was referenced by
|
||||
seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape);
|
||||
const queue = getReferencedByPaths(state, 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(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash)) {
|
||||
queue.push(...getReferencedByPaths(state, currentPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return array of values that needs emit
|
||||
return flatMapIter(seenFileNamesMap.values(), value => value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ namespace ts.server {
|
|||
/*@internal*/
|
||||
resolutionCache: ResolutionCache;
|
||||
|
||||
private builder: BuilderState | undefined;
|
||||
private builderState: BuilderState | undefined;
|
||||
/**
|
||||
* Set of files names that were updated since the last call to getChangesSinceVersion.
|
||||
*/
|
||||
|
@ -460,18 +460,8 @@ namespace ts.server {
|
|||
return [];
|
||||
}
|
||||
this.updateGraph();
|
||||
if (!this.builder) {
|
||||
this.builder = createBuilderState({
|
||||
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames(),
|
||||
createHash: data => this.projectService.host.createHash(data),
|
||||
onUpdateProgramInitialized: noop,
|
||||
onSourceFileAdd: noop,
|
||||
onSourceFileChanged: noop,
|
||||
onSourceFileRemoved: noop
|
||||
});
|
||||
}
|
||||
this.builder.updateProgram(this.program);
|
||||
return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path, this.cancellationToken),
|
||||
this.builderState = createBuilderState(this.program, this.projectService.toCanonicalFileName, this.builderState);
|
||||
return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash(data)),
|
||||
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
|
||||
}
|
||||
|
||||
|
@ -507,7 +497,7 @@ namespace ts.server {
|
|||
}
|
||||
this.languageService.cleanupSemanticCache();
|
||||
this.languageServiceEnabled = false;
|
||||
this.builder = undefined;
|
||||
this.builderState = undefined;
|
||||
this.resolutionCache.closeTypeRootsWatch();
|
||||
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
|
||||
}
|
||||
|
@ -548,7 +538,7 @@ namespace ts.server {
|
|||
this.rootFilesMap = undefined;
|
||||
this.externalFiles = undefined;
|
||||
this.program = undefined;
|
||||
this.builder = undefined;
|
||||
this.builderState = undefined;
|
||||
this.resolutionCache.clear();
|
||||
this.resolutionCache = undefined;
|
||||
this.cachedUnresolvedImportsPerFile = undefined;
|
||||
|
|
Loading…
Reference in a new issue