diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index c9d36d1e2c4..cadfaf55e85 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -512,7 +512,7 @@ export class ShadowCss { return cssText.replace(_cssColonHostRe, (_, hostSelectors: string, otherSelectors: string) => { if (hostSelectors) { const convertedSelectors: string[] = []; - for (const hostSelector of this._splitOnTopLevelCommas(hostSelectors)) { + for (const hostSelector of this._splitOnTopLevelCommas(hostSelectors, true)) { const trimmedHostSelector = hostSelector.trim(); if (!trimmedHostSelector) break; const convertedSelector = @@ -533,8 +533,9 @@ export class ShadowCss { * Yields each part of the string between top-level commas. Terminates if an extra closing paren is found. * * @param text The string to split + * @param returnOnClosingParen Whether to return when exiting the current level of parentheses nesting */ - private *_splitOnTopLevelCommas(text: string): Generator { + private *_splitOnTopLevelCommas(text: string, returnOnClosingParen: boolean): Generator { const length = text.length; let parens = 0; let prev = 0; @@ -546,8 +547,8 @@ export class ShadowCss { parens++; } else if (charCode === chars.$RPAREN) { parens--; - if (parens < 0) { - // Found an extra closing paren. Assume we want the list terminated here + if (parens < 0 && returnOnClosingParen) { + // Found an extra closing paren. yield text.slice(prev, i); return; } @@ -582,7 +583,7 @@ export class ShadowCss { // individually and stitches them back together. This ensures that individual selectors don't // affect each other. const results: string[] = []; - for (const part of this._splitOnTopLevelCommas(cssText)) { + for (const part of this._splitOnTopLevelCommas(cssText, false)) { results.push(this._convertColonHostContextInSelectorPart(part)); } return results.join(','); @@ -615,7 +616,7 @@ export class ShadowCss { // Extract comma-separated selectors between the parentheses const newContextSelectors: string[] = []; let endIndex = 0; // Index of the closing paren of the :host-context() - for (const selector of this._splitOnTopLevelCommas(afterPrefix.substring(1))) { + for (const selector of this._splitOnTopLevelCommas(afterPrefix.substring(1), true)) { endIndex = endIndex + selector.length + 1; const trimmed = selector.trim(); if (trimmed) { diff --git a/packages/compiler/test/shadow_css/shadow_css_spec.ts b/packages/compiler/test/shadow_css/shadow_css_spec.ts index 2777b83272d..89b1a74efda 100644 --- a/packages/compiler/test/shadow_css/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css/shadow_css_spec.ts @@ -339,6 +339,13 @@ describe('ShadowCss', () => { expect(css).toEqualCss('div[contenta] {background-image:url("a.jpg"); color:red;}'); }); + it('should handle when quoted content contains a closing parenthesis', () => { + // Regression test for https://github.com/angular/angular/issues/65137 + expect(shim('p { background-image: url(")") } p { color: red }', 'contenta')).toEqualCss( + 'p[contenta] { background-image: url(")") } p[contenta] { color: red }', + ); + }); + it('should shim rules with an escaped quote inside quoted content', () => { const styleStr = 'div::after { content: "\\"" }'; const css = shim(styleStr, 'contenta');