Skip to content

Commit b0d85c0

Browse files
authored
Merge pull request #28450 from Microsoft/libErrors
Remove lib file errors from builder cache when global files are to be emitted
2 parents 7d4e0e6 + 1b8bfc8 commit b0d85c0

File tree

4 files changed

+191
-5
lines changed

4 files changed

+191
-5
lines changed

src/compiler/builder.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ namespace ts {
3838
* Already seen affected files
3939
*/
4040
seenAffectedFiles: Map<true> | undefined;
41+
/**
42+
* whether this program has cleaned semantic diagnostics cache for lib files
43+
*/
44+
cleanedDiagnosticsOfLibFiles?: boolean;
4145
/**
4246
* True if the semantic diagnostics were copied from the old state
4347
*/
@@ -64,9 +68,11 @@ namespace ts {
6468
state.semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
6569
}
6670
state.changedFilesSet = createMap<true>();
71+
6772
const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState);
73+
const oldCompilerOptions = useOldState ? oldState!.program.getCompilerOptions() : undefined;
6874
const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile &&
69-
!compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldState!.program.getCompilerOptions());
75+
!compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!);
7076
if (useOldState) {
7177
// Verify the sanity of old state
7278
if (!oldState!.currentChangedFilePath) {
@@ -83,6 +89,8 @@ namespace ts {
8389
// Update changed files and copy semantic diagnostics if we can
8490
const referencedMap = state.referencedMap;
8591
const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined;
92+
const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck;
93+
const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck;
8694
state.fileInfos.forEach((info, sourceFilePath) => {
8795
let oldInfo: Readonly<BuilderState.FileInfo> | undefined;
8896
let newReferences: BuilderState.ReferencedSet | undefined;
@@ -101,6 +109,11 @@ namespace ts {
101109
state.changedFilesSet.set(sourceFilePath, true);
102110
}
103111
else if (canCopySemanticDiagnostics) {
112+
const sourceFile = state.program.getSourceFileByPath(sourceFilePath as Path)!;
113+
114+
if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { return; }
115+
if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { return; }
116+
104117
// Unchanged file copy diagnostics
105118
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
106119
if (diagnostics) {
@@ -193,6 +206,19 @@ namespace ts {
193206
return;
194207
}
195208

209+
// Clean lib file diagnostics if its all files excluding default files to emit
210+
if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles && !state.cleanedDiagnosticsOfLibFiles) {
211+
state.cleanedDiagnosticsOfLibFiles = true;
212+
const options = state.program.getCompilerOptions();
213+
if (forEach(state.program.getSourceFiles(), f =>
214+
state.program.isSourceFileDefaultLibrary(f) &&
215+
!skipTypeChecking(f, options) &&
216+
removeSemanticDiagnosticsOf(state, f.path)
217+
)) {
218+
return;
219+
}
220+
}
221+
196222
// If there was change in signature for the changed file,
197223
// then delete the semantic diagnostics for files that are affected by using exports of this module
198224

@@ -268,7 +294,7 @@ namespace ts {
268294
*/
269295
function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) {
270296
if (!state.semanticDiagnosticsFromOldState) {
271-
return false;
297+
return true;
272298
}
273299
state.semanticDiagnosticsFromOldState.delete(path);
274300
state.semanticDiagnosticsPerFile!.delete(path);

src/compiler/builderState.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ namespace ts.BuilderState {
368368
}
369369

370370
// If this is non module emit, or its a global file, it depends on all the source files
371-
if (!state.referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) {
371+
if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) {
372372
return getAllFileNames(state, programOfThisState);
373373
}
374374

@@ -430,6 +430,22 @@ namespace ts.BuilderState {
430430
return true;
431431
}
432432

433+
/**
434+
* Return true if file contains anything that augments to global scope we need to build them as if
435+
* they are global files as well as module
436+
*/
437+
function containsGlobalScopeAugmentation(sourceFile: SourceFile) {
438+
return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration));
439+
}
440+
441+
/**
442+
* Return true if the file will invalidate all files because it affectes global scope
443+
*/
444+
function isFileAffectingGlobalScope(sourceFile: SourceFile) {
445+
return containsGlobalScopeAugmentation(sourceFile) ||
446+
!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile);
447+
}
448+
433449
/**
434450
* Gets all files of the program excluding the default library file
435451
*/
@@ -473,7 +489,7 @@ namespace ts.BuilderState {
473489
* When program emits modular code, gets the files affected by the sourceFile whose shape has changed
474490
*/
475491
function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map<string>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined, exportedModulesMapCache: ComputingExportedModulesMap | undefined) {
476-
if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) {
492+
if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) {
477493
return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape);
478494
}
479495

src/compiler/checker.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25110,6 +25110,11 @@ namespace ts {
2511025110
checkParameterInitializer(node);
2511125111
}
2511225112
}
25113+
if (symbol.declarations.length > 1) {
25114+
if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) {
25115+
error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name));
25116+
}
25117+
}
2511325118
}
2511425119
else {
2511525120
// Node is a secondary declaration, check that type is identical to primary declaration and check that
@@ -25125,7 +25130,6 @@ namespace ts {
2512525130
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined);
2512625131
}
2512725132
if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) {
25128-
error(getNameOfDeclaration(symbol.valueDeclaration), Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name));
2512925133
error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name));
2513025134
}
2513125135
}

