Skip to content

Commit 6beff5e

Browse files
crisbetoAndrewKushnir
authored andcommitted
refactor(compiler): rework and expose APIs to be used in schematics (#48730)
Reworks some of the existing compiler APIs to make them easier to use in a schematic and exposes a few new ones to surface information we already had. High-level list of changes: * `getPotentialImportsFor` now requires a class reference, instead of a `PotentialDirective | PotentialPipe`. * New `getNgModuleMetadata` method has been added to the type checker. * New `getPipeMetadata` method has been added to the type checker. * New `getUsedDirectives` method has been added to the type checker. * New `getUsedPipes` method has been added to the type checker. * The `decorator` property was exposed on the `TypeCheckableDirectiveMeta`. The property was already present at runtime, but it wasn't specified on the interface. PR Close #48730
1 parent 5f21c6d commit 6beff5e

File tree

10 files changed

+104
-38
lines changed

10 files changed

+104
-38
lines changed

packages/compiler-cli/private/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ts_library(
1414
"//packages/compiler-cli/src/ngtsc/perf",
1515
"//packages/compiler-cli/src/ngtsc/reflection",
1616
"//packages/compiler-cli/src/ngtsc/sourcemaps",
17+
"//packages/compiler-cli/src/ngtsc/typecheck/api",
1718
"//packages/compiler-cli/src/transformers/downlevel_decorators_transform",
1819
"@npm//typescript",
1920
],

packages/compiler-cli/private/migrations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export {forwardRefResolver} from '../src/ngtsc/annotations';
1515
export {Reference} from '../src/ngtsc/imports';
1616
export {DynamicValue, PartialEvaluator, ResolvedValue, ResolvedValueMap, StaticInterpreter} from '../src/ngtsc/partial_evaluator';
1717
export {reflectObjectLiteral, TypeScriptReflectionHost} from '../src/ngtsc/reflection';
18+
export {PotentialImport, PotentialImportKind, PotentialImportMode, TemplateTypeChecker} from '../src/ngtsc/typecheck/api';

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface TypeCheckableDirectiveMeta extends DirectiveMeta, DirectiveType
2626
outputs: ClassPropertyMapping;
2727
isStandalone: boolean;
2828
hostDirectives: HostDirectiveMeta[]|null;
29+
decorator: ts.Decorator|null;
2930
}
3031

3132
export type TemplateId = string&{__brand: 'TemplateId'};

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ import ts from 'typescript';
1111

1212
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
1313
import {ErrorCode} from '../../diagnostics';
14+
import {Reference} from '../../imports';
15+
import {NgModuleMeta, PipeMeta} from '../../metadata';
16+
import {ClassDeclaration} from '../../reflection';
1417

1518
import {FullTemplateMapping, NgTemplateDiagnostic, TypeCheckableDirectiveMeta} from './api';
1619
import {GlobalCompletion} from './completion';
17-
import {PotentialDirective, PotentialImport, PotentialPipe} from './scope';
20+
import {PotentialDirective, PotentialImport, PotentialImportMode, PotentialPipe} from './scope';
1821
import {ElementSymbol, Symbol, TcbLocation, TemplateSymbol} from './symbols';
1922

2023
/**
@@ -150,8 +153,8 @@ export interface TemplateTypeChecker {
150153
* In the context of an Angular trait, generate potential imports for a directive.
151154
*/
152155
getPotentialImportsFor(
153-
toImport: PotentialDirective|PotentialPipe,
154-
inComponent: ts.ClassDeclaration): ReadonlyArray<PotentialImport>;
156+
toImport: Reference<ClassDeclaration>, inComponent: ts.ClassDeclaration,
157+
importMode: PotentialImportMode): ReadonlyArray<PotentialImport>;
155158

156159
/**
157160
* Get the primary decorator for an Angular class (such as @Component). This does not work for
@@ -184,6 +187,26 @@ export interface TemplateTypeChecker {
184187
*/
185188
getDirectiveMetadata(dir: ts.ClassDeclaration): TypeCheckableDirectiveMeta|null;
186189

