From e3a401d20c440823f179be8737e50a13bce9df66 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 7 Mar 2019 08:31:31 +0000 Subject: [PATCH] refactor(ivy): define new `AttributeMarker.Template` marker (#29041) This commit adds a new `AttributeMarker` type that will be used, in a future commit, to mark attributes as coming from an inline-template expansion, rather than the element that is being contained in the template. PR Close #29041 --- packages/compiler/src/core.ts | 25 ++++++++++++++++ .../compiler/src/render3/view/template.ts | 7 +++-- packages/core/src/render3/di.ts | 9 +++--- packages/core/src/render3/interfaces/node.ts | 29 +++++++++++++++++++ .../core/src/render3/node_selector_matcher.ts | 16 ++++++++-- .../cyclic_import/bundle.golden_symbols.json | 9 ++++++ .../bundling/todo/bundle.golden_symbols.json | 21 ++++++++++++++ 7 files changed, 106 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index 8c3e9e4216..ae78dd3cf6 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -450,4 +450,29 @@ export const enum AttributeMarker { * ``` */ Bindings = 3, + + /** + * Signals that the following attribute names were hoisted from an inline-template declaration. + * + * For example, given the following HTML: + * + * ``` + *
+ * ``` + * + * the generated code for the `template()` instruction would include: + * + * ``` + * ['dirA', '', AttributeMarker.Bindings, 'dirB', AttributeMarker.Template, 'ngFor', 'ngForOf', + * 'ngForTrackBy', 'let-value'] + * ``` + * + * while the generated code for the `element()` instruction inside the template function would + * include: + * + * ``` + * ['dirA', '', AttributeMarker.Bindings, 'dirB'] + * ``` + */ + Template = 4, } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 9ac5181ec7..aa9dfbb989 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -1044,7 +1044,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver * attrs = [prop, value, prop2, value2, * CLASSES, class1, class2, * STYLES, style1, value1, style2, value2, - * BINDINGS, name1, name2, name3, ...] + * BINDINGS, name1, name2, name3, + * TEMPLATE, name4, name5, ...] * ``` * * Note that this function will fully ignore all synthetic (@foo) attribute values @@ -1068,8 +1069,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } - // it's important that this occurs before BINDINGS because once `elementStart` - // comes across the BINDINGS marker then it will continue reading each value as + // it's important that this occurs before BINDINGS and TEMPLATE because once `elementStart` + // comes across the BINDINGS or TEMPLATE markers then it will continue reading each value as // as single property value cell by cell. if (styles) { styles.populateInitialStylingAttrs(attrExprs); diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 571733e2c4..b75f578d9a 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -17,7 +17,7 @@ import {getComponentDef, getDirectiveDef, getPipeDef} from './definition'; import {NG_ELEMENT_ID} from './fields'; import {DirectiveDef} from './interfaces/definition'; import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE, isFactory} from './interfaces/injector'; -import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node'; +import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, isNameOnlyAttributeMarker} from './interfaces/node'; import {DECLARATION_VIEW, INJECTOR, LView, TData, TVIEW, TView, T_HOST} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert'; import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state'; @@ -272,9 +272,10 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str if (attrs) { for (let i = 0; i < attrs.length; i = i + 2) { const attrName = attrs[i]; - if (attrName === AttributeMarker.Bindings) break; - // TODO: What happens here if an attribute has a namespace? - // TODO: What happens here if the attribute name happens to match a CSS class or style? + // If we hit a `Bindings` or `Template` marker then we are done. + if (isNameOnlyAttributeMarker(attrName)) break; + // TODO(FW-1137): Skip namespaced attributes + // TODO(FW-1139): supports classes/styles in @Attribute injection if (attrName == attrNameToInject) { return attrs[i + 1] as string; } diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 1350085948..79bf033e9e 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -122,6 +122,35 @@ export const enum AttributeMarker { * ``` */ Bindings = 3, + + /** + * Signals that the following attribute names were hoisted from an inline-template declaration. + * + * For example, given the following HTML: + * + * ``` + *
+ * ``` + * + * the generated code for the `template()` instruction would include: + * + * ``` + * ['dirA', '', AttributeMarker.Bindings, 'dirB', AttributeMarker.Template, 'ngFor', 'ngForOf', + * 'ngForTrackBy', 'let-value'] + * ``` + * + * while the generated code for the `element()` instruction inside the template function would + * include: + * + * ``` + * ['dirA', '', AttributeMarker.Bindings, 'dirB'] + * ``` + */ + Template = 4, +} + +export function isNameOnlyAttributeMarker(marker: string | AttributeMarker) { + return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template; } /** diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts index f85c15b28d..b5c63a4980 100644 --- a/packages/core/src/render3/node_selector_matcher.ts +++ b/packages/core/src/render3/node_selector_matcher.ts @@ -9,7 +9,8 @@ import '../util/ng_dev_mode'; import {assertDefined, assertNotEqual} from '../util/assert'; -import {AttributeMarker, TAttributes, TNode, TNodeType, unusedValueExportToPlacateAjd as unused1} from './interfaces/node'; + +import {AttributeMarker, TAttributes, TNode, TNodeType, isNameOnlyAttributeMarker, unusedValueExportToPlacateAjd as unused1} from './interfaces/node'; import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection'; import {getInitialClassNameValue} from './styling/class_and_style_bindings'; @@ -61,7 +62,16 @@ export function isNodeMatchingSelector( ngDevMode && assertDefined(selector[0], 'Selector should have a tag name'); let mode: SelectorFlags = SelectorFlags.ELEMENT; const nodeAttrs = tNode.attrs || []; - const nameOnlyMarkerIdx = nodeAttrs.indexOf(AttributeMarker.Bindings); + + // Find the index of first attribute that has no value, only a name. + let nameOnlyMarkerIdx = nodeAttrs && nodeAttrs.length; + for (let i = 0; i < nodeAttrs.length; i++) { + const nodeAttr = nodeAttrs[i]; + if (isNameOnlyAttributeMarker(nodeAttr)) { + nameOnlyMarkerIdx = i; + break; + } + } // When processing ":not" selectors, we skip to the next ":not" if the // current one doesn't match @@ -174,7 +184,7 @@ function findAttrIndexInNode(name: string, attrs: TAttributes | null): number { // NOTE(benlesh): will not find namespaced attributes. This is by design. i += 4; } else { - if (maybeAttrName === AttributeMarker.Bindings) { + if (isNameOnlyAttributeMarker(maybeAttrName)) { nameOnlyMode = true; } i += nameOnlyMode ? 1 : 2; diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 4da4db673a..40a176494e 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -374,6 +374,9 @@ { "name": "getLViewParent" }, + { + "name": "getNameOnlyMarkerIndex" + }, { "name": "getNativeAnchorNode" }, @@ -494,6 +497,9 @@ { "name": "isLView" }, + { + "name": "isNameOnlyAttributeMarker" + }, { "name": "isNodeMatchingSelector" }, @@ -518,6 +524,9 @@ { "name": "locateHostElement" }, + { + "name": "matchTemplateAttribute" + }, { "name": "namespaceHTML" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 87495a342b..53a801a8d9 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -323,9 +323,21 @@ { "name": "_c18" }, + { + "name": "_c19" + }, { "name": "_c2" }, + { + "name": "_c20" + }, + { + "name": "_c21" + }, + { + "name": "_c22" + }, { "name": "_c3" }, @@ -749,6 +761,9 @@ { "name": "getMultiStylesStartIndex" }, + { + "name": "getNameOnlyMarkerIndex" + }, { "name": "getNativeAnchorNode" }, @@ -983,6 +998,9 @@ { "name": "isListLikeIterable" }, + { + "name": "isNameOnlyAttributeMarker" + }, { "name": "isNodeMatchingSelector" }, @@ -1040,6 +1058,9 @@ { "name": "markViewDirty" }, + { + "name": "matchTemplateAttribute" + }, { "name": "namespaceHTML" },