From 1ba1105b78ab6ae0768040c9f77f08fa3769520c Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Wed, 21 Dec 2022 10:38:51 +0100 Subject: [PATCH] fix(compiler): handle css selectors with space after an escaped character. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Css, selectors with escaped characters require a space after if the following character is a hex character. ie: .\fc ber which matches class="über" These escaped selectors happen for example when esbuild run with `optimization.minify` fixes #48524 --- packages/compiler/src/shadow_css.ts | 14 +++++++++++++- .../compiler/test/shadow_css/shadow_css_spec.ts | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index f6332d878f0d..3d1d351f0707 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -152,6 +152,7 @@ const animationKeywords = new Set([ in comments in lieu of the next selector when running under polyfill. */ export class ShadowCss { + // TODO: Is never re-assigned, could be removed. strictStyling: boolean = true; /* @@ -729,6 +730,15 @@ export class ShadowCss { while ((res = sep.exec(selector)) !== null) { const separator = res[1]; const part = selector.slice(startIndex, res.index).trim(); + + // 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 + // also keep in mind that backslashes are replaced by a placeholder by SafeSelector + // These escaped selectors happen for example when esbuild runs with optimization.minify. + if (part.match(_placeholderRe) && selector[res.index + 1]?.match(/[a-fA-F\d]/)) { + continue; + } + shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1; const scopedPart = shouldScope ? _scopeSelectorPart(part) : part; scopedSelector += `${scopedPart} ${separator} `; @@ -777,7 +787,7 @@ class SafeSelector { } restore(content: string): string { - return content.replace(/__ph-(\d+)__/g, (_ph, index) => this.placeholders[+index]); + return content.replace(_placeholderRe, (_ph, index) => this.placeholders[+index]); } content(): string { @@ -833,6 +843,8 @@ const _colonHostContextRe = /:host-context/gim; const _commentRe = /\/\*[\s\S]*?\*\//g; +const _placeholderRe = /__ph-(\d+)__/g; + function stripComments(input: string): string { return input.replace(_commentRe, ''); } diff --git a/packages/compiler/test/shadow_css/shadow_css_spec.ts b/packages/compiler/test/shadow_css/shadow_css_spec.ts index 45ca4a93e023..bf0209656250 100644 --- a/packages/compiler/test/shadow_css/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css/shadow_css_spec.ts @@ -76,6 +76,15 @@ describe('ShadowCss', () => { .toEqualCss('.one\\:two[contenta] .three\\:four[contenta] {}'); }); + it('should handle escaped selector with space (if followed by a hex char)', () => { + // When esbuild runs with optimization.minify + // selectors are escaped: .über becomes .\fc ber. + // The space here isn't a separator between 2 selectors + expect(shim('.\\fc ber {}', 'contenta')).toEqual('.\\fc ber[contenta] {}'); + expect(shim('.\\fc ker {}', 'contenta')).toEqual('.\\fc[contenta] ker[contenta] {}'); + expect(shim('.pr\\fc fung {}', 'contenta')).toEqual('.pr\\fc fung[contenta] {}'); + }); + it('should handle ::shadow', () => { const css = shim('x::shadow > y {}', 'contenta'); expect(css).toEqualCss('x[contenta] > y[contenta] {}');