8000 Type checking support for host bindings by crisbeto · Pull Request #60267 · angular/angular · GitHub
[go: up one dir, main page]

Skip to content

Type checking support for host bindings #60267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Prev Previous commit
Next Next commit
refactor(compiler-cli): enable type checking of directive classes
Updates the directive handler to enable type checking of directives. Currently this only applies to checking of host bindings.
  • Loading branch information
crisbeto committed Mar 17, 2025
commit a17c0cde01738125d29a4b5ed5798df93a56c3d2
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/transform",
"//packages/compiler-cli/src/ngtsc/translator",
"//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"@npm//@types/node",
"@npm//typescript",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
makeBindingParser,
R3ClassMetadata,
R3DirectiveMetadata,
R3TargetBinder,
WrappedNodeExpr,
} from '@angular/compiler';
import ts from 'typescript';
Expand Down Expand Up @@ -47,7 +48,7 @@ import {
Decorator,
ReflectionHost,
} from '../../../reflection';
import {LocalModuleScopeRegistry} from '../../../scope';
import {LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../../scope';
import {
AnalysisOutput,
CompilationMode,
Expand Down Expand Up @@ -79,6 +80,12 @@ import {
import {extractDirectiveMetadata, extractHostBindingResources, HostBindingNodes} from './shared';
import {DirectiveSymbol} from './symbol';
import {JitDeclarationRegistry} from '../../common/src/jit_declaration_registry';
import {
HostBindingsContext,
TypeCheckableDirectiveMeta,
TypeCheckContext,
} from '../../../typecheck/api';
import {createHostElement} from '../../../typecheck';

const FIELD_DECORATORS = [
'Input',
Expand Down Expand Up @@ -138,11 +145,14 @@ export class DirectiveDecoratorHandler
private perf: PerfRecorder,
private importTracker: ImportedSymbolsTracker,
private includeClassMetadata: boolean,
private typeCheckScopeRegistry: TypeCheckScopeRegistry,
private readonly compilationMode: CompilationMode,
private readonly jitDeclarationRegistry: JitDeclarationRegistry,
private readonly resourceRegistry: ResourceRegistry,
private readonly strictStandalone: boolean,
private readonly implicitStandaloneValue: boolean,
private readonly usePoisonedData: boolean,
private readonly typeCheckHostBindings: boolean,
) {}

readonly precedence = HandlerPrecedence.PRIMARY;
Expand Down Expand Up @@ -303,6 +313,53 @@ export class DirectiveDecoratorHandler
});
}

typeCheck(
ctx: TypeCheckContext,
node: ClassDeclaration,
meta: Readonly<DirectiveHandlerData>,
): void {
// Currently type checking in directives is only supported for host bindings
// so we can skip everything below if type checking is disabled.
if (!this.typeCheckHostBindings) {
return;
}

if (!ts.isClassDeclaration(node) || (meta.isPoisoned && !this.usePoisonedData)) {
return;
}
const scope = this.typeCheckScopeRegistry.getTypeCheckScope(node);
if (scope.isPoisoned && !this.usePoisonedData) {
// Don't type-check components that had errors in their scopes, unless requested.
return;
}

const hostElement = createHostElement(
'directive',
meta.meta.selector,
node,
meta.hostBindingNodes.literal,
meta.hostBindingNodes.bindingDecorators,
meta.hostBindingNodes.listenerDecorators,
);

if (hostElement !== null) {
const binder = new R3TargetBinder<TypeCheckableDirectiveMeta>(scope.matcher);
const hostBindingsContext: HostBindingsContext = {
node: hostElement,
sourceMapping: {type: 'direct', node},
};

ctx.addDirective(
new Reference(node),
binder,
scope.schemas,
null,
hostBindingsContext,
meta.meta.isStandalone,
);
}
}

resolve(
node: ClassDeclaration,
analysis: DirectiveHandlerData,
Expand Down Expand Up @@ -354,7 +411,13 @@ export class DirectiveDecoratorHandler
diagnostics.push(...hostDirectivesDiagnotics);
}

return {diagnostics: diagnostics.length > 0 ? diagnostics : undefined};
if (diagnostics.length > 0) {
return {diagnostics};
}

// Note: we need to produce *some* sort of the data in order
// for the host binding diagnostics to be surfaced.
return {data: {}};
}

compileFull(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {ImportedSymbolsTracker, ReferenceEmitter} from '../../../imports';
import {
CompoundMetadataReader,
DtsMetadataReader,
HostDirectivesResolver,
LocalMetadataRegistry,
ResourceRegistry,
} from '../../../metadata';
Expand All @@ -31,7 +32,11 @@ import {
isNamedClassDeclaration,
TypeScriptReflectionHost,
} from '../../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../scope';
import {
LocalModuleScopeRegistry,
MetadataDtsModuleScopeResolver,
TypeCheckScopeRegistry,
} from '../../../scope';
import {getDeclaration, makeProgram} from '../../../testing';
import {CompilationMode} from '../../../transform';
import {
Expand Down Expand Up @@ -201,6 +206,12 @@ runInEachFileSystem(() => {
const importTracker = new ImportedSymbolsTracker();
const jitDeclarationRegistry = new JitDeclarationRegistry();
const resourceRegistry = new ResourceRegistry();
const hostDirectivesResolver = new HostDirectivesResolver(metaReader);
const typeCheckScopeRegistry = new TypeCheckScopeRegistry(
scopeRegistry,
metaReader,
hostDirectivesResolver,
);

const handler = new DirectiveDecoratorHandler(
reflectionHost,
Expand All @@ -218,11 +229,14 @@ runInEachFileSystem(() => {
NOOP_PERF_RECORDER,
importTracker,
/*includeClassMetadata*/ true,
typeCheckScopeRegistry,
/*compilationMode */ CompilationMode.FULL,
jitDeclarationRegistry,
resourceRegistry,
/* strictStandalone */ false,
/* implicitStandaloneValue */ true,
/* usePoisonedData */ false,
/* typeCheckHostBindings */ true,
);

const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);
Expand Down
3 changes: 3 additions & 0 deletions packages/compiler-cli/src/ngtsc/core/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1525,11 +1525,14 @@ export class NgCompiler {
this.delegatingPerfRecorder,
importTracker,
supportTestBed,
typeCheckScopeRegistry,
compilationMode,
jitDeclarationRegistry,
resourceRegistry,
!!this.options.strictStandalone,
this.implicitStandaloneValue,
this.usePoisonedData,
typeCheckHostBindings,
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null, unknown>>,
// Pipe handler must be before injectable handler in list so pipe factories are printed
// before injectable factories (so injectable factories can delegate to them)
Expand Down
0