Merge pull request #25593 from Microsoft/errorInFileWithDeepImport

Report errors correctly in watch mode by invalidating errors from files referencing modules that export from changed file
This commit is contained in:
Sheetal Nandi 2018-08-01 17:06:27 -07:00 committed by GitHub
commit 76f7ee998a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 255 additions and 24 deletions

View File

@ -30,10 +30,18 @@ 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
*/
seenAffectedFiles: Map<true> | undefined;
/**
* True if the semantic diagnostics were copied from the old state
*/
semanticDiagnosticsFromOldState?: Map<true>;
/**
* program corresponding to this state
*/
@ -96,6 +104,10 @@ namespace ts {
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
if (diagnostics) {
state.semanticDiagnosticsPerFile!.set(sourceFilePath, diagnostics);
if (!state.semanticDiagnosticsFromOldState) {
state.semanticDiagnosticsFromOldState = createMap<true>();
}
state.semanticDiagnosticsFromOldState.set(sourceFilePath, true);
}
}
});
@ -120,17 +132,17 @@ namespace ts {
while (true) {
const { affectedFiles } = state;
if (affectedFiles) {
const { seenAffectedFiles, semanticDiagnosticsPerFile } = state;
const seenAffectedFiles = state.seenAffectedFiles!;
let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217
while (affectedFilesIndex < affectedFiles.length) {
const affectedFile = affectedFiles[affectedFilesIndex];
if (!seenAffectedFiles!.has(affectedFile.path)) {
if (!seenAffectedFiles.has(affectedFile.path)) {
// Set the next affected file as seen and remove the cached semantic diagnostics
state.affectedFilesIndex = affectedFilesIndex;
semanticDiagnosticsPerFile!.delete(affectedFile.path);
cleanSemanticDiagnosticsOfAffectedFile(state, affectedFile);
return affectedFile;
}
seenAffectedFiles!.set(affectedFile.path, true);
seenAffectedFiles.set(affectedFile.path, true);
affectedFilesIndex++;
}
@ -140,6 +152,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,14 +173,74 @@ 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;
state.seenAffectedFiles = state.seenAffectedFiles || createMap<true>();
}
}
/**
* Remove the semantic diagnostics cached from old state for affected File and the files that are referencing modules that export entities from affected file
*/
function cleanSemanticDiagnosticsOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile) {
if (removeSemanticDiagnosticsOf(state, affectedFile.path)) {
// If there are no more diagnostics from old cache, done
return;
}
// If there was change in signature for the changed file,
// then delete the semantic diagnostics for files that are affected by using exports of this module
if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) {
return;
}
Debug.assert(!!state.currentAffectedFilesExportedModulesMap);
// Go through exported modules from cache first
// If exported modules has path, all files referencing file exported from are affected
if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) =>
exportedModules &&
exportedModules.has(affectedFile.path) &&
removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path)
)) {
return;
}
// If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) =>
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
exportedModules.has(affectedFile.path) &&
removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path)
);
}
/**
* removes the semantic diagnostics of files referencing referencedPath and
* returns true if there are no more semantic diagnostics from old state
*/
function removeSemanticDiagnosticsOfFilesReferencingPath(state: BuilderProgramState, referencedPath: Path) {
return forEachEntry(state.referencedMap!, (referencesInFile, filePath) =>
referencesInFile.has(referencedPath) && removeSemanticDiagnosticsOf(state, filePath as Path)
);
}
/**
* Removes semantic diagnostics for path and
* returns true if there are no more semantic diagnostics from the old state
*/
function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) {
if (!state.semanticDiagnosticsFromOldState) {
return false;
}
state.semanticDiagnosticsFromOldState.delete(path);
state.semanticDiagnosticsPerFile!.delete(path);
return !state.semanticDiagnosticsFromOldState.size;
}
/**
* This is called after completing operation on the next affected file.
* The operations here are postponed to ensure that cancellation during the iteration is handled correctly

View File

@ -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,61 @@ 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(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(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) {
if (!exportedModulesFromDeclarationEmit) {
exportedModulesMapCache.set(sourceFile.path, false);
return;
}
let exportedModules: Map<true> | undefined;
exportedModulesFromDeclarationEmit.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);
}
});
}
}
/**
* Get all the dependencies of the sourceFile
*/
@ -347,7 +429,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 +452,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));
}
}

View File

@ -3914,6 +3914,7 @@ namespace ts {
const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context);
const specifier = getSpecifierForModuleSymbol(chain[0], context);
const lit = createLiteralTypeNode(createLiteral(specifier));
if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]);
context.approximateLength += specifier.length + 10; // specifier + import("")
if (!nonRootParts || isEntityName(nonRootParts)) {
if (nonRootParts) {
@ -28088,7 +28089,8 @@ namespace ts {
setAccessor,
getAccessor
};
}
},
getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined)
};
function isInHeritageClause(node: PropertyAccessEntityNameExpression) {

View File

@ -113,6 +113,7 @@ namespace ts {
let bundleInfo: BundleInfo = createDefaultBundleInfo();
let emitSkipped = false;
let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined;
// Emit each output file
performance.mark("beforePrint");
@ -125,6 +126,7 @@ namespace ts {
diagnostics: emitterDiagnostics.getDiagnostics(),
emittedFiles: emittedFilesList,
sourceMaps: sourceMapDataList,
exportedModulesFromDeclarationEmit
};
function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) {
@ -222,6 +224,10 @@ namespace ts {
if (!declBlocked || emitOnlyDtsFiles) {
Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform");
printSourceFileOrBundle(declarationFilePath, declarationMapPath, declarationTransform.transformed[0], /* bundleInfopath*/ undefined, declarationPrinter, declarationSourceMap);
if (emitOnlyDtsFiles && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) {
const sourceFile = declarationTransform.transformed[0] as SourceFile;
exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit;
}
}
declarationTransform.dispose();
}

View File

@ -37,6 +37,7 @@ namespace ts {
let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined;
let lateStatementReplacementMap: Map<VisitResult<LateVisibilityPaintedStatement>>;
let suppressNewDiagnosticContexts: boolean;
let exportedModulesFromDeclarationEmit: Symbol[] | undefined;
const host = context.getEmitHost();
const symbolTracker: SymbolTracker = {
@ -46,6 +47,7 @@ namespace ts {
reportPrivateInBaseOfClassExpression,
moduleResolverHost: host,
trackReferencedAmbientModule,
trackExternalModuleSymbolOfImportTypeNode
};
let errorNameNode: DeclarationName | undefined;
@ -115,6 +117,12 @@ namespace ts {
}
}
function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) {
if (!isBundledEmit) {
(exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol);
}
}
function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) {
if (symbol.flags & SymbolFlags.TypeParameter) return;
handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true));
@ -224,6 +232,7 @@ namespace ts {
combinedStatements = setTextRange(createNodeArray([...combinedStatements, createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined)]), combinedStatements);
}
const updated = updateSourceFileNode(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib);
updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit;
return updated;
function getFileReferencesForUsedTypeReferences() {
@ -483,10 +492,18 @@ namespace ts {
function rewriteModuleSpecifier<T extends Node>(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T | undefined): T | StringLiteral {
if (!input) return undefined!; // TODO: GH#18217
resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportType);
if (input.kind === SyntaxKind.StringLiteral && isBundledEmit) {
const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent);
if (newName) {
return createLiteral(newName);
if (isStringLiteralLike(input)) {
if (isBundledEmit) {
const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent);
if (newName) {
return createLiteral(newName);
}
}
else {
const symbol = resolver.getSymbolOfExternalModuleSpecifier(input);
if (symbol) {
(exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol);
}
}
}
return input;

View File

@ -2633,8 +2633,13 @@ namespace ts {
/* @internal */ pragmas: PragmaMap;
/* @internal */ localJsxNamespace?: __String;
/* @internal */ localJsxFactory?: EntityName;
/*@internal*/ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
}
/*@internal*/
export type ExportedModulesFromDeclarationEmit = ReadonlyArray<Symbol>;
export interface Bundle extends Node {
kind: SyntaxKind.Bundle;
prepends: ReadonlyArray<InputFiles | UnparsedSource>;
@ -2878,6 +2883,7 @@ namespace ts {
diagnostics: ReadonlyArray<Diagnostic>;
emittedFiles?: string[]; // Array of files the compiler wrote to disk
/* @internal */ sourceMaps?: SourceMapData[]; // Array of sourceMapData if compiler emitted sourcemaps
/* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
}
/* @internal */
@ -3381,6 +3387,7 @@ namespace ts {
isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean;
getJsxFactoryEntity(location?: Node): EntityName | undefined;
getAllAccessorDeclarations(declaration: AccessorDeclaration): AllAccessorDeclarations;
getSymbolOfExternalModuleSpecifier(node: StringLiteralLike): Symbol | undefined;
}
export const enum SymbolFlags {
@ -5338,6 +5345,7 @@ namespace ts {
reportInaccessibleUniqueSymbolError?(): void;
moduleResolverHost?: ModuleSpecifierResolutionHost;
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
trackExternalModuleSymbolOfImportTypeNode?(symbol: Symbol): void;
}
export interface TextSpan {

View File

@ -1270,6 +1270,49 @@ export default test;`;
checkOutputErrorsIncremental(host, expectedErrors);
}
});
it("updates errors when deep import file changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `import {B} from './b';
declare var console: any;
let b = new B();
console.log(b.c.d);`
};
const bFile: File = {
path: `${currentDirectory}/b.ts`,
content: `import {C} from './c';
export class B
{
c = new C();
}`
};
const cFile: File = {
path: `${currentDirectory}/c.ts`,
content: `export class C
{
d = 1;
}`
};
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: `{}`
};
const files = [aFile, bFile, cFile, config, libFile];
const host = createWatchedSystem(files, { currentDirectory });
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);
});
});
describe("tsc-watch emit with outFile or out setting", () => {