Skip to content

Commit 4857546

Browse files
authored
Region-based semantic diagnostics (#57842)
1 parent b258429 commit 4857546

File tree

161 files changed

+4872
-927
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+4872
-927
lines changed

src/compiler/checker.ts

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47502,6 +47502,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4750247502
}
4750347503

4750447504
function checkSourceElementWorker(node: Node): void {
47505+
if (getNodeCheckFlags(node) & NodeCheckFlags.PartiallyTypeChecked) {
47506+
return;
47507+
}
47508+
4750547509
if (canHaveJSDoc(node)) {
4750647510
forEach(node.jsDoc, ({ comment, tags }) => {
4750747511
checkJSDocCommentWorker(comment);
@@ -47530,6 +47534,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4753047534
errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected);
4753147535
}
4753247536

47537+
// If editing this, keep `isSourceElement` in utilities up to date.
4753347538
switch (kind) {
4753447539
case SyntaxKind.TypeParameter:
4753547540
return checkTypeParameter(node as TypeParameterDeclaration);
@@ -47894,12 +47899,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4789447899
tracing?.pop();
4789547900
}
4789647901

47897-
function checkSourceFile(node: SourceFile) {
47898-
tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true);
47899-
performance.mark("beforeCheck");
47900-
checkSourceFileWorker(node);
47901-
performance.mark("afterCheck");
47902-
performance.measure("Check", "beforeCheck", "afterCheck");
47902+
function checkSourceFile(node: SourceFile, nodesToCheck: Node[] | undefined) {
47903+
tracing?.push(tracing.Phase.Check, nodesToCheck ? "checkSourceFileNodes" : "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true);
47904+
const beforeMark = nodesToCheck ? "beforeCheckNodes" : "beforeCheck";
47905+
const afterMark = nodesToCheck ? "afterCheckNodes" : "afterCheck";
47906+
performance.mark(beforeMark);
47907+
nodesToCheck ? checkSourceFileNodesWorker(node, nodesToCheck) : checkSourceFileWorker(node);
47908+
performance.mark(afterMark);
47909+
performance.measure("Check", beforeMark, afterMark);
4790347910
tracing?.pop();
4790447911
}
4790547912

@@ -47938,6 +47945,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4793847945
clear(potentialReflectCollisions);
4793947946
clear(potentialUnusedRenamedBindingElementsInTypes);
4794047947

47948+
if (links.flags & NodeCheckFlags.PartiallyTypeChecked) {
47949+
potentialThisCollisions = links.potentialThisCollisions!;
47950+
potentialNewTargetCollisions = links.potentialNewTargetCollisions!;
47951+
potentialWeakMapSetCollisions = links.potentialWeakMapSetCollisions!;
47952+
potentialReflectCollisions = links.potentialReflectCollisions!;
47953+
potentialUnusedRenamedBindingElementsInTypes = links.potentialUnusedRenamedBindingElementsInTypes!;
47954+
}
47955+
4794147956
forEach(node.statements, checkSourceElement);
4794247957
checkSourceElement(node.endOfFileToken);
4794347958

@@ -47989,13 +48004,49 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4798948004
}
4799048005
}
4799148006

47992-
function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] {
48007+
function checkSourceFileNodesWorker(file: SourceFile, nodes: readonly Node[]) {
48008+
const links = getNodeLinks(file);
48009+
if (!(links.flags & NodeCheckFlags.TypeChecked)) {
48010+
if (skipTypeChecking(file, compilerOptions, host)) {
48011+
return;
48012+
}
48013+
48014+
// Grammar checking
48015+
checkGrammarSourceFile(file);
48016+
48017+
clear(potentialThisCollisions);
48018+
clear(potentialNewTargetCollisions);
48019+
clear(potentialWeakMapSetCollisions);
48020+
clear(potentialReflectCollisions);
48021+
clear(potentialUnusedRenamedBindingElementsInTypes);
48022+
48023+
forEach(nodes, checkSourceElement);
48024+
48025+
checkDeferredNodes(file);
48026+
48027+
(links.potentialThisCollisions || (links.potentialThisCollisions = [])).push(...potentialThisCollisions);
48028+
(links.potentialNewTargetCollisions || (links.potentialNewTargetCollisions = [])).push(...potentialNewTargetCollisions);
48029+
(links.potentialWeakMapSetCollisions || (links.potentialWeakMapSetCollisions = [])).push(...potentialWeakMapSetCollisions);
48030+
(links.potentialReflectCollisions || (links.potentialReflectCollisions = [])).push(...potentialReflectCollisions);
48031+
(links.potentialUnusedRenamedBindingElementsInTypes || (links.potentialUnusedRenamedBindingElementsInTypes = [])).push(
48032+
...potentialUnusedRenamedBindingElementsInTypes,
48033+
);
48034+
48035+
links.flags |= NodeCheckFlags.PartiallyTypeChecked;
48036+
for (const node of nodes) {
48037+
const nodeLinks = getNodeLinks(node);
48038+
nodeLinks.flags |= NodeCheckFlags.PartiallyTypeChecked;
48039+
}
48040+
}
48041+
}
48042+
48043+
function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken, nodesToCheck?: Node[]): Diagnostic[] {
4799348044
try {
4799448045
// Record the cancellation token so it can be checked later on during checkSourceElement.
4799548046
// Do this in a finally block so we can ensure that it gets reset back to nothing after
4799648047
// this call is done.
4799748048
cancellationToken = ct;
47998-
return getDiagnosticsWorker(sourceFile);
48049+
return getDiagnosticsWorker(sourceFile, nodesToCheck);
4799948050
}
4800048051
finally {
4800148052
cancellationToken = undefined;
@@ -48010,7 +48061,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4801048061
deferredDiagnosticsCallbacks = [];
4801148062
}
4801248063

48013-
function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile) {
48064+
function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile, nodesToCheck?: Node[]) {
4801448065
ensurePendingDiagnosticWorkComplete();
4801548066
// then setup diagnostics for immediate invocation (as we are about to collect them, and
4801648067
// this avoids the overhead of longer-lived callbacks we don't need to allocate)
@@ -48019,11 +48070,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4801948070
// thus much more likely retaining the same union ordering as before we had lazy diagnostics)
4802048071
const oldAddLazyDiagnostics = addLazyDiagnostic;
4802148072
addLazyDiagnostic = cb => cb();
48022-
checkSourceFile(sourceFile);
48073+
checkSourceFile(sourceFile, nodesToCheck);
4802348074
addLazyDiagnostic = oldAddLazyDiagnostics;
4802448075
}
4802548076

