From 5cfdd7897bd48dbfd4e2bc74a801ac73ae18f767 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 21 Nov 2025 10:56:42 +0100 Subject: [PATCH] refactor(compiler-cli): track public methods during analysis Updates the directive analysis to track the public methods of the class. --- .../src/ngtsc/metadata/src/api.ts | 3 +++ .../src/ngtsc/metadata/src/inheritance.ts | 5 +++++ .../src/ngtsc/metadata/src/util.ts | 21 ++++++++++++++++++- .../src/ngtsc/scope/test/local_spec.ts | 1 + .../src/ngtsc/typecheck/testing/index.ts | 4 ++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/api.ts b/packages/compiler-cli/src/ngtsc/metadata/src/api.ts index bc57d529f11..b2d2b503d2f 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/api.ts @@ -116,6 +116,9 @@ export interface DirectiveTypeCheckMeta { */ undeclaredInputFields: Set; + /** Names of the public methods of the class. */ + publicMethods: Set; + /** * Whether the Directive's class is generic, i.e. `class MyDir {...}`. */ diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/inheritance.ts b/packages/compiler-cli/src/ngtsc/metadata/src/inheritance.ts index 97825d08756..66c6c723162 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/inheritance.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/inheritance.ts @@ -36,6 +36,7 @@ export function flattenInheritedDirectiveMetadata( const undeclaredInputFields = new Set(); const restrictedInputFields = new Set(); const stringLiteralInputFields = new Set(); + const publicMethods = new Set(); let hostDirectives: HostDirectiveMeta[] | null = null; let isDynamic = false; let inputs = ClassPropertyMapping.empty(); @@ -72,6 +73,9 @@ export function flattenInheritedDirectiveMetadata( for (const field of meta.stringLiteralInputFields) { stringLiteralInputFields.add(field); } + for (const name of meta.publicMethods) { + publicMethods.add(name); + } if (meta.hostDirectives !== null && meta.hostDirectives.length > 0) { hostDirectives ??= []; hostDirectives.push(...meta.hostDirectives); @@ -88,6 +92,7 @@ export function flattenInheritedDirectiveMetadata( undeclaredInputFields, restrictedInputFields, stringLiteralInputFields, + publicMethods, baseClass: isDynamic ? 'dynamic' : null, isStructural, hostDirectives, diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/util.ts b/packages/compiler-cli/src/ngtsc/metadata/src/util.ts index e3668894ffb..e659627d01d 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/util.ts @@ -12,6 +12,7 @@ import {OwningModule, Reference} from '../../imports'; import { ClassDeclaration, ClassMember, + ClassMemberAccessLevel, ClassMemberKind, isNamedClassDeclaration, ReflectionHost, @@ -176,7 +177,24 @@ export function extractDirectiveTypeCheckMeta( reflector: ReflectionHost, ): DirectiveTypeCheckMeta { const members = reflector.getMembersOfClass(node); - const staticMembers = members.filter((member) => member.isStatic); + const publicMethods = new Set(); + const staticMembers: ClassMember[] = []; + + for (const member of members) { + if (member.isStatic) { + staticMembers.push(member); + } + + if ( + member.kind === ClassMemberKind.Method && + !member.isStatic && + (member.accessLevel === ClassMemberAccessLevel.PublicReadonly || + member.accessLevel === ClassMemberAccessLevel.PublicWritable) + ) { + publicMethods.add(member.name); + } + } + const ngTemplateGuards = staticMembers .map(extractTemplateGuard) .filter((guard): guard is TemplateGuardMeta => guard !== null); @@ -225,6 +243,7 @@ export function extractDirectiveTypeCheckMeta( restrictedInputFields, stringLiteralInputFields, undeclaredInputFields, + publicMethods, isGeneric: arity !== null && arity > 0, }; } diff --git a/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts b/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts index 27d8ae2b472..9a112e527c4 100644 --- a/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts +++ b/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts @@ -352,6 +352,7 @@ function fakeDirective(ref: Reference): DirectiveMeta { restrictedInputFields: new Set(), stringLiteralInputFields: new Set(), undeclaredInputFields: new Set(), + publicMethods: new Set(), isGeneric: false, baseClass: null, isPoisoned: false, diff --git a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts index 525aaa5282d..d8155fae533 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts @@ -308,6 +308,7 @@ export interface TestDirective | 'restrictedInputFields' | 'stringLiteralInputFields' | 'undeclaredInputFields' + | 'publicMethods' | 'inputs' | 'outputs' | 'hostDirectives' @@ -334,6 +335,7 @@ export interface TestDirective restrictedInputFields?: string[]; stringLiteralInputFields?: string[]; undeclaredInputFields?: string[]; + publicMethods?: string[]; isGeneric?: boolean; code?: string; ngContentSelectors?: string[] | null; @@ -879,6 +881,7 @@ function getDirectiveMetaFromDeclaration( restrictedInputFields: new Set(decl.restrictedInputFields || []), stringLiteralInputFields: new Set(decl.stringLiteralInputFields || []), undeclaredInputFields: new Set(decl.undeclaredInputFields || []), + publicMethods: new Set(decl.publicMethods || []), isGeneric: decl.isGeneric ?? false, outputs: ClassPropertyMapping.fromMappedObject(decl.outputs || {}), queries: decl.queries || [], @@ -938,6 +941,7 @@ function makeScope(program: ts.Program, sf: ts.SourceFile, decls: TestDeclaratio restrictedInputFields: new Set(decl.restrictedInputFields ?? []), stringLiteralInputFields: new Set(decl.stringLiteralInputFields ?? []), undeclaredInputFields: new Set(decl.undeclaredInputFields ?? []), + publicMethods: new Set(decl.publicMethods ?? []), isGeneric: decl.isGeneric ?? false, isPoisoned: false, isStructural: false,