Repository: angular/angular
File: packages/compiler/src/shadow_css.ts (TypeScript)

Code before:
          const convertedSelector =
            _polyfillHostNoCombinator + hostSelector.replace(_polyfillHost, '') + otherSelectors;
          convertedSelectors.push(convertedSelector);
        }
        return convertedSelectors.join(',');
      } else {
        return _polyfillHostNoCombinator + otherSelectors;
      }
    });
  }

  /**
   * Generator function that splits a string on top-level commas (commas that are not inside parentheses).
   * Yields each part of the string between top-level commas. Terminates if an extra closing paren is found.
   *
   * @param text The string to split
   */
  private *_splitOnTopLevelCommas(text: string): Generator<string> {
    const length = text.length;
    let parens = 0;
    let prev = 0;

    for (let i = 0; i < length; i++) {
      const char = text[i];

      if (char === '(') {
        parens++;
      } else if (char === ')') {
        parens--;
        if (parens < 0) {
          // Found an extra closing paren. Assume we want the list terminated here
          yield text.slice(prev, i);
          return;
        }
      } else if (char === ',' && parens === 0) {
        // Found a top-level comma, yield the current chunk
        yield text.slice(prev, i);
        prev = i + 1;
      }
    }

    // Yield the final chunk
    yield text.slice(prev);
  }

  /*
   * convert a rule like :host-context(.foo) > .bar { }
   *
   * to
   *
   * .foo<scopeName> > .bar, .foo <scopeName> > .bar { }

Review comment (bug):
A bit of an edge case, but is it possible to hit a selector here that has an escaped or quoted paren? E.g. `:host-context([someAttr=")"])`? I'm guessing the old approach didn't handle this either though...

Code after:
          if (!hostSelector) break;
          const convertedSelector =
            _polyfillHostNoCombinator + hostSelector.replace(_polyfillHost, '') + otherSelectors;
          convertedSelectors.push(convertedSelector);
        }
        return convertedSelectors.join(',');
      } else {
        return _polyfillHostNoCombinator + otherSelectors;
      }
    });
  }

  /**
   * Generator function that splits a string on top-level commas (commas that are not inside parentheses).
   * Yields each part of the string between top-level commas. Terminates if an extra closing paren is found.
   *
   * @param text The string to split
   */
  private *_splitOnTopLevelCommas(text: string): Generator<string> {
    const length = text.length;
    let parens = 0;
    let prev = 0;

    for (let i = 0; i < length; i++) {
      const charCode = text.charCodeAt(i);

      if (charCode === chars.$LPAREN) {
        parens++;
      } else if (charCode === chars.$RPAREN) {
        parens--;
        if (parens < 0) {
          // Found an extra closing paren. Assume we want the list terminated here
          yield text.slice(prev, i);
          return;
        }
      } else if (charCode === chars.$COMMA && parens === 0) {
        // Found a top-level comma, yield the current chunk
        yield text.slice(prev, i);
        prev = i + 1;
      }
    }

    // Yield the final chunk
    yield text.slice(prev);
  }

  /*
   * convert a rule like :host-context(.foo) > .bar { }
   *
   * to
   *