Skip to content

Commit 7c92d09

Browse files
committed
When removing the errors for the exports from the file, apart from removing transitive exports, remove the diagnostics of file that import these exports
Fixes #28983
1 parent fecbdb6 commit 7c92d09

File tree

2 files changed

+159
-27
lines changed

2 files changed

+159
-27
lines changed

src/compiler/builder.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,19 @@ namespace ts {
281281
}
282282

283283
// If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
284-
return !!forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
284+
if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
285285
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
286286
exportedModules.has(filePath) &&
287287
removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile)
288+
)) {
289+
return true;
290+
}
291+
292+
// Remove diagnostics of files that import this file (without going to exports of referencing files)
293+
return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) =>
294+
referencesInFile.has(filePath) &&
295+
!seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file
296+
removeSemanticDiagnosticsOf(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal
288297
);
289298
}
290299

src/testRunner/unittests/tscWatchMode.ts

Lines changed: 149 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,18 @@ namespace ts.tscWatch {
2727
return () => watch.getCurrentProgram();
2828
}
2929

30+
interface Watch {
31+
(): Program;
32+
getBuilderProgram(): EmitAndSemanticDiagnosticsBuilderProgram;
33+
}
34+
3035
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
3136
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
3237
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
3338
const watch = createWatchProgram(compilerHost);
34-
return () => watch.getCurrentProgram().getProgram();
39+
const result = (() => watch.getCurrentProgram().getProgram()) as Watch;
40+
result.getBuilderProgram = () => watch.getCurrentProgram();
41+
return result;
3542
}
3643

3744
function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) {
@@ -182,7 +189,22 @@ namespace ts.tscWatch {
182189
assert.equal(host.exitCode, expectedExitCode);
183190
}
184191

185-
function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic {
192+
function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain {
193+
return !!(message as DiagnosticMessageChain).messageText;
194+
}
195+
function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ..._args: (string | number)[]): Diagnostic {
196+
let text: DiagnosticMessageChain | string;
197+
if (isDiagnosticMessageChain(message)) {
198+
text = message;
199+
}
200+
else {
201+
text = getLocaleSpecificMessage(message);
202+
203+
if (arguments.length > 4) {
204+
text = formatStringFromArgs(text, arguments, 4);
205+
}
206+
}
207+
186208
return {
187209
file,
188210
start,
@@ -194,40 +216,22 @@ namespace ts.tscWatch {
194216
};
195217
}
196218

197-
function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
198-
let text = getLocaleSpecificMessage(message);
199-
200-
if (arguments.length > 1) {
201-
text = formatStringFromArgs(text, arguments, 1);
202-
}
203-
204-
return getDiagnosticOfFileFrom(/*file*/ undefined, text, /*start*/ undefined, /*length*/ undefined, message);
219+
function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
220+
return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args);
205221
}
206222

207-
function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
208-
let text = getLocaleSpecificMessage(message);
209-
210-
if (arguments.length > 4) {
211-
text = formatStringFromArgs(text, arguments, 4);
212-
}
213-
214-
return getDiagnosticOfFileFrom(file, text, start, length, message);
223+
function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
224+
return getDiagnosticOfFileFrom(file, start, length, message, ...args);
215225
}
216226

217227
function getUnknownCompilerOption(program: Program, configFile: File, option: string) {
218228
const quotedOption = `"${option}"`;
219229
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
220230
}
221231

222-
function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
223-
let text = getLocaleSpecificMessage(message);
224-
225-
if (arguments.length > 5) {
226-
text = formatStringFromArgs(text, arguments, 5);
227-
}
228-
232+
function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
229233
return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!,
230-
text, start, length, message);
234+
start, length, message, ...args);
231235
}
232236

233237
function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) {
@@ -1706,6 +1710,125 @@ interface Document {
17061710
});
17071711
});
17081712

