mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 12:51:30 -05:00
Use exported modules through declaration emit to invalidate the semantic diagnostics
Fixes #24986
This commit is contained in:
@@ -30,6 +30,10 @@ namespace ts {
|
||||
* These will be commited whenever the iteration through affected files of current changed file is complete
|
||||
*/
|
||||
currentAffectedFilesSignatures: Map<string> | undefined;
|
||||
/**
|
||||
* Newly computed visible to outside referencedSet
|
||||
*/
|
||||
currentAffectedFilesExportedModulesMap: BuilderState.ComputingExportedModulesMap | undefined;
|
||||
/**
|
||||
* Already seen affected files
|
||||
*/
|
||||
@@ -128,6 +132,8 @@ namespace ts {
|
||||
// Set the next affected file as seen and remove the cached semantic diagnostics
|
||||
state.affectedFilesIndex = affectedFilesIndex;
|
||||
semanticDiagnosticsPerFile!.delete(affectedFile.path);
|
||||
// Remove semantic diagnostics for files that are affected by using exports of this module
|
||||
BuilderState.getFilesAffectedByExportedModule(state, affectedFile.path, state.currentAffectedFilesExportedModulesMap).forEach(path => semanticDiagnosticsPerFile!.delete(path));
|
||||
return affectedFile;
|
||||
}
|
||||
seenAffectedFiles!.set(affectedFile.path, true);
|
||||
@@ -140,6 +146,7 @@ namespace ts {
|
||||
// Commit the changes in file signature
|
||||
BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures!);
|
||||
state.currentAffectedFilesSignatures!.clear();
|
||||
BuilderState.updateExportedFilesMapFromCache(state, state.currentAffectedFilesExportedModulesMap);
|
||||
state.affectedFiles = undefined;
|
||||
}
|
||||
|
||||
@@ -160,7 +167,10 @@ namespace ts {
|
||||
|
||||
// Get next batch of affected files
|
||||
state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap();
|
||||
state.affectedFiles = BuilderState.getFilesAffectedBy(state, state.program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures);
|
||||
if (state.exportedModulesMap) {
|
||||
state.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap || createMap<BuilderState.ReferencedSet | false>();
|
||||
}
|
||||
state.affectedFiles = BuilderState.getFilesAffectedBy(state, state.program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap);
|
||||
state.currentChangedFilePath = nextKey.value as Path;
|
||||
state.semanticDiagnosticsPerFile!.delete(nextKey.value as Path);
|
||||
state.affectedFilesIndex = 0;
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace ts {
|
||||
export interface EmitOutput {
|
||||
outputFiles: OutputFile[];
|
||||
emitSkipped: boolean;
|
||||
/* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
|
||||
}
|
||||
|
||||
export interface OutputFile {
|
||||
@@ -17,7 +18,7 @@ namespace ts {
|
||||
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, exportedModulesFromDeclarationEmit: emitResult.exportedModulesFromDeclarationEmit };
|
||||
|
||||
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
|
||||
outputFiles.push({ name: fileName, writeByteOrderMark, text });
|
||||
@@ -35,6 +36,11 @@ namespace ts {
|
||||
* Thus non undefined value indicates, module emit
|
||||
*/
|
||||
readonly referencedMap: ReadonlyMap<BuilderState.ReferencedSet> | undefined;
|
||||
/**
|
||||
* Contains the map of exported modules ReferencedSet=exorted module files from the file if module emit is enabled
|
||||
* Otherwise undefined
|
||||
*/
|
||||
readonly exportedModulesMap: Map<BuilderState.ReferencedSet> | undefined;
|
||||
/**
|
||||
* Map of files that have already called update signature.
|
||||
* That means hence forth these files are assumed to have
|
||||
@@ -70,6 +76,30 @@ namespace ts.BuilderState {
|
||||
*/
|
||||
export type ComputeHash = (data: string) => string;
|
||||
|
||||
/**
|
||||
* Exported modules to from declaration emit being computed.
|
||||
* This can contain false in the affected file path to specify that there are no exported module(types from other modules) for this file
|
||||
*/
|
||||
export type ComputingExportedModulesMap = Map<ReferencedSet | false>;
|
||||
|
||||
/**
|
||||
* Get the referencedFile from the imported module symbol
|
||||
*/
|
||||
function getReferencedFileFromImportedModuleSymbol(symbol: Symbol) {
|
||||
if (symbol.declarations && symbol.declarations[0]) {
|
||||
const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]);
|
||||
return declarationSourceFile && declarationSourceFile.path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the referencedFile from the import name node from file
|
||||
*/
|
||||
function getReferencedFileFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike) {
|
||||
const symbol = checker.getSymbolAtLocation(importName);
|
||||
return symbol && getReferencedFileFromImportedModuleSymbol(symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
|
||||
*/
|
||||
@@ -82,12 +112,9 @@ namespace ts.BuilderState {
|
||||
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 declarationSourceFilePath = getReferencedFileFromImportLiteral(checker, importName);
|
||||
if (declarationSourceFilePath) {
|
||||
addReferencedFile(declarationSourceFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,6 +164,7 @@ namespace ts.BuilderState {
|
||||
export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<BuilderState>): BuilderState {
|
||||
const fileInfos = createMap<FileInfo>();
|
||||
const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap<ReferencedSet>() : undefined;
|
||||
const exportedModulesMap = referencedMap ? createMap<ReferencedSet>() : undefined;
|
||||
const hasCalledUpdateShapeSignature = createMap<true>();
|
||||
const useOldState = canReuseOldState(referencedMap, oldState);
|
||||
|
||||
@@ -149,6 +177,13 @@ namespace ts.BuilderState {
|
||||
if (newReferences) {
|
||||
referencedMap.set(sourceFile.path, newReferences);
|
||||
}
|
||||
// Copy old visible to outside files map
|
||||
if (useOldState) {
|
||||
const exportedModules = oldState!.exportedModulesMap!.get(sourceFile.path);
|
||||
if (exportedModules) {
|
||||
exportedModulesMap!.set(sourceFile.path, exportedModules);
|
||||
}
|
||||
}
|
||||
}
|
||||
fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature });
|
||||
}
|
||||
@@ -156,6 +191,7 @@ namespace ts.BuilderState {
|
||||
return {
|
||||
fileInfos,
|
||||
referencedMap,
|
||||
exportedModulesMap,
|
||||
hasCalledUpdateShapeSignature,
|
||||
allFilesExcludingDefaultLibraryFile: undefined,
|
||||
allFileNames: undefined
|
||||
@@ -165,7 +201,7 @@ namespace ts.BuilderState {
|
||||
/**
|
||||
* 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> {
|
||||
export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: Map<string>, exportedModulesMapCache?: ComputingExportedModulesMap): 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
|
||||
@@ -176,11 +212,11 @@ namespace ts.BuilderState {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash)) {
|
||||
if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) {
|
||||
return [sourceFile];
|
||||
}
|
||||
|
||||
const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash);
|
||||
const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache);
|
||||
if (!cacheToUpdateSignature) {
|
||||
// Commit all the signatures in the signature cache
|
||||
updateSignaturesFromCache(state, signatureCache);
|
||||
@@ -202,8 +238,9 @@ namespace ts.BuilderState {
|
||||
/**
|
||||
* 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) {
|
||||
function updateShapeSignature(state: Readonly<BuilderState>, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map<string>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) {
|
||||
Debug.assert(!!sourceFile);
|
||||
Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state");
|
||||
|
||||
// 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)) {
|
||||
@@ -222,16 +259,105 @@ namespace ts.BuilderState {
|
||||
const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken);
|
||||
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
|
||||
latestSignature = computeHash(emitOutput.outputFiles[0].text);
|
||||
if (exportedModulesMapCache && latestSignature !== prevSignature) {
|
||||
updateExportedModules(programOfThisState, sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache);
|
||||
}
|
||||
}
|
||||
else {
|
||||
latestSignature = prevSignature!; // TODO: GH#18217
|
||||
}
|
||||
|
||||
}
|
||||
cacheToUpdateSignature.set(sourceFile.path, latestSignature);
|
||||
|
||||
return !prevSignature || latestSignature !== prevSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coverts the declaration emit result into exported modules map
|
||||
*/
|
||||
function updateExportedModules(programOfThisState: Program, sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) {
|
||||
if (!exportedModulesFromDeclarationEmit) {
|
||||
exportedModulesMapCache.set(sourceFile.path, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const checker = programOfThisState.getTypeChecker();
|
||||
let exportedModules: Map<true> | undefined;
|
||||
|
||||
exportedModulesFromDeclarationEmit.exportedModuleSpecifiers.forEach(importName =>
|
||||
addExportedModule(getReferencedFileFromImportLiteral(checker, importName)));
|
||||
exportedModulesFromDeclarationEmit.exportedModuleSymbolsUsingImportTypeNodes.forEach(symbol =>
|
||||
addExportedModule(getReferencedFileFromImportedModuleSymbol(symbol)));
|
||||
|
||||
exportedModulesMapCache.set(sourceFile.path, exportedModules || false);
|
||||
|
||||
function addExportedModule(exportedModulePath: Path | undefined) {
|
||||
if (exportedModulePath) {
|
||||
if (!exportedModules) {
|
||||
exportedModules = createMap<true>();
|
||||
}
|
||||
exportedModules.set(exportedModulePath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the exported modules from cache into state's exported modules map
|
||||
* This should be called whenever it is safe to commit the state of the builder
|
||||
*/
|
||||
export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) {
|
||||
if (exportedModulesMapCache) {
|
||||
Debug.assert(!!state.exportedModulesMap);
|
||||
exportedModulesMapCache.forEach((exportedModules, path) => {
|
||||
if (exportedModules) {
|
||||
state.exportedModulesMap!.set(path, exportedModules);
|
||||
}
|
||||
else {
|
||||
state.exportedModulesMap!.delete(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the files affected by exported module
|
||||
*/
|
||||
export function getFilesAffectedByExportedModule(state: BuilderState, path: Path, exportedModulesMapCache?: ComputingExportedModulesMap): ReadonlyArray<Path> {
|
||||
if (!state.exportedModulesMap) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
Debug.assert(!!exportedModulesMapCache);
|
||||
let affectedFiles: Map<true> | undefined;
|
||||
// Go through exported modules from cache first
|
||||
exportedModulesMapCache!.forEach((exportedModules, exportedFromPath) => {
|
||||
// If exported modules has path, all files referencing file exported from are affected
|
||||
if (exportedModules && exportedModules.has(path)) {
|
||||
addFilesReferencing(exportedFromPath as Path);
|
||||
}
|
||||
});
|
||||
state.exportedModulesMap.forEach((exportedModules, exportedFromPath) => {
|
||||
// If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
|
||||
if (!exportedModulesMapCache!.has(exportedFromPath) && exportedModules.has(path)) {
|
||||
addFilesReferencing(exportedFromPath as Path);
|
||||
}
|
||||
});
|
||||
|
||||
return affectedFiles ? arrayFrom(affectedFiles.keys()) as Path[] : emptyArray;
|
||||
|
||||
function addFilesReferencing(referencingFilePath: Path) {
|
||||
state.referencedMap!.forEach((referencesInFile, filePath) => {
|
||||
if (referencesInFile.has(referencingFilePath)) {
|
||||
if (!affectedFiles) {
|
||||
affectedFiles = createMap<true>();
|
||||
}
|
||||
affectedFiles.set(filePath, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the dependencies of the sourceFile
|
||||
*/
|
||||
@@ -347,7 +473,7 @@ namespace ts.BuilderState {
|
||||
/**
|
||||
* 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) {
|
||||
function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map<string>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined, exportedModulesMapCache: ComputingExportedModulesMap | undefined) {
|
||||
if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) {
|
||||
return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape);
|
||||
}
|
||||
@@ -370,7 +496,7 @@ namespace ts.BuilderState {
|
||||
if (!seenFileNamesMap.has(currentPath)) {
|
||||
const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!;
|
||||
seenFileNamesMap.set(currentPath, currentSourceFile);
|
||||
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!)) { // TODO: GH#18217
|
||||
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!, exportedModulesMapCache)) { // TODO: GH#18217
|
||||
queue.push(...getReferencedByPaths(state, currentPath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,11 +1304,14 @@ export class B
|
||||
const watch = createWatchOfConfigFile("tsconfig.json", host);
|
||||
checkProgramActualFiles(watch(), [aFile.path, bFile.path, cFile.path, libFile.path]);
|
||||
checkOutputErrorsInitial(host, emptyArray);
|
||||
const modifiedTimeOfAJs = host.getModifiedTime(`${currentDirectory}/a.js`);
|
||||
host.writeFile(cFile.path, cFile.content.replace("d", "d2"));
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkOutputErrorsIncremental(host, [
|
||||
getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C")
|
||||
]);
|
||||
// File a need not be rewritten
|
||||
assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user