8000 refactor(compiler-cli): enable type checking of directive classes (#6… · angular/angular@eb4c22e · GitHub
[go: up one dir, main page]

Skip to content

Commit eb4c22e

Browse files
crisbetopkozlowski-opensource
authored andcommitted
refactor(compiler-cli): enable type checking of directive classes (#60267)
Updates the directive handler to enable type checking of directives. Currently this only applies to checking of host bindings. PR Close #60267
1 parent 84b3721 commit eb4c22e

File tree

4 files changed

+85
-3
lines changed

4 files changed

+85
-3
lines changed

packages/compiler-cli/src/ngtsc/annotations/directive/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ ts_library(
2020
"//packages/compiler-cli/src/ngtsc/scope",
2121
"//packages/compiler-cli/src/ngtsc/transform",
2222
"//packages/compiler-cli/src/ngtsc/translator",
23+
"//packages/compiler-cli/src/ngtsc/typecheck",
24+
"//packages/compiler-cli/src/ngtsc/typecheck/api",
2325
"@npm//@types/node",
2426
"@npm//typescript",
2527
],

packages/compiler-cli/src/ngtsc/annotations/directive/src/handler.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
makeBindingParser,
1717
R3ClassMetadata,
1818
R3DirectiveMetadata,
19+
R3TargetBinder,
1920
WrappedNodeExpr,
2021
} from '@angular/compiler';
2122
import ts from 'typescript';
@@ -47,7 +48,7 @@ import {
4748
Decorator,
4849
ReflectionHost,
4950
} from '../../../reflection';
50-
import {LocalModuleScopeRegistry} from '../../../scope';
51+
import {LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../../scope';
5152
import {
5253
AnalysisOutput,
5354
CompilationMode,
@@ -79,6 +80,12 @@ import {
7980
import {extractDirectiveMetadata, extractHostBindingResources, HostBindingNodes} from './shared';
8081
import {DirectiveSymbol} from './symbol';
8182
import {JitDeclarationRegistry} from '../../common/src/jit_declaration_registry';
83+
import {
84+
HostBindingsContext,
85+
TypeCheckableDirectiveMeta,
86+
TypeCheckContext,
87+
} from '../../../typecheck/api';
88+
import {createHostElement} from '../../../typecheck';
8289

8390
const FIELD_DECORATORS = [
8491
'Input',
@@ -138,11 +145,14 @@ export class DirectiveDecoratorHandler
138145
private perf: PerfRecorder,
139146
private importTracker: ImportedSymbolsTracker,
140147
private includeClassMetadata: boolean,
148+
private typeCheckScopeRegistry: TypeCheckScopeRegistry,
141149
private readonly compilationMode: CompilationMode,
142150
private readonly jitDeclarationRegistry: JitDeclarationRegistry,
143151
private readonly resourceRegistry: ResourceRegistry,
144152
private readonly strictStandalone: boolean,
145153
private readonly implicitStandaloneValue: boolean,
154+
private readonly usePoisonedData: boolean,
155+
private readonly typeCheckHostBindings: boolean,
146156
) {}
147157

148158
readonly precedence = HandlerPrecedence.PRIMARY;
@@ -303,6 +313,53 @@ export class DirectiveDecoratorHandler
303313
});
304314
}
305315

316+
typeCheck(
317+
ctx: TypeCheckContext,
318+
node: ClassDeclaration,
319+
meta: Readonly<DirectiveHandlerData>,
320+
): void {
321+
// Currently type checking in directives is only supported for host bindings
322+
// so we can skip everything below if type checking is disabled.
323+
if (!this.typeCheckHostBindings) {
324+
return;
325+
}
326+
327+
if (!ts.isClassDeclaration(node) || (meta.isPoisoned && !this.usePoisonedData)) {
328+
return;
329+
}
330+
const scope = this.typeCheckScopeRegistry.getTypeCheckScope(node);
331+
if (scope.isPoisoned && !this.usePoisonedData) {
332+
// Don't type-check components that had errors in their scopes, unless requested.
333+
return;
334+
}
335+
336+
const hostElement = createHostElement(
337+
'directive',
338+
meta.meta.selector,
339+
node,
340+
meta.hostBindingNodes.literal,
341+
meta.hostBindingNodes.bindingDecorators,
342+
meta.hostBindingNodes.listenerDecorators,
343+
);
344+
345+
if (hostElement !== null) {
346+
const binder = new R3TargetBinder<TypeCheckableDirectiveMeta>(scope.matcher);
347+
const hostBindingsContext: HostBindingsContext = {
348+
node: hostElement,
349+
sourceMapping: {type: 'direct', node},
350+
};
351+
352+
ctx.addDirective(
353+
new Reference(node),
354+
binder,
355+
scope.schemas,
356+
null,
357+
hostBindingsContext,
358+
meta.meta.isStandalone,
359+
);
360+
}
361+
}
362+
306363
resolve(
307364
node: ClassDeclaration,
308365
analysis: DirectiveHandlerData,
@@ -354,7 +411,13 @@ export class DirectiveDecoratorHandler
354411
diagnostics.push(...hostDirectivesDiagnotics);
355412
}
356413

357-
return {diagnostics: diagnostics.length > 0 ? diagnostics : undefined};
414+
if (diagnostics.length > 0) {
415+
return {diagnostics};
416+
}
417+
418+
// Note: we need to produce *some* sort of the data in order
419+
// for the host binding diagnostics to be surfaced.
420+
return {data: {}};
358421
}
359422

