Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cb9fb34
Test incremental invalidation after compiler option changes
jakebailey Oct 9, 2026
c21517a
Fix compiler option invalidation in incremental builds
jakebailey Oct 9, 2026
d14e7b6
Test incremental emit after changing the inherited paths base
jakebailey Oct 9, 2026
2dee4b8
Invalidate inherited paths when their base directory changes
jakebailey Oct 9, 2026
8d2def8
Test incremental module detection changes
jakebailey Oct 10, 2026
2fc6cfe
Invalidate incremental state after module detection changes
jakebailey Oct 10, 2026
d9e920d
Test incremental declaration specifiers after module suffix changes
jakebailey Oct 10, 2026
6735d91
Invalidate incremental state after module suffix changes
jakebailey Oct 10, 2026
30e8544
Test incremental target-dependent diagnostics
jakebailey Oct 10, 2026
a602fd3
Invalidate semantic diagnostics after target changes
jakebailey Oct 10, 2026
0b91a15
Test incremental module-dependent diagnostics
jakebailey Oct 10, 2026
d12fe86
Invalidate semantic diagnostics after module changes
jakebailey Oct 10, 2026
cc1ecf2
Test incremental imported-helper diagnostics
jakebailey Oct 10, 2026
aacae87
Invalidate semantic diagnostics after imported-helper changes
jakebailey Oct 10, 2026
7468380
Test incremental JSX namespace diagnostics
jakebailey Oct 10, 2026
465e38b
Invalidate semantic diagnostics after JSX namespace changes
jakebailey Oct 10, 2026
4979bee
Test incremental const-enum namespace merge diagnostics
jakebailey Oct 10, 2026
03e9cc0
Invalidate semantic diagnostics after const-enum preservation changes
jakebailey Oct 10, 2026
aafa83a
Test incremental declarations after library reordering
jakebailey Oct 10, 2026
558287b
Invalidate incremental state after global file reordering
jakebailey Oct 10, 2026
ffad73e
Test global-file deletion after a non-global deletion
jakebailey Oct 10, 2026
090e940
Continue scanning deletions until a global file is found
jakebailey Oct 10, 2026
10699ae
Run deletion-order regression subtests in parallel
jakebailey Oct 10, 2026
4d48fcf
Refresh incremental baselines after option invalidation fixes
jakebailey Oct 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 31 additions & 23 deletions tools/scripts/tsc/generate-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export function validateOptions(model: OptionsModel): void {
if (option.declaration?.affectsSemanticDiagnostics) {
assert(option.declaration.affectsBuildInfo, `Semantic diagnostics must affect build info: ${option.name}`);
}
if (option.declaration?.affectsEmit || option.declaration?.affectsDeclarationPath) {
assert(option.declaration.affectsBuildInfo, `Emit options must affect build info: ${option.name}`);
}
if (option.transpile) {
const { value, declarationValue, unless } = option.transpile;
assert(value === "clear" || option.type === "Tristate", `Non-boolean transpile value: ${option.name}`);
Expand Down Expand Up @@ -294,6 +297,23 @@ export function prepareCompilerOptions(options: RawCompilerOptions, currentDirec
`;
}

function optionValuesDiffer(type: GoCompilerOptionType, a: string, b: string): string {
switch (type) {
case "*int":
return `${a} != ${b} && (${a} == nil || ${b} == nil || *${a} != *${b})`;
case "[]string":
case "[]PluginImport":
case "[]tspath.RootedDirectoryPath":
return `(${a} == nil) != (${b} == nil) || !slices.Equal(${a}, ${b})`;
case "*collections.OrderedMap[string, []string]":
return `!${a}.EqualFunc(${b}, func(a, b []string) bool {
return (a == nil) == (b == nil) && slices.Equal(a, b)
})`;
default:
return `${a} != ${b}`;
}
}

function optionsEquality(name: string, fields: { name: string; type: GoCompilerOptionType; }[]): string {
return `func (options *${name}) Equals(other *${name}) bool {
if options == other { return true }
Expand All @@ -302,24 +322,7 @@ function optionsEquality(name: string, fields: { name: string; type: GoCompilerO
fields.map(field => {
const a = `options.${field.name}`;
const b = `other.${field.name}`;
let differs: string;
switch (field.type) {
case "*int":
differs = `${a} != ${b} && (${a} == nil || ${b} == nil || *${a} != *${b})`;
break;
case "[]string":
case "[]PluginImport":
case "[]tspath.RootedDirectoryPath":
differs = `(${a} == nil) != (${b} == nil) || !slices.Equal(${a}, ${b})`;
break;
case "*collections.OrderedMap[string, []string]":
differs = `!${a}.EqualFunc(${b}, func(a, b []string) bool {
return (a == nil) == (b == nil) && slices.Equal(a, b)
})`;
break;
default:
differs = `${a} != ${b}`;
}
const differs = optionValuesDiffer(field.type, a, b);
return `if ${differs} { return false }`;
}).join("\n")
}
Expand Down Expand Up @@ -538,19 +541,22 @@ export function generateOptionComparisons(model = options): string {
const expressions = model.compilerOptions.flatMap(option => {
const declaration = option.declaration;
if (!declaration?.[flag]) return [];
const kind = optionKind(option);
assert(kind === "Boolean" || kind === "String" || kind === "Enum", `Unsupported comparison type for ${option.name}: ${option.type}`);
const value = (receiver: string) => {
const field = `${receiver}.${fieldName(option)}`;
if (declaration.strictFlag) return `${receiver}.GetStrictOptionValue(${field})`;
return field;
};
return [`${value("oldOptions")} != ${value("newOptions")}`];
const expressions = [optionValuesDiffer(goType(option), value("oldOptions"), value("newOptions"))];
if (option.name === "paths") {
expressions.push('oldOptions.GetPathsBasePath("") != newOptions.GetPathsBasePath("")');
}
return expressions;
});
return `func CompilerOptionsAffect${name}(oldOptions *core.CompilerOptions, newOptions *core.CompilerOptions) bool {
if oldOptions == newOptions { return false }
if oldOptions == nil || newOptions == nil { return true }
return ${expressions.join(" ||\n") || "false"}
${expressions.map(expression => `if ${expression} { return true }`).join("\n")}
return false
}
`;
}).join("\n")
Expand Down Expand Up @@ -796,6 +802,7 @@ function storedParser(name: string, declarations: StoredDeclaration[]): string {

export function generateOptions(): Map<string, string> {
validateOptions(options);
const comparisons = generateOptionComparisons();
const buildOptions = options.buildOptions.filter((option): option is StoredDeclaration => option.field !== undefined);
return new Map([
["packages/typescript/src/api/compilerOptions.generated.ts", generateCompilerOptionsAPI()],
Expand Down Expand Up @@ -846,6 +853,7 @@ ${enumMaps()}
package tsoptions

import (
${comparisons.includes("slices.") ? '"slices"\n' : ""}
"github.com/microsoft/TypeScript/tsc/internal/collections"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/tspath"
Expand All @@ -854,7 +862,7 @@ import (
${parser()}
${storedParser("TypeAcquisition", options.typeAcquisition)}
${storedParser("BuildOptions", buildOptions)}
${generateOptionComparisons()}
${comparisons}
${generateBuildInfoOptions()}
${mergeCompilerOptions()}
${configDirSubstitution()}
Expand Down
2 changes: 2 additions & 0 deletions tools/scripts/tsc/options-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ export interface DeclarationMetadata {
allowConfigDirTemplateSubstitution?: boolean;
affectsDeclarationPath?: boolean;
affectsSemanticDiagnostics?: boolean;
/** Required for every option participating in incremental invalidation. */
affectsBuildInfo?: boolean;
/** Changes can alter JavaScript, declarations, or declaration diagnostics. */
affectsEmit?: boolean;
strictFlag?: boolean;
listPreserveFalsyValues?: boolean;
Expand Down
30 changes: 26 additions & 4 deletions tools/scripts/tsc/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ test("semantic diagnostic options must be stored in build info", () => {
assert.throws(() => validateOptions(model), /Semantic diagnostics must affect build info: noImplicitAny/);
});

test("emit options must be stored in build info", () => {
for (const flag of ["affectsEmit", "affectsDeclarationPath"] as const) {
const model = structuredClone(options);
const declaration = model.compilerOptions.find(option => option.name === "jsxFragmentFactory")!.declaration!;
declaration.affectsSemanticDiagnostics = false;
declaration.affectsEmit = false;
declaration.affectsBuildInfo = false;
declaration[flag] = true;
assert.throws(() => validateOptions(model), /Emit options must affect build info: jsxFragmentFactory/);
}
});

test("compiler test variations accept finite options without requiring affects metadata", () => {
const source = generateOptions().get("tsc/internal/testrunner/options_generated.go")!;
for (const name of ["strict", "module", "noCheck", "preserveSymlinks", "noEmit", "isolatedModules"]) {
Expand Down Expand Up @@ -186,12 +198,22 @@ test("metadata enum artifacts are current and use the shared alias ordering", ()
assert.deepEqual(members, [{ name: "Value", value: "7" }, { name: "Alias", value: "Value" }]);
});

test("option comparisons reject types that require deep equality", () => {
for (const name of ["maxNodeModuleJsDepth", "types", "paths", "plugins"]) {
const model = structuredClone(options);
test("option comparisons use value equality for every stored option type", () => {
const model = structuredClone(options);
for (const name of ["maxNodeModuleJsDepth", "types", "paths", "plugins", "rootDirs"]) {
model.compilerOptions.find(option => option.name === name)!.declaration!.affectsEmit = true;
assert.throws(() => generateOptionComparisons(model), new RegExp(`Unsupported comparison type for ${name}:`));
}
const source = generateOptionComparisons(model);
assert.match(source, /oldOptions.MaxNodeModuleJsDepth != newOptions.MaxNodeModuleJsDepth && \(oldOptions.MaxNodeModuleJsDepth == nil/);
for (const name of ["Types", "Plugins", "RootDirs"]) {
assert(source.includes(`(oldOptions.${name} == nil) != (newOptions.${name} == nil) || !slices.Equal(oldOptions.${name}, newOptions.${name})`));
}
assert.match(source, /!oldOptions.Paths.EqualFunc\(newOptions.Paths, func\(a, b \[\]string\) bool/);
assert.equal(source.split('oldOptions.GetPathsBasePath("") != newOptions.GetPathsBasePath("")').length - 1, 2);
assert.match(source, /return \(a == nil\) == \(b == nil\) && slices.Equal\(a, b\)/);
assert.match(source, /oldOptions.GetStrictOptionValue\(oldOptions.StrictNullChecks\) != newOptions.GetStrictOptionValue\(newOptions.StrictNullChecks\)/);
assert.match(source, /if \(oldOptions.ModuleSuffixes/);
assert.doesNotMatch(source, / \|\|\noldOptions/);
});

test("option section headings are detached line comments", () => {
Expand Down
Loading
Loading