diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index 0145e73def6d..dc079928bc35 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -713,7 +713,7 @@ export class ShadowCss { // (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]/)) { + if (part.match(/__esc-ph-(\d+)__/) && selector[res.index + 1]?.match(/[a-fA-F\d]/)) { continue; } @@ -752,7 +752,13 @@ class SafeSelector { // pseudo-class, but writing `.foo\:blue` will match, because the colon was escaped. // Replace all escape sequences (`\` followed by a character) with a placeholder so // that our handling of pseudo-selectors doesn't mess with them. - selector = this._escapeRegexMatches(selector, /(\\.)/g); + // Escaped characters have a specific placeholder so they can be detected separately. + selector = selector.replace(/(\\.)/g, (_, keep) => { + const replaceBy = `__esc-ph-${this.index}__`; + this.placeholders.push(keep); + this.index++; + return replaceBy; + }); // Replaces the expression in `:nth-child(2n + 1)` with a placeholder. // WS and "+" would otherwise be interpreted as selector separators. @@ -765,7 +771,7 @@ class SafeSelector { } restore(content: string): string { - return content.replace(_placeholderRe, (_ph, index) => this.placeholders[+index]); + return content.replace(/__(?:ph|esc-ph)-(\d+)__/g, (_ph, index) => this.placeholders[+index]); } content(): string { @@ -825,8 +831,6 @@ const _commentWithHashRe = /\/\*\s*#\s*source(Mapping)?URL=/g; const COMMENT_PLACEHOLDER = '%COMMENT%'; const _commentWithHashPlaceHolderRe = new RegExp(COMMENT_PLACEHOLDER, 'g'); -const _placeholderRe = /__ph-(\d+)__/g; - const BLOCK_PLACEHOLDER = '%BLOCK%'; const _ruleRe = new RegExp( `(\\s*(?:${COMMENT_PLACEHOLDER}\\s*)*)([^;\\{\\}]+?)(\\s*)((?:{%BLOCK%}?\\s*;?)|(?:\\s*;))`, diff --git a/packages/compiler/test/shadow_css/host_and_host_context_spec.ts b/packages/compiler/test/shadow_css/host_and_host_context_spec.ts index 3ba1c605e587..0b1523193a58 100644 --- a/packages/compiler/test/shadow_css/host_and_host_context_spec.ts +++ b/packages/compiler/test/shadow_css/host_and_host_context_spec.ts @@ -27,6 +27,19 @@ describe('ShadowCss, :host and :host-context', () => { expect(shim(':host([a=b]) {}', 'contenta', 'a-host')).toEqualCss('[a=b][a-host] {}'); }); + it('should handle attribute and next operator without spaces', () => { + expect(shim(':host[foo]>div {}', 'contenta', 'a-host')) + .toEqualCss(('[foo][a-host] > div[contenta] {}')); + }); + + // we know that the following test doesn't pass + // the host attribute is added before the space + // We advise to a more simple class name that doesn't require escaping + xit('should handle host with escaped class selector', () => { + // here we're looking to shim :host.prüfung (an escaped ü is replaced by "\\fc ") + expect(shim(':host.pr\\fc fung {}', 'contenta', 'a-host')).toEqual('.pr\\fc fung[a-host] {}'); + }); + it('should handle multiple tag selectors', () => { expect(shim(':host(ul,li) {}', 'contenta', 'a-host')).toEqualCss('ul[a-host], li[a-host] {}'); expect(shim(':host(ul,li) > .z {}', 'contenta', 'a-host')) diff --git a/packages/compiler/test/shadow_css/shadow_css_spec.ts b/packages/compiler/test/shadow_css/shadow_css_spec.ts index 6b77fb42ed34..81d2114c9e53 100644 --- a/packages/compiler/test/shadow_css/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css/shadow_css_spec.ts @@ -72,6 +72,7 @@ describe('ShadowCss', () => { expect(shim('one\\:two {}', 'contenta')).toEqualCss('one\\:two[contenta] {}'); expect(shim('one\\\\:two {}', 'contenta')).toEqualCss('one\\\\[contenta]:two {}'); expect(shim('.one\\:two {}', 'contenta')).toEqualCss('.one\\:two[contenta] {}'); + expect(shim('.one\\:\\fc ber {}', 'contenta')).toEqualCss('.one\\:\\fc ber[contenta] {}'); expect(shim('.one\\:two .three\\:four {}', 'contenta')) .toEqualCss('.one\\:two[contenta] .three\\:four[contenta] {}'); });