Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ecfb74d

Browse files
crisbetoAndrewKushnir
authored andcommitted
fix(compiler): handle :host-context with comma-separated child selector (#59276)
Both `:host` and `:host-context` work by looking for a specific character sequence that is terminated by `,` or `{` and replacing selectors inside of it with scoped versions. This is implemented as a regex which isn't aware of things like nested selectors. Normally this is fine for `:host`, because each `:host` produces one scoped selector which doesn't affect any child selectors, however it breaks down with `:host-context` which replaces each instance with two selectors. For example, if we have a selector in the form of `:host-context(.foo) a:not(.a, .b)`, the compiler ends up determining that `.a,` is the end selector and produces `.foo[a-host] a[contenta]:not(.a, .foo [a-host] a[contenta]:not(.a, .b) {}`. These changes resolve the issue by splitting the CSS alogn top-level commas, processing the `:host-context` in them individually, and stiching the CSS back together. PR Close #59276
1 parent 6483621 commit ecfb74d

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

‎packages/compiler/src/shadow_css.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,41 @@ export class ShadowCss {
541541
* .foo<scopeName> .bar { ... }
542542
*/
543543
private _convertColonHostContext(cssText: string): string {
544+
const length = cssText.length;
545+
let parens = 0;
546+
let prev = 0;
547+
let result = '';
548+
549+
// Splits up the selectors on their top-level commas, processes the :host-context in them
550+
// individually and stitches them back together. This ensures that individual selectors don't
551+
// affect each other.
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+
}
573+
}
574+
575+
return result;
576+
}
577+
578+
private _convertColonHostContextInSelectorPart(cssText: string): string {
544579
return cssText.replace(_cssColonHostContextReGlobal, (selectorText, pseudoPrefix) => {
545580
// We have captured a selector that contains a `:host-context` rule.
546581

@@ -1010,13 +1045,16 @@ const _cssContentUnscopedRuleRe =
10101045
const _polyfillHost = '-shadowcsshost';
10111046
// note: :host-context pre-processed to -shadowcsshostcontext.
10121047
const _polyfillHostContext = '-shadowcsscontext';
1013-
const _parenSuffix = '(?:\\((' + '(?:\\([^)(]*\\)|[^)(]*)+?' + ')\\))?([^,{]*)';
1014-
const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix, 'gim');
1048+
const _parenSuffix = '(?:\\((' + '(?:\\([^)(]*\\)|[^)(]*)+?' + ')\\))';
1049+
const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix + '?([^,{]*)', 'gim');
1050+
// note: :host-context patterns are terminated with `{`, as opposed to :host which
1051+
// is both `{` and `,` because :host-context handles top-level commas differently.
1052+
const _hostContextPattern = _polyfillHostContext + _parenSuffix + '?([^{]*)';
10151053
const _cssColonHostContextReGlobal = new RegExp(
1016-
_cssScopedPseudoFunctionPrefix + '(' + _polyfillHostContext + _parenSuffix + ')',
1054+
`${_cssScopedPseudoFunctionPrefix}(${_hostContextPattern})`,
10171055
'gim',
10181056
);
1019-
const _cssColonHostContextRe = new RegExp(_polyfillHostContext + _parenSuffix, 'im');
1057+
const _cssColonHostContextRe = new RegExp(_hostContextPattern, 'im');
10201058
const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
10211059
const _polyfillHostNoCombinatorOutsidePseudoFunction = new RegExp(
10221060
`${_polyfillHostNoCombinator}(?![^(]*\\))`,

‎packages/compiler/test/shadow_css/host_and_host_context_spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,23 @@ describe('ShadowCss, :host and :host-context', () => {
257257
'{}',
258258
);
259259
});
260+
261+
it('should handle :host-context with comma-separated child selector', () => {
262+
expect(shim(':host-context(.foo) a:not(.a, .b) {}', 'contenta', 'a-host')).toEqualCss(
263+
'.foo[a-host] a[contenta]:not(.a, .b), .foo [a-host] a[contenta]:not(.a, .b) {}',
264+
);
265+
expect(
266+
shim(
267+
':host-context(.foo) a:not([a], .b), .bar, :host-context(.baz) a:not([c], .d) {}',
268+
'contenta',
269+
'a-host',
270+
),
271+
).toEqualCss(
272+
'.foo[a-host] a[contenta]:not([a], .b), .foo [a-host] a[contenta]:not([a], .b), ' +
273+
'.bar[contenta], .baz[a-host] a[contenta]:not([c], .d), ' +
274+
'.baz [a-host] a[contenta]:not([c], .d) {}',
275+
);
276+
});
260277
});
261278

262279
describe(':host-context and :host combination selector', () => {

0 commit comments

Comments
 (0)