refactor(compiler): expose utility for creating CSS selectors from AST nodes (#52726)

When doing directive matching in the compiler, we need to be able to create a selector from an AST node. We already have the utility, but these changes simplify the public API and expose it so it can be used in `compiler-cli`.

PR Close #52726
This commit is contained in:
Kristiyan Kostadinov 2023-11-08 10:31:14 +01:00 committed by Andrew Kushnir
parent 2d41b336a3
commit adbea7befb
4 changed files with 34 additions and 32 deletions

View file

@ -67,6 +67,7 @@ export * from './render3/view/api';
export {BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, Element as TmplAstElement, Icu as TmplAstIcu, Node as TmplAstNode, RecursiveVisitor as TmplAstRecursiveVisitor, Reference as TmplAstReference, Template as TmplAstTemplate, Text as TmplAstText, TextAttribute as TmplAstTextAttribute, Variable as TmplAstVariable, DeferredBlock as TmplAstDeferredBlock, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockError as TmplAstDeferredBlockError, DeferredTrigger as TmplAstDeferredTrigger, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, DeferredBlockTriggers as TmplAstDeferredBlockTriggers, UnknownBlock as TmplAstUnknownBlock} from './render3/r3_ast';
export * from './render3/view/t2_api';
export * from './render3/view/t2_binder';
export {createCssSelectorFromNode} from './render3/view/util';
export {Identifiers as R3Identifiers} from './render3/r3_identifiers';
export {R3ClassMetadata, CompileClassMetadataFn, compileClassMetadata, compileComponentClassMetadata} from './render3/r3_class_metadata_compiler';
export {compileClassDebugInfo, R3ClassDebugInfo} from './render3/r3_class_debug_info_compiler';

View file

@ -11,8 +11,7 @@ import {SelectorMatcher} from '../../selector';
import {BoundAttribute, BoundEvent, BoundText, Comment, Content, DeferredBlock, DeferredBlockError, DeferredBlockLoading, DeferredBlockPlaceholder, DeferredTrigger, Element, ForLoopBlock, ForLoopBlockEmpty, HoverDeferredTrigger, Icu, IfBlock, IfBlockBranch, InteractionDeferredTrigger, Node, Reference, SwitchBlock, SwitchBlockCase, Template, Text, TextAttribute, UnknownBlock, Variable, ViewportDeferredTrigger, Visitor} from '../r3_ast';
import {BoundTarget, DirectiveMeta, ReferenceTarget, ScopedNode, Target, TargetBinder} from './t2_api';
import {createCssSelector} from './template';
import {getAttrsForDirectiveMatching} from './util';
import {createCssSelectorFromNode} from './util';
/**
* Processes `Target`s with a given set of directives and performs a binding operation, which
@ -307,17 +306,17 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
}
visitElement(element: Element): void {
this.visitElementOrTemplate(element.name, element);
this.visitElementOrTemplate(element);
}
visitTemplate(template: Template): void {
this.visitElementOrTemplate('ng-template', template);
this.visitElementOrTemplate(template);
}
visitElementOrTemplate(elementName: string, node: Element|Template): void {
visitElementOrTemplate(node: Element|Template): void {
// First, determine the HTML shape of the node for the purpose of directive matching.
// Do this by building up a `CssSelector` for the node.
const cssSelector = createCssSelector(elementName, getAttrsForDirectiveMatching(node));
const cssSelector = createCssSelectorFromNode(node);
// Next, use the `SelectorMatcher` to get the list of directives on the node.
const directives: DirectiveT[] = [];

View file

@ -2570,30 +2570,6 @@ class TrackByBindingScope extends BindingScope {
}
}
/**
* Creates a `CssSelector` given a tag name and a map of attributes
*/
export function createCssSelector(
elementName: string, attributes: {[name: string]: string}): CssSelector {
const cssSelector = new CssSelector();
const elementNameNoNs = splitNsName(elementName)[1];
cssSelector.setElement(elementNameNoNs);
Object.getOwnPropertyNames(attributes).forEach((name) => {
const nameNoNs = splitNsName(name)[1];
const value = attributes[name];
cssSelector.addAttribute(nameNoNs, value);
if (name.toLowerCase() === 'class') {
const classes = value.trim().split(/\s+/);
classes.forEach(className => cssSelector.addClassName(className));
}
});
return cssSelector;
}
/**
* Creates an array of expressions out of an `ngProjectAs` attributes
* which can be added to the instruction parameters.

View file

@ -8,8 +8,10 @@
import {ConstantPool} from '../../constant_pool';
import {BindingType, Interpolation} from '../../expression_parser/ast';
import {splitNsName} from '../../ml_parser/tags';
import * as o from '../../output/output_ast';
import {ParseSourceSpan} from '../../parse_util';
import {CssSelector} from '../../selector';
import * as t from '../r3_ast';
import {Identifiers as R3} from '../r3_identifiers';
import {ForwardRefHandling} from '../util';
@ -277,6 +279,31 @@ export class DefinitionMap<T = any> {
}
}
/**
* Creates a `CssSelector` from an AST node.
*/
export function createCssSelectorFromNode(node: t.Element|t.Template): CssSelector {
const elementName = node instanceof t.Element ? node.name : 'ng-template';
const attributes = getAttrsForDirectiveMatching(node);
const cssSelector = new CssSelector();
const elementNameNoNs = splitNsName(elementName)[1];
cssSelector.setElement(elementNameNoNs);
Object.getOwnPropertyNames(attributes).forEach((name) => {
const nameNoNs = splitNsName(name)[1];
const value = attributes[name];
cssSelector.addAttribute(nameNoNs, value);
if (name.toLowerCase() === 'class') {
const classes = value.trim().split(/\s+/);
classes.forEach(className => cssSelector.addClassName(className));
}
});
return cssSelector;
}
/**
* Extract a map of properties to values for a given element or template node, which can be used
* by the directive matching machinery.
@ -286,8 +313,7 @@ export class DefinitionMap<T = any> {
* object maps a property name to its (static) value. For any bindings, this map simply maps the
* property name to an empty string.
*/
export function getAttrsForDirectiveMatching(elOrTpl: t.Element|
t.Template): {[name: string]: string} {
function getAttrsForDirectiveMatching(elOrTpl: t.Element|t.Template): {[name: string]: string} {
const attributesMap: {[name: string]: string} = {};