|
6 | 6 | * found in the LICENSE file at https://angular.io/license |
7 | 7 | */ |
8 | 8 |
|
9 | | -import {AST, BindingPipe, BindingType, BoundTarget, Call, createCssSelectorFromNode, CssSelector, DYNAMIC_TYPE, ImplicitReceiver, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, SafeCall, SafePropertyRead, SchemaMetadata, SelectorMatcher, ThisReceiver, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstElement, TmplAstForLoopBlock, TmplAstHoverDeferredTrigger, TmplAstIcu, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstInteractionDeferredTrigger, TmplAstNode, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstVariable, TmplAstViewportDeferredTrigger, TransplantedType} from '@angular/compiler'; |
| 9 | +import {AST, BindingPipe, BindingType, BoundTarget, Call, DYNAMIC_TYPE, ImplicitReceiver, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, SafeCall, SafePropertyRead, SchemaMetadata, ThisReceiver, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstElement, TmplAstForLoopBlock, TmplAstHoverDeferredTrigger, TmplAstIcu, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstInteractionDeferredTrigger, TmplAstNode, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable, TmplAstViewportDeferredTrigger, TransplantedType} from '@angular/compiler'; |
10 | 10 | import ts from 'typescript'; |
11 | 11 |
|
12 | 12 | import {Reference} from '../../imports'; |
@@ -905,100 +905,6 @@ class TcbDomSchemaCheckerOp extends TcbOp { |
905 | 905 | } |
906 | 906 |
|
907 | 907 |
|
908 | | -/** |
909 | | - * A `TcbOp` that finds and flags control flow nodes that interfere with content projection. |
910 | | - * |
911 | | - * Context: |
912 | | - * `@if` and `@for` try to emulate the content projection behavior of `*ngIf` and `*ngFor` |
913 | | - * in order to reduce breakages when moving from one syntax to the other (see #52414), however the |
914 | | - * approach only works if there's only one element at the root of the control flow expression. |
915 | | - * This means that a stray sibling node (e.g. text) can prevent an element from being projected |
916 | | - * into the right slot. The purpose of the `TcbOp` is to find any places where a node at the root |
917 | | - * of a control flow expression *would have been projected* into a specific slot, if the control |
918 | | - * flow node didn't exist. |
919 | | - */ |
920 | | -class TcbControlFlowContentProjectionOp extends TcbOp { |
921 | | - constructor( |
922 | | - private tcb: Context, private element: TmplAstElement, private ngContentSelectors: string[], |
923 | | - private componentName: string) { |
924 | | - super(); |
925 | | - } |
926 | | - |
927 | | - override readonly optional = false; |
928 | | - |
929 | | - override execute(): null { |
930 | | - const controlFlowToCheck = this.findPotentialControlFlowNodes(); |
931 | | - |
932 | | - if (controlFlowToCheck.length > 0) { |
933 | | - const matcher = new SelectorMatcher<string>(); |
934 | | - |
935 | | - for (const selector of this.ngContentSelectors) { |
936 | | - // `*` is a special selector for the catch-all slot. |
937 | | - if (selector !== '*') { |
938 | | - matcher.addSelectables(CssSelector.parse(selector), selector); |
939 | | - } |
940 | | - } |
941 | | - |
942 | | - for (const root of controlFlowToCheck) { |
943 | | - for (const child of root.children) { |
944 | | - if (child instanceof TmplAstElement || child instanceof TmplAstTemplate) { |
945 | | - matcher.match(createCssSelectorFromNode(child), (_, originalSelector) => { |
946 | | - this.tcb.oobRecorder.controlFlowPreventingContentProjection( |
947 | | - this.tcb.id, child, this.componentName, originalSelector, root, |
948 | | - this.tcb.hostPreserveWhitespaces); |
949 | | - }); |
950 | | - } |
951 | | - } |
952 | | - } |
953 | | - } |
954 | | - |
955 | | - return null; |
956 | | - } |
957 | | - |
958 | | - private findPotentialControlFlowNodes() { |
959 | | - const result: Array<TmplAstIfBlockBranch|TmplAstForLoopBlock> = []; |
960 | | - |
961 | | - for (const child of this.element.children) { |
962 | | - let eligibleNode: TmplAstForLoopBlock|TmplAstIfBlockBranch|null = null; |
963 | | - |
964 | | - // Only `@for` blocks and the first branch of `@if` blocks participate in content projection. |
965 | | - if (child instanceof TmplAstForLoopBlock) { |
966 | | - eligibleNode = child; |
967 | | - } else if (child instanceof TmplAstIfBlock) { |
968 | | - eligibleNode = child.branches[0]; // @if blocks are guaranteed to have at least one branch. |
969 | | - } |
970 | | - |
971 | | - // Skip nodes with less than two children since it's impossible |
972 | | - // for them to run into the issue that we're checking for. |
973 | | - if (eligibleNode === null || eligibleNode.children.length < 2) { |
974 | | - continue; |
975 | | - } |
976 | | - |
977 | | - // Count the number of root nodes while skipping empty text where relevant. |
978 | | - const rootNodeCount = eligibleNode.children.reduce((count, node) => { |
979 | | - // Normally `preserveWhitspaces` would have been accounted for during parsing, however |
980 | | - // in `ngtsc/annotations/component/src/resources.ts#parseExtractedTemplate` we enable |
981 | | - // `preserveWhitespaces` to preserve the accuracy of source maps diagnostics. This means |
982 | | - // that we have to account for it here since the presence of text nodes affects the |
983 | | - // content projection behavior. |
984 | | - if (!(node instanceof TmplAstText) || this.tcb.hostPreserveWhitespaces || |
985 | | - node.value.trim().length > 0) { |
986 | | - count++; |
987 | | - } |
988 | | - |
989 | | - return count; |
990 | | - }, 0); |
991 | | - |
992 | | - // Content projection can only be affected if there is more than one root node. |
993 | | - if (rootNodeCount > 1) { |
994 | | - result.push(eligibleNode); |
995 | | - } |
996 | | - } |
997 | | - |
998 | | - return result; |
999 | | - } |
1000 | | -} |
1001 | | - |
1002 | 908 | /** |
1003 | 909 | * Mapping between attributes names that don't correspond to their element property names. |
1004 | 910 | * Note: this mapping has to be kept in sync with the equally named mapping in the runtime. |
@@ -1931,7 +1837,6 @@ class Scope { |
1931 | 1837 | if (node instanceof TmplAstElement) { |
1932 | 1838 | const opIndex = this.opQueue.push(new TcbElementOp(this.tcb, this, node)) - 1; |
1933 | 1839 | this.elementOpMap.set(node, opIndex); |
1934 | | - this.appendContentProjectionCheckOp(node); |
1935 | 1840 | this.appendDirectivesAndInputsOfNode(node); |
1936 | 1841 | this.appendOutputsOfNode(node); |
1937 | 1842 | this.appendChildren(node); |
@@ -2128,22 +2033,6 @@ class Scope { |
2128 | 2033 | } |
2129 | 2034 | } |
2130 | 2035 |
|
2131 | | - private appendContentProjectionCheckOp(root: TmplAstElement): void { |
2132 | | - const meta = |
2133 | | - this.tcb.boundTarget.getDirectivesOfNode(root)?.find(meta => meta.isComponent) || null; |
2134 | | - |
2135 | | - if (meta !== null && meta.ngContentSelectors !== null && meta.ngContentSelectors.length > 0) { |
2136 | | - const selectors = meta.ngContentSelectors; |
2137 | | - |
2138 | | - // We don't need to generate anything for components that don't have projection |
2139 | | - // slots, or they only have one catch-all slot (represented by `*`). |
2140 | | - if (selectors.length > 1 || (selectors.length === 1 && selectors[0] !== '*')) { |
2141 | | - this.opQueue.push( |
2142 | | - new TcbControlFlowContentProjectionOp(this.tcb, root, selectors, meta.name)); |
2143 | | - } |
2144 | | - } |
2145 | | - } |
2146 | | - |
2147 | 2036 | private appendDeferredBlock(block: TmplAstDeferredBlock): void { |
2148 | 2037 | this.appendDeferredTriggers(block, block.triggers); |
2149 | 2038 | this.appendDeferredTriggers(block, block.prefetchTriggers); |
|
0 commit comments