360423
compileFull(

packages/compiler-cli/src/ngtsc/annotations/directive/test/directive_spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {ImportedSymbolsTracker, ReferenceEmitter} from '../../../imports';
2121
import {
2222
CompoundMetadataReader,
2323
DtsMetadataReader,
24+
HostDirectivesResolver,
2425
LocalMetadataRegistry,
2526
ResourceRegistry,
2627
} from '../../../metadata';
@@ -31,7 +32,11 @@ import {
3132
isNamedClassDeclaration,
3233
TypeScriptReflectionHost,
3334
} from '../../../reflection';
34-
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../scope';
35+
import {
36+
LocalModuleScopeRegistry,
37+
MetadataDtsModuleScopeResolver,
38+
TypeCheckScopeRegistry,
39+
} from '../../../scope';
3540
import {getDeclaration, makeProgram} from '../../../testing';
3641
import {CompilationMode} from '../../../transform';
3742
import {
@@ -201,6 +206,12 @@ runInEachFileSystem(() => {
201206
const importTracker = new ImportedSymbolsTracker();
202207
const jitDeclarationRegistry = new JitDeclarationRegistry();
203208
const resourceRegistry = new ResourceRegistry();
209+
const hostDirectivesResolver = new HostDirectivesResolver(metaReader);
210+
const typeCheckScopeRegistry = new TypeCheckScopeRegistry(
211+
scopeRegistry,
212+
metaReader,
213+
hostDirectivesResolver,
214+
);
204215

205216
const handler = new DirectiveDecoratorHandler(
206217
reflectionHost,
@@ -218,11 +229,14 @@ runInEachFileSystem(() => {
218229
NOOP_PERF_RECORDER,
219230
importTracker,
220231
/*includeClassMetadata*/ true,
232+
typeCheckScopeRegistry,
221233
/*compilationMode */ CompilationMode.FULL,
222234
jitDeclarationRegistry,
223235
resourceRegistry,
224236
/* strictStandalone */ false,
225237
/* implicitStandaloneValue */ true,
238+
/* usePoisonedData */ false,
239+
/* typeCheckHostBindings */ true,
226240
);
227241

228242
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);

packages/compiler-cli/src/ngtsc/core/src/compiler.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,11 +1525,14 @@ export class NgCompiler {
15251525
this.delegatingPerfRecorder,
15261526
importTracker,
15271527
supportTestBed,
1528+
typeCheckScopeRegistry,
15281529
compilationMode,
15291530
jitDeclarationRegistry,
15301531
resourceRegistry,
15311532
!!this.options.strictStandalone,
15321533
this.implicitStandaloneValue,
1534+
this.usePoisonedData,
1535+
typeCheckHostBindings,
15331536
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null, unknown>>,
15341537
// Pipe handler must be before injectable handler in list so pipe factories are printed
15351538
// before injectable factories (so injectable factories can delegate to them)

0 commit comments

Comments
 (0)
0