48026-
function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
48077+
function getDiagnosticsWorker(sourceFile: SourceFile, nodesToCheck: Node[] | undefined): Diagnostic[] {
4802748078
if (sourceFile) {
4802848079
ensurePendingDiagnosticWorkComplete();
4802948080
// Some global diagnostics are deferred until they are needed and
@@ -48032,9 +48083,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4803248083
const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
4803348084
const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length;
4803448085

48035-
checkSourceFileWithEagerDiagnostics(sourceFile);
48036-
48086+
checkSourceFileWithEagerDiagnostics(sourceFile, nodesToCheck);
4803748087
const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
48088+
if (nodesToCheck) {
48089+
// No need to get global diagnostics.
48090+
return semanticDiagnostics;
48091+
}
4803848092
const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
4803948093
if (currentGlobalDiagnostics !== previousGlobalDiagnostics) {
4804048094
// If the arrays are not the same reference, new diagnostics were added.
@@ -48053,7 +48107,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4805348107

4805448108
// Global diagnostics are always added when a file is not provided to
4805548109
// getDiagnostics
48056-
forEach(host.getSourceFiles(), checkSourceFileWithEagerDiagnostics);
48110+
forEach(host.getSourceFiles(), file => checkSourceFileWithEagerDiagnostics(file));
4805748111
return diagnostics.getDiagnostics();
4805848112
}
4805948113

src/compiler/program.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2961,8 +2961,12 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
29612961
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken);
29622962
}
29632963

2964-
function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
2965-
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken);
2964+
function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken, nodesToCheck?: Node[]): readonly Diagnostic[] {
2965+
return getDiagnosticsHelper(
2966+
sourceFile,
2967+
(sourceFile, cancellationToken) => getSemanticDiagnosticsForFile(sourceFile, cancellationToken, nodesToCheck),
2968+
cancellationToken,
2969+
);
29662970
}
29672971

29682972
function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined {
@@ -2972,7 +2976,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
29722976
}
29732977

29742978
function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
2975-
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken);
2979+
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken, /*nodesToCheck*/ undefined);
29762980
}
29772981

29782982
function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] {
@@ -3026,18 +3030,33 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
30263030
}
30273031
}
30283032

3029-
function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
3033+
function getSemanticDiagnosticsForFile(
3034+
sourceFile: SourceFile,
3035+
cancellationToken: CancellationToken | undefined,
3036+
nodesToCheck: Node[] | undefined,
3037+
): readonly Diagnostic[] {
30303038
return concatenate(
3031-
filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options),
3039+
filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken, nodesToCheck), options),
30323040
getProgramDiagnostics(sourceFile),
30333041
);
30343042
}
30353043

3036-
function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
3044+
function getBindAndCheckDiagnosticsForFile(
3045+
sourceFile: SourceFile,
3046+
cancellationToken: CancellationToken | undefined,
3047+
nodesToCheck: Node[] | undefined,
3048+
): readonly Diagnostic[] {
3049+
if (nodesToCheck) {
3050+
return getBindAndCheckDiagnosticsForFileNoCache(sourceFile, cancellationToken, nodesToCheck);
3051+
}
30373052
return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache);
30383053
}
30393054