190+
/**
191+
* Retrieve the type checking engine's metadata for the given NgModule class, if available.
192+
*/
193+
getNgModuleMetadata(module: ts.ClassDeclaration): NgModuleMeta|null;
194+
195+
/**
196+
* Retrieve the type checking engine's metadata for the given pipe class, if available.
197+
*/
198+
getPipeMetadata(pipe: ts.ClassDeclaration): PipeMeta|null;
199+
200+
/**
201+
* Gets the directives that have been used in a component's template.
202+
*/
203+
getUsedDirectives(component: ts.ClassDeclaration): TypeCheckableDirectiveMeta[]|null;
204+
205+
/**
206+
* Gets the pipes that have been used in a component's template.
207+
*/
208+
getUsedPipes(component: ts.ClassDeclaration): string[]|null;
209+
187210
/**
188211
* Reset the `TemplateTypeChecker`'s state for the given class, so that it will be recomputed on
189212
* the next request.

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,18 @@ export interface PotentialPipe {
8989
*/
9090
isInScope: boolean;
9191
}
92+
93+
/**
94+
* Possible modes in which to look up a potential import.
95+
*/
96+
export enum PotentialImportMode {
97+
/** Whether an import is standalone is inferred based on its metadata. */
98+
Normal,
99+
100+
/**
101+
* An import is assumed to be standalone and is imported directly. This is useful for migrations
102+
* where a declaration wasn't standalone when the program was created, but will become standalone
103+
* as a part of the migration.
104+
*/
105+
ForceDirect,
106+
}

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {AST, CssSelector, DomElementSchemaRegistry, ExternalExpr, LiteralPrimitive, ParseSourceSpan, PropertyRead, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, WrappedNodeExpr} from '@angular/compiler';
9+
import {AST, CssSelector, DomElementSchemaRegistry, ExternalExpr, LiteralPrimitive, ParseSourceSpan, PropertyRead, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute, WrappedNodeExpr} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {ErrorCode, ngErrorCode} from '../../diagnostics';
13-
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
13+
import {absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
1414
import {Reference, ReferenceEmitKind, ReferenceEmitter} from '../../imports';
1515
import {IncrementalBuild} from '../../incremental/api';
16-
import {DirectiveMeta, MetadataReader, MetadataReaderWithIndex, MetaKind, NgModuleIndex, PipeMeta} from '../../metadata';
16+
import {DirectiveMeta, MetadataReader, MetadataReaderWithIndex, MetaKind, NgModuleIndex, NgModuleMeta, PipeMeta} from '../../metadata';
1717
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
1818
import {ProgramDriver, UpdateMode} from '../../program_driver';
1919
import {ClassDeclaration, DeclarationNode, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
2020
import {ComponentScopeKind, ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope';
2121
import {isShim} from '../../shims';
2222
import {getSourceFileOrNull, isSymbolWithValueDeclaration} from '../../util/src/typescript';
23-
import {ElementSymbol, FullTemplateMapping, GlobalCompletion, NgTemplateDiagnostic, OptimizeFor, PotentialDirective, PotentialImport, PotentialImportKind, PotentialPipe, ProgramTypeCheckAdapter, Symbol, TcbLocation, TemplateDiagnostic, TemplateId, TemplateSymbol, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../api';
23+
import {ElementSymbol, FullTemplateMapping, GlobalCompletion, NgTemplateDiagnostic, OptimizeFor, PotentialDirective, PotentialImport, PotentialImportKind, PotentialImportMode, PotentialPipe, ProgramTypeCheckAdapter, Symbol, TcbLocation, TemplateDiagnostic, TemplateId, TemplateSymbol, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../api';
2424
import {makeTemplateDiagnostic} from '../diagnostics';
2525

2626
import {CompletionEngine} from './completion';
@@ -99,6 +99,14 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
9999
return data.template;
100100
}
101101

102+
getUsedDirectives(component: ts.ClassDeclaration): TypeCheckableDirectiveMeta[]|null {
103+
return this.getLatestComponentState(component).data?.boundTarget.getUsedDirectives() || null;
104+
}
105+
106+
getUsedPipes(component: ts.ClassDeclaration): string[]|null {
107+
return this.getLatestComponentState(component).data?.boundTarget.getUsedPipes() || null;
108+
}
109+
102110
private getLatestComponentState(component: ts.ClassDeclaration):
103111
{data: TemplateData|null, tcb: ts.Node|null, tcbPath: AbsoluteFsPath, tcbIsShim: boolean} {
104112
this.ensureShimForComponent(component);
@@ -598,6 +606,20 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
598606
return this.typeCheckScopeRegistry.getTypeCheckDirectiveMetadata(new Reference(dir));
599607
}
600608

609+
getNgModuleMetadata(module: ts.ClassDeclaration): NgModuleMeta|null {
610+
if (!isNamedClassDeclaration(module)) {
611+
return null;
612+
}
613+
return this.metaReader.getNgModuleMetadata(new Reference(module));
614+
}
615+
616+
getPipeMetadata(pipe: ts.ClassDeclaration): PipeMeta|null {
617+
if (!isNamedClassDeclaration(pipe)) {
618+
return null;
619+
}
620+
return this.metaReader.getPipeMetadata(new Reference(pipe));
621+
}
622+
601623
getPotentialElementTags(component: ts.ClassDeclaration): Map<string, PotentialDirective|null> {
602624
if (this.elementTagCache.has(component)) {
603625
return this.elementTagCache.get(component)!;
@@ -712,18 +734,18 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
712734
}
713735

714736
getPotentialImportsFor(
715-
toImport: PotentialDirective|PotentialPipe,
716-
inContext: ts.ClassDeclaration): ReadonlyArray<PotentialImport> {
737+
toImport: Reference<ClassDeclaration>, inContext: ts.ClassDeclaration,
738+
importMode: PotentialImportMode): ReadonlyArray<PotentialImport> {
717739
const imports: PotentialImport[] = [];
718740

719-
const meta = this.metaReader.getDirectiveMetadata(toImport.ref) ??
720-
this.metaReader.getPipeMetadata(toImport.ref);
741+
const meta =
742+
this.metaReader.getDirectiveMetadata(toImport) ?? this.metaReader.getPipeMetadata(toImport);
721743
if (meta === null) {
722744
return imports;
723745
}
724746

725-
if (meta.isStandalone) {
726-
const emitted = this.emit(PotentialImportKind.Standalone, toImport.ref, inContext);
747+
if (meta.isStandalone || importMode === PotentialImportMode.ForceDirect) {
748+
const emitted = this.emit(PotentialImportKind.Standalone, toImport, inContext);
727749
if (emitted !== null) {
728750
imports.push(emitted);
729751
}
@@ -732,7 +754,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
732754
const exportingNgModules = this.ngModuleIndex.getNgModulesExporting(meta.ref.node);
733755
if (exportingNgModules !== null) {
734756
for (const exporter of exportingNgModules) {
735-
const emittedRef = this.emit(PotentialImportKind.Standalone, exporter, inContext);
757+
const emittedRef = this.emit(PotentialImportKind.NgModule, exporter, inContext);
736758
if (emittedRef !== null) {
737759
imports.push(emittedRef);
738760
}

packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ function getDirectiveMetaFromDeclaration(
663663
isStandalone: !!decl.isStandalone,
664664
baseClass: null,
665665
animationTriggerNames: null,
666+
decorator: null,
666667
hostDirectives: decl.hostDirectives === undefined ? null : decl.hostDirectives.map(hostDecl => {
667668
return {
668669
directive: new Reference(resolveDeclaration(hostDecl.directive)),

packages/compiler-cli/test/ngtsc/ls_typecheck_helpers_spec.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import {PotentialImportMode} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
89
import ts from 'typescript';
910

1011
import {DiagnosticCategoryLabel} from '../../src/ngtsc/core/api';
@@ -34,7 +35,7 @@ runInEachFileSystem(() => {
3435
it('for components', () => {
3536
env.write('test.ts', `
3637
import {Component} from '@angular/core';
37-
38+
3839
@Component({
3940
standalone: true,
4041
selector: 'test-cmp',
@@ -52,7 +53,7 @@ runInEachFileSystem(() => {
5253
it('for pipes', () => {
5354
env.write('test.ts', `
5455
import {Pipe, PipeTransform} from '@angular/core';
55-
56+
5657
@Pipe({name: 'expPipe'})
5758
export class ExpPipe implements PipeTransform {
5859
transform(value: number, exponent = 1): number {
@@ -70,7 +71,7 @@ runInEachFileSystem(() => {
7071
it('for NgModules', () => {
7172
env.write('test.ts', `
7273
import {NgModule} from '@angular/core';
73-
74+
7475
@NgModule({
7576
declarations: [],
7677
imports: [],
@@ -91,15 +92,15 @@ runInEachFileSystem(() => {
9192
it('for components', () => {
9293
env.write('test.ts', `
9394
import {Component, NgModule} from '@angular/core';
94-
95+
9596
@NgModule({
9697
declarations: [AppCmp],
9798
imports: [],
9899
providers: [],
99100
bootstrap: [AppCmp]
100101
})
101102
export class AppModule {}
102-
103+
103104
@Component({
104105
selector: 'app-cmp',
105106
template: '<div></div>',
@@ -118,15 +119,15 @@ runInEachFileSystem(() => {
118119
it('for standalone components (which should be null)', () => {
119120
env.write('test.ts', `
120121
import {Component, NgModule} from '@angular/core';
121-
122+
122123
@NgModule({
123124
declarations: [AppCmp],
124125
imports: [],
125126
providers: [],
126127
bootstrap: [AppCmp]
127128
})
128129
export class AppModule {}
129-
130+
130131
@Component({
131132
selector: 'app-cmp',
132133
template: '<div></div>',
@@ -146,14 +147,14 @@ runInEachFileSystem(() => {
146147
it('for pipes', () => {
147148
env.write('test.ts', `
148149
import {Component, NgModule, Pipe, PipeTransform} from '@angular/core';
149-
150+
150151
@NgModule({
151152
declarations: [ExpPipe],
152153
imports: [],
153154
providers: [],
154155
})
155156
export class PipeModule {}
156-
157+
157158
@Pipe({name: 'expPipe'})
158159
export class ExpPipe implements PipeTransform {
159160
transform(value: number, exponent = 1): number {
@@ -175,7 +176,7 @@ runInEachFileSystem(() => {
175176
it('which are out of scope', () => {
176177
env.write('one.ts', `
177178
import {Component} from '@angular/core';
178-
179+
179180
@Component({
180181
standalone: true,
181182
selector: 'one-cmp',
@@ -186,7 +187,7 @@ runInEachFileSystem(() => {
186187

187188
env.write('two.ts', `
188189
import {Component} from '@angular/core';
189-
190+
190191
@Component({
191192
standalone: true,
192193
selector: 'two-cmp',
@@ -206,7 +207,7 @@ runInEachFileSystem(() => {
206207
it('which are out of scope', () => {
207208
env.write('one.ts', `
208209
import {Pipe} from '@angular/core';
209-
210+
210211
@Pipe({
211212
name: 'foo-pipe',
212213
standalone: true,
@@ -217,7 +218,7 @@ runInEachFileSystem(() => {
217218

218219
env.write('two.ts', `
219220
import {Component} from '@angular/core';
220-
221+
221222
@Component({
222223
standalone: true,
223224
selector: 'two-cmp',
@@ -237,7 +238,7 @@ runInEachFileSystem(() => {
237238
it('for out of scope standalone components', () => {
238239
env.write('one.ts', `
239240
import {Component} from '@angular/core';
240-
241+
241242
@Component({
242243
standalone: true,
243244
selector: 'one-cmp',
@@ -248,7 +249,7 @@ runInEachFileSystem(() => {
248249

249250
env.write('two.ts', `
250251
import {Component} from '@angular/core';
251-
252+
252253
@Component({
253254
standalone: true,
254255
selector: 'two-cmp',
@@ -263,7 +264,8 @@ runInEachFileSystem(() => {
263264

264265
const TwoCmpDir = checker.getPotentialTemplateDirectives(OneCmpClass)
265266
.filter(d => d.selector === 'two-cmp')[0];
266-
const imports = checker.getPotentialImportsFor(TwoCmpDir, OneCmpClass);
267+
const imports =
268+
checker.getPotentialImportsFor(TwoCmpDir.ref, OneCmpClass, PotentialImportMode.Normal);
267269

268270
expect(imports.length).toBe(1);
269271
expect(imports[0].moduleSpecifier).toBe('./two');
@@ -273,7 +275,7 @@ runInEachFileSystem(() => {
273275
it('for out of scope ngModules', () => {
274276
env.write('one.ts', `
275277
import {Component} from '@angular/core';
276-
278+
277279
@Component({
278280
standalone: true,
279281
selector: 'one-cmp',
@@ -284,7 +286,7 @@ runInEachFileSystem(() => {
284286

285287
env.write('two.ts', `
286288
import {Component} from '@angular/core';
287-
289+
288290
@Component({
289291
selector: 'two-cmp',
290292
template: '<div></div>',
@@ -296,7 +298,7 @@ runInEachFileSystem(() => {
296298
import { NgModule } from '@angular/core';
297299
import { CommonModule } from '@angular/common';
298300
import { TwoCmp } from './two';
299-
301+
300302
@NgModule({
301303
declarations: [
302304
TwoCmp
@@ -318,7 +320,8 @@ runInEachFileSystem(() => {
318320

319321
const TwoNgMod = checker.getPotentialTemplateDirectives(OneCmpClass)
320322
.filter(d => d.selector === 'two-cmp')[0];
321-
const imports = checker.getPotentialImportsFor(TwoNgMod, OneCmpClass);
323+
const imports =
324+
checker.getPotentialImportsFor(TwoNgMod.ref, OneCmpClass, PotentialImportMode.Normal);
322325

323326
expect(imports.length).toBe(1);
324327
expect(imports[0].moduleSpecifier).toBe('./twomod');

packages/compiler/src/render3/view/t2_binder.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,6 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
331331
class TemplateBinder extends RecursiveAstVisitor implements Visitor {
332332
private visitNode: (node: Node) => void;
333333

334-
private pipesUsed: string[] = [];
335-
336334
private constructor(
337335
private bindings: Map<AST, Reference|Variable>,
338336
private symbols: Map<Reference|Variable, Template>, private usedPipes: Set<string>,

0 commit comments

Comments
 (0)