If module exports const enum, invalidate js files along with dts as it can impact js emit as well (#58594)

This commit is contained in:
Sheetal Nandi
2024-05-21 13:57:57 -07:00
committed by GitHub
parent 6f72e24544
commit 06841520db
8 changed files with 3942 additions and 31 deletions
+38 -6
View File
@@ -50,6 +50,7 @@ import {
getOptionsNameMap,
getOwnKeys,
getRelativePathFromDirectory,
getSourceFileOfNode,
getTsBuildInfoEmitOutputFilePath,
handleNoEmitOptions,
HostForComputeHash,
@@ -74,10 +75,13 @@ import {
sameMap,
SemanticDiagnosticsBuilderProgram,
SignatureInfo,
skipAlias,
skipTypeChecking,
some,
SourceFile,
sourceFileMayBeEmitted,
SourceMapEmitResult,
SymbolFlags,
toPath,
tryAddToSet,
WriteFileCallback,
@@ -762,6 +766,7 @@ function handleDtsMayChangeOfAffectedFile(
function handleDtsMayChangeOf(
state: BuilderProgramState,
path: Path,
invalidateJsFiles: boolean,
cancellationToken: CancellationToken | undefined,
host: BuilderProgramHost,
): void {
@@ -785,7 +790,10 @@ function handleDtsMayChangeOf(
/*useFileVersionAsSignature*/ true,
);
// If not dts emit, nothing more to do
if (getEmitDeclarations(state.compilerOptions)) {
if (invalidateJsFiles) {
addToAffectedFilesPendingEmit(state, path, getBuilderFileEmit(state.compilerOptions));
}
else if (getEmitDeclarations(state.compilerOptions)) {
addToAffectedFilesPendingEmit(state, path, state.compilerOptions.declarationMap ? BuilderFileEmit.AllDts : BuilderFileEmit.Dts);
}
}
@@ -814,6 +822,7 @@ function isChangedSignature(state: BuilderProgramState, path: Path) {
function handleDtsMayChangeOfGlobalScope(
state: BuilderProgramState,
filePath: Path,
invalidateJsFiles: boolean,
cancellationToken: CancellationToken | undefined,
host: BuilderProgramHost,
): boolean {
@@ -824,6 +833,7 @@ function handleDtsMayChangeOfGlobalScope(
handleDtsMayChangeOf(
state,
file.resolvedPath,
invalidateJsFiles,
cancellationToken,
host,
)
@@ -856,8 +866,16 @@ function handleDtsMayChangeOfReferencingExportOfAffectedFile(
const currentPath = queue.pop()!;
if (!seenFileNamesMap.has(currentPath)) {
seenFileNamesMap.set(currentPath, true);
if (handleDtsMayChangeOfGlobalScope(state, currentPath, cancellationToken, host)) return;
handleDtsMayChangeOf(state, currentPath, cancellationToken, host);
if (
handleDtsMayChangeOfGlobalScope(
state,
currentPath,
/*invalidateJsFiles*/ false,
cancellationToken,
host,
)
) return;
handleDtsMayChangeOf(state, currentPath, /*invalidateJsFiles*/ false, cancellationToken, host);
if (isChangedSignature(state, currentPath)) {
const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!;
queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath));
@@ -867,14 +885,26 @@ function handleDtsMayChangeOfReferencingExportOfAffectedFile(
}
const seenFileAndExportsOfFile = new Set<string>();
// If exported const enum, we need to ensure that js files are emitted as well since the const enum value changed
const invalidateJsFiles = !!affectedFile.symbol?.exports && !!forEachEntry(
affectedFile.symbol.exports,
exported => {
if ((exported.flags & SymbolFlags.ConstEnum) !== 0) return true;
const aliased = skipAlias(exported, state.program!.getTypeChecker());
if (aliased === exported) return false;
return (aliased.flags & SymbolFlags.ConstEnum) !== 0 &&
some(aliased.declarations, d => getSourceFileOfNode(d) === affectedFile);
},
);
// Go through files that reference affected file and handle dts emit and semantic diagnostics for them and their references
state.referencedMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => {
if (handleDtsMayChangeOfGlobalScope(state, exportedFromPath, cancellationToken, host)) return true;
if (handleDtsMayChangeOfGlobalScope(state, exportedFromPath, invalidateJsFiles, cancellationToken, host)) return true;
const references = state.referencedMap!.getKeys(exportedFromPath);
return references && forEachKey(references, filePath =>
handleDtsMayChangeOfFileAndExportsOfFile(
state,
filePath,
invalidateJsFiles,
seenFileAndExportsOfFile,
cancellationToken,
host,
@@ -889,20 +919,22 @@ function handleDtsMayChangeOfReferencingExportOfAffectedFile(
function handleDtsMayChangeOfFileAndExportsOfFile(
state: BuilderProgramState,
filePath: Path,
invalidateJsFiles: boolean,
seenFileAndExportsOfFile: Set<string>,
cancellationToken: CancellationToken | undefined,
host: BuilderProgramHost,
): boolean | undefined {
if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) return undefined;
if (handleDtsMayChangeOfGlobalScope(state, filePath, cancellationToken, host)) return true;
handleDtsMayChangeOf(state, filePath, cancellationToken, host);
if (handleDtsMayChangeOfGlobalScope(state, filePath, invalidateJsFiles, cancellationToken, host)) return true;
handleDtsMayChangeOf(state, filePath, invalidateJsFiles, cancellationToken, host);
// Remove the diagnostics of files that import this file and handle all its exports too
state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath =>
handleDtsMayChangeOfFileAndExportsOfFile(
state,
referencingFilePath,
invalidateJsFiles,
seenFileAndExportsOfFile,
cancellationToken,
host,
+100 -25
View File
@@ -1,6 +1,6 @@
import * as ts from "../../_namespaces/ts.js";
import * as Utils from "../../_namespaces/Utils.js";
import * as vfs from "../../_namespaces/vfs.js";
import { dedent } from "../../_namespaces/Utils.js";
import { FileSystem } from "../../_namespaces/vfs.js";
import { jsonToReadableText } from "../helpers.js";
import {
compilerOptionsToConfigJson,
@@ -27,7 +27,7 @@ describe("unittests:: tsc:: incremental::", () => {
fs: () =>
loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
"/src/project/tsconfig.json": dedent`
{
"compilerOptions": {
"target": "es5",
@@ -48,7 +48,7 @@ describe("unittests:: tsc:: incremental::", () => {
fs: () =>
loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
"/src/project/tsconfig.json": dedent`
{
"compilerOptions": {
"incremental": true,
@@ -85,7 +85,7 @@ describe("unittests:: tsc:: incremental::", () => {
fs: () =>
loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": Utils.dedent`
"/src/project/tsconfig.json": dedent`
{
"compilerOptions": {
"incremental": true,
@@ -115,7 +115,7 @@ describe("unittests:: tsc:: incremental::", () => {
});
describe("with noEmitOnError", () => {
let projFs: vfs.FileSystem;
let projFs: FileSystem;
before(() => {
projFs = getFsForNoEmitOnError();
});
@@ -275,25 +275,25 @@ const a: string = 10;`,
function fs() {
return loadProjectFromFiles({
"/src/project/src/class.ts": Utils.dedent`
"/src/project/src/class.ts": dedent`
export class classC {
prop = 1;
}`,
"/src/project/src/indirectClass.ts": Utils.dedent`
"/src/project/src/indirectClass.ts": dedent`
import { classC } from './class';
export class indirectClass {
classC = new classC();
}`,
"/src/project/src/directUse.ts": Utils.dedent`
"/src/project/src/directUse.ts": dedent`
import { indirectClass } from './indirectClass';
new indirectClass().classC.prop;`,
"/src/project/src/indirectUse.ts": Utils.dedent`
"/src/project/src/indirectUse.ts": dedent`
import { indirectClass } from './indirectClass';
new indirectClass().classC.prop;`,
"/src/project/src/noChangeFile.ts": Utils.dedent`
"/src/project/src/noChangeFile.ts": dedent`
export function writeLog(s: string) {
}`,
"/src/project/src/noChangeFileWithEmitSpecificError.ts": Utils.dedent`
"/src/project/src/noChangeFileWithEmitSpecificError.ts": dedent`
function someFunc(arguments: boolean, ...rest: any[]) {
}`,
"/src/project/tsconfig.json": jsonToReadableText({ compilerOptions }),
@@ -307,12 +307,12 @@ const a: string = 10;`,
subScenario: `when global file is added, the signatures are updated`,
fs: () =>
loadProjectFromFiles({
"/src/project/src/main.ts": Utils.dedent`
"/src/project/src/main.ts": dedent`
/// <reference path="./filePresent.ts"/>
/// <reference path="./fileNotFound.ts"/>
function main() { }
`,
"/src/project/src/anotherFileWithSameReferenes.ts": Utils.dedent`
"/src/project/src/anotherFileWithSameReferenes.ts": dedent`
/// <reference path="./filePresent.ts"/>
/// <reference path="./fileNotFound.ts"/>
function anotherFileWithSameReferenes() { }
@@ -495,7 +495,7 @@ declare global {
module: "esnext",
},
}),
"/src/project/index.tsx": Utils.dedent`
"/src/project/index.tsx": dedent`
declare namespace JSX {
interface ElementChildrenAttribute { children: {}; }
interface IntrinsicElements { div: {} }
@@ -518,7 +518,7 @@ declare global {
subScenario: "ts file with no-default-lib that augments the global scope",
fs: () =>
loadProjectFromFiles({
"/src/project/src/main.ts": Utils.dedent`
"/src/project/src/main.ts": dedent`
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
@@ -529,7 +529,7 @@ declare global {
export {};
`,
"/src/project/tsconfig.json": Utils.dedent`
"/src/project/tsconfig.json": dedent`
{
"compilerOptions": {
"target": "ESNext",
@@ -590,12 +590,12 @@ console.log(a);`,
fs: () =>
loadProjectFromFiles({
"/src/project/tsconfig.json": jsonToReadableText({ compilerOptions: { declaration } }),
"/src/project/main.ts": Utils.dedent`
"/src/project/main.ts": dedent`
import MessageablePerson from './MessageablePerson.js';
function logMessage( person: MessageablePerson ) {
console.log( person.message );
}`,
"/src/project/MessageablePerson.ts": Utils.dedent`
"/src/project/MessageablePerson.ts": dedent`
const Messageable = () => {
return class MessageableClass {
public message = 'hello';
@@ -609,7 +609,7 @@ console.log(a);`,
appendText(
fs,
"/lib/lib.d.ts",
Utils.dedent`
dedent`
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;`,
),
@@ -922,12 +922,12 @@ console.log(a);`,
},
include: ["src"],
}),
"/src/project/src/box.ts": Utils.dedent`
"/src/project/src/box.ts": dedent`
export interface Box<T> {
unbox(): T
}
`,
"/src/project/src/bug.js": Utils.dedent`
"/src/project/src/bug.js": dedent`
import * as B from "./box.js"
import * as W from "./wrap.js"
@@ -947,7 +947,7 @@ console.log(a);`,
export const bug = wrap({ n: box(1) });
`,
"/src/project/src/wrap.ts": Utils.dedent`
"/src/project/src/wrap.ts": dedent`
export type Wrap<C> = {
[K in keyof C]: { wrapped: C[K] }
}
@@ -974,14 +974,14 @@ console.log(a);`,
skipDefaultLibCheck: true,
},
}),
"/src/project/index.ts": Utils.dedent`
"/src/project/index.ts": dedent`
import ky from 'ky';
export const api = ky.extend({});
`,
"/src/project/package.json": jsonToReadableText({
type: "module",
}),
"/src/project/node_modules/ky/distribution/index.d.ts": Utils.dedent`
"/src/project/node_modules/ky/distribution/index.d.ts": dedent`
type KyInstance = {
extend(options: Record<string,unknown>): KyInstance;
}
@@ -1003,4 +1003,79 @@ console.log(a);`,
},
],
});
describe("with const enums", () => {
enum AliasType {
None = "",
SameFile = "aliased ",
DifferentFile = "aliased in different file ",
}
function fileWithEnum(withAlias: AliasType) {
return withAlias !== AliasType.DifferentFile ? "/src/project/b.d.ts" : "/src/project/worker.d.ts";
}
function verify(withAlias: AliasType, preserveConstEnums: boolean) {
verifyTsc({
scenario: "incremental",
subScenario: `with ${withAlias}const enums${preserveConstEnums ? " with preserveConstEnums" : ""}`,
commandLineArgs: ["-i", `/src/project/a.ts`, "--tsbuildinfofile", "/src/project/a.tsbuildinfo", ...preserveConstEnums ? ["--preserveConstEnums"] : []],
fs: () =>
loadProjectFromFiles({
"/src/project/a.ts": dedent`
import {A} from "./c"
let a = A.ONE
`,
"/src/project/b.d.ts": withAlias === AliasType.SameFile ?
dedent`
declare const enum AWorker {
ONE = 1
}
export { AWorker as A };
` :
withAlias === AliasType.DifferentFile ?
dedent`
export { AWorker as A } from "./worker";
` :
dedent`
export const enum A {
ONE = 1
}
`,
"/src/project/c.ts": dedent`
import {A} from "./b"
let b = A.ONE
export {A}
`,
"/src/project/worker.d.ts": dedent`
export const enum AWorker {
ONE = 1
}
`,
}),
edits: [
{
caption: "change enum value",
edit: fs => replaceText(fs, fileWithEnum(withAlias), "1", "2"),
},
{
caption: "change enum value again",
edit: fs => replaceText(fs, fileWithEnum(withAlias), "2", "3"),
},
{
caption: "something else changes in b.d.ts",
edit: fs => appendText(fs, "/src/project/b.d.ts", "export const randomThing = 10;"),
},
{
caption: "something else changes in b.d.ts again",
edit: fs => appendText(fs, "/src/project/b.d.ts", "export const randomThing2 = 10;"),
},
],
});
}
verify(/*withAlias*/ AliasType.None, /*preserveConstEnums*/ false);
verify(/*withAlias*/ AliasType.SameFile, /*preserveConstEnums*/ false);
verify(/*withAlias*/ AliasType.DifferentFile, /*preserveConstEnums*/ false);
verify(/*withAlias*/ AliasType.None, /*preserveConstEnums*/ true);
verify(/*withAlias*/ AliasType.SameFile, /*preserveConstEnums*/ true);
verify(/*withAlias*/ AliasType.DifferentFile, /*preserveConstEnums*/ true);
});
});