src/testRunner/unittests/tscWatchMode.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,6 +1564,146 @@ export class Data2 {
15641564
verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib2Data2, lib1Public, lib1ToolsPublic, lib1ToolsInterface]);
15651565
});
15661566
});
1567+
1568+
describe("updates errors in lib file", () => {
1569+
const currentDirectory = "/user/username/projects/myproject";
1570+
const field = "fullscreen";
1571+
const fieldWithoutReadonly = `interface Document {
1572+
${field}: boolean;
1573+
}`;
1574+
1575+
const libFileWithDocument: File = {
1576+
path: libFile.path,
1577+
content: `${libFile.content}
1578+
interface Document {
1579+
readonly ${field}: boolean;
1580+
}`
1581+
};
1582+
1583+
function getDiagnostic(program: Program, file: File) {
1584+
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(field), field.length, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, field);
1585+
}
1586+
1587+
function verifyLibFileErrorsWith(aFile: File) {
1588+
const files = [aFile, libFileWithDocument];
1589+
1590+
function verifyLibErrors(options: CompilerOptions) {
1591+
const host = createWatchedSystem(files, { currentDirectory });
1592+
const watch = createWatchOfFilesAndCompilerOptions([aFile.path], host, options);
1593+
checkProgramActualFiles(watch(), [aFile.path, libFile.path]);
1594+
checkOutputErrorsInitial(host, getErrors());
1595+
1596+
host.writeFile(aFile.path, aFile.content.replace(fieldWithoutReadonly, "var x: string;"));
1597+
host.runQueuedTimeoutCallbacks();
1598+
checkProgramActualFiles(watch(), [aFile.path, libFile.path]);
1599+
checkOutputErrorsIncremental(host, emptyArray);
1600+
1601+
host.writeFile(aFile.path, aFile.content);
1602+
host.runQueuedTimeoutCallbacks();
1603+
checkProgramActualFiles(watch(), [aFile.path, libFile.path]);
1604+
checkOutputErrorsIncremental(host, getErrors());
1605+
1606+
function getErrors() {
1607+
return [
1608+
...(options.skipLibCheck || options.skipDefaultLibCheck ? [] : [getDiagnostic(watch(), libFileWithDocument)]),
1609+
getDiagnostic(watch(), aFile)
1610+
];
1611+
}
1612+
}
1613+
1614+
it("with default options", () => {
1615+
verifyLibErrors({});
1616+
});
1617+
it("with skipLibCheck", () => {
1618+
verifyLibErrors({ skipLibCheck: true });
1619+
});
1620+
it("with skipDefaultLibCheck", () => {
1621+
verifyLibErrors({ skipDefaultLibCheck: true });
1622+
});
1623+
}
1624+
1625+
describe("when non module file changes", () => {
1626+
const aFile: File = {
1627+
path: `${currentDirectory}/a.ts`,
1628+
content: `${fieldWithoutReadonly}
1629+
var y: number;`
1630+
};
1631+
verifyLibFileErrorsWith(aFile);
1632+
});
1633+
1634+
describe("when module file with global definitions changes", () => {
1635+
const aFile: File = {
1636+
path: `${currentDirectory}/a.ts`,
1637+
content: `export {}
1638+
declare global {
1639+
${fieldWithoutReadonly}
1640+
var y: number;
1641+
}`
1642+
};
1643+
verifyLibFileErrorsWith(aFile);
1644+
});
1645+
});
1646+
1647+
it("when skipLibCheck and skipDefaultLibCheck changes", () => {
1648+
const currentDirectory = "/user/username/projects/myproject";
1649+
const field = "fullscreen";
1650+
const aFile: File = {
1651+
path: `${currentDirectory}/a.ts`,
1652+
content: `interface Document {
1653+
${field}: boolean;
1654+
}`
1655+
};
1656+
const bFile: File = {
1657+
path: `${currentDirectory}/b.d.ts`,
1658+
content: `interface Document {
1659+
${field}: boolean;
1660+
}`
1661+
};
1662+
const libFileWithDocument: File = {
1663+
path: libFile.path,
1664+
content: `${libFile.content}
1665+
interface Document {
1666+
readonly ${field}: boolean;
1667+
}`
1668+
};
1669+
const configFile: File = {
1670+
path: `${currentDirectory}/tsconfig.json`,
1671+
content: "{}"
1672+
};
1673+
1674+
const files = [aFile, bFile, configFile, libFileWithDocument];
1675+
1676+
const host = createWatchedSystem(files, { currentDirectory });
1677+
const watch = createWatchOfConfigFile("tsconfig.json", host);
1678+
verifyProgramFiles();
1679+
checkOutputErrorsInitial(host, [
1680+
getDiagnostic(libFileWithDocument),
1681+
getDiagnostic(aFile),
1682+
getDiagnostic(bFile)
1683+
]);
1684+
1685+
verifyConfigChange({ skipLibCheck: true }, [aFile]);
1686+
verifyConfigChange({ skipDefaultLibCheck: true }, [aFile, bFile]);
1687+
verifyConfigChange({}, [libFileWithDocument, aFile, bFile]);
1688+
verifyConfigChange({ skipDefaultLibCheck: true }, [aFile, bFile]);
1689+
verifyConfigChange({ skipLibCheck: true }, [aFile]);
1690+
verifyConfigChange({}, [libFileWithDocument, aFile, bFile]);
1691+
1692+
function verifyConfigChange(compilerOptions: CompilerOptions, errorInFiles: ReadonlyArray<File>) {
1693+
host.writeFile(configFile.path, JSON.stringify({ compilerOptions }));
1694+
host.runQueuedTimeoutCallbacks();
1695+
verifyProgramFiles();
1696+
checkOutputErrorsIncremental(host, errorInFiles.map(getDiagnostic));
1697+
}
1698+
1699+
function getDiagnostic(file: File) {
1700+
return getDiagnosticOfFileFromProgram(watch(), file.path, file.content.indexOf(field), field.length, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, field);
1701+
}
1702+
1703+
function verifyProgramFiles() {
1704+
checkProgramActualFiles(watch(), [aFile.path, bFile.path, libFile.path]);
1705+
}
1706+
});
15671707
});
15681708

15691709
describe("tsc-watch emit with outFile or out setting", () => {

0 commit comments

Comments
 (0)