@@ -15,6 +15,7 @@ import {
1515 PropertyRead ,
1616 PropertyWrite ,
1717 R3TargetBinder ,
18+ SelectorlessMatcher ,
1819 SelectorMatcher ,
1920 TmplAstElement ,
2021 TmplAstLetDeclaration ,
@@ -279,6 +280,7 @@ export const ALL_ENABLED_CONFIG: Readonly<TypeCheckingConfig> = {
279280 unusedStandaloneImports : 'warning' ,
280281 allowSignalsInTwoWayBindings : true ,
281282 checkTwoWayBoundEvents : true ,
283+ selectorlessEnabled : false ,
282284} ;
283285
284286// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead.
@@ -299,7 +301,7 @@ export interface TestDirective
299301 >
300302 >
301303 > {
302- selector : string ;
304+ selector : string | null ;
303305 name : string ;
304306 file ?: AbsoluteFsPath ;
305307 type : 'directive' ;
@@ -369,6 +371,7 @@ export function tcb(
369371 const clazz = getClass ( sf , 'Test' ) ;
370372 const templateUrl = 'synthetic.html' ;
371373 const { nodes, errors} = parseTemplate ( template , templateUrl , templateParserOptions ) ;
374+ const selectorlessEnabled = templateParserOptions ?. enableSelectorless ?? false ;
372375
373376 if ( errors !== null ) {
374377 throw new Error ( 'Template parse errors: \n' + errors . join ( '\n' ) ) ;
@@ -378,6 +381,7 @@ export function tcb(
378381 declarations ,
379382 ( decl ) => getClass ( sf , decl . name ) ,
380383 new Map ( ) ,
384+ selectorlessEnabled ,
381385 ) ;
382386 const binder = new R3TargetBinder < DirectiveMeta > ( matcher ) ;
383387 const boundTarget = binder . bind ( { template : nodes } ) ;
@@ -419,6 +423,7 @@ export function tcb(
419423 suggestionsForSuboptimalTypeInference : false ,
420424 allowSignalsInTwoWayBindings : true ,
421425 checkTwoWayBoundEvents : true ,
426+ selectorlessEnabled,
422427 ...config ,
423428 } ;
424429 options = options || { emitSpans : false } ;
@@ -608,6 +613,7 @@ export function setup(
608613 return getClass ( declFile , decl . name ) ;
609614 } ,
610615 fakeMetadataRegistry ,
616+ overrides . parseOptions ?. enableSelectorless ?? false ,
611617 ) ;
612618 const binder = new R3TargetBinder < DirectiveMeta > ( matcher ) ;
613619 const classRef = new Reference ( classDecl ) ;
@@ -776,8 +782,8 @@ function prepareDeclarations(
776782 declarations : TestDeclaration [ ] ,
777783 resolveDeclaration : DeclarationResolver ,
778784 metadataRegistry : Map < string , TypeCheckableDirectiveMeta > ,
785+ selectorlessEnabled : boolean ,
779786) {
780- const matcher = new SelectorMatcher < DirectiveMeta [ ] > ( ) ;
781787 const pipes = new Map < string , PipeMeta > ( ) ;
782788 const hostDirectiveResolder = new HostDirectivesResolver (
783789 getFakeMetadataReader ( metadataRegistry as Map < string , DirectiveMeta > ) ,
@@ -809,13 +815,23 @@ function prepareDeclarations(
809815
810816 // We need to make two passes over the directives so that all declarations
811817 // have been registered by the time we resolve the host directives.
812- for ( const meta of directives ) {
813- const selector = CssSelector . parse ( meta . selector || '' ) ;
814- const matches = [ ...hostDirectiveResolder . resolve ( meta ) , meta ] as DirectiveMeta [ ] ;
815- matcher . addSelectables ( selector , matches ) ;
816- }
817818
818- return { matcher, pipes} ;
819+ if ( selectorlessEnabled ) {
820+ const registry = new Map < string , DirectiveMeta [ ] > ( ) ;
821+ for ( const meta of directives ) {
822+ registry . set ( meta . name , [ meta , ...hostDirectiveResolder . resolve ( meta ) ] ) ;
823+ }
824+ return { matcher : new SelectorlessMatcher < DirectiveMeta [ ] > ( registry ) , pipes} ;
825+ } else {
826+ const matcher = new SelectorMatcher < DirectiveMeta [ ] > ( ) ;
827+ for ( const meta of directives ) {
828+ const selector = CssSelector . parse ( meta . selector || '' ) ;
829+ const matches = [ ...hostDirectiveResolder . resolve ( meta ) , meta ] as DirectiveMeta [ ] ;
830+ matcher . addSelectables ( selector , matches ) ;
831+ }
832+
833+ return { matcher, pipes} ;
834+ }
819835}
820836
821837export function getClass ( sf : ts . SourceFile , name : string ) : ClassDeclaration < ts . ClassDeclaration > {
0 commit comments