1713+
describe("Emit times and Error updates in builder after program changes", () => {
1714+
function getOutputFileStampAndError(host: WatchedSystem, watch: Watch, file: File) {
1715+
const builderProgram = watch.getBuilderProgram();
1716+
const state = builderProgram.getState();
1717+
return {
1718+
file,
1719+
fileStamp: host.getModifiedTime(file.path.replace(".ts", ".js")),
1720+
errors: builderProgram.getSemanticDiagnostics(watch().getSourceFileByPath(file.path as Path)),
1721+
errorsFromOldState: !!state.semanticDiagnosticsFromOldState && state.semanticDiagnosticsFromOldState.has(file.path)
1722+
};
1723+
}
1724+
1725+
function getOutputFileStampsAndErrors(host: WatchedSystem, watch: Watch, directoryFiles: ReadonlyArray<File>) {
1726+
return directoryFiles.map(d => getOutputFileStampAndError(host, watch, d));
1727+
}
1728+
1729+
function findStampAndErrors(stampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>, file: File) {
1730+
return find(stampsAndErrors, info => info.file === file)!;
1731+
}
1732+
1733+
function verifyOutputFileStampsAndErrors(
1734+
file: File,
1735+
emitExpected: boolean,
1736+
errorRefershExpected: boolean,
1737+
beforeChangeFileStampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>,
1738+
afterChangeFileStampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>
1739+
) {
1740+
const beforeChange = findStampAndErrors(beforeChangeFileStampsAndErrors, file);
1741+
const afterChange = findStampAndErrors(afterChangeFileStampsAndErrors, file);
1742+
if (emitExpected) {
1743+
assert.notStrictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Expected emit for file ${file.path}`);
1744+
}
1745+
else {
1746+
assert.strictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Did not expect new emit for file ${file.path}`);
1747+
}
1748+
if (errorRefershExpected) {
1749+
if (afterChange.errors !== emptyArray || beforeChange.errors !== emptyArray) {
1750+
assert.notStrictEqual(afterChange.errors, beforeChange.errors, `Expected new errors for file ${file.path}`);
1751+
}
1752+
assert.isFalse(afterChange.errorsFromOldState, `Expected errors to be not copied from old state for file ${file.path}`);
1753+
}
1754+
else {
1755+
assert.strictEqual(afterChange.errors, beforeChange.errors, `Expected errors to not change for file ${file.path}`);
1756+
assert.isTrue(afterChange.errorsFromOldState, `Expected errors to be copied from old state for file ${file.path}`);
1757+
}
1758+
}
1759+
1760+
it("updates errors in file not exporting a deep multilevel import that changes", () => {
1761+
const currentDirectory = "/user/username/projects/myproject";
1762+
const aFile: File = {
1763+
path: `${currentDirectory}/a.ts`,
1764+
content: `export interface Point {
1765+
name: string;
1766+
c: Coords;
1767+
}
1768+
export interface Coords {
1769+
x2: number;
1770+
y: number;
1771+
}`
1772+
};
1773+
const bFile: File = {
1774+
path: `${currentDirectory}/b.ts`,
1775+
content: `import { Point } from "./a";
1776+
export interface PointWrapper extends Point {
1777+
}`
1778+
};
1779+
const cFile: File = {
1780+
path: `${currentDirectory}/c.ts`,
1781+
content: `import { PointWrapper } from "./b";
1782+
export function getPoint(): PointWrapper {
1783+
return {
1784+
name: "test",
1785+
c: {
1786+
x: 1,
1787+
y: 2
1788+
}
1789+
}
1790+
};`
1791+
};
1792+
const dFile: File = {
1793+
path: `${currentDirectory}/d.ts`,
1794+
content: `import { getPoint } from "./c";
1795+
getPoint().c.x;`
1796+
};
1797+
const eFile: File = {
1798+
path: `${currentDirectory}/e.ts`,
1799+
content: `import "./d";`
1800+
};
1801+
const config: File = {
1802+
path: `${currentDirectory}/tsconfig.json`,
1803+
content: `{}`
1804+
};
1805+
const directoryFiles = [aFile, bFile, cFile, dFile, eFile];
1806+
const files = [...directoryFiles, config, libFile];
1807+
const host = createWatchedSystem(files, { currentDirectory });
1808+
const watch = createWatchOfConfigFile("tsconfig.json", host);
1809+
checkProgramActualFiles(watch(), [aFile.path, bFile.path, cFile.path, dFile.path, eFile.path, libFile.path]);
1810+
checkOutputErrorsInitial(host, [
1811+
getDiagnosticOfFileFromProgram(watch(), cFile.path, cFile.content.indexOf("x: 1"), 4, chainDiagnosticMessages(
1812+
chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, "x", "Coords"),
1813+
Diagnostics.Type_0_is_not_assignable_to_type_1,
1814+
"{ x: number; y: number; }",
1815+
"Coords"
1816+
)),
1817+
getDiagnosticOfFileFromProgram(watch(), dFile.path, dFile.content.lastIndexOf("x"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "x", "Coords")
1818+
]);
1819+
const beforeChange = getOutputFileStampsAndErrors(host, watch, directoryFiles);
1820+
host.writeFile(aFile.path, aFile.content.replace("x2", "x"));
1821+
host.runQueuedTimeoutCallbacks();
1822+
checkOutputErrorsIncremental(host, emptyArray);
1823+
const afterChange = getOutputFileStampsAndErrors(host, watch, directoryFiles);
1824+
verifyOutputFileStampsAndErrors(aFile, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange);
1825+
verifyOutputFileStampsAndErrors(bFile, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange);
1826+
verifyOutputFileStampsAndErrors(cFile, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange);
1827+
verifyOutputFileStampsAndErrors(dFile, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange);
1828+
verifyOutputFileStampsAndErrors(eFile, /*emitExpected*/ false, /*errorRefershExpected*/ false, beforeChange, afterChange);
1829+
});
1830+
});
1831+
17091832
describe("tsc-watch emit with outFile or out setting", () => {
17101833
function createWatchForOut(out?: string, outFile?: string) {
17111834
const host = createWatchedSystem([]);

0 commit comments

Comments
 (0)