Newer
Older
vue-indexer / node_modules / puppeteer-core / src / common / PSelectorParser.ts
/**
 * @license
 * Copyright 2023 Google Inc.
 * SPDX-License-Identifier: Apache-2.0
 */

import {
  type Token,
  tokenize,
  TOKENS,
  stringify,
} from '../../third_party/parsel-js/parsel-js.js';
import type {
  ComplexPSelector,
  ComplexPSelectorList,
  CompoundPSelector,
} from '../injected/PQuerySelector.js';
import {PCombinator} from '../injected/PQuerySelector.js';

TOKENS['nesting'] = /&/g;
TOKENS['combinator'] = /\s*(>>>>?|[\s>+~])\s*/g;

const ESCAPE_REGEXP = /\\[\s\S]/g;
const unquote = (text: string): string => {
  if (text.length <= 1) {
    return text;
  }
  if ((text[0] === '"' || text[0] === "'") && text.endsWith(text[0])) {
    text = text.slice(1, -1);
  }
  return text.replace(ESCAPE_REGEXP, match => {
    return match[1] as string;
  });
};

/**
 * @internal
 */
export function parsePSelectors(
  selector: string
): [
  selector: ComplexPSelectorList,
  isPureCSS: boolean,
  hasPseudoClasses: boolean,
  hasAria: boolean,
] {
  let isPureCSS = true;
  let hasAria = false;
  let hasPseudoClasses = false;
  const tokens = tokenize(selector);
  if (tokens.length === 0) {
    return [[], isPureCSS, hasPseudoClasses, false];
  }
  let compoundSelector: CompoundPSelector = [];
  let complexSelector: ComplexPSelector = [compoundSelector];
  const selectors: ComplexPSelectorList = [complexSelector];
  const storage: Token[] = [];
  for (const token of tokens) {
    switch (token.type) {
      case 'combinator':
        switch (token.content) {
          case PCombinator.Descendent:
            isPureCSS = false;
            if (storage.length) {
              compoundSelector.push(stringify(storage));
              storage.splice(0);
            }
            compoundSelector = [];
            complexSelector.push(PCombinator.Descendent);
            complexSelector.push(compoundSelector);
            continue;
          case PCombinator.Child:
            isPureCSS = false;
            if (storage.length) {
              compoundSelector.push(stringify(storage));
              storage.splice(0);
            }
            compoundSelector = [];
            complexSelector.push(PCombinator.Child);
            complexSelector.push(compoundSelector);
            continue;
        }
        break;
      case 'pseudo-element':
        if (!token.name.startsWith('-p-')) {
          break;
        }
        isPureCSS = false;
        if (storage.length) {
          compoundSelector.push(stringify(storage));
          storage.splice(0);
        }
        const name = token.name.slice(3);
        if (name === 'aria') {
          hasAria = true;
        }
        compoundSelector.push({
          name,
          value: unquote(token.argument ?? ''),
        });
        continue;
      case 'pseudo-class':
        hasPseudoClasses = true;
        break;
      case 'comma':
        if (storage.length) {
          compoundSelector.push(stringify(storage));
          storage.splice(0);
        }
        compoundSelector = [];
        complexSelector = [compoundSelector];
        selectors.push(complexSelector);
        continue;
    }
    storage.push(token);
  }
  if (storage.length) {
    compoundSelector.push(stringify(storage));
  }
  return [selectors, isPureCSS, hasPseudoClasses, hasAria];
}