Skip to content

Commit bc9a067

Browse files
crisbetommalerba
authored andcommitted
refactor(compiler-cli): add flag to enable selectorless (angular#60977)
Adds a private flag that we can use to enable selectorless as it's being developed. PR Close angular#60977
1 parent c2987d8 commit bc9a067

File tree

12 files changed

+61
-9
lines changed

12 files changed

+61
-9
lines changed

packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression>
120120
i18nNormalizeLineEndingsInICUs: isInline,
121121
enableBlockSyntax,
122122
enableLetSyntax,
123+
// TODO(crisbeto): figure out how this is enabled.
124+
enableSelectorless: false,
123125
});
124126
if (template.errors !== null) {
125127
const errors = template.errors.map((err) => err.toString()).join('\n');

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,13 +272,15 @@ export class ComponentDecoratorHandler
272272
private readonly enableHmr: boolean,
273273
private readonly implicitStandaloneValue: boolean,
274274
private readonly typeCheckHostBindings: boolean,
275+
private readonly enableSelectorless: boolean,
275276
) {
276277
this.extractTemplateOptions = {
277278
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
278279
i18nNormalizeLineEndingsInICUs: this.i18nNormalizeLineEndingsInICUs,
279280
usePoisonedData: this.usePoisonedData,
280281
enableBlockSyntax: this.enableBlockSyntax,
281282
enableLetSyntax: this.enableLetSyntax,
283+
enableSelectorless: this.enableSelectorless,
282284
preserveSignificantWhitespace: this.i18nPreserveSignificantWhitespace,
283285
};
284286

@@ -308,6 +310,7 @@ export class ComponentDecoratorHandler
308310
usePoisonedData: boolean;
309311
enableBlockSyntax: boolean;
310312
enableLetSyntax: boolean;
313+
enableSelectorless: boolean;
311314
preserveSignificantWhitespace?: boolean;
312315
};
313316

@@ -705,6 +708,7 @@ export class ComponentDecoratorHandler
705708
usePoisonedData: this.usePoisonedData,
706709
enableBlockSyntax: this.enableBlockSyntax,
707710
enableLetSyntax: this.enableLetSyntax,
711+
enableSelectorless: this.enableSelectorless,
708712
preserveSignificantWhitespace: this.i18nPreserveSignificantWhitespace,
709713
},
710714
this.compilationMode,

packages/compiler-cli/src/ngtsc/annotations/component/src/resources.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export interface ExtractTemplateOptions {
135135
i18nNormalizeLineEndingsInICUs: boolean;
136136
enableBlockSyntax: boolean;
137137
enableLetSyntax: boolean;
138+
enableSelectorless: boolean;
138139
preserveSignificantWhitespace?: boolean;
139140
}
140141

@@ -319,6 +320,7 @@ function parseExtractedTemplate(
319320
escapedString,
320321
enableBlockSyntax: options.enableBlockSyntax,
321322
enableLetSyntax: options.enableLetSyntax,
323+
enableSelectorless: options.enableSelectorless,
322324
};
323325

324326
const parsedTemplate = parseTemplate(sourceStr, sourceMapUrl ?? '', {

packages/compiler-cli/src/ngtsc/annotations/component/test/component_spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ function setup(
161161
/* enableHmr */ false,
162162
/* implicitStandaloneValue */ true,
163163
/* typeCheckHostBindings */ true,
164+
/* enableSelectorless */ false,
164165
);
165166
return {reflectionHost, handler, resourceLoader, metaRegistry};
166167
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ export interface InternalOptions {
121121
*/
122122
_enableHmr?: boolean;
123123

124+
/**
125+
* Whether selectorless is enabled.
126+
*
127+
* @internal
128+
*/
129+
_enableSelectorless?: boolean;
130+
124131
// TODO(crisbeto): this is a temporary flag that will be removed in v20.
125132
/**
126133
* Whether to check the event side of two-way bindings.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ export class NgCompiler {
391391
private readonly angularCoreVersion: string | null;
392392
private readonly enableHmr: boolean;
393393
private readonly implicitStandaloneValue: boolean;
394+
private readonly enableSelectorless: boolean;
394395

395396
/**
396397
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
@@ -463,6 +464,7 @@ export class NgCompiler {
463464
// TODO(crisbeto): remove this flag and base `enableBlockSyntax` on the `angularCoreVersion`.
464465
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
465466
this.enableLetSyntax = options['_enableLetSyntax'] ?? true;
467+
this.enableSelectorless = options['_enableSelectorless'] ?? false;
466468
// Standalone by default is enabled since v19. We need to toggle it here,
467469
// because the language service extension may be running with the latest
468470
// version of the compiler against an older version of Angular.
@@ -1084,6 +1086,7 @@ export class NgCompiler {
10841086
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
10851087
allowSignalsInTwoWayBindings,
10861088
checkTwoWayBoundEvents,
1089+
selectorlessEnabled: this.enableSelectorless,
10871090
};
10881091
} else {
10891092
typeCheckingConfig = {
@@ -1119,6 +1122,7 @@ export class NgCompiler {
11191122
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
11201123
allowSignalsInTwoWayBindings,
11211124
checkTwoWayBoundEvents,
1125+
selectorlessEnabled: this.enableSelectorless,
11221126
};
11231127
}
11241128

@@ -1507,6 +1511,7 @@ export class NgCompiler {
15071511
this.enableHmr,
15081512
this.implicitStandaloneValue,
15091513
typeCheckHostBindings,
1514+
this.enableSelectorless,
15101515
),
15111516

15121517
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ export interface TypeCheckingConfig {
362362
* Whether the event side of a two-way binding should be type checked.
363363
*/
364364
checkTwoWayBoundEvents: boolean;
365+
366+
/**
367+
* Whether selectorless syntax is enabled.
368+
*/
369+
selectorlessEnabled: boolean;
365370
}
366371

367372
export type SourceMapping =

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

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

821837
export function getClass(sf: ts.SourceFile, name: string): ClassDeclaration<ts.ClassDeclaration> {

packages/compiler/src/template/pipeline/src/ingest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ function ingestNodes(unit: ViewCompilationUnit, template: t.Node[]): void {
231231
ingestForBlock(unit, node);
232232
} else if (node instanceof t.LetDeclaration) {
233233
ingestLetDeclaration(unit, node);
234+
} else if (node instanceof t.Component) {
235+
// TODO(crisbeto): account for selectorless nodes.
234236
} else {
235237
throw new Error(`Unsupported template node: ${node.constructor.name}`);
236238
}

packages/core/schematics/migrations/signal-migration/src/passes/reference_resolution/identify_template_references.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ function extractTemplateWithoutCompilerAnalysis(
135135
usePoisonedData: true,
136136
enableI18nLegacyMessageIdFormat: options.enableI18nLegacyMessageIdFormat !== false,
137137
i18nNormalizeLineEndingsInICUs: options.i18nNormalizeLineEndingsInICUs === true,
138+
enableSelectorless: false,
138139
},
139140
CompilationMode.FULL,
140141
).nodes;

0 commit comments

Comments
 (0)