3040-
function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
3055+
function getBindAndCheckDiagnosticsForFileNoCache(
3056+
sourceFile: SourceFile,
3057+
cancellationToken: CancellationToken | undefined,
3058+
nodesToCheck?: Node[],
3059+
): readonly Diagnostic[] {
30413060
return runWithCancellationToken(() => {
30423061
if (skipTypeChecking(sourceFile, options, program)) {
30433062
return emptyArray;
@@ -3048,32 +3067,40 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
30483067
Debug.assert(!!sourceFile.bindDiagnostics);
30493068

30503069
const isJs = sourceFile.scriptKind === ScriptKind.JS || sourceFile.scriptKind === ScriptKind.JSX;
3051-
const isCheckJs = isJs && isCheckJsEnabledForFile(sourceFile, options);
30523070
const isPlainJs = isPlainJsFile(sourceFile, options.checkJs);
3071+
const isCheckJs = isJs && isCheckJsEnabledForFile(sourceFile, options);
30533072

3054-
// By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External
3055-
// - plain JS: .js files with no // ts-check and checkJs: undefined
3056-
// - check JS: .js files with either // ts-check or checkJs: true
3057-
// - external: files that are added by plugins
30583073
let bindDiagnostics = sourceFile.bindDiagnostics;
3059-
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile, cancellationToken);
3074+
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile, cancellationToken, nodesToCheck);
30603075
if (isPlainJs) {
30613076
bindDiagnostics = filter(bindDiagnostics, d => plainJSErrors.has(d.code));
30623077
checkDiagnostics = filter(checkDiagnostics, d => plainJSErrors.has(d.code));
30633078
}
30643079
// skip ts-expect-error errors in plain JS files, and skip JSDoc errors except in checked JS
3065-
return getMergedBindAndCheckDiagnostics(sourceFile, !isPlainJs, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined);
3080+
return getMergedBindAndCheckDiagnostics(
3081+
sourceFile,
3082+
!isPlainJs,
3083+
!!nodesToCheck,
3084+
bindDiagnostics,
3085+
checkDiagnostics,
3086+
isCheckJs ? sourceFile.jsDocDiagnostics : undefined,
3087+
);
30663088
});
30673089
}
30683090

3069-
function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, includeBindAndCheckDiagnostics: boolean, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) {
3091+
function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, includeBindAndCheckDiagnostics: boolean, partialCheck: boolean, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) {
30703092
const flatDiagnostics = flatten(allDiagnostics);
30713093
if (!includeBindAndCheckDiagnostics || !sourceFile.commentDirectives?.length) {
30723094
return flatDiagnostics;
30733095
}
30743096

30753097
const { diagnostics, directives } = getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics);
30763098

3099+
// When doing a partial check, we can't be sure a directive is unused.
3100+
if (partialCheck) {
3101+
return diagnostics;
3102+
}
3103+
30773104
for (const errorExpectation of directives.getUnusedExpectations()) {
30783105
diagnostics.push(createDiagnosticForRange(sourceFile, errorExpectation.range, Diagnostics.Unused_ts_expect_error_directive));
30793106
}

src/compiler/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4716,6 +4716,9 @@ export interface Program extends ScriptReferenceHost {
47164716
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[];
47174717
/** The first time this is called, it will return global diagnostics (no location). */
47184718
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[];
4719+
/** @internal */
4720+
getSemanticDiagnostics(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined, nodesToCheck: Node[]): readonly Diagnostic[];
4721+
47194722
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[];
47204723
getConfigFileParsingDiagnostics(): readonly Diagnostic[];
47214724
/** @internal */ getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[];
@@ -5264,7 +5267,7 @@ export interface TypeChecker {
52645267
/** @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker;
52655268

52665269
// Should not be called directly. Should only be accessed through the Program instance.
5267-
/** @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
5270+
/** @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken, nodesToCheck?: Node[]): Diagnostic[];
52685271
/** @internal */ getGlobalDiagnostics(): Diagnostic[];
52695272
/** @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken, forceDts?: boolean): EmitResolver;
52705273
/** @internal */ requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag): boolean;
@@ -6118,6 +6121,7 @@ export const enum NodeCheckFlags {
61186121
ContainsClassWithPrivateIdentifiers = 1 << 20, // Marked on all block-scoped containers containing a class with private identifiers.
61196122
ContainsSuperPropertyInStaticInitializer = 1 << 21, // Marked on all block-scoped containers containing a static initializer with 'super.x' or 'super[x]'.
61206123
InCheckIdentifier = 1 << 22,
6124+
PartiallyTypeChecked = 1 << 23, // Node has been partially type checked
61216125

61226126
/** These flags are LazyNodeCheckFlags and can be calculated lazily by `hasNodeCheckFlag` */
61236127
LazyFlags = SuperInstance
@@ -6177,6 +6181,11 @@ export interface NodeLinks {
61776181
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
61786182
fakeScopeForSignatureDeclaration?: "params" | "typeParams"; // If present, this is a fake scope injected into an enclosing declaration chain.
61796183
assertionExpressionType?: Type; // Cached type of the expression of a type assertion
6184+
potentialThisCollisions?: Node[];
6185+
potentialNewTargetCollisions?: Node[];
6186+
potentialWeakMapSetCollisions?: Node[];
6187+
potentialReflectCollisions?: Node[];
6188+
potentialUnusedRenamedBindingElementsInTypes?: BindingElement[];
61806189
externalHelpersModule?: Symbol; // Resolved symbol for the external helpers module
61816190
}
61826191

0 commit comments

Comments
 (0)