Skip to content

Commit 13c8df6

Browse files
committed
refactor(compiler-cli): decouple TemplateSymbolBuilder from ts.TypeChecker
This updates the SymbolBuilder to no longer use ts.TypeChecker internally to build symbols for the language service. These lookups are deferred/done later using the newly expanded template type checker API.
1 parent 5600c4f commit 13c8df6

26 files changed

Lines changed: 1185 additions & 704 deletions

File tree

packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import {
4545
TsCompletionEntryInfo,
4646
} from './scope';
4747
import {
48+
BindingSymbol,
49+
ClassSymbol,
4850
ElementSymbol,
4951
SelectorlessComponentSymbol,
5052
SelectorlessDirectiveSymbol,
@@ -178,6 +180,17 @@ export interface TemplateTypeChecker {
178180
): SelectorlessDirectiveSymbol | null;
179181
getSymbolOfNode(node: AST | TmplAstNode, component: ts.ClassDeclaration): Symbol | null;
180182

183+
/**
184+
* Translates a symbol's TCB location to its corresponding ts.Type using the program's type checker.
185+
* This is used by compiler checks that need semantic type information from a positional symbol.
186+
*/
187+
getTypeOfSymbol(symbol: Symbol | BindingSymbol | ClassSymbol): ts.Type | null;
188+
189+
/**
190+
* Translates a symbol's TCB location to its corresponding ts.Symbol using the program's type checker.
191+
*/
192+
getTsSymbolOfSymbol(symbol: Symbol | BindingSymbol | ClassSymbol): ts.Symbol | null;
193+
181194
/**
182195
* Get "global" `Completion`s in the given context.
183196
*

packages/compiler-cli/src/ngtsc/typecheck/api/scope.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,6 @@ export interface TsCompletionEntryInfo {
6161
export interface PotentialDirective {
6262
ref: Reference<ClassDeclaration>;
6363

64-
/**
65-
* The `ts.Symbol` for the directive class.
66-
*/
67-
tsSymbol: SymbolWithValueDeclaration;
68-
6964
/**
7065
* The module which declares the directive.
7166
*/
@@ -106,11 +101,6 @@ export interface PotentialDirective {
106101
export interface PotentialPipe {
107102
ref: Reference<ClassDeclaration>;
108103

109-
/**
110-
* The `ts.Symbol` for the pipe class.
111-
*/
112-
tsSymbol: ts.Symbol;
113-
114104
/**
115105
* Name of the pipe.
116106
*/

packages/compiler-cli/src/ngtsc/typecheck/api/symbols.ts

Lines changed: 19 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {
10+
MatchSource,
1011
TmplAstComponent,
1112
TmplAstDirective,
1213
TmplAstElement,
@@ -80,20 +81,20 @@ export interface TcbLocation {
8081

8182
/** The location in the file where node appears. */
8283
positionInFile: number;
84+
85+
/** The end position in the TCB file. Used to correctly resolve AST expressions. */
86+
endInFile?: number;
8387
}
8488

8589
/**
8690
* A generic representation of some node in a template.
8791
*/
8892
export interface TsNodeSymbolInfo {
89-
/** The `ts.Type` of the template node. */
90-
tsType: ts.Type;
91-
92-
/** The `ts.Symbol` for the template node */
93-
tsSymbol: ts.Symbol | null;
94-
9593
/** The position of the most relevant part of the template node. */
9694
tcbLocation: TcbLocation;
95+
96+
/** The position of the expression used to determine the type. */
97+
tcbTypeLocation?: TcbLocation;
9798
}
9899

99100
/**
@@ -102,29 +103,17 @@ export interface TsNodeSymbolInfo {
102103
export interface ExpressionSymbol {
103104
kind: SymbolKind.Expression;
104105

105-
/** The `ts.Type` of the expression AST. */
106-
tsType: ts.Type;
107-
108-
/**
109-
* The `ts.Symbol` of the entity. This could be `null`, for example `AST` expression
110-
* `{{foo.bar + foo.baz}}` does not have a `ts.Symbol` but `foo.bar` and `foo.baz` both do.
111-
*/
112-
tsSymbol: ts.Symbol | null;
113-
114106
/** The position of the most relevant part of the expression. */
115107
tcbLocation: TcbLocation;
108+
109+
/** The position of the expression used to determine the type. */
110+
tcbTypeLocation?: TcbLocation;
116111
}
117112

118113
/** Represents either an input or output binding in a template. */
119114
export interface BindingSymbol {
120115
kind: SymbolKind.Binding;
121116

122-
/** The `ts.Type` of the class member on the directive that is the target of the binding. */
123-
tsType: ts.Type;
124-
125-
/** The `ts.Symbol` of the class member on the directive that is the target of the binding. */
126-
tsSymbol: ts.Symbol;
127-
128117
/**
129118
* The `DirectiveSymbol` or `ElementSymbol` for the Directive, Component, or `HTMLElement` with
130119
* the binding.
@@ -133,6 +122,9 @@ export interface BindingSymbol {
133122

134123
/** The location in the shim file where the field access for the binding appears. */
135124
tcbLocation: TcbLocation;
125+
126+
/** The position of the expression used to determine the type. */
127+
tcbTypeLocation?: TcbLocation;
136128
}
137129

138130
/**
@@ -161,24 +153,6 @@ export interface OutputBindingSymbol {
161153
export interface ReferenceSymbol {
162154
kind: SymbolKind.Reference;
163155

164-
/**
165-
* The `ts.Type` of the Reference value.
166-
*
167-
* `TmplAstTemplate` - The type of the `TemplateRef`
168-
* `TmplAstElement` - The `ts.Type` for the `HTMLElement`.
169-
* Directive - The `ts.Type` for the class declaration.
170-
*/
171-
tsType: ts.Type;
172-
173-
/**
174-
* The `ts.Symbol` for the Reference value.
175-
*
176-
* `TmplAstTemplate` - A `TemplateRef` symbol.
177-
* `TmplAstElement` - The symbol for the `HTMLElement`.
178-
* Directive - The symbol for the class declaration of the directive.
179-
*/
180-
tsSymbol: ts.Symbol;
181-
182156
/**
183157
* Depending on the type of the reference, this is one of the following:
184158
* - `TmplAstElement` when the local ref refers to the HTML element
@@ -219,20 +193,6 @@ export interface ReferenceSymbol {
219193
export interface VariableSymbol {
220194
kind: SymbolKind.Variable;
221195

222-
/**
223-
* The `ts.Type` of the entity.
224-
*
225-
* This will be `any` if there is no `ngTemplateContextGuard`.
226-
*/
227-
tsType: ts.Type;
228-
229-
/**
230-
* The `ts.Symbol` for the context variable.
231-
*
232-
* This will be `null` if there is no `ngTemplateContextGuard`.
233-
*/
234-
tsSymbol: ts.Symbol | null;
235-
236196
/**
237197
* The node in the `TemplateAst` where the variable is declared. That is, the node for the `let-`
238198
* node in the template.
@@ -244,10 +204,7 @@ export interface VariableSymbol {
244204
*/
245205
localVarLocation: TcbLocation;
246206

247-
/**
248-
* The location in the shim file for the initializer node of the variable that represents the
249-
* template variable.
250-
*/
207+
/** The location in the shim file for the initializer node of the variable that represents the template variable. */
251208
initializerLocation: TcbLocation;
252209
}
253210

@@ -257,23 +214,16 @@ export interface VariableSymbol {
257214
export interface LetDeclarationSymbol {
258215
kind: SymbolKind.LetDeclaration;
259216

260-
/** The `ts.Type` of the entity. */
261-
tsType: ts.Type;
262-
263-
/**
264-
* The `ts.Symbol` for the declaration.
265-
*
266-
* This will be `null` if the symbol could not be resolved using the type checker.
267-
*/
268-
tsSymbol: ts.Symbol | null;
269-
270217
/** The node in the `TemplateAst` where the `@let` is declared. */
271218
declaration: TmplAstLetDeclaration;
272219

273220
/**
274221
* The location in the shim file for the identifier of the `@let` declaration.
275222
*/
276223
localVarLocation: TcbLocation;
224+
225+
/** The location in the shim file of the `@let` declaration's initializer expression. */
226+
initializerLocation: TcbLocation;
277227
}
278228

279229
/**
@@ -282,12 +232,6 @@ export interface LetDeclarationSymbol {
282232
export interface ElementSymbol {
283233
kind: SymbolKind.Element;
284234

285-
/** The `ts.Type` for the `HTMLElement`. */
286-
tsType: ts.Type;
287-
288-
/** The `ts.Symbol` for the `HTMLElement`. */
289-
tsSymbol: ts.Symbol | null;
290-
291235
/** A list of directives applied to the element. */
292236
directives: DirectiveSymbol[];
293237

@@ -310,12 +254,6 @@ export interface TemplateSymbol {
310254
export interface SelectorlessComponentSymbol {
311255
kind: SymbolKind.SelectorlessComponent;
312256

313-
/** The `ts.Type` for the component class. */
314-
tsType: ts.Type;
315-
316-
/** The `ts.Symbol` for the component class. */
317-
tsSymbol: ts.Symbol | null;
318-
319257
/**
320258
* Includes the component class itself and any host directives
321259
* that may have been applied as a side-effect of it.
@@ -333,12 +271,6 @@ export interface SelectorlessComponentSymbol {
333271
export interface SelectorlessDirectiveSymbol {
334272
kind: SymbolKind.SelectorlessDirective;
335273

336-
/** The `ts.Type` for the directive class. */
337-
tsType: ts.Type;
338-
339-
/** The `ts.Symbol` for the directive class. */
340-
tsSymbol: ts.Symbol | null;
341-
342274
/**
343275
* Includes the directive class itself and any host directives
344276
* that may have been applied as a side-effect of it.
@@ -356,9 +288,6 @@ export interface SelectorlessDirectiveSymbol {
356288
interface DirectiveSymbolBase extends PotentialDirective {
357289
kind: SymbolKind.Directive;
358290

359-
/** The `ts.Type` for the class declaration. */
360-
tsType: ts.Type;
361-
362291
/** The location in the shim file for the variable that holds the type of the directive. */
363292
tcbLocation: TcbLocation;
364293
}
@@ -368,9 +297,9 @@ interface DirectiveSymbolBase extends PotentialDirective {
368297
* template.
369298
*/
370299
export type DirectiveSymbol =
371-
| (DirectiveSymbolBase & {isHostDirective: false})
300+
| (DirectiveSymbolBase & {matchSource: MatchSource.Selector})
372301
| (DirectiveSymbolBase & {
373-
isHostDirective: true;
302+
matchSource: MatchSource.HostDirective;
374303
exposedInputs: Record<string, string> | null;
375304
exposedOutputs: Record<string, string> | null;
376305
});
@@ -393,15 +322,6 @@ export interface DomBindingSymbol {
393322
export interface PipeSymbol {
394323
kind: SymbolKind.Pipe;
395324

396-
/** The `ts.Type` of the transform node. */
397-
tsType: ts.Type;
398-
399-
/**
400-
* The `ts.Symbol` for the transform call. This could be `null` when `checkTypeOfPipes` is set to
401-
* `false` because the transform call would be of the form `(_pipe1 as any).transform()`
402-
*/
403-
tsSymbol: ts.Symbol | null;
404-
405325
/** The position of the transform call in the template. */
406326
tcbLocation: TcbLocation;
407327

@@ -411,12 +331,6 @@ export interface PipeSymbol {
411331

412332
/** Represents an instance of a class found in the TCB, i.e. `var _pipe1: MyPipe = null!; */
413333
export interface ClassSymbol {
414-
/** The `ts.Type` of class. */
415-
tsType: ts.Type;
416-
417-
/** The `ts.Symbol` for class. */
418-
tsSymbol: SymbolWithValueDeclaration;
419-
420334
/** The position for the variable declaration for the class instance. */
421335
tcbLocation: TcbLocation;
422336
}

packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,12 @@ function buildDiagnosticForSignal(
168168
node: PropertyRead,
169169
component: ts.ClassDeclaration,
170170
): Array<NgTemplateDiagnostic<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>> {
171-
// check for `{{ mySignal }}`
172171
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
173-
if (symbol !== null && symbol.kind === SymbolKind.Expression && isSignalReference(symbol)) {
172+
if (
173+
symbol !== null &&
174+
symbol.kind === SymbolKind.Expression &&
175+
isSignalReference(symbol, ctx.templateTypeChecker)
176+
) {
174177
const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(
175178
symbol.tcbLocation,
176179
)!;
@@ -192,11 +195,19 @@ function buildDiagnosticForSignal(
192195
if (!isFunctionInstanceProperty(node.name) && !isSignalInstanceProperty(node.name)) {
193196
return [];
194197
}
198+
199+
// If the receiver is not a PropertyRead, it means it's not a simple property access
200+
// (e.g., it could be a MethodCall like `mySignal().set`). In that case, we assume
201+
// it was invoked and skip the warning.
202+
if (!(node.receiver instanceof PropertyRead)) {
203+
return [];
204+
}
205+
195206
const symbolOfReceiver = ctx.templateTypeChecker.getSymbolOfNode(node.receiver, component);
196207
if (
197208
symbolOfReceiver !== null &&
198209
symbolOfReceiver.kind === SymbolKind.Expression &&
199-
isSignalReference(symbolOfReceiver)
210+
isSignalReference(symbolOfReceiver, ctx.templateTypeChecker)
200211
) {
201212
const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(
202213
symbolOfReceiver.tcbLocation,

packages/compiler-cli/src/ngtsc/typecheck/extended/checks/nullish_coalescing_not_nullable/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class NullishCoalescingNotNullableCheck extends TemplateCheckWithVisitor<ErrorCo
3939
if (symbolLeft === null || symbolLeft.kind !== SymbolKind.Expression) {
4040
return [];
4141
}
42-
const typeLeft = symbolLeft.tsType;
43-
if (typeLeft.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
42+
const typeLeft = ctx.templateTypeChecker.getTypeOfSymbol(symbolLeft);
43+
if (!typeLeft || typeLeft.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
4444
// We should not make assumptions about the any and unknown types; using a nullish coalescing
4545
// operator is acceptable for those.
4646
return [];

packages/compiler-cli/src/ngtsc/typecheck/extended/checks/optional_chain_not_nullable/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ class OptionalChainNotNullableCheck extends TemplateCheckWithVisitor<ErrorCode.O
6262
if (symbolLeft === null || symbolLeft.kind !== SymbolKind.Expression) {
6363
return [];
6464
}
65-
const typeLeft = symbolLeft.tsType;
66-
if (typeLeft.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
65+
const typeLeft = ctx.templateTypeChecker.getTypeOfSymbol(symbolLeft);
66+
if (!typeLeft || typeLeft.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
6767
// We should not make assumptions about the any and unknown types; using a nullish coalescing
6868
// operator is acceptable for those.
6969
return [];

packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_function_in_event_binding/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ function assertExpressionInvoked(
103103
const symbol = ctx.templateTypeChecker.getSymbolOfNode(expression, component);
104104

105105
if (symbol !== null && symbol.kind === SymbolKind.Expression) {
106-
if (symbol.tsType.getCallSignatures()?.length > 0) {
106+
const type = ctx.templateTypeChecker.getTypeOfSymbol(symbol);
107+
if (type && type.getCallSignatures()?.length > 0) {
107108
const fullExpressionText = generateStringFromExpression(expression, expressionText);
108109
const errorString = formatExtendedError(
109110
ErrorCode.UNINVOKED_FUNCTION_IN_EVENT_BINDING,

packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_function_in_text_interpolation/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ function assertExpressionInvoked(
4545
const symbol = ctx.templateTypeChecker.getSymbolOfNode(expression, component);
4646

4747
if (symbol !== null && symbol.kind === SymbolKind.Expression) {
48-
if (symbol.tsType.getCallSignatures()?.length > 0) {
48+
const type = ctx.templateTypeChecker.getTypeOfSymbol(symbol);
49+
if (type && type.getCallSignatures()?.length > 0) {
4950
const errorString = formatExtendedError(
5051
ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION,
5152
`Function in text interpolation should be invoked: ${expression.name}()`,

0 commit comments

Comments
 (0)