From a040fb720af7db08b328a9f78511c9881f50482d Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 24 Apr 2024 14:15:47 +0000 Subject: [PATCH] fix(compiler): maintain multiline CSS selectors during CSS scoping (#55509) Previously, multiline selectors were being converted into single lines, resulting in sourcemap disruptions due to shifts in line numbers. Closes #55508 PR Close #55509 --- packages/compiler/src/shadow_css.ts | 21 ++++++++++++------- .../test/shadow_css/shadow_css_spec.ts | 7 +++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index 682f8399c29bb..b01c534d2250e 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -658,8 +658,8 @@ export class ShadowCss { private _scopeSelector(selector: string, scopeSelector: string, hostSelector: string): string { return selector - .split(',') - .map((part) => part.trim().split(_shadowDeepSelectors)) + .split(/ ?, ?/) + .map((part) => part.split(_shadowDeepSelectors)) .map((deepParts) => { const [shallowPart, ...otherParts] = deepParts; const applyScope = (shallowPart: string) => { @@ -727,10 +727,10 @@ export class ShadowCss { let scopedP = p.trim(); if (!scopedP) { - return ''; + return p; } - if (p.indexOf(_polyfillHostNoCombinator) > -1) { + if (p.includes(_polyfillHostNoCombinator)) { scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector); } else { // remove :host since it should be unnecessary @@ -765,13 +765,18 @@ export class ShadowCss { // - `tag:host` -> `tag[h]` (this is to avoid breaking legacy apps, should not match anything) // - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a // `:host-context(tag)`) - const hasHost = selector.indexOf(_polyfillHostNoCombinator) > -1; + const hasHost = selector.includes(_polyfillHostNoCombinator); // Only scope parts after the first `-shadowcsshost-no-combinator` when it is present let shouldScope = !hasHost; while ((res = sep.exec(selector)) !== null) { const separator = res[1]; - const part = selector.slice(startIndex, res.index).trim(); + // Do not trim the selector, as otherwise this will break sourcemaps + // when they are defined on multiple lines + // Example: + // div, + // p { color: red} + const part = selector.slice(startIndex, res.index); // A space following an escaped hex value and followed by another hex character // (ie: ".\fc ber" for ".über") is not a separator between 2 selectors @@ -781,14 +786,14 @@ export class ShadowCss { continue; } - shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1; + shouldScope = shouldScope || part.includes(_polyfillHostNoCombinator); const scopedPart = shouldScope ? _scopeSelectorPart(part) : part; scopedSelector += `${scopedPart} ${separator} `; startIndex = sep.lastIndex; } const part = selector.substring(startIndex); - shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1; + shouldScope = shouldScope || part.includes(_polyfillHostNoCombinator); scopedSelector += shouldScope ? _scopeSelectorPart(part) : part; // replace the placeholders with their original values diff --git a/packages/compiler/test/shadow_css/shadow_css_spec.ts b/packages/compiler/test/shadow_css/shadow_css_spec.ts index 0c8a51491f571..ee6d7363afa4c 100644 --- a/packages/compiler/test/shadow_css/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css/shadow_css_spec.ts @@ -117,6 +117,13 @@ describe('ShadowCss', () => { expect(css).toEqualCss('div[contenta]::after { content:"{}"}'); }); + it('should keep retain multiline selectors', () => { + // This is needed as shifting in line number will cause sourcemaps to break. + const styleStr = '.foo,\n.bar { color: red;}'; + const css = shim(styleStr, 'contenta'); + expect(css).toEqual('.foo[contenta], \n.bar[contenta] { color: red;}'); + }); + describe('comments', () => { // Comments should be kept in the same position as otherwise inline sourcemaps break due to // shift in lines.