Skip to content

Commit 7b2e6ca

Browse files
mattrbeckthePunderWoman
authored andcommitted
Revert "fix(compiler): support arbitrary nesting in :host-context()"
This reverts commit f9d0818.
1 parent 6036eef commit 7b2e6ca

File tree

2 files changed

+42
-80
lines changed

2 files changed

+42
-80
lines changed

packages/compiler/src/shadow_css.ts

Lines changed: 42 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.dev/license
77
*/
8-
import * as chars from './chars';
98

109
/**
1110
* The following set contains all keywords that can be used in the animation css shorthand
@@ -526,40 +525,6 @@ export class ShadowCss {
526525
});
527526
}
528527

529-
/**
530-
* Generator function that splits a string on top-level commas (commas that are not inside parentheses).
531-
* Yields each part of the string between top-level commas. Terminates if an extra closing paren is found.
532-
*
533-
* @param text The string to split
534-
*/
535-
private *_splitOnTopLevelCommas(text: string): Generator<string> {
536-
const length = text.length;
537-
let parens = 0;
538-
let prev = 0;
539-
540-
for (let i = 0; i < length; i++) {
541-
const charCode = text.charCodeAt(i);
542-
543-
if (charCode === chars.$LPAREN) {
544-
parens++;
545-
} else if (charCode === chars.$RPAREN) {
546-
parens--;
547-
if (parens < 0) {
548-
// Found an extra closing paren. Assume we want the list terminated here
549-
yield text.slice(prev, i);
550-
return;
551-
}
552-
} else if (charCode === chars.$COMMA && parens === 0) {
553-
// Found a top-level comma, yield the current chunk
554-
yield text.slice(prev, i);
555-
prev = i + 1;
556-
}
557-
}
558-
559-
// Yield the final chunk
560-
yield text.slice(prev);
561-
}
562-
563528
/*
564529
* convert a rule like :host-context(.foo) > .bar { }
565530
*
@@ -576,14 +541,38 @@ export class ShadowCss {
576541
* .foo<scopeName> .bar { ... }
577542
*/
578543
private _convertColonHostContext(cssText: string): string {
544+
const length = cssText.length;
545+
let parens = 0;
546+
let prev = 0;
547+
let result = '';
548+
579549
// Splits up the selectors on their top-level commas, processes the :host-context in them
580550
// individually and stitches them back together. This ensures that individual selectors don't
581551
// affect each other.
582-
const results: string[] = [];
583-
for (const part of this._splitOnTopLevelCommas(cssText)) {
584-
results.push(this._convertColonHostContextInSelectorPart(part));
552+
for (let i = 0; i < length; i++) {
553+
const char = cssText[i];
554+
555+
// If we hit a comma and there are no open parentheses, take the current chunk and process it.
556+
if (char === ',' && parens === 0) {
557+
result += this._convertColonHostContextInSelectorPart(cssText.slice(prev, i)) + ',';
558+
prev = i + 1;
559+
continue;
560+
}
561+
562+
// We've hit the end. Take everything since the last comma.
563+
if (i === length - 1) {
564+
result += this._convertColonHostContextInSelectorPart(cssText.slice(prev));
565+
break;
566+
}
567+
568+
if (char === '(') {
569+
parens++;
570+
} else if (char === ')') {
571+
parens--;
572+
}
585573
}
586-
return results.join(',');
574+
575+
return result;
587576
}
588577

589578
private _convertColonHostContextInSelectorPart(cssText: string): string {
@@ -598,28 +587,18 @@ export class ShadowCss {
598587

599588
// There may be more than `:host-context` in this selector so `selectorText` could look like:
600589
// `:host-context(.one):host-context(.two)`.
601-
// Loop until every :host-context in the compound selector has been processed.
602-
let startIndex = selectorText.indexOf(_polyfillHostContext);
603-
while (startIndex !== -1) {
604-
const afterPrefix = selectorText.substring(startIndex + _polyfillHostContext.length);
605-
606-
if (!afterPrefix || afterPrefix[0] !== '(') {
607-
// Edge case of :host-context with no parens (e.g. `:host-context .inner`)
608-
selectorText = afterPrefix;
609-
startIndex = selectorText.indexOf(_polyfillHostContext);
610-
continue;
611-
}
612-
613-
// Extract comma-separated selectors between the parentheses
614-
const newContextSelectors: string[] = [];
615-
let endIndex = 0; // Index of the closing paren of the :host-context()
616-
for (const selector of this._splitOnTopLevelCommas(afterPrefix.substring(1))) {
617-
endIndex = endIndex + selector.length + 1;
618-
const trimmed = selector.trim();
619-
if (trimmed) {
620-
newContextSelectors.push(trimmed);
621-
}
622-
}
590+
// Execute `_cssColonHostContextRe` over and over until we have extracted all the
591+
// `:host-context` selectors from this selector.
592+
let match: RegExpExecArray | null;
593+
while ((match = _cssColonHostContextRe.exec(selectorText))) {
594+
// `match` = [':host-context(<selectors>)<rest>', <selectors>, <rest>]
595+
596+
// The `<selectors>` could actually be a comma separated list: `:host-context(.one, .two)`.
597+
const newContextSelectors = (match[1] ?? '')
598+
.trim()
599+
.split(',')
600+
.map((m) => m.trim())
601+
.filter((m) => m !== '');
623602

624603
// We must duplicate the current selector group for each of these new selectors.
625604
// For example if the current groups are:
@@ -648,8 +627,7 @@ export class ShadowCss {
648627
}
649628

650629
// Update the `selectorText` and see repeat to see if there are more `:host-context`s.
651-
selectorText = afterPrefix.substring(endIndex + 1);
652-
startIndex = selectorText.indexOf(_polyfillHostContext);
630+
selectorText = match[2];
653631
}
654632

655633
// The context selectors now must be combined with each other to capture all the possible
@@ -1082,6 +1060,7 @@ const _cssColonHostContextReGlobal = new RegExp(
10821060
`${_cssScopedPseudoFunctionPrefix}(${_hostContextPattern})`,
10831061
'gim',
10841062
);
1063+
const _cssColonHostContextRe = new RegExp(_hostContextPattern, 'im');
10851064
const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
10861065
const _polyfillHostNoCombinatorOutsidePseudoFunction = new RegExp(
10871066
`${_polyfillHostNoCombinator}(?![^(]*\\))`,

packages/compiler/test/shadow_css/host_and_host_context_spec.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -172,20 +172,6 @@ describe('ShadowCss, :host and :host-context', () => {
172172
);
173173
});
174174

175-
it('should transform :host-context with nested pseudo selectors', () => {
176-
expect(shim(':host-context(:where(.foo:not(.bar))) {}', 'contenta', 'hosta')).toEqualCss(
177-
':where(.foo:not(.bar))[hosta], :where(.foo:not(.bar)) [hosta] {}',
178-
);
179-
expect(shim(':host-context(:is(.foo:not(.bar))) {}', 'contenta', 'hosta')).toEqualCss(
180-
':is(.foo:not(.bar))[hosta], :is(.foo:not(.bar)) [hosta] {}',
181-
);
182-
expect(
183-
shim(':host-context(:where(.foo:not(.bar, .baz))) .inner {}', 'contenta', 'hosta'),
184-
).toEqualCss(
185-
':where(.foo:not(.bar, .baz))[hosta] .inner[contenta], :where(.foo:not(.bar, .baz)) [hosta] .inner[contenta] {}',
186-
);
187-
});
188-
189175
it('should handle tag selector', () => {
190176
expect(shim(':host-context(div) {}', 'contenta', 'a-host')).toEqualCss(
191177
'div[a-host], div [a-host] {}',
@@ -260,9 +246,6 @@ describe('ShadowCss, :host and :host-context', () => {
260246
expect(shim(':host-context() .inner {}', 'contenta', 'a-host')).toEqualCss(
261247
'[a-host] .inner[contenta] {}',
262248
);
263-
expect(shim(':host-context :host-context(.a) {}', 'contenta', 'host-a')).toEqualCss(
264-
'.a[host-a], .a [host-a] {}',
265-
);
266249
});
267250

268251
// More than one selector such as this is not valid as part of the :host-context spec.

0 commit comments

Comments
 (0)