diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ac035284725..637e77c545f 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -38,6 +38,10 @@ namespace ts { * Already seen affected files */ seenAffectedFiles: Map | undefined; + /** + * whether this program has cleaned semantic diagnostics cache for lib files + */ + cleanedDiagnosticsOfLibFiles?: boolean; /** * True if the semantic diagnostics were copied from the old state */ @@ -64,9 +68,11 @@ namespace ts { state.semanticDiagnosticsPerFile = createMap>(); } state.changedFilesSet = createMap(); + const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); + const oldCompilerOptions = useOldState ? oldState!.program.getCompilerOptions() : undefined; const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && - !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldState!.program.getCompilerOptions()); + !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); if (useOldState) { // Verify the sanity of old state if (!oldState!.currentChangedFilePath) { @@ -83,6 +89,8 @@ namespace ts { // Update changed files and copy semantic diagnostics if we can const referencedMap = state.referencedMap; const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; + const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; + const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; state.fileInfos.forEach((info, sourceFilePath) => { let oldInfo: Readonly | undefined; let newReferences: BuilderState.ReferencedSet | undefined; @@ -101,6 +109,11 @@ namespace ts { state.changedFilesSet.set(sourceFilePath, true); } else if (canCopySemanticDiagnostics) { + const sourceFile = state.program.getSourceFileByPath(sourceFilePath as Path)!; + + if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { return; } + if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { return; } + // Unchanged file copy diagnostics const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); if (diagnostics) { @@ -193,6 +206,19 @@ namespace ts { return; } + // Clean lib file diagnostics if its all files excluding default files to emit + if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles && !state.cleanedDiagnosticsOfLibFiles) { + state.cleanedDiagnosticsOfLibFiles = true; + const options = state.program.getCompilerOptions(); + if (forEach(state.program.getSourceFiles(), f => + state.program.isSourceFileDefaultLibrary(f) && + !skipTypeChecking(f, options) && + removeSemanticDiagnosticsOf(state, f.path) + )) { + 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 @@ -268,7 +294,7 @@ namespace ts { */ function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { if (!state.semanticDiagnosticsFromOldState) { - return false; + return true; } state.semanticDiagnosticsFromOldState.delete(path); state.semanticDiagnosticsPerFile!.delete(path); diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 7cd67d99f72..7c7bebde9f9 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -368,7 +368,7 @@ namespace ts.BuilderState { } // If this is non module emit, or its a global file, it depends on all the source files - if (!state.referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { return getAllFileNames(state, programOfThisState); } @@ -430,6 +430,22 @@ namespace ts.BuilderState { return true; } + /** + * Return true if file contains anything that augments to global scope we need to build them as if + * they are global files as well as module + */ + function containsGlobalScopeAugmentation(sourceFile: SourceFile) { + return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); + } + + /** + * Return true if the file will invalidate all files because it affectes global scope + */ + function isFileAffectingGlobalScope(sourceFile: SourceFile) { + return containsGlobalScopeAugmentation(sourceFile) || + !isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile); + } + /** * Gets all files of the program excluding the default library file */ @@ -473,7 +489,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, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { - if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { + if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f06a284a729..219cf553263 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25110,6 +25110,11 @@ namespace ts { checkParameterInitializer(node); } } + if (symbol.declarations.length > 1) { + if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { + error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + } + } } else { // Node is a secondary declaration, check that type is identical to primary declaration and check that @@ -25125,7 +25130,6 @@ namespace ts { checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); } if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { - error(getNameOfDeclaration(symbol.valueDeclaration), Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } } diff --git a/src/testRunner/unittests/tscWatchMode.ts b/src/testRunner/unittests/tscWatchMode.ts index f77b4bce0eb..65f64b9da78 100644 --- a/src/testRunner/unittests/tscWatchMode.ts +++ b/src/testRunner/unittests/tscWatchMode.ts @@ -1564,6 +1564,146 @@ export class Data2 { verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib2Data2, lib1Public, lib1ToolsPublic, lib1ToolsInterface]); }); }); + + describe("updates errors in lib file", () => { + const currentDirectory = "/user/username/projects/myproject"; + const field = "fullscreen"; + const fieldWithoutReadonly = `interface Document { + ${field}: boolean; +}`; + + const libFileWithDocument: File = { + path: libFile.path, + content: `${libFile.content} +interface Document { + readonly ${field}: boolean; +}` + }; + + function getDiagnostic(program: Program, file: File) { + return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(field), field.length, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, field); + } + + function verifyLibFileErrorsWith(aFile: File) { + const files = [aFile, libFileWithDocument]; + + function verifyLibErrors(options: CompilerOptions) { + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfFilesAndCompilerOptions([aFile.path], host, options); + checkProgramActualFiles(watch(), [aFile.path, libFile.path]); + checkOutputErrorsInitial(host, getErrors()); + + host.writeFile(aFile.path, aFile.content.replace(fieldWithoutReadonly, "var x: string;")); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), [aFile.path, libFile.path]); + checkOutputErrorsIncremental(host, emptyArray); + + host.writeFile(aFile.path, aFile.content); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), [aFile.path, libFile.path]); + checkOutputErrorsIncremental(host, getErrors()); + + function getErrors() { + return [ + ...(options.skipLibCheck || options.skipDefaultLibCheck ? [] : [getDiagnostic(watch(), libFileWithDocument)]), + getDiagnostic(watch(), aFile) + ]; + } + } + + it("with default options", () => { + verifyLibErrors({}); + }); + it("with skipLibCheck", () => { + verifyLibErrors({ skipLibCheck: true }); + }); + it("with skipDefaultLibCheck", () => { + verifyLibErrors({ skipDefaultLibCheck: true }); + }); + } + + describe("when non module file changes", () => { + const aFile: File = { + path: `${currentDirectory}/a.ts`, + content: `${fieldWithoutReadonly} +var y: number;` + }; + verifyLibFileErrorsWith(aFile); + }); + + describe("when module file with global definitions changes", () => { + const aFile: File = { + path: `${currentDirectory}/a.ts`, + content: `export {} +declare global { +${fieldWithoutReadonly} +var y: number; +}` + }; + verifyLibFileErrorsWith(aFile); + }); + }); + + it("when skipLibCheck and skipDefaultLibCheck changes", () => { + const currentDirectory = "/user/username/projects/myproject"; + const field = "fullscreen"; + const aFile: File = { + path: `${currentDirectory}/a.ts`, + content: `interface Document { + ${field}: boolean; +}` + }; + const bFile: File = { + path: `${currentDirectory}/b.d.ts`, + content: `interface Document { + ${field}: boolean; +}` + }; + const libFileWithDocument: File = { + path: libFile.path, + content: `${libFile.content} +interface Document { + readonly ${field}: boolean; +}` + }; + const configFile: File = { + path: `${currentDirectory}/tsconfig.json`, + content: "{}" + }; + + const files = [aFile, bFile, configFile, libFileWithDocument]; + + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfConfigFile("tsconfig.json", host); + verifyProgramFiles(); + checkOutputErrorsInitial(host, [ + getDiagnostic(libFileWithDocument), + getDiagnostic(aFile), + getDiagnostic(bFile) + ]); + + verifyConfigChange({ skipLibCheck: true }, [aFile]); + verifyConfigChange({ skipDefaultLibCheck: true }, [aFile, bFile]); + verifyConfigChange({}, [libFileWithDocument, aFile, bFile]); + verifyConfigChange({ skipDefaultLibCheck: true }, [aFile, bFile]); + verifyConfigChange({ skipLibCheck: true }, [aFile]); + verifyConfigChange({}, [libFileWithDocument, aFile, bFile]); + + function verifyConfigChange(compilerOptions: CompilerOptions, errorInFiles: ReadonlyArray) { + host.writeFile(configFile.path, JSON.stringify({ compilerOptions })); + host.runQueuedTimeoutCallbacks(); + verifyProgramFiles(); + checkOutputErrorsIncremental(host, errorInFiles.map(getDiagnostic)); + } + + function getDiagnostic(file: File) { + return getDiagnosticOfFileFromProgram(watch(), file.path, file.content.indexOf(field), field.length, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, field); + } + + function verifyProgramFiles() { + checkProgramActualFiles(watch(), [aFile.path, bFile.path, libFile.path]); + } + }); }); describe("tsc-watch emit with outFile or out setting", () => {