Best JavaScript code snippet using playwright-internal
parse-css.js
Source:parse-css.js  
1/**2 * @license3 * Copyright 2015 The AMP HTML Authors. All Rights Reserved.4 *5 * Licensed under the Apache License, Version 2.0 (the "License");6 * you may not use this file except in compliance with the License.7 * You may obtain a copy of the License at8 *9 *      http://www.apache.org/licenses/LICENSE-2.010 *11 * Unless required by applicable law or agreed to in writing, software12 * distributed under the License is distributed on an "AS-IS" BASIS,13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14 * See the License for the specific language governing permissions and15 * limitations under the license.16 *17 * Credits:18 *   This original version of this file was derived from19 *   https://github.com/tabatkins/parse-css by Tab Atkins,20 *   licensed under the CC0 license21 *   (http://creativecommons.org/publicdomain/zero/1.0/).22 */23goog.provide('parse_css.AtRule');24goog.provide('parse_css.BlockType');25goog.provide('parse_css.Declaration');26goog.provide('parse_css.ParsedCssUrl');27goog.provide('parse_css.QualifiedRule');28goog.provide('parse_css.Rule');29goog.provide('parse_css.RuleVisitor');30goog.provide('parse_css.Stylesheet');31goog.provide('parse_css.TokenStream');32goog.provide('parse_css.extractAFunction');33goog.provide('parse_css.extractASimpleBlock');34goog.provide('parse_css.extractUrls');35goog.provide('parse_css.parseAStylesheet');36goog.provide('parse_css.parseInlineStyle');37goog.provide('parse_css.parseMediaQueries');38goog.provide('parse_css.stripVendorPrefix');39goog.require('amp.validator.LIGHT');40goog.require('amp.validator.ValidationError.Code');41goog.require('goog.asserts');42goog.require('goog.string');43goog.require('parse_css.EOFToken');44goog.require('parse_css.ErrorToken');45goog.require('parse_css.TRIVIAL_EOF_TOKEN');46goog.require('parse_css.TRIVIAL_ERROR_TOKEN');47goog.require('parse_css.Token');48goog.require('parse_css.TokenType');49/**50 * @param {!Array<?>} arr51 * @return {!Array<!Object>}52 */53function arrayToJSON(arr) {54  const json = [];55  for (let i = 0; i < arr.length; i++) {56    json.push(arr[i].toJSON());57  }58  return json;59}60/**61 * A TokenStream is essentially an array of Token objects62 * with a reference to a current position. Consume/Reconsume methods63 * move the current position. tokenAt, current, and next inspect tokens64 * at specific points.65 */66parse_css.TokenStream = class {67  /**68   * @param {!Array<!parse_css.Token>} tokens69   */70  constructor(tokens) {71    goog.asserts.assert(72        tokens.length > 0,73        'Internal Error: empty TokenStream - must have EOF token');74    goog.asserts.assert(75        tokens[tokens.length - 1].tokenType === parse_css.TokenType.EOF_TOKEN,76        'Internal Error: TokenStream must end with EOF');77    /** @type {!Array<!parse_css.Token>} */78    this.tokens = tokens;79    /** @type {number} */80    this.pos = -1;81  }82  /**83   * Returns the token at an absolute position in the token stream.84   *85   * @param {number} num86   * @return {!parse_css.Token}87   */88  tokenAt(num) {89    // The last token is guaranteed to be the EOF token (with correct90    // line / col!) so any request past the length of the array91    // fetches that.92    return (num < this.tokens.length) ? this.tokens[num] :93      this.tokens[this.tokens.length - 1];94  }95  /**96   * Returns the token at the current position in the token stream.97   * @return {!parse_css.Token}98   */99  current() {100    return this.tokenAt(this.pos);101  }102  /**103   * Returns the token at the next position in the token stream.104   * @return {!parse_css.Token}105   */106  next() {107    return this.tokenAt(this.pos + 1);108  }109  /**110   * Advances the stream by one.111   */112  consume() {113    this.pos++;114  }115  /** Rewinds to the previous position in the input. */116  reconsume() {117    this.pos--;118  }119};120/**121 * Strips vendor prefixes from identifiers, e.g. property names or names122 * of at rules. E.g., "-moz-keyframes" -> "keyframes".123 * @param {string} prefixedString124 * @return {string}125 */126parse_css.stripVendorPrefix = function(prefixedString) {127  // Checking for '-' is an optimization.128  if (prefixedString !== '' && prefixedString[0] === '-') {129    if (goog.string./*OK*/ startsWith(prefixedString, '-o-'))130    {return prefixedString.substr('-o-'.length);}131    if (goog.string./*OK*/ startsWith(prefixedString, '-moz-'))132    {return prefixedString.substr('-moz-'.length);}133    if (goog.string./*OK*/ startsWith(prefixedString, '-ms-'))134    {return prefixedString.substr('-ms-'.length);}135    if (goog.string./*OK*/ startsWith(prefixedString, '-webkit-'))136    {return prefixedString.substr('-webkit-'.length);}137  }138  return prefixedString;139};140/**141 * Returns a Stylesheet object with nested parse_css.Rules.142 *143 * The top level Rules in a Stylesheet are always a series of144 * QualifiedRule's or AtRule's.145 *146 * @param {!Array<!parse_css.Token>} tokenList147 * @param {!Object<string,parse_css.BlockType>} atRuleSpec block type rules for148 * all CSS AT rules this canonicalizer should handle.149 * @param {parse_css.BlockType} defaultSpec default block type for types not150 * found in atRuleSpec.151 * @param {!Array<!parse_css.ErrorToken>} errors output array for the errors.152 * @return {!parse_css.Stylesheet}153 */154parse_css.parseAStylesheet = function(155  tokenList, atRuleSpec, defaultSpec, errors) {156  const canonicalizer = new Canonicalizer(atRuleSpec, defaultSpec);157  const stylesheet = new parse_css.Stylesheet();158  stylesheet.rules =159      canonicalizer.parseAListOfRules(tokenList, /* topLevel */ true, errors);160  tokenList[0].copyPosTo(stylesheet);161  const eof = /** @type {!parse_css.EOFToken} */162      (tokenList[tokenList.length - 1]);163  stylesheet.eof = eof;164  return stylesheet;165};166/**167 * Returns a array of Declaration objects.168 *169 * @param {!Array<!parse_css.Token>} tokenList170 * @param {!Array<!parse_css.ErrorToken>} errors output array for the errors.171 * @return {!Array<!parse_css.Declaration>}172 */173parse_css.parseInlineStyle = function(tokenList, errors) {174  const canonicalizer =175      new Canonicalizer({}, parse_css.BlockType.PARSE_AS_DECLARATIONS);176  return canonicalizer.parseAListOfDeclarations(tokenList, errors);177};178/**179 * Abstract super class for the parser rules.180 */181parse_css.Rule = class extends parse_css.Token {182  constructor() {183    super();184    /** @type {parse_css.TokenType} */185    this.tokenType = parse_css.TokenType.UNKNOWN;186  }187  /** @param {!parse_css.RuleVisitor} visitor */188  accept(visitor) {}189  /**190   * @param {number=} opt_indent191   * @return {string}192   */193  toString(opt_indent) {194    return JSON.stringify(this.toJSON(), null, opt_indent);195  }196};197parse_css.Stylesheet = class extends parse_css.Rule {198  constructor() {199    super();200    /** @type {!Array<!parse_css.Rule>} */201    this.rules = [];202    /** @type {?parse_css.EOFToken} */203    this.eof = null;204    /** @type {parse_css.TokenType} */205    this.tokenType = parse_css.TokenType.STYLESHEET;206  }207  /** @inheritDoc */208  accept(visitor) {209    visitor.visitStylesheet(this);210    for (const rule of this.rules) {211      rule.accept(visitor);212    }213    visitor.leaveStylesheet(this);214  }215};216if (!amp.validator.LIGHT) {217  /** @inheritDoc */218  parse_css.Stylesheet.prototype.toJSON = function() {219    const json = parse_css.Rule.prototype.toJSON.call(this);220    json['rules'] = arrayToJSON(this.rules);221    json['eof'] = this.eof.toJSON();222    return json;223  };224}225parse_css.AtRule = class extends parse_css.Rule {226  /**227   * @param {string} name228   */229  constructor(name) {230    super();231    /** @type {string} */232    this.name = name;233    /** @type {!Array<!parse_css.Token>} */234    this.prelude = [];235    /** @type {!Array<!parse_css.Rule>} */236    this.rules = [];237    /** @type {!Array<!parse_css.Declaration>} */238    this.declarations = [];239    /** @type {parse_css.TokenType} */240    this.tokenType = parse_css.TokenType.AT_RULE;241  }242  /** @inheritDoc */243  accept(visitor) {244    visitor.visitAtRule(this);245    for (const rule of this.rules) {246      rule.accept(visitor);247    }248    for (const declaration of this.declarations) {249      declaration.accept(visitor);250    }251    visitor.leaveAtRule(this);252  }253};254if (!amp.validator.LIGHT) {255  /** @inheritDoc */256  parse_css.AtRule.prototype.toJSON = function() {257    const json = parse_css.Rule.prototype.toJSON.call(this);258    json['name'] = this.name;259    json['prelude'] = arrayToJSON(this.prelude);260    json['rules'] = arrayToJSON(this.rules);261    json['declarations'] = arrayToJSON(this.declarations);262    return json;263  };264}265parse_css.QualifiedRule = class extends parse_css.Rule {266  constructor() {267    super();268    /** @type {!Array<!parse_css.Token>} */269    this.prelude = [];270    /** @type {!Array<!parse_css.Declaration>} */271    this.declarations = [];272    /** @type {parse_css.TokenType} */273    this.tokenType = parse_css.TokenType.QUALIFIED_RULE;274  }275  /** @inheritDoc */276  accept(visitor) {277    visitor.visitQualifiedRule(this);278    for (const declaration of this.declarations) {279      declaration.accept(visitor);280    }281    visitor.leaveQualifiedRule(this);282  }283};284if (!amp.validator.LIGHT) {285  /** @inheritDoc */286  parse_css.QualifiedRule.prototype.toJSON = function() {287    const json = parse_css.Rule.prototype.toJSON.call(this);288    json['prelude'] = arrayToJSON(this.prelude);289    json['declarations'] = arrayToJSON(this.declarations);290    return json;291  };292  /** @return {string} The concatenation of the qualified rule name. */293  parse_css.QualifiedRule.prototype.ruleName = function() {294    let ruleName = '';295    for (let i = 0; i < this.prelude.length; ++i) {296      const prelude =297      /** @type {!parse_css.IdentToken} */ (this.prelude[i]);298      if (prelude.value) {ruleName += prelude.value;}299    }300    return ruleName;301  };302}303parse_css.Declaration = class extends parse_css.Rule {304  /**305   * @param {string} name306   */307  constructor(name) {308    super();309    /** @type {string} */310    this.name = name;311    /** @type {!Array<!parse_css.Token>} */312    this.value = [];313    /** @type {boolean} */314    this.important = false;315    /** @type {parse_css.TokenType} */316    this.tokenType = parse_css.TokenType.DECLARATION;317  }318  /**319   * For a declaration, if the first non-whitespace token is an identifier,320   * returns its string value. Otherwise, returns the empty string.321   * @return {string}322   */323  firstIdent() {324    if (this.value.length === 0) {325      return '';326    }327    if (this.value[0].tokenType === parse_css.TokenType.IDENT) {328      return /** @type {!parse_css.StringValuedToken} */ (this.value[0]).value;329    }330    if (this.value.length >= 2 &&331        (this.value[0].tokenType === parse_css.TokenType.WHITESPACE) &&332        this.value[1].tokenType === parse_css.TokenType.IDENT) {333      return /** @type {!parse_css.StringValuedToken} */ (this.value[1]).value;334    }335    return '';336  }337  /** @inheritDoc */338  accept(visitor) {339    visitor.visitDeclaration(this);340    visitor.leaveDeclaration(this);341  }342};343if (!amp.validator.LIGHT) {344  /** @inheritDoc */345  parse_css.Declaration.prototype.toJSON = function() {346    const json = parse_css.Rule.prototype.toJSON.call(this);347    json['name'] = this.name;348    json['important'] = this.important;349    json['value'] = arrayToJSON(this.value);350    return json;351  };352}353/**354 * A visitor for Rule subclasses (StyleSheet, AtRule, QualifiedRule,355 * Declaration). Pass this to the Rule::Accept method.356 * Visitation order is to call the Visit* method on the current node,357 * then visit the children, then call the Leave* method on the current node.358 */359parse_css.RuleVisitor = class {360  constructor() {}361  /** @param {!parse_css.Stylesheet} stylesheet */362  visitStylesheet(stylesheet) {}363  /** @param {!parse_css.Stylesheet} stylesheet */364  leaveStylesheet(stylesheet) {}365  /** @param {!parse_css.AtRule} atRule */366  visitAtRule(atRule) {}367  /** @param {!parse_css.AtRule} atRule */368  leaveAtRule(atRule) {}369  /** @param {!parse_css.QualifiedRule} qualifiedRule */370  visitQualifiedRule(qualifiedRule) {}371  /** @param {!parse_css.QualifiedRule} qualifiedRule */372  leaveQualifiedRule(qualifiedRule) {}373  /** @param {!parse_css.Declaration} declaration */374  visitDeclaration(declaration) {}375  /** @param {!parse_css.Declaration} declaration */376  leaveDeclaration(declaration) {}377};378/**379 * Enum describing how to parse the rules inside a CSS AT Rule.380 * @enum {string}381 */382parse_css.BlockType = {383  // Parse this simple block as a list of rules384  // (Either Qualified Rules or AT Rules)385  'PARSE_AS_RULES': 'PARSE_AS_RULES',386  // Parse this simple block as a list of declarations387  'PARSE_AS_DECLARATIONS': 'PARSE_AS_DECLARATIONS',388  // Ignore this simple block, do not parse. This is generally used389  // in conjunction with a later step emitting an error for this rule.390  'PARSE_AS_IGNORE': 'PARSE_AS_IGNORE',391};392/**393 * A canonicalizer is created with a specific spec for canonicalizing CSS AT394 * rules. It otherwise has no state.395 * @private396 */397class Canonicalizer {398  /**399   * @param {!Object<string,parse_css.BlockType>} atRuleSpec block400   * type rules for all CSS AT rules this canonicalizer should handle.401   * @param {parse_css.BlockType} defaultSpec default block type for402   * types not found in atRuleSpec.403   */404  constructor(atRuleSpec, defaultSpec) {405    /**406     * @type {!Object<string,parse_css.BlockType>}407     * @private408     */409    this.atRuleSpec_ = atRuleSpec;410    /**411     * @type {parse_css.BlockType}412     * @private413     */414    this.defaultAtRuleSpec_ = defaultSpec;415  }416  /**417   * Returns a type telling us how to canonicalize a given AT rule's block.418   * @param {!parse_css.AtRule} atRule419   * @return {!parse_css.BlockType}420   */421  blockTypeFor(atRule) {422    const maybeBlockType =423        this.atRuleSpec_[parse_css.stripVendorPrefix(atRule.name)];424    if (maybeBlockType !== undefined) {425      return maybeBlockType;426    } else {427      return this.defaultAtRuleSpec_;428    }429  }430  /**431   * Parses and returns a list of rules, such as at the top level of a stylesheet.432   * Return list has only QualifiedRule's and AtRule's as top level elements.433   * @param {!Array<!parse_css.Token>} tokenList434   * @param {boolean} topLevel435   * @param {!Array<!parse_css.ErrorToken>} errors output array for the errors.436   * @return {!Array<!parse_css.Rule>}437   */438  parseAListOfRules(tokenList, topLevel, errors) {439    const tokenStream = new parse_css.TokenStream(tokenList);440    const rules = [];441    while (true) {442      tokenStream.consume();443      const current = tokenStream.current().tokenType;444      if (current === parse_css.TokenType.WHITESPACE) {445        continue;446      } else if (current === parse_css.TokenType.EOF_TOKEN) {447        return rules;448      } else if (449        current === parse_css.TokenType.CDO ||450          current === parse_css.TokenType.CDC) {451        if (topLevel) {452          continue;453        }454        this.parseAQualifiedRule(tokenStream, rules, errors);455      } else if (current === parse_css.TokenType.AT_KEYWORD) {456        rules.push(this.parseAnAtRule(tokenStream, errors));457      } else {458        this.parseAQualifiedRule(tokenStream, rules, errors);459      }460    }461  }462  /**463   * Parses an At Rule.464   *465   * @param {!parse_css.TokenStream} tokenStream466   * @param {!Array<!parse_css.ErrorToken>} errors output array for the errors.467   * @return {!parse_css.AtRule}468   */469  parseAnAtRule(tokenStream, errors) {470    goog.asserts.assert(471        tokenStream.current().tokenType === parse_css.TokenType.AT_KEYWORD,472        'Internal Error: parseAnAtRule precondition not met');473    const startToken =474    /** @type {!parse_css.AtKeywordToken} */ (tokenStream.current());475    const rule = new parse_css.AtRule(startToken.value);476    if (!amp.validator.LIGHT) {477      startToken.copyPosTo(rule);478    }479    while (true) {480      tokenStream.consume();481      const current = tokenStream.current().tokenType;482      if (current === parse_css.TokenType.SEMICOLON ||483          current === parse_css.TokenType.EOF_TOKEN) {484        rule.prelude.push(tokenStream.current());485        return rule;486      }487      if (current === parse_css.TokenType.OPEN_CURLY) {488        rule.prelude.push(489            tokenStream.current().copyPosTo(new parse_css.EOFToken()));490        /** @type {!Array<!parse_css.Token>} */491        const contents = parse_css.extractASimpleBlock(tokenStream);492        switch (this.blockTypeFor(rule)) {493          case parse_css.BlockType.PARSE_AS_RULES: {494            rule.rules =495                this.parseAListOfRules(contents, /* topLevel */ false, errors);496            break;497          }498          case parse_css.BlockType.PARSE_AS_DECLARATIONS: {499            rule.declarations = this.parseAListOfDeclarations(contents, errors);500            break;501          }502          case parse_css.BlockType.PARSE_AS_IGNORE: {503            break;504          }505          default: {506            goog.asserts.fail(507                'Unrecognized blockType ' + this.blockTypeFor(rule));508            break;509          }510        }511        return rule;512      }513      consumeAComponentValue(tokenStream, rule.prelude);514    }515  }516  /**517   * Parses one Qualified rule or ErrorToken appended to either rules or errors518   * respectively. Rule will include a prelude with the CSS selector (if any)519   * and a list of declarations.520   *521   * @param {!parse_css.TokenStream} tokenStream522   * @param {!Array<!parse_css.Rule>} rules output array for new rule523   * @param {!Array<!parse_css.ErrorToken>} errors output array for new error.524   */525  parseAQualifiedRule(tokenStream, rules, errors) {526    goog.asserts.assert(527        tokenStream.current().tokenType !== parse_css.TokenType.EOF_TOKEN &&528            tokenStream.current().tokenType !== parse_css.TokenType.AT_KEYWORD,529        'Internal Error: parseAQualifiedRule precondition not met');530    if (amp.validator.LIGHT && errors.length > 0) {531      return;532    }533    const rule = tokenStream.current().copyPosTo(new parse_css.QualifiedRule());534    tokenStream.reconsume();535    while (true) {536      tokenStream.consume();537      const current = tokenStream.current().tokenType;538      if (current === parse_css.TokenType.EOF_TOKEN) {539        if (amp.validator.LIGHT) {540          errors.push(parse_css.TRIVIAL_ERROR_TOKEN);541        } else {542          errors.push(rule.copyPosTo(new parse_css.ErrorToken(543              amp.validator.ValidationError.Code544                  .CSS_SYNTAX_EOF_IN_PRELUDE_OF_QUALIFIED_RULE,545              ['style'])));546        }547        return;548      }549      if (current === parse_css.TokenType.OPEN_CURLY) {550        rule.prelude.push(551            tokenStream.current().copyPosTo(new parse_css.EOFToken()));552        // This consumes declarations (ie: "color: red;" ) inside553        // a qualified rule as that rule's value.554        rule.declarations = this.parseAListOfDeclarations(555            parse_css.extractASimpleBlock(tokenStream), errors);556        rules.push(rule);557        return;558      }559      // This consumes a CSS selector as the rules prelude.560      consumeAComponentValue(tokenStream, rule.prelude);561    }562  }563  /**564   * @param {!Array<!parse_css.Token>} tokenList565   * @param {!Array<!parse_css.ErrorToken>} errors output array for the errors.566   * @return {!Array<!parse_css.Declaration>}567   */568  parseAListOfDeclarations(tokenList, errors) {569    if (amp.validator.LIGHT && errors.length > 0) {570      return [];571    }572    /** @type {!Array<!parse_css.Declaration>} */573    const decls = [];574    const tokenStream = new parse_css.TokenStream(tokenList);575    while (true) {576      tokenStream.consume();577      const current = tokenStream.current().tokenType;578      if (current === parse_css.TokenType.WHITESPACE ||579          current === parse_css.TokenType.SEMICOLON) {580        continue;581      } else if (current === parse_css.TokenType.EOF_TOKEN) {582        return decls;583      } else if (current === parse_css.TokenType.AT_KEYWORD) {584        // The CSS3 Parsing spec allows for AT rules inside lists of585        // declarations, but our grammar does not so we deviate a tiny bit here.586        // We consume an AT rule, but drop it and instead push an error token.587        if (amp.validator.LIGHT) {588          errors.push(parse_css.TRIVIAL_ERROR_TOKEN);589          return [];590        }591        const atRule = this.parseAnAtRule(tokenStream, errors);592        errors.push(atRule.copyPosTo(new parse_css.ErrorToken(593            amp.validator.ValidationError.Code.CSS_SYNTAX_INVALID_AT_RULE,594            ['style', atRule.name])));595      } else if (current === parse_css.TokenType.IDENT) {596        this.parseADeclaration(tokenStream, decls, errors);597      } else {598        if (amp.validator.LIGHT) {599          errors.push(parse_css.TRIVIAL_ERROR_TOKEN);600          return [];601        }602        errors.push(tokenStream.current().copyPosTo(new parse_css.ErrorToken(603            amp.validator.ValidationError.Code.CSS_SYNTAX_INVALID_DECLARATION,604            ['style'])));605        tokenStream.reconsume();606        while (607          !(tokenStream.next().tokenType === parse_css.TokenType.SEMICOLON ||608              tokenStream.next().tokenType === parse_css.TokenType.EOF_TOKEN)) {609          tokenStream.consume();610          const dummyTokenList = [];611          consumeAComponentValue(tokenStream, dummyTokenList);612        }613      }614    }615  }616  /**617   * Adds one element to either declarations or errors.618   * @param {!parse_css.TokenStream} tokenStream619   * @param {!Array<!parse_css.Declaration>} declarations output array for620   * declarations621   * @param {!Array<!parse_css.ErrorToken>} errors output array for the errors.622   */623  parseADeclaration(tokenStream, declarations, errors) {624    goog.asserts.assert(625        tokenStream.current().tokenType === parse_css.TokenType.IDENT,626        'Internal Error: parseADeclaration precondition not met');627    if (amp.validator.LIGHT && errors.length > 0) {628      return;629    }630    const startToken =631    /** @type {!parse_css.IdentToken} */ (tokenStream.current());632    const decl =633        startToken.copyPosTo(new parse_css.Declaration(startToken.value));634    while (tokenStream.next().tokenType === parse_css.TokenType.WHITESPACE) {635      tokenStream.consume();636    }637    tokenStream.consume();638    if (!(tokenStream.current().tokenType === parse_css.TokenType.COLON)) {639      if (amp.validator.LIGHT) {640        errors.push(parse_css.TRIVIAL_ERROR_TOKEN);641        return;642      }643      errors.push(startToken.copyPosTo(new parse_css.ErrorToken(644          amp.validator.ValidationError.Code.CSS_SYNTAX_INCOMPLETE_DECLARATION,645          ['style'])));646      tokenStream.reconsume();647      while (648        !(tokenStream.next().tokenType === parse_css.TokenType.SEMICOLON ||649            tokenStream.next().tokenType === parse_css.TokenType.EOF_TOKEN)) {650        tokenStream.consume();651      }652      return;653    }654    while (655      !(tokenStream.next().tokenType === parse_css.TokenType.SEMICOLON ||656          tokenStream.next().tokenType === parse_css.TokenType.EOF_TOKEN)) {657      tokenStream.consume();658      consumeAComponentValue(tokenStream, decl.value);659    }660    decl.value.push(tokenStream.next().copyPosTo(new parse_css.EOFToken()));661    let foundImportant = false;662    for (let i = decl.value.length - 1; i >= 0; i--) {663      if (decl.value[i].tokenType === parse_css.TokenType.WHITESPACE) {664        continue;665      } else if (666        decl.value[i].tokenType === parse_css.TokenType.IDENT &&667          /** @type {parse_css.IdentToken} */668          (decl.value[i]).ASCIIMatch('important')) {669        foundImportant = true;670      } else if (671        foundImportant &&672          decl.value[i].tokenType === parse_css.TokenType.DELIM &&673        /** @type {parse_css.DelimToken} */ (decl.value[i]).value === '!') {674        decl.value.splice(i, decl.value.length);675        decl.important = true;676        break;677      } else {678        break;679      }680    }681    declarations.push(decl);682  }683}684/**685 * Consumes one or more tokens from a tokenStream, appending them to a686 * tokenList.687 * @param {!parse_css.TokenStream} tokenStream688 * @param {!Array<!parse_css.Token>} tokenList output array for tokens.689 */690function consumeAComponentValue(tokenStream, tokenList) {691  const current = tokenStream.current().tokenType;692  if (current === parse_css.TokenType.OPEN_CURLY ||693      current === parse_css.TokenType.OPEN_SQUARE ||694      current === parse_css.TokenType.OPEN_PAREN) {695    consumeASimpleBlock(tokenStream, tokenList);696  } else if (current === parse_css.TokenType.FUNCTION_TOKEN) {697    consumeAFunction(tokenStream, tokenList);698  } else {699    tokenList.push(tokenStream.current());700  }701}702/**703 * Appends a simple block's contents to a tokenList, consuming from704 * the stream all those tokens that it adds to the tokenList,705 * including the start/end grouping token.706 * @param {!parse_css.TokenStream} tokenStream707 * @param {!Array<!parse_css.Token>} tokenList output array for tokens.708 */709function consumeASimpleBlock(tokenStream, tokenList) {710  const current = tokenStream.current().tokenType;711  goog.asserts.assert(712      (current === parse_css.TokenType.OPEN_CURLY ||713       current === parse_css.TokenType.OPEN_SQUARE ||714       current === parse_css.TokenType.OPEN_PAREN),715      'Internal Error: consumeASimpleBlock precondition not met');716  const startToken =717  /** @type {!parse_css.GroupingToken} */ (tokenStream.current());718  const {mirror} = startToken;719  tokenList.push(startToken);720  while (true) {721    tokenStream.consume();722    const current = tokenStream.current().tokenType;723    if (current === parse_css.TokenType.EOF_TOKEN) {724      tokenList.push(tokenStream.current());725      return;726    } else if (727      (current === parse_css.TokenType.CLOSE_CURLY ||728         current === parse_css.TokenType.CLOSE_SQUARE ||729         current === parse_css.TokenType.CLOSE_PAREN) &&730      /** @type {parse_css.GroupingToken} */ (tokenStream.current()).value ===731            mirror) {732      tokenList.push(tokenStream.current());733      return;734    } else {735      consumeAComponentValue(tokenStream, tokenList);736    }737  }738}739/**740 * Returns a simple block's contents in tokenStream, excluding the741 * start/end grouping token, and appended with an EOFToken.742 * @param {!parse_css.TokenStream} tokenStream743 * @return {!Array<!parse_css.Token>}744 */745parse_css.extractASimpleBlock = function(tokenStream) {746  /** @type {!Array<!parse_css.Token>} */747  const consumedTokens = [];748  consumeASimpleBlock(tokenStream, consumedTokens);749  // A simple block always has a start token (e.g. '{') and750  // either a closing token or EOF token.751  goog.asserts.assert(consumedTokens.length >= 2);752  // Exclude the start token. Convert end token to EOF.753  const end = consumedTokens.length - 1;754  consumedTokens[end] = amp.validator.LIGHT ?755    parse_css.TRIVIAL_EOF_TOKEN :756    consumedTokens[end].copyPosTo(new parse_css.EOFToken());757  return consumedTokens.slice(1);758};759/**760 * Appends a function's contents to a tokenList, consuming from the761 * stream all those tokens that it adds to the tokenList, including762 * the function token and end grouping token.763 * @param {!parse_css.TokenStream} tokenStream764 * @param {!Array<!parse_css.Token>} tokenList output array for tokens.765 */766function consumeAFunction(tokenStream, tokenList) {767  goog.asserts.assert(768      tokenStream.current().tokenType === parse_css.TokenType.FUNCTION_TOKEN,769      'Internal Error: consumeAFunction precondition not met');770  tokenList.push(tokenStream.current());771  while (true) {772    tokenStream.consume();773    const current = tokenStream.current().tokenType;774    if (current === parse_css.TokenType.EOF_TOKEN ||775        current === parse_css.TokenType.CLOSE_PAREN) {776      tokenList.push(tokenStream.current());777      return;778    } else {779      consumeAComponentValue(tokenStream, tokenList);780    }781  }782}783/**784 * Returns a function's contents in tokenList, including the leading785 * FunctionToken, but excluding the trailing CloseParen token and786 * appended with an EOFToken instead.787 * @param {!parse_css.TokenStream} tokenStream788 * @return {!Array<!parse_css.Token>}789 */790parse_css.extractAFunction = function(tokenStream) {791  /** @type {!Array<!parse_css.Token>} */792  const consumedTokens = [];793  consumeAFunction(tokenStream, consumedTokens);794  // A function always has a start FunctionToken and795  // either a CloseParenToken or EOFToken.796  goog.asserts.assert(consumedTokens.length >= 2);797  // Convert end token to EOF.798  const end = consumedTokens.length - 1;799  consumedTokens[end] = amp.validator.LIGHT ?800    parse_css.TRIVIAL_EOF_TOKEN :801    consumedTokens[end].copyPosTo(new parse_css.EOFToken());802  return consumedTokens;803};804/**805 * Used by parse_css.ExtractUrls to return urls it has seen. This represents806 * URLs in CSS such as url(http://foo.com/) and url("http://bar.com/").807 * For this token, line() and col() indicate the position information808 * of the left-most CSS token that's part of the URL. E.g., this would be809 * the URLToken instance or the FunctionToken instance.810 */811parse_css.ParsedCssUrl = class extends parse_css.Token {812  constructor() {813    super();814    /** @type {parse_css.TokenType} */815    this.tokenType = parse_css.TokenType.PARSED_CSS_URL;816    /**817     * The decoded URL. This string will not contain CSS string escapes,818     * quotes, or similar. Encoding is utf8.819     * @type {string}820     */821    this.utf8Url = '';822    /**823     * A rule scope, in case the url was encountered within an at-rule.824     * If not within an at-rule, this string is empty.825     * @type {string}826     */827    this.atRuleScope = '';828  }829};830if (!amp.validator.LIGHT) {831  /** @inheritDoc */832  parse_css.ParsedCssUrl.prototype.toJSON = function() {833    const json = parse_css.Token.prototype.toJSON.call(this);834    json['utf8Url'] = this.utf8Url;835    json['atRuleScope'] = this.atRuleScope;836    return json;837  };838}839/**840 * Parses a CSS URL token; typically takes the form "url(http://foo)".841 * Preconditions: tokens[token_idx] is a URL token842 *                and token_idx + 1 is in range.843 * @param {!Array<!parse_css.Token>} tokens844 * @param {number} tokenIdx845 * @param {!parse_css.ParsedCssUrl} parsed846 */847function parseUrlToken(tokens, tokenIdx, parsed) {848  goog.asserts.assert(tokenIdx + 1 < tokens.length);849  const token = tokens[tokenIdx];850  goog.asserts.assert(token.tokenType === parse_css.TokenType.URL);851  token.copyPosTo(parsed);852  parsed.utf8Url = /** @type {parse_css.URLToken}*/ (token).value;853}854/**855 * Parses a CSS function token named 'url', including the string and closing856 * paren. Typically takes the form "url('http://foo')".857 * Returns the token_idx past the closing paren, or -1 if parsing fails.858 * Preconditions: tokens[token_idx] is a URL token859 *                and tokens[token_idx]->StringValue() == "url"860 * @param {!Array<!parse_css.Token>} tokens861 * @param {number} tokenIdx862 * @param {!parse_css.ParsedCssUrl} parsed863 * @return {number}864 */865function parseUrlFunction(tokens, tokenIdx, parsed) {866  const token = tokens[tokenIdx];867  goog.asserts.assert(token.tokenType == parse_css.TokenType.FUNCTION_TOKEN);868  goog.asserts.assert(869      /** @type {parse_css.FunctionToken} */ (token).value === 'url');870  goog.asserts.assert(871      tokens[tokens.length - 1].tokenType === parse_css.TokenType.EOF_TOKEN);872  token.copyPosTo(parsed);873  ++tokenIdx; // We've digested the function token above.874  // Safe: tokens ends w/ EOF_TOKEN.875  goog.asserts.assert(tokenIdx < tokens.length);876  // Consume optional whitespace.877  while (tokens[tokenIdx].tokenType === parse_css.TokenType.WHITESPACE) {878    ++tokenIdx;879    // Safe: tokens ends w/ EOF_TOKEN.880    goog.asserts.assert(tokenIdx < tokens.length);881  }882  // Consume URL.883  if (tokens[tokenIdx].tokenType !== parse_css.TokenType.STRING) {884    return -1;885  }886  parsed.utf8Url =887    /** @type {parse_css.StringToken} */ (tokens[tokenIdx]).value;888  ++tokenIdx;889  // Safe: tokens ends w/ EOF_TOKEN.890  goog.asserts.assert(tokenIdx < tokens.length);891  // Consume optional whitespace.892  while (tokens[tokenIdx].tokenType === parse_css.TokenType.WHITESPACE) {893    ++tokenIdx;894    // Safe: tokens ends w/ EOF_TOKEN.895    goog.asserts.assert(tokenIdx < tokens.length);896  }897  // Consume ')'898  if (tokens[tokenIdx].tokenType !== parse_css.TokenType.CLOSE_PAREN) {899    return -1;900  }901  return tokenIdx + 1;902}903/**904 * Helper class for implementing parse_css.extractUrls.905 * @private906 */907class UrlFunctionVisitor extends parse_css.RuleVisitor {908  /**909   * @param {!Array<!parse_css.ParsedCssUrl>} parsedUrls910   * @param {!Array<!parse_css.ErrorToken>} errors911   */912  constructor(parsedUrls, errors) {913    super();914    /** @type {!Array<!parse_css.ParsedCssUrl>} */915    this.parsedUrls = parsedUrls;916    /** @type {!Array<!parse_css.ErrorToken>} */917    this.errors = errors;918    /** @type {string} */919    this.atRuleScope = '';920  }921  /** @inheritDoc */922  visitAtRule(atRule) {923    this.atRuleScope = atRule.name;924  }925  /** @inheritDoc */926  leaveAtRule(atRule) {927    this.atRuleScope = '';928  }929  /** @inheritDoc */930  visitQualifiedRule(qualifiedRule) {931    this.atRuleScope = '';932  }933  /** @inheritDoc */934  visitDeclaration(declaration) {935    goog.asserts.assert(declaration.value.length > 0);936    goog.asserts.assert(937        declaration.value[declaration.value.length - 1].tokenType ===938        parse_css.TokenType.EOF_TOKEN);939    if (amp.validator.LIGHT && this.errors.length > 0) {940      return;941    }942    for (let ii = 0; ii < declaration.value.length - 1;) {943      const token = declaration.value[ii];944      if (token.tokenType === parse_css.TokenType.URL) {945        const parsedUrl = new parse_css.ParsedCssUrl();946        parseUrlToken(declaration.value, ii, parsedUrl);947        parsedUrl.atRuleScope = this.atRuleScope;948        this.parsedUrls.push(parsedUrl);949        ++ii;950        continue;951      }952      if (token.tokenType === parse_css.TokenType.FUNCTION_TOKEN &&953      /** @type {!parse_css.FunctionToken} */ (token).value === 'url') {954        const parsedUrl = new parse_css.ParsedCssUrl();955        ii = parseUrlFunction(declaration.value, ii, parsedUrl);956        if (ii === -1) {957          if (amp.validator.LIGHT) {958            this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN);959          } else {960            this.errors.push(token.copyPosTo(new parse_css.ErrorToken(961                amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL,962                ['style'])));963          }964          return;965        }966        parsedUrl.atRuleScope = this.atRuleScope;967        this.parsedUrls.push(parsedUrl);968        continue;969      }970      // It's neither a url token nor a function token named url. So, we skip.971      ++ii;972    }973  }974}975/**976 * Extracts the URLs within the provided stylesheet, emitting them into977 * parsedUrls and errors into errors.978 * @param {!parse_css.Stylesheet} stylesheet979 * @param {!Array<!parse_css.ParsedCssUrl>} parsedUrls980 * @param {!Array<!parse_css.ErrorToken>} errors981 */982parse_css.extractUrls = function(stylesheet, parsedUrls, errors) {983  const parsedUrlsOldLength = parsedUrls.length;984  const errorsOldLength = errors.length;985  const visitor = new UrlFunctionVisitor(parsedUrls, errors);986  stylesheet.accept(visitor);987  // If anything went wrong, delete the urls we've already emitted.988  if (errorsOldLength !== errors.length) {989    parsedUrls.splice(parsedUrlsOldLength);990  }991};992/**993 * Helper class for implementing parse_css.parseMediaQueries.994 * @private995 */996class MediaQueryVisitor extends parse_css.RuleVisitor {997  /**998   * @param {!Array<!parse_css.IdentToken>} mediaTypes999   * @param {!Array<!parse_css.IdentToken>} mediaFeatures1000   * @param {!Array<!parse_css.ErrorToken>} errors1001   */1002  constructor(mediaTypes, mediaFeatures, errors) {1003    super();1004    /** @type {!Array<!parse_css.IdentToken>} */1005    this.mediaTypes = mediaTypes;1006    /** @type {!Array<!parse_css.IdentToken>} */1007    this.mediaFeatures = mediaFeatures;1008    /** @type {!Array<!parse_css.ErrorToken>} */1009    this.errors = errors;1010  }1011  /** @inheritDoc */1012  visitAtRule(atRule) {1013    if (atRule.name.toLowerCase() !== 'media') {return;}1014    const tokenStream = new parse_css.TokenStream(atRule.prelude);1015    tokenStream.consume(); // Advance to first token.1016    if (!this.parseAMediaQueryList_(tokenStream)) {1017      if (amp.validator.LIGHT) {1018        this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN);1019      } else {1020        this.errors.push(atRule.copyPosTo(new parse_css.ErrorToken(1021            amp.validator.ValidationError.Code.CSS_SYNTAX_MALFORMED_MEDIA_QUERY,1022            ['style'])));1023      }1024    }1025  }1026  /**1027   * Maybe consume one whitespace token.1028   * @param {!parse_css.TokenStream} tokenStream1029   * @private1030   */1031  maybeConsumeAWhitespaceToken_(tokenStream) {1032    // While the grammar calls for consuming multiple whitespace tokens,1033    // our tokenizer already collapses whitespace so only one token can ever1034    // be present.1035    if (tokenStream.current().tokenType === parse_css.TokenType.WHITESPACE)1036    {tokenStream.consume();}1037  }1038  /**1039   * Parse a media query list1040   * @param {!parse_css.TokenStream} tokenStream1041   * @return {boolean}1042   * @private1043   */1044  parseAMediaQueryList_(tokenStream) {1045    // https://www.w3.org/TR/css3-mediaqueries/#syntax1046    // : S* [media_query [ ',' S* media_query ]* ]?1047    // ;1048    this.maybeConsumeAWhitespaceToken_(tokenStream);1049    if (tokenStream.current().tokenType !== parse_css.TokenType.EOF_TOKEN) {1050      if (!this.parseAMediaQuery_(tokenStream)) {return false;}1051      while (tokenStream.current().tokenType === parse_css.TokenType.COMMA) {1052        tokenStream.consume(); // ','1053        this.maybeConsumeAWhitespaceToken_(tokenStream);1054        if (!this.parseAMediaQuery_(tokenStream)) {return false;}1055      }1056    }1057    return tokenStream.current().tokenType === parse_css.TokenType.EOF_TOKEN;1058  }1059  /**1060   * Parse a media query1061   * @param {!parse_css.TokenStream} tokenStream1062   * @return {boolean}1063   * @private1064   */1065  parseAMediaQuery_(tokenStream) {1066    // : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*1067    // | expression [ AND S* expression ]*1068    // ;1069    //1070    // Below we parse media queries with this equivalent grammar:1071    // : (expression | [ONLY | NOT]? S* media_type S* )1072    // [ AND S* expression ]*1073    // ;1074    //1075    // This is more convenient because we know that expressions must start with1076    // '(', so it's simpler to use as a check to distinguis the expression case1077    // from the media type case.1078    if (tokenStream.current().tokenType === parse_css.TokenType.OPEN_PAREN) {1079      if (!this.parseAMediaExpression_(tokenStream)) {return false;}1080    } else {1081      if (tokenStream.current().tokenType === parse_css.TokenType.IDENT &&1082          (1083            /** @type {parse_css.IdentToken} */1084            (tokenStream.current()).ASCIIMatch('only') ||1085              /** @type {parse_css.IdentToken} */1086              (tokenStream.current()).ASCIIMatch('not'))) {1087        tokenStream.consume(); // 'ONLY' | 'NOT'1088      }1089      this.maybeConsumeAWhitespaceToken_(tokenStream);1090      if (!this.parseAMediaType_(tokenStream)) {return false;}1091      this.maybeConsumeAWhitespaceToken_(tokenStream);1092    }1093    while (tokenStream.current().tokenType === parse_css.TokenType.IDENT &&1094           /** @type {parse_css.IdentToken} */1095           (tokenStream.current()).ASCIIMatch('and')) {1096      tokenStream.consume(); // 'AND'1097      this.maybeConsumeAWhitespaceToken_(tokenStream);1098      if (!this.parseAMediaExpression_(tokenStream)) {return false;}1099    }1100    return true;1101  }1102  /**1103   * Parse a media type1104   * @param {!parse_css.TokenStream} tokenStream1105   * @return {boolean}1106   * @private1107   */1108  parseAMediaType_(tokenStream) {1109    // : IDENT1110    // ;1111    if (tokenStream.current().tokenType === parse_css.TokenType.IDENT) {1112      this.mediaTypes.push(1113          /** @type {!parse_css.IdentToken} */ (tokenStream.current()));1114      tokenStream.consume();1115      return true;1116    }1117    return false;1118  }1119  /**1120   * Parse a media expression1121   * @param {!parse_css.TokenStream} tokenStream1122   * @return {boolean}1123   * @private1124   */1125  parseAMediaExpression_(tokenStream) {1126    //  : '(' S* media_feature S* [ ':' S* expr ]? ')' S*1127    //  ;1128    if (tokenStream.current().tokenType !== parse_css.TokenType.OPEN_PAREN)1129    {return false;}1130    tokenStream.consume(); // '('1131    this.maybeConsumeAWhitespaceToken_(tokenStream);1132    if (!this.parseAMediaFeature_(tokenStream)) {return false;}1133    this.maybeConsumeAWhitespaceToken_(tokenStream);1134    if (tokenStream.current().tokenType === parse_css.TokenType.COLON) {1135      tokenStream.consume(); // '('1136      this.maybeConsumeAWhitespaceToken_(tokenStream);1137      // The CSS3 grammar at this point just tells us to expect some1138      // expr. Which tokens are accepted here are defined by the media1139      // feature found above. We don't implement media features here, so1140      // we just loop over tokens until we find a CLOSE_PAREN or EOF.1141      // While expr in general may have arbitrary sets of open/close parens,1142      // it seems that https://www.w3.org/TR/css3-mediaqueries/#media11143      // suggests that media features cannot:1144      //1145      // "Media features only accept single values: one keyword, one number,1146      // or a number with a unit identifier. (The only exceptions are the1147      // âaspect-ratioâ and âdevice-aspect-ratioâ media features.)1148      while (1149        tokenStream.current().tokenType !== parse_css.TokenType.EOF_TOKEN &&1150          tokenStream.current().tokenType !== parse_css.TokenType.CLOSE_PAREN)1151      {tokenStream.consume();}1152    }1153    if (tokenStream.current().tokenType !== parse_css.TokenType.CLOSE_PAREN)1154    {return false;}1155    tokenStream.consume(); // ')'1156    this.maybeConsumeAWhitespaceToken_(tokenStream);1157    return true;1158  }1159  /**1160   * Parse a media feature1161   * @param {!parse_css.TokenStream} tokenStream1162   * @return {boolean}1163   * @private1164   */1165  parseAMediaFeature_(tokenStream) {1166    // : IDENT1167    // ;1168    if (tokenStream.current().tokenType === parse_css.TokenType.IDENT) {1169      this.mediaFeatures.push(1170          /** @type {!parse_css.IdentToken} */ (tokenStream.current()));1171      tokenStream.consume();1172      return true;1173    }1174    return false;1175  }1176}1177/**1178 * Parses media queries within the provided stylesheet, emitting the set of1179 * discovered media types and media features, as well as errors if parsing1180 * failed.1181 * parsedUrls and errors into errors.1182 * @param {!parse_css.Stylesheet} stylesheet1183 * @param {!Array<!parse_css.IdentToken>} mediaTypes1184 * @param {!Array<!parse_css.IdentToken>} mediaFeatures1185 * @param {!Array<!parse_css.ErrorToken>} errors1186 */1187parse_css.parseMediaQueries = function(1188  stylesheet, mediaTypes, mediaFeatures, errors) {1189  const visitor = new MediaQueryVisitor(mediaTypes, mediaFeatures, errors);1190  stylesheet.accept(visitor);...css-selectors.js
Source:css-selectors.js  
1/**2 * @license3 * Copyright 2015 The AMP HTML Authors. All Rights Reserved.4 *5 * Licensed under the Apache License, Version 2.0 (the "License");6 * you may not use this file except in compliance with the License.7 * You may obtain a copy of the License at8 *9 *      http://www.apache.org/licenses/LICENSE-2.010 *11 * Unless required by applicable law or agreed to in writing, software12 * distributed under the License is distributed on an "AS-IS" BASIS,13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14 * See the License for the specific language governing permissions and15 * limitations under the license.16 */17goog.provide('parse_css.AttrSelector');18goog.provide('parse_css.ClassSelector');19goog.provide('parse_css.Combinator');20goog.provide('parse_css.IdSelector');21goog.provide('parse_css.PseudoSelector');22goog.provide('parse_css.Selector');23goog.provide('parse_css.SelectorVisitor');24goog.provide('parse_css.SelectorsGroup');25goog.provide('parse_css.SimpleSelectorSequence');26goog.provide('parse_css.TypeSelector');27goog.provide('parse_css.parseAClassSelector');28goog.provide('parse_css.parseASelector');29goog.provide('parse_css.parseASelectorsGroup');30goog.provide('parse_css.parseASimpleSelectorSequence');31goog.provide('parse_css.parseATypeSelector');32goog.provide('parse_css.parseAnAttrSelector');33goog.provide('parse_css.parseAnIdSelector');34goog.provide('parse_css.parseSelectors');35goog.provide('parse_css.traverseSelectors');36goog.require('goog.asserts');37goog.require('parse_css.EOFToken');38goog.require('parse_css.ErrorToken');39goog.require('parse_css.Token');40goog.require('parse_css.TokenStream');41goog.require('parse_css.extractAFunction');42/**43 * Abstract super class for CSS Selectors. The Token class, which this44 * class inherits from, has line, col, and tokenType fields.45 */46parse_css.Selector = class extends parse_css.Token {47  /** @param {!function(!parse_css.Selector)} lambda */48  forEachChild(lambda) {}49  /** @param {!parse_css.SelectorVisitor} visitor */50  accept(visitor) {}51};52/**53 * A super class for making visitors (by overriding the types of interest).54 * The parse_css.traverseSelectros function can be used to visit nodes in a55 * parsed CSS selector.56 */57parse_css.SelectorVisitor = class {58  constructor() {}59  /** @param {!parse_css.TypeSelector} typeSelector */60  visitTypeSelector(typeSelector) {}61  /** @param {!parse_css.IdSelector} idSelector */62  visitIdSelector(idSelector) {}63  /** @param {!parse_css.AttrSelector} attrSelector */64  visitAttrSelector(attrSelector) {}65  /** @param {!parse_css.PseudoSelector} pseudoSelector */66  visitPseudoSelector(pseudoSelector) {}67  /** @param {!parse_css.ClassSelector} classSelector */68  visitClassSelector(classSelector) {}69  /** @param {!parse_css.SimpleSelectorSequence} sequence */70  visitSimpleSelectorSequence(sequence) {}71  /** @param {!parse_css.Combinator} combinator */72  visitCombinator(combinator) {}73  /** @param {!parse_css.SelectorsGroup} group */74  visitSelectorsGroup(group) {}75};76/**77 * Visits selectorNode and its children, recursively, by calling the78 * appropriate methods on the provided visitor.79 * @param {!parse_css.Selector} selectorNode80 * @param {!parse_css.SelectorVisitor} visitor81 */82parse_css.traverseSelectors = function(selectorNode, visitor) {83  /** @type {!Array<!parse_css.Selector>} */84  const toVisit = [selectorNode];85  while (toVisit.length > 0) {86    /** @type {!parse_css.Selector} */87    const node = toVisit.shift();88    node.accept(visitor);89    node.forEachChild(child => { toVisit.push(child); });90  }91};92/**93 * This node models type selectors and universial selectors.94 * http://www.w3.org/TR/css3-selectors/#type-selectors95 * http://www.w3.org/TR/css3-selectors/#universal-selector96 */97parse_css.TypeSelector = class extends parse_css.Selector {98  /**99   * Choices for namespacePrefix:100   * - 'a specific namespace prefix' means 'just that specific namespace'.101   * - '' means 'without a namespace'102   * - '*' means 'any namespace including without a namespace'103   * - null means the default namespace if one is declared, and '*' otherwise.104   *105   * The universal selector is covered by setting the elementName to '*'.106   *107   * @param {?string} namespacePrefix108   * @param {string} elementName109   */110  constructor(namespacePrefix, elementName) {111    super();112    /** @type {?string} */113    this.namespacePrefix = namespacePrefix;114    /** @type {string} */115    this.elementName = elementName;116    /** @type {parse_css.TokenType} */117    this.tokenType = parse_css.TokenType.TYPE_SELECTOR;118  }119  /**120   * Serializes the selector to a string (in this case CSS syntax that121   * could be used to recreate it).122   * @return {string}123   */124  toString() {125    if (this.namespacePrefix === null) {126      return this.elementName;127    }128    return this.namespacePrefix + '|' + this.elementName;129  }130  /** @inheritDoc */131  toJSON() {132    const json = super.toJSON();133    json['namespacePrefix'] = this.namespacePrefix;134    json['elementName'] = this.elementName;135    return json;136  }137  /** @inheritDoc */138  accept(visitor) {139    visitor.visitTypeSelector(this);140  }141};142/**143 * Helper function for determining whether the provided token is a specific144 * delimiter.145 * @param {!parse_css.Token} token146 * @param {string} delimChar147 * @return {boolean}148 */149function isDelim(token, delimChar) {150  if (!(token instanceof parse_css.DelimToken)) {151    return false;152  }153  const delimToken = goog.asserts.assertInstanceof(token, parse_css.DelimToken);154  return delimToken.value === delimChar;155}156/**157 * tokenStream.current() is the first token of the type selector.158 * @param {!parse_css.TokenStream} tokenStream159 * @return {!parse_css.TypeSelector}160 */161parse_css.parseATypeSelector = function(tokenStream) {162  /** @type {?string} */163  let namespacePrefix = null;164  /** @type {string} */165  let elementName = '*';166  const start = tokenStream.current();167  if (isDelim(tokenStream.current(), '|')) {168    namespacePrefix = '';169    tokenStream.consume();170  } else if (isDelim(tokenStream.current(), '*') &&171      isDelim(tokenStream.next(), '|')) {172    namespacePrefix = '*';173    tokenStream.consume();174    tokenStream.consume();175  } else if (tokenStream.current() instanceof parse_css.IdentToken &&176      isDelim(tokenStream.next(), '|')) {177    const ident = goog.asserts.assertInstanceof(178        tokenStream.current(), parse_css.IdentToken);179    namespacePrefix = ident.value;180    tokenStream.consume();181    tokenStream.consume();182  }183  if (tokenStream.current() instanceof parse_css.DelimToken &&184      isDelim(tokenStream.current(), '*')) {185    elementName = '*';186    tokenStream.consume();187  } else if (tokenStream.current() instanceof parse_css.IdentToken) {188    const ident = goog.asserts.assertInstanceof(189        tokenStream.current(), parse_css.IdentToken);190    elementName = ident.value;191    tokenStream.consume();192  }193  const selector =  new parse_css.TypeSelector(194      namespacePrefix, elementName);195  start.copyStartPositionTo(selector);196  return selector;197};198/**199 * An ID selector references some document id.200 * http://www.w3.org/TR/css3-selectors/#id-selectors201 * Typically written as '#foo'.202 */203parse_css.IdSelector = class extends parse_css.Selector {204  /**205   * @param {string} value206   */207  constructor(value) {208    super();209    /** @type {string} */210    this.value = value;211    /** @type {parse_css.TokenType} */212    this.tokenType = parse_css.TokenType.ID_SELECTOR;213  }214  /** @return {string} */215  toString() { return '#' + this.value; }216  /** @inheritDoc */217  toJSON() {218    const json = super.toJSON();219    json['value'] = this.value;220    return json;221  }222  /** @inheritDoc */223  accept(visitor) {224    visitor.visitIdSelector(this);225  }226};227/**228 * tokenStream.current() must be the hash token.229 * @param {!parse_css.TokenStream} tokenStream230 * @return {!parse_css.IdSelector}231 */232parse_css.parseAnIdSelector = function(tokenStream) {233  goog.asserts.assertInstanceof(234      tokenStream.current(), parse_css.HashToken,235      'Precondition violated: must start with HashToken');236  const hash = goog.asserts.assertInstanceof(237      tokenStream.current(), parse_css.HashToken);238  tokenStream.consume();239  const selector = new parse_css.IdSelector(hash.value);240  hash.copyStartPositionTo(selector);241  return selector;242};243/**244 * An attribute selector matches document nodes based on their attributes.245 * http://www.w3.org/TR/css3-selectors/#attribute-selectors246 *247 * Typically written as '[foo=bar]'.248 */249parse_css.AttrSelector = class extends parse_css.Selector {250  /**251   * @param {string?} namespacePrefix252   * @param {!string} attrName253   * @param {!string} matchOperator is either the string254   * representation of the match operator (e.g., '=' or '~=') or '',255   * in which case the attribute selector is a check for the presence256   * of the attribute.257   * @param {!string} value is the value to apply the match operator258   * against, or if matchOperator is '', then this must be empty as259   * well.260   */261  constructor(namespacePrefix, attrName, matchOperator, value) {262    super();263    /** @type {string?} */264    this.namespacePrefix = namespacePrefix;265    /** @type {!string} */266    this.attrName = attrName;267    /** @type {string?} */268    this.matchOperator = matchOperator;269    /** @type {string?} */270    this.value = value;271    /** @type {parse_css.TokenType} */272    this.tokenType = parse_css.TokenType.ATTR_SELECTOR;273  }274  /** @inheritDoc */275  toJSON() {276    const json = super.toJSON();277    json['namespacePrefix'] = this.namespacePrefix;278    json['attrName'] = this.attrName;279    json['matchOperator'] = this.matchOperator;280    json['value'] = this.value;281    return json;282  }283  /** @inheritDoc */284  accept(visitor) {285    visitor.visitAttrSelector(this);286  }287};288/**289 * Helper for parseAnAttrSelector.290 * @private291 * @param {!parse_css.Token} start292 * @return {!parse_css.ErrorToken}293 */294function newInvalidAttrSelectorError(start) {295  const error = new parse_css.ErrorToken(296      amp.validator.ValidationError.Code.CSS_SYNTAX_INVALID_ATTR_SELECTOR,297      ['style']);298  start.copyStartPositionTo(error);299  return error;300}301/**302 * tokenStream.current() must be the open square token.303 * @param {!parse_css.TokenStream} tokenStream304 * @return {!parse_css.AttrSelector|!parse_css.ErrorToken}305 */306parse_css.parseAnAttrSelector = function(tokenStream) {307  goog.asserts.assert(308      tokenStream.current() instanceof parse_css.OpenSquareToken,309      'Precondition violated: must be an OpenSquareToken');310  const start = tokenStream.current();311  tokenStream.consume();  // Consumes '['.312  if (tokenStream.current() instanceof parse_css.WhitespaceToken) {313    tokenStream.consume();314  }315  // This part is defined in https://www.w3.org/TR/css3-selectors/#attrnmsp:316  // Attribute selectors and namespaces. It is similar to parseATypeSelector.317  let namespacePrefix = null;318  if (isDelim(tokenStream.current(), '|')) {319    namespacePrefix = '';320    tokenStream.consume();321  } else if (isDelim(tokenStream.current(), '*') &&322      isDelim(tokenStream.next(), '|')) {323    namespacePrefix = '*';324    tokenStream.consume();325    tokenStream.consume();326  } else if (tokenStream.current() instanceof parse_css.IdentToken &&327      isDelim(tokenStream.next(), '|')) {328    const ident = goog.asserts.assertInstanceof(329        tokenStream.current(), parse_css.IdentToken);330    namespacePrefix = ident.value;331    tokenStream.consume();332    tokenStream.consume();333  }334  // Now parse the attribute name. This part is mandatory.335  if (!(tokenStream.current() instanceof parse_css.IdentToken)) {336    return newInvalidAttrSelectorError(start);337  }338  const ident = goog.asserts.assertInstanceof(339      tokenStream.current(), parse_css.IdentToken);340  const attrName = ident.value;341  tokenStream.consume();342  if (tokenStream.current() instanceof parse_css.WhitespaceToken) {343    tokenStream.consume();344  }345  // After the attribute name, we may see an operator; if we do, then346  // we must see either a string or an identifier. This covers347  // 6.3.1 Attribute presence and value selectors348  // (https://www.w3.org/TR/css3-selectors/#attribute-representation) and349  // 6.3.2 Substring matching attribute selectors350  // (https://www.w3.org/TR/css3-selectors/#attribute-substrings).351  /** @type {string} */352  let matchOperator = '';353  if (isDelim(tokenStream.current(), '=')) {354    matchOperator = '=';355    tokenStream.consume();356  } else if (tokenStream.current() instanceof parse_css.IncludeMatchToken) {357    matchOperator = '~=';358    tokenStream.consume();359  } else if (tokenStream.current() instanceof parse_css.DashMatchToken) {360    matchOperator = '|=';361    tokenStream.consume();362  } else if (tokenStream.current() instanceof parse_css.PrefixMatchToken) {363    matchOperator = '^=';364    tokenStream.consume();365  } else if (tokenStream.current() instanceof parse_css.SuffixMatchToken) {366    matchOperator = '$=';367    tokenStream.consume();368  } else if (tokenStream.current() instanceof parse_css.SubstringMatchToken) {369    matchOperator = '*=';370    tokenStream.consume();371  }372  if (tokenStream.current() instanceof parse_css.WhitespaceToken) {373    tokenStream.consume();374  }375  /** @type {string} */376  let value = '';377  if (matchOperator !== '') {  // If we saw an operator, parse the value.378    if (tokenStream.current() instanceof parse_css.IdentToken) {379      const ident = goog.asserts.assertInstanceof(380        tokenStream.current(), parse_css.IdentToken);381      value = ident.value;382      tokenStream.consume();383    } else if (tokenStream.current() instanceof parse_css.StringToken) {384      const str = goog.asserts.assertInstanceof(385        tokenStream.current(), parse_css.StringToken);386      value = str.value;387      tokenStream.consume();388    } else {389      return newInvalidAttrSelectorError(start);390    }391  }392  if (tokenStream.current() instanceof parse_css.WhitespaceToken) {393    tokenStream.consume();394  }395  // The attribute selector must in any case terminate with a close square396  // token.397  if (!(tokenStream.current() instanceof parse_css.CloseSquareToken)) {398    return newInvalidAttrSelectorError(start);399  }400  tokenStream.consume();401  const selector = new parse_css.AttrSelector(402      namespacePrefix, attrName, matchOperator, value);403  start.copyStartPositionTo(selector);404  return selector;405};406/**407 * A pseudo selector can match either pseudo classes or pseudo elements.408 * http://www.w3.org/TR/css3-selectors/#pseudo-classes409 * http://www.w3.org/TR/css3-selectors/#pseudo-elements.410 *411 * Typically written as ':visited', ':lang(fr)', and '::first-line'.412 *413 * isClass: Pseudo selectors with a single colon (e.g., ':visited')414 * are pseudo class selectors. Selectors with two colons (e.g.,415 * '::first-line') are pseudo elements.416 *417 * func: If it's a function style pseudo selector, like lang(fr), then func418 * the function tokens. TODO(powdercloud): parse this in more detail.419 */420parse_css.PseudoSelector = class extends parse_css.Selector {421  /**422   * @param {boolean} isClass423   * @param {string} name424   * @param {!Array<!parse_css.Token>} func425   */426  constructor(isClass, name, func) {427    super();428    /** @type {boolean} */429    this.isClass = isClass;430    /** @type {string} */431    this.name = name;432    /** @type {!Array<!parse_css.Token>} */433    this.func = func;434    /** @type {parse_css.TokenType} */435    this.tokenType = parse_css.TokenType.PSEUDO_SELECTOR;436  }437  /** @inheritDoc */438  toJSON() {439    const json = super.toJSON();440    json['isClass'] = this.isClass;441    json['name'] = this.name;442    if (this.func.length !== 0) {443      json['func'] = recursiveArrayToJSON(this.func);444    }445    return json;446  }447  /** @inheritDoc */448  accept(visitor) {449    visitor.visitPseudoSelector(this);450  }451};452/**453 * tokenStream.current() must be the ColonToken. Returns an error if454 * the pseudo token can't be parsed (e.g., a lone ':').455 * @param {!parse_css.TokenStream} tokenStream456 * @return {!parse_css.PseudoSelector|!parse_css.ErrorToken}457 */458parse_css.parseAPseudoSelector = function(tokenStream) {459  goog.asserts.assert(tokenStream.current() instanceof parse_css.ColonToken,460                      'Precondition violated: must be a ":"');461  const firstColon = tokenStream.current();462  tokenStream.consume();463  let isClass = true;464  if (tokenStream.current() instanceof parse_css.ColonToken) {465    // '::' starts a pseudo element, ':' starts a pseudo class.466    isClass = false;467    tokenStream.consume();468  }469  let name = '';470  /** @type {!Array<!parse_css.Token>} */471  let func = [];472  if (tokenStream.current() instanceof parse_css.IdentToken) {473    const ident = goog.asserts.assertInstanceof(474        tokenStream.current(), parse_css.IdentToken);475    name = ident.value;476    tokenStream.consume();477  } else if (tokenStream.current() instanceof parse_css.FunctionToken) {478    const funcToken = goog.asserts.assertInstanceof(479        tokenStream.current(), parse_css.FunctionToken);480    name = funcToken.value;481    func = parse_css.extractAFunction(tokenStream);482    tokenStream.consume();483  } else {484    const error = new parse_css.ErrorToken(485        amp.validator.ValidationError.Code.CSS_SYNTAX_ERROR_IN_PSEUDO_SELECTOR,486        ['style']);487    firstColon.copyStartPositionTo(error);488    return error;489  }490  const selector = new parse_css.PseudoSelector(491      isClass, name, func);492  firstColon.copyStartPositionTo(selector);493  return selector;494};495/**496 * A class selector of the form '.value' is a shorthand notation for497 * an attribute match of the form '[class~=value]'.498 * http://www.w3.org/TR/css3-selectors/#class-html499 */500parse_css.ClassSelector = class extends parse_css.Selector {501  /**502   * @param {string} value the class to match.503   */504  constructor(value) {505    super();506    /** @type {string} */507    this.value = value;508    /** @type {parse_css.TokenType} */509    this.tokenType = parse_css.TokenType.CLASS_SELECTOR;510  }511  /** @return {string} */512  toString() { return '.' + this.value; }513  /** @inheritDoc */514  toJSON() {515    const json = super.toJSON();516    json['value'] = this.value;517    return json;518  }519  /** @inheritDoc */520  accept(visitor) {521    visitor.visitClassSelector(this);522  }523};524/**525 * tokenStream.current() must be the '.' delimiter token.526 * @param {!parse_css.TokenStream} tokenStream527 * @return {!parse_css.ClassSelector}528 */529parse_css.parseAClassSelector = function(tokenStream) {530  goog.asserts.assert(531      isDelim(tokenStream.current(), '.') &&532      tokenStream.next() instanceof parse_css.IdentToken,533      'Precondition violated: must start with "." and follow with ident');534  const dot = tokenStream.current();535  tokenStream.consume();536  const ident = goog.asserts.assertInstanceof(537      tokenStream.current(), parse_css.IdentToken);538  tokenStream.consume();539  const selector = new parse_css.ClassSelector(ident.value);540  selector.line = dot.line;541  selector.col = dot.col;542  return selector;543};544/**545 * @param {!Array<!Object>} array546 * @return {!Array<string>}547 */548function recursiveArrayToJSON(array) {549  const json = [];550  for (const entry of array) {551    json.push(entry.toJSON());552  }553  return json;554}555/**556 * Models a simple selector sequence, e.g. '*|foo#id'.557 */558parse_css.SimpleSelectorSequence = class extends parse_css.Selector {559  /**560   * @param {!parse_css.TypeSelector} typeSelector561   * @param {!Array<!parse_css.Selector>} otherSelectors562   */563  constructor(typeSelector, otherSelectors) {564    super();565    /** @type {!parse_css.TypeSelector} */566    this.typeSelector = typeSelector;567    /** @type {!Array<!parse_css.Selector>} */568    this.otherSelectors = otherSelectors;569    /** @type {parse_css.TokenType} */570    this.tokenType = parse_css.TokenType.SIMPLE_SELECTOR_SEQUENCE;571  }572  /** @inheritDoc */573  toJSON() {574    const json = super.toJSON();575    json['typeSelector'] = this.typeSelector.toJSON();576    json['otherSelectors'] = recursiveArrayToJSON(this.otherSelectors);577    return json;578  }579  /** @inheritDoc */580  forEachChild(lambda) {581    lambda(this.typeSelector);582    for (const other of this.otherSelectors) {583      lambda(other);584    }585  }586  /** @inheritDoc */587  accept(visitor) {588    visitor.visitSimpleSelectorSequence(this);589  }590};591/**592 * tokenStream.current must be the first token of the sequence.593 * This function will return an error if no selector is found.594 * @param {!parse_css.TokenStream} tokenStream595 * @return {!parse_css.SimpleSelectorSequence|!parse_css.ErrorToken}596 */597parse_css.parseASimpleSelectorSequence = function(tokenStream) {598  const line = tokenStream.current().line;599  const col = tokenStream.current().col;600  let typeSelector = null;601  if (isDelim(tokenStream.current(), '*') ||602      isDelim(tokenStream.current(), '|') ||603      tokenStream.current() instanceof parse_css.IdentToken) {604    typeSelector = parse_css.parseATypeSelector(tokenStream);605  }606  /** @type {!Array<!parse_css.Selector>} */607  const otherSelectors = [];608  while (true) {609    if (tokenStream.current() instanceof parse_css.HashToken) {610      otherSelectors.push(parse_css.parseAnIdSelector(tokenStream));611    } else if (isDelim(tokenStream.current(), '.') &&612        tokenStream.next() instanceof parse_css.IdentToken) {613      otherSelectors.push(parse_css.parseAClassSelector(tokenStream));614    } else if (tokenStream.current() instanceof parse_css.OpenSquareToken) {615      const maybeAttrSelector = parse_css.parseAnAttrSelector(tokenStream);616      if (maybeAttrSelector instanceof parse_css.ErrorToken) {617        return maybeAttrSelector;618      }619      otherSelectors.push(maybeAttrSelector);620    } else if (tokenStream.current() instanceof parse_css.ColonToken) {621      const maybePseudo = parse_css.parseAPseudoSelector(tokenStream);622      if (maybePseudo instanceof parse_css.ErrorToken) {623        return maybePseudo;624      }625      otherSelectors.push(maybePseudo);626      // NOTE: If adding more 'else if' clauses here, be sure to udpate627      // isSimpleSelectorSequenceStart accordingly.628    } else {629      if (typeSelector === null) {630        if (otherSelectors.length == 0) {631          const error = new parse_css.ErrorToken(632              amp.validator.ValidationError.Code.CSS_SYNTAX_MISSING_SELECTOR,633              ['style']);634          error.line = tokenStream.current().line;635          error.col = tokenStream.current().col;636          return error;637        }638        // If no type selector is given then the universal selector is implied.639        typeSelector = new parse_css.TypeSelector(640            /*namespacePrefix=*/null, /*elementName=*/'*');641        typeSelector.line = line;642        typeSelector.col = col;643      }644      const sequence = new parse_css.SimpleSelectorSequence(645          typeSelector, otherSelectors);646      sequence.line = line;647      sequence.col = col;648      return sequence;649    }650  }651};652/**653 * @enum {string}654 */655parse_css.CombinatorType = {656  'DESCENDANT': 'DESCENDANT',657  'CHILD': 'CHILD',658  'ADJACENT_SIBLING': 'ADJACENT_SIBLING',659  'GENERAL_SIBLING': 'GENERAL_SIBLING'660};661/**662 * Models a combinator, as described in663 * http://www.w3.org/TR/css3-selectors/#combinators.664 */665parse_css.Combinator = class extends parse_css.Selector {666  /**667   * @param {!parse_css.CombinatorType} combinatorType668   * @param {!parse_css.SimpleSelectorSequence|!parse_css.Combinator} left669   * @param {!parse_css.SimpleSelectorSequence} right670   */671  constructor(combinatorType, left, right) {672    super();673    /** @type {!parse_css.CombinatorType} */674    this.combinatorType = combinatorType;675    /** @type {!parse_css.SimpleSelectorSequence|!parse_css.Combinator} */676    this.left = left;677    /** @type {!parse_css.SimpleSelectorSequence} */678    this.right = right;679    /** @type {parse_css.TokenType} */680    this.tokenType = parse_css.TokenType.COMBINATOR;681  }682  /** @inheritDoc */683  toJSON() {684    const json = super.toJSON();685    json['combinatorType'] = this.combinatorType;686    json['left'] = this.left.toJSON();687    json['right'] = this.right.toJSON();688    return json;689  }690  /** @inheritDoc */691  forEachChild(lambda) {692    lambda(this.left);693    lambda(this.right);694  }695  /** @inheritDoc */696  accept(visitor) {697    visitor.visitCombinator(this);698  }699};700/**701 * The CombinatorType for a given token; helper function used when702 * constructing a Combinator instance.703 * @param {!parse_css.Token} token704 * @return {!parse_css.CombinatorType}705 */706function combinatorTypeForToken(token) {707  if (token instanceof parse_css.WhitespaceToken) {708    return parse_css.CombinatorType.DESCENDANT;709  } else if (isDelim(token, '>')) {710    return parse_css.CombinatorType.CHILD;711  } else if (isDelim(token, '+')) {712    return parse_css.CombinatorType.ADJACENT_SIBLING;713  } else if (isDelim(token, '~')) {714    return parse_css.CombinatorType.GENERAL_SIBLING;715  }716  goog.asserts.fail('Internal Error: not a combinator token');717}718/**719 * Whether or not the provided token could be the start of a simple720 * selector sequence. See the simple_selector_sequence production in721 * http://www.w3.org/TR/css3-selectors/#grammar.722 * @param {!parse_css.Token} token723 * @return {boolean}724 */725function isSimpleSelectorSequenceStart(token) {726  // Type selector start.727  if (isDelim(token, '*') || isDelim(token, '|') ||728      (token instanceof parse_css.IdentToken)) {729    return true;730  }731  // Id selector start.732  if (token instanceof parse_css.HashToken) {733    return true;734  }735  // Class selector start.736  if (isDelim(token, '.')) {737    return true;738  }739  // Attr selector start.740  if (token instanceof parse_css.OpenSquareToken) {741    return true;742  }743  // A pseudo selector.744  if (token instanceof parse_css.ColonToken) {745    return true;746  }747  // TODO(johannes): add the others.748  return false;749}750/**751 * The selector production from752 * http://www.w3.org/TR/css3-selectors/#grammar753 * Returns an ErrorToken if no selector is found.754 * @param {!parse_css.TokenStream} tokenStream755 * @return {!parse_css.SimpleSelectorSequence|756 *          !parse_css.Combinator|!parse_css.ErrorToken}757 */758parse_css.parseASelector = function(tokenStream) {759  if (!isSimpleSelectorSequenceStart(tokenStream.current())) {760    const error = new parse_css.ErrorToken(761        amp.validator.ValidationError.Code.CSS_SYNTAX_NOT_A_SELECTOR_START,762        ['style']);763    error.line = tokenStream.current().line;764    error.col = tokenStream.current().col;765    return error;766  }767  let left = parse_css.parseASimpleSelectorSequence(tokenStream);768  if (left instanceof parse_css.ErrorToken) {769    return left;770  }771  while (true) {772    // Consume whitespace in front of combinators, while being careful773    // to not eat away the infamous "whitespace operator" (sigh, haha).774    if ((tokenStream.current() instanceof parse_css.WhitespaceToken) &&775        !isSimpleSelectorSequenceStart(tokenStream.next())) {776      tokenStream.consume();777    }778    // If present, grab the combinator token which we'll use for line779    // / column info.780    if (!(((tokenStream.current() instanceof parse_css.WhitespaceToken) &&781        isSimpleSelectorSequenceStart(tokenStream.next())) ||782        isDelim(tokenStream.current(), '+') ||783        isDelim(tokenStream.current(), '>') ||784        isDelim(tokenStream.current(), '~'))) {785      return left;786    }787    const combinatorToken = tokenStream.current();788    tokenStream.consume();789    if (tokenStream.current() instanceof parse_css.WhitespaceToken) {790      tokenStream.consume();791    }792    const right = parse_css.parseASimpleSelectorSequence(tokenStream);793    if (right instanceof parse_css.ErrorToken) {794      return right;  // TODO(johannes): more than one error / partial tree.795    }796    left = new parse_css.Combinator(797        combinatorTypeForToken(combinatorToken), left, right);798    left.line = combinatorToken.line;799    left.col = combinatorToken.col;800  }801};802/**803 * Models a selectors group, as described in804 * http://www.w3.org/TR/css3-selectors/#grouping.805 */806parse_css.SelectorsGroup = class extends parse_css.Selector {807  /**808   * @param {!Array<!parse_css.SimpleSelectorSequence|809   *         !parse_css.Combinator>} elements810   */811  constructor(elements) {812    super();813    /** @type {!Array<!parse_css.SimpleSelectorSequence|814        !parse_css.Combinator>} */815    this.elements = elements;816    /** @type {parse_css.TokenType} */817    this.tokenType = parse_css.TokenType.SELECTORS_GROUP;818  }819  /** @inheritDoc */820  toJSON() {821    const json = super.toJSON();822    json['elements'] = recursiveArrayToJSON(this.elements);823    return json;824  }825  /** @inheritDoc */826  forEachChild(lambda) {827    for (const child of this.elements) {828      lambda(child);829    }830  }831  /** @param {!parse_css.SelectorVisitor} visitor */832  accept(visitor) {833    visitor.visitSelectorsGroup(this);834  }835};836/**837 * The selectors_group production from838 * http://www.w3.org/TR/css3-selectors/#grammar.839 * In addition, this parsing routine checks that no input remains,840 * that is, after parsing the production we reached the end of |token_stream|.841 * @param {!parse_css.TokenStream} tokenStream842 * @return {!parse_css.SelectorsGroup|843 *          !parse_css.SimpleSelectorSequence|!parse_css.Combinator|844 *          !parse_css.ErrorToken}845 */846parse_css.parseASelectorsGroup = function(tokenStream) {847  if (!isSimpleSelectorSequenceStart(tokenStream.current())) {848    const error = new parse_css.ErrorToken(849        amp.validator.ValidationError.Code.CSS_SYNTAX_NOT_A_SELECTOR_START,850        ['style']);851    tokenStream.current().copyStartPositionTo(error);852    return error;853  }854  const start = tokenStream.current();855  const elements = [parse_css.parseASelector(tokenStream)];856  if (elements[0] instanceof parse_css.ErrorToken) {857    return elements[0];858  }859  while (true) {860    if (tokenStream.current() instanceof parse_css.WhitespaceToken) {861      tokenStream.consume();862    }863    if (tokenStream.current() instanceof parse_css.CommaToken) {864      tokenStream.consume();865      if (tokenStream.current() instanceof parse_css.WhitespaceToken) {866        tokenStream.consume();867      }868      elements.push(parse_css.parseASelector(tokenStream));869      if (elements[elements.length - 1] instanceof parse_css.ErrorToken) {870        return elements[elements.length - 1];871      }872      continue;873    }874    // We're about to claim success and return a selector,875    // but before we do, we check that no unparsed input remains.876    if (!(tokenStream.current() instanceof parse_css.EOFToken)) {877      const error = new parse_css.ErrorToken(878          amp.validator.ValidationError.Code879              .CSS_SYNTAX_UNPARSED_INPUT_REMAINS_IN_SELECTOR,880          ['style']);881      tokenStream.current().copyStartPositionTo(error);882      return error;883    }884    if (elements.length == 1) {885      return elements[0];886    }887    const group = new parse_css.SelectorsGroup(elements);888    start.copyStartPositionTo(group);889    return group;890  }...DestructuringTransformer.js
Source:DestructuringTransformer.js  
1// Copyright 2012 Traceur Authors.2//3// Licensed under the Apache License, Version 2.0 (the 'License');4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an 'AS IS' BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.14import { ARRAY, CALL, PROTOTYPE, SLICE } from "../syntax/PredefinedName.js";15import {16  ARRAY_LITERAL_EXPRESSION,17  ARRAY_PATTERN,18  BINDING_ELEMENT,19  BLOCK,20  CALL_EXPRESSION,21  IDENTIFIER_EXPRESSION,22  LITERAL_EXPRESSION,23  MEMBER_EXPRESSION,24  MEMBER_LOOKUP_EXPRESSION,25  OBJECT_LITERAL_EXPRESSION,26  OBJECT_PATTERN,27  OBJECT_PATTERN_FIELD,28  PAREN_EXPRESSION,29  VARIABLE_DECLARATION_LIST,30} from "../syntax/trees/ParseTreeType.js";31import {32  BindingElement,33  Catch,34  ForInStatement,35  ForOfStatement,36  FunctionDeclaration,37  FunctionExpression,38  LiteralExpression,39  SetAccessor,40} from "../syntax/trees/ParseTrees.js";41import { TempVarTransformer } from "./TempVarTransformer.js";42import { EQUAL, IDENTIFIER, IN, LET, VAR } from "../syntax/TokenType.js";43import {44  createArgumentList,45  createAssignmentExpression,46  createBinaryOperator,47  createBindingIdentifier,48  createBlock,49  createCallExpression,50  createCommaExpression,51  createConditionalExpression,52  createExpressionStatement,53  createIdentifierExpression,54  createMemberExpression,55  createMemberLookupExpression,56  createNumberLiteral,57  createOperatorToken,58  createParenExpression,59  createStringLiteral,60  createVariableDeclaration,61  createVariableDeclarationList,62  createVariableStatement,63} from "./ParseTreeFactory.js";64import { options } from "../options.js";65import { prependStatements } from "./PrependStatements.js";66var stack = [];67/**68 * Collects assignments in the desugaring of a pattern.69 */70class Desugaring {71  /**72   * @param {ParseTree} rvalue73   */74  constructor(rvalue) {75    this.rvalue = rvalue;76  }77}78/**79 * Collects assignments as assignment expressions. This is the80 * desugaring for assignment expressions.81 */82class AssignmentExpressionDesugaring extends Desugaring {83  /**84   * @param {ParseTree} rvalue85   */86  constructor(rvalue) {87    super(rvalue);88    this.expressions = [];89  }90  assign(lvalue, rvalue) {91    this.expressions.push(createAssignmentExpression(lvalue, rvalue));92  }93}94/**95 * Collects assignments as variable declarations. This is the96 * desugaring for 'var', 'const' declarations.97 */98class VariableDeclarationDesugaring extends Desugaring {99  /**100   * @param {ParseTree} rvalue101   */102  constructor(rvalue) {103    super(rvalue);104    this.declarations = [];105  }106  assign(lvalue, rvalue) {107    if (lvalue.type === BINDING_ELEMENT) {108      this.declarations.push(createVariableDeclaration(lvalue.binding, rvalue));109      return;110    }111    if (lvalue.type == IDENTIFIER_EXPRESSION)112      lvalue = createBindingIdentifier(lvalue);113    this.declarations.push(createVariableDeclaration(lvalue, rvalue));114  }115}116/**117 * Creates something like "ident" in rvalue ? rvalue.ident : initializer118 */119function createConditionalMemberExpression(rvalue, identToken, initializer) {120  if (identToken.type !== IDENTIFIER) {121    return createConditionalMemberLookupExpression(122      rvalue,123      new LiteralExpression(null, identToken),124      initializer125    );126  }127  if (!initializer) return createMemberExpression(rvalue, identToken);128  return createConditionalExpression(129    createBinaryOperator(130      createStringLiteral(identToken.value),131      createOperatorToken(IN),132      rvalue133    ),134    createMemberExpression(rvalue, identToken),135    initializer136  );137}138/**139 * Creates something like [index] in rvalue ? rvalue[index] : initializer140 */141function createConditionalMemberLookupExpression(rvalue, index, initializer) {142  if (!initializer) return createMemberLookupExpression(rvalue, index);143  return createConditionalExpression(144    createBinaryOperator(index, createOperatorToken(IN), rvalue),145    createMemberLookupExpression(rvalue, index),146    initializer147  );148}149/**150 * Desugars destructuring assignment.151 *152 * @see <a href="http://wiki.ecmascript.org/doku.php?id=harmony:destructuring#assignments">harmony:destructuring</a>153 */154export class DestructuringTransformer extends TempVarTransformer {155  /**156   * @param {ArrayPattern} tree157   * @return {ParseTree}158   */159  transformArrayPattern(tree) {160    // Patterns should be desugared by their parent nodes.161    throw new Error("unreachable");162  }163  /**164   * @param {ObjectPattern} tree165   * @return {ParseTree}166   */167  transformObjectPattern(tree) {168    // Patterns should be desugard by their parent nodes.169    throw new Error("unreachable");170  }171  /**172   * Transforms:173   *   [a, [b, c]] = x174   * From an assignment expression into:175   *   (function (rvalue) {176   *     a = rvalue[0];177   *     [b, c] = rvalue[1];178   *   }).call(this, x);179   *180   * Nested patterns are desugared by recursive calls to transform.181   *182   * @param {BinaryOperator} tree183   * @return {ParseTree}184   */185  transformBinaryOperator(tree) {186    if (tree.operator.type == EQUAL && tree.left.isPattern()) {187      return this.transformAny(this.desugarAssignment_(tree.left, tree.right));188    } else {189      return super.transformBinaryOperator(tree);190    }191  }192  /**193   * @param {ParseTree} lvalue194   * @param {ParseTree} rvalue195   * @return {ParseTree}196   */197  desugarAssignment_(lvalue, rvalue) {198    var tempIdent = createIdentifierExpression(this.addTempVar());199    var desugaring = new AssignmentExpressionDesugaring(tempIdent);200    this.desugarPattern_(desugaring, lvalue);201    desugaring.expressions.unshift(202      createAssignmentExpression(tempIdent, rvalue)203    );204    desugaring.expressions.push(tempIdent);205    return createParenExpression(createCommaExpression(desugaring.expressions));206  }207  /**208   * Transforms:209   *   [a, [b, c]] = x210   * From a variable declaration list into:211   *   tmp = x, a = x[0], [b, c] = x[1]212   *213   * We do it this way (as opposed to a block with a declaration and214   * initialization statements) so that we can translate const215   * declarations, which must be initialized at declaration.216   *217   * Nested patterns are desugared by recursive calls to transform.218   *219   * @param {VariableDeclarationList} tree220   * @return {ParseTree}221   */222  transformVariableDeclarationList(tree) {223    if (!this.destructuringInDeclaration_(tree)) {224      // No lvalues to desugar.225      return super.transformVariableDeclarationList(tree);226    }227    this.pushTempVarState();228    // Desugar one level of patterns.229    var desugaredDeclarations = [];230    tree.declarations.forEach((declaration) => {231      if (declaration.lvalue.isPattern()) {232        desugaredDeclarations.push(233          ...this.desugarVariableDeclaration_(declaration)234        );235      } else {236        desugaredDeclarations.push(declaration);237      }238    });239    // Desugar more.240    var transformedTree = this.transformVariableDeclarationList(241      createVariableDeclarationList(tree.declarationType, desugaredDeclarations)242    );243    this.popTempVarState();244    return transformedTree;245  }246  transformForInStatement(tree) {247    return this.transformForInOrOf_(248      tree,249      super.transformForInStatement,250      ForInStatement251    );252  }253  transformForOfStatement(tree) {254    return this.transformForInOrOf_(255      tree,256      super.transformForOfStatement,257      ForOfStatement258    );259  }260  /**261   * Transforms for-in and for-of loops.262   * @param  {ForInStatement|ForOfStatement} tree The for-in or for-of loop.263   * @param  {Function} superMethod The super method to call if no pattern is264   *     present.265   * @param  {Function} constr The constructor used to create the transformed266   *     tree.267   * @return {ForInStatement|ForOfStatement} The transformed tree.268   * @private269   */270  transformForInOrOf_(tree, superMethod, constr) {271    if (272      !tree.initializer.isPattern() &&273      (tree.initializer.type !== VARIABLE_DECLARATION_LIST ||274        !this.destructuringInDeclaration_(tree.initializer))275    ) {276      return superMethod.call(this, tree);277    }278    this.pushTempVarState();279    var declarationType, lvalue;280    if (tree.initializer.isPattern()) {281      declarationType = null;282      lvalue = tree.initializer;283    } else {284      declarationType = tree.initializer.declarationType;285      lvalue = tree.initializer.declarations[0].lvalue;286    }287    // for (var pattern in coll) {288    //289    // =>290    //291    // for (var $tmp in coll) {292    //   var pattern = $tmp;293    //294    // And when the initializer is an assignment expression.295    //296    // for (pattern in coll) {297    //298    // =>299    //300    // for (var $tmp in coll) {301    //   pattern = $tmp;302    var statements = [];303    var binding = this.desugarBinding_(lvalue, statements, declarationType);304    var initializer = createVariableDeclarationList(VAR, binding, null);305    var collection = this.transformAny(tree.collection);306    var body = this.transformAny(tree.body);307    if (body.type !== BLOCK) body = createBlock(body);308    statements.push(...body.statements);309    body = createBlock(statements);310    this.popTempVarState();311    return new constr(tree.location, initializer, collection, body);312  }313  transformFunctionDeclaration(tree) {314    return this.transformFunction_(tree, FunctionDeclaration);315  }316  transformFunctionExpression(tree) {317    return this.transformFunction_(tree, FunctionExpression);318  }319  transformFunction_(tree, constructor) {320    stack.push([]);321    var transformedTree =322      constructor === FunctionExpression323        ? super.transformFunctionExpression(tree)324        : super.transformFunctionDeclaration(tree);325    var statements = stack.pop();326    if (!statements.length) return transformedTree;327    // Prepend the var statements to the block.328    statements = prependStatements(329      transformedTree.functionBody.statements,330      ...statements331    );332    return new constructor(333      transformedTree.location,334      transformedTree.name,335      transformedTree.isGenerator,336      transformedTree.formalParameterList,337      createBlock(statements)338    );339  }340  transformSetAccessor(tree) {341    stack.push([]);342    var transformedTree = super.transformSetAccessor(tree);343    var statements = stack.pop();344    if (!statements.length) return transformedTree;345    // Prepend the var statements to the block.346    statements.push(...transformedTree.body.statements);347    return new SetAccessor(348      transformedTree.location,349      transformedTree.isStatic,350      transformedTree.name,351      transformedTree.parameter,352      createBlock(statements)353    );354  }355  transformBindingElement(tree) {356    // If this has an initializer the default parameter transformer moves the357    // pattern into the function body and it will be taken care of by the358    // variable pass.359    if (!tree.binding.isPattern() || tree.initializer) return tree;360    // function f(pattern) { }361    //362    // =>363    //364    // function f($tmp) {365    //   var pattern = $tmp;366    // }367    var statements = stack[stack.length - 1];368    var binding = this.desugarBinding_(tree.binding, statements, VAR);369    return new BindingElement(null, binding, null);370  }371  transformCatch(tree) {372    if (!tree.binding.isPattern()) return super.transformCatch(tree);373    // catch(pattern) {374    //375    // =>376    //377    // catch ($tmp) {378    //   let pattern = $tmp379    var body = this.transformAny(tree.catchBody);380    var statements = [];381    var kind = options.blockBinding ? LET : VAR;382    var binding = this.desugarBinding_(tree.binding, statements, kind);383    statements.push(...body.statements);384    return new Catch(tree.location, binding, createBlock(statements));385  }386  /**387   * Helper for transformations that transforms a binding to a temp binding388   * as well as a statement added into a block. For example, this is used by389   * function, for-in/of and catch.390   * @param  {ParseTree} bindingTree The tree with the binding pattern.391   * @param  {Array} statements Array that we add the assignment/variable392   *     declaration to.393   * @param {TokenType?} declarationType The kind of variable declaration to394   *     generate or null if an assignment expression is to be used.395   * @return {BindingIdentifier} The binding tree.396   */397  desugarBinding_(bindingTree, statements, declarationType) {398    var varName = this.getTempIdentifier();399    var binding = createBindingIdentifier(varName);400    var idExpr = createIdentifierExpression(varName);401    var desugaring;402    if (declarationType === null)403      desugaring = new AssignmentExpressionDesugaring(idExpr);404    else desugaring = new VariableDeclarationDesugaring(idExpr);405    this.desugarPattern_(desugaring, bindingTree);406    if (declarationType === null) {407      statements.push(408        createExpressionStatement(createCommaExpression(desugaring.expressions))409      );410    } else {411      statements.push(412        createVariableStatement(413          // Desugar more.414          this.transformVariableDeclarationList(415            createVariableDeclarationList(416              declarationType,417              desugaring.declarations418            )419          )420        )421      );422    }423    return binding;424  }425  /**426   * @param {VariableDeclarationList} tree427   * @return {boolean}428   */429  destructuringInDeclaration_(tree) {430    return tree.declarations.some((declaration) =>431      declaration.lvalue.isPattern()432    );433  }434  /**435   * @param {VariableDeclaration} tree436   * @return {Array.<VariableDeclaration>}437   */438  desugarVariableDeclaration_(tree) {439    var tempRValueName = this.getTempIdentifier();440    var tempRValueIdent = createIdentifierExpression(tempRValueName);441    var desugaring;442    var initializer;443    // Don't use parens for these cases:444    // - tree.initializer is assigned to a temporary.445    // - tree.initializer normally doesn't need parens for member access.446    // Don't use temporary if:447    // - there is only one value to assign (and no initializer).448    switch (tree.initializer.type) {449      // Paren not necessary.450      case ARRAY_LITERAL_EXPRESSION:451      case CALL_EXPRESSION:452      case IDENTIFIER_EXPRESSION:453      case LITERAL_EXPRESSION:454      case MEMBER_EXPRESSION:455      case MEMBER_LOOKUP_EXPRESSION:456      case OBJECT_LITERAL_EXPRESSION:457      case PAREN_EXPRESSION:458        initializer = tree.initializer;459      // Paren necessary for single value case.460      default:461        // [1] Try first using a temporary (used later as the base rvalue).462        desugaring = new VariableDeclarationDesugaring(tempRValueIdent);463        desugaring.assign(desugaring.rvalue, tree.initializer);464        var initializerFound = this.desugarPattern_(desugaring, tree.lvalue);465        // [2] Was the temporary necessary? Then return.466        if (initializerFound || desugaring.declarations.length > 2)467          return desugaring.declarations;468        initializer = initializer || createParenExpression(tree.initializer);469        // [3] Redo everything without the temporary.470        desugaring = new VariableDeclarationDesugaring(initializer);471        this.desugarPattern_(desugaring, tree.lvalue);472        return desugaring.declarations;473    }474  }475  /**476   * @param {Desugaring} desugaring477   * @param {ParseTree} tree478   * @return {boolean} True if any of the patterns have an initializer.479   */480  desugarPattern_(desugaring, tree) {481    var initializerFound = false;482    switch (tree.type) {483      case ARRAY_PATTERN: {484        var pattern = tree;485        for (var i = 0; i < pattern.elements.length; i++) {486          var lvalue = pattern.elements[i];487          if (lvalue === null) {488            // A skip, for example [a,,c]489            continue;490          } else if (lvalue.isSpreadPatternElement()) {491            // Rest of the array, for example [x, ...y] = [1, 2, 3]492            desugaring.assign(493              lvalue.lvalue,494              createCallExpression(495                createMemberExpression(ARRAY, PROTOTYPE, SLICE, CALL),496                createArgumentList(desugaring.rvalue, createNumberLiteral(i))497              )498            );499          } else {500            if (lvalue.initializer) initializerFound = true;501            desugaring.assign(502              lvalue,503              createConditionalMemberLookupExpression(504                desugaring.rvalue,505                createNumberLiteral(i),506                lvalue.initializer507              )508            );509          }510        }511        break;512      }513      case OBJECT_PATTERN: {514        var pattern = tree;515        pattern.fields.forEach((field) => {516          var lookup;517          switch (field.type) {518            case BINDING_ELEMENT:519              if (field.initializer) initializerFound = true;520              lookup = createConditionalMemberExpression(521                desugaring.rvalue,522                field.binding.identifierToken,523                field.initializer524              );525              desugaring.assign(526                createIdentifierExpression(field.binding),527                lookup528              );529              break;530            case OBJECT_PATTERN_FIELD:531              if (field.element.initializer) initializerFound = true;532              lookup = createConditionalMemberExpression(533                desugaring.rvalue,534                field.identifier,535                field.element.initializer536              );537              desugaring.assign(field.element, lookup);538              break;539            case IDENTIFIER_EXPRESSION:540              lookup = createMemberExpression(541                desugaring.rvalue,542                field.identifierToken543              );544              desugaring.assign(field, lookup);545              break;546            default:547              throw Error("unreachable");548          }549        });550        break;551      }552      case PAREN_EXPRESSION:553        return this.desugarPattern_(desugaring, tree.expression);554      default:555        throw new Error("unreachable");556    }557    return initializerFound;558  }559  /**560   * @param {UniqueIdentifierGenerator} identifierGenerator561   * @param {ParseTree} tree562   * @return {ParseTree}563   */564  static transformTree(identifierGenerator, tree) {565    return new DestructuringTransformer(identifierGenerator).transformAny(tree);566  }...parse.js
Source:parse.js  
1import {2    cat,3    oneOf,4    opt,5    match,6    map,7    many,8    not,9    EOFError,10} from './comb';11import {12    NullToken,13    BoolToken,14    NumberToken,15    StringToken,16    IdentToken,17    InfixToken,18    BracketsToken,19    BracesToken,20    ParensToken,21    DelimToken,22    WhitespaceToken,23    SpaceToken,24    BreakToken,25    IndentToken,26} from './lex';27import { OP_PREC } from './shared';28class TokenCursor {29    constructor (tokenStream, ctx) {30        this.tokens = tokenStream;31        this.pos = [0];32        this.proxy = null;33        this.errors = [];34        this.ctx = ctx;35        this.prevTok = null;36    }37    peek () {38        if (this.eof()) throw new EOFError(`unexpected stream end`);39        let t = { contents: this.tokens };40        for (const p of this.pos) {41            t = t.contents[p];42        }43        return t;44    }45    span () {46        if (this.eof()) return this.prevTok.span;47        return this.peek().span;48    }49    next () {50        const t = this.peek();51        this.pos[this.pos.length - 1]++;52        this.errors = [];53        this.prevTok = t;54        return t;55    }56    enter () {57        const t = this.peek();58        if (!Array.isArray(t.contents)) this.throw(`cannot enter token without contents`);59        this.pos.push(0);60    }61    exitAssertingEnd () {62        if (!this.eof()) this.throw(`attempt to exit token without reading all contents`);63        this.pos.pop();64    }65    eof () {66        const pos = [...this.pos];67        const lastPos = pos.pop();68        let t = { contents: this.tokens };69        for (const p of pos) {70            t = t.contents[p];71        }72        return t.contents.length === lastPos;73    }74    topLevelEof () {75        return this.pos.length === 1 && this.eof();76    }77    clone () {78        const tc = new TokenCursor(this.tokens, this.ctx);79        tc.pos = [...this.pos];80        tc.errors = this.errors;81        return tc;82    }83    copyFrom (tc) {84        this.tokens = tc.tokens;85        this.ctx = tc.ctx;86        this.pos = [...tc.pos];87        this.errors = tc.errors;88    }89    addErrorToCurrentPos (err) {90        this.errors.push(err);91    }92    throw (err) {93        if (typeof err === 'string') throw new ParseError(err, this.clone());94        else throw err;95    }96    getCurrentError (fallback = 'unknown error') {97        if (this.errors.length) {98            return new ParseError(this.errors, this.clone());99        }100        return new ParseError(fallback, this.clone());101    }102}103class ParseError {104    constructor (msgOrErrs, state = null) {105        this.contents = msgOrErrs;106        this.state = state;107    }108    get nextFewTokens () {109        const s = this.state.clone();110        const tokens = [];111        for (let i = 0; i < 10; i++) {112            if (s.eof()) break;113            tokens.push(s.next());114        }115        return tokens;116    }117    get _debug__stringified () {118        return this.toString();119    }120    toString () {121        if (typeof this.contents === 'string') {122            return this.contents;123        } else {124            return this.contents.map(x => x.toString()).join('\n');125        }126    }127    valueOf () {128        return `[ParseError ${this.toString()}]`;129    }130    getSpan () {131        if (!this.state) return null;132        return this.state.span();133    }134}135const group = (gclass, inner) => tok => {136    const node = tok.peek();137    if (!(node instanceof gclass)) tok.throw(`unexpected ${node}, expected ${gclass.name}`);138    tok.enter();139    const i = inner(tok);140    tok.exitAssertingEnd();141    tok.next();142    return i;143};144const ctxify = (inner) => tok => {145    const res = inner(tok);146    res.ctx = tok.ctx;147    return res;148};149const nbws = many(match(x => x instanceof SpaceToken, 'non-breaking whitespace'));150const anyws = many(match(x => x instanceof WhitespaceToken, 'whitespace'));151const bws = tok => {152    const r = anyws(tok);153    for (const x of r) if (x instanceof BreakToken) return null;154    tok.throw('expected line break');155};156const tnull = ctxify(map(match(x => x instanceof NullToken, 'null'), () => ({ type: 'u' })));157const tnumber = ctxify(map(match(x => x instanceof NumberToken, 'number'), x => ({ type: 'n', value: parseFloat(x.int + '.' + (x.frac || '0'), 10) })));158const tbool = ctxify(map(match(x => x instanceof BoolToken, 'bool'), x => ({ type: 'b', value: x.value })));159const tstring = ctxify(map(match(x => x instanceof StringToken, 'string'), x => ({ type: 's', value: x.contents })));160const primitive = oneOf(tnull, tbool, tnumber, tstring);161const delim = match(x => x instanceof DelimToken, 'delimiter');162const callArgsInner = map(cat(163    many(map(cat(anyws, expr, anyws, delim), x => x[1])),164    anyws,165    opt(expr),166    anyws,167), ([a,, b]) => a.concat(b));168const callArgs = map(cat(nbws, group(ParensToken, callArgsInner)), a => a[1]);169const callExpr = ctxify(map(cat(170    match(x => x instanceof IdentToken && !(x instanceof InfixToken), 'callee identifier'),171    opt(callArgs),172), ([a, c]) => {173    if (c.length) {174        const ex = { type: 'c', func: { type: 'r', name: a.ident }, args: c[0] };175        ex.func.parent = ex;176        for (const arg of c[0]) arg.parent = ex;177        return ex;178    } else {179        return { type: 'r', name: a.ident };180    }181}));182const IS_INFIX = Symbol();183const IS_INFIX_OP = Symbol();184const groupExpr = map(185    group(ParensToken, cat(anyws, expr, anyws)),186    a => {187        const ex = a[1];188        if (!ex) return ex;189        delete ex[IS_INFIX]; // mark this non-infix so fixPrec doesn't mess it up190        return ex;191    },192);193const matrixInner = map(cat(194    many(map(cat(anyws, expr, anyws, delim), a => a[1])),195    opt(map(cat(anyws, expr), a => a[1])),196    anyws,197), ([a, b]) => a.concat(b));198const matrixExpr = ctxify(map(group(BracketsToken, matrixInner), items => {199    const MATRIX_TYPES = 'ubnsm';200    let isPure = true;201    for (const item of items) {202        if (!MATRIX_TYPES.includes(item.type)) {203            isPure = false;204        }205    }206    if (isPure) return { type: 'm', value: items.map(item => item.value) };207    else {208        const ex = { type: 'l', items };209        for (const item of items) item.parent = ex;210        return ex;211    }212}));213const arrow = match(x => x instanceof InfixToken && x.ident === '->', '->');214const fatArrow = match(x => x instanceof InfixToken && x.ident === '=>', '=>');215const equals = match(x => x instanceof InfixToken && x.ident === '=', '=');216const switchIdent = match(x => x instanceof IdentToken && !x.isRaw && x.ident === 'switch', 'switch keyword');217const wildcardSwitchKey = match(x => x instanceof IdentToken && !x.isRaw && x.ident === 'otherwise', 'otherwise');218const notLastSwitchCase = not(wildcardSwitchKey, expr, 'wildcard case');219const undelimSwitchCase = map(220    cat(anyws, notLastSwitchCase, anyws, fatArrow, anyws, expr),221    ([, a,,,, e]) => ({ cond: a, value: e }),222);223const switchCaseDelim = oneOf(bws, cat(nbws, delim, anyws));224const delimSwitchCase = map(cat(undelimSwitchCase, switchCaseDelim), a => a[0]);225const wildcardSwitchCase = map(226    cat(anyws, wildcardSwitchKey, anyws, expr),227    ([,,, e]) => ({ cond: null, value: e }),228);229const lastSwitchCase = oneOf(230    wildcardSwitchCase,231    undelimSwitchCase,232);233const switchCases = map(cat(234    many(delimSwitchCase),235    opt(lastSwitchCase),236    anyws, opt(delim), anyws,237), ([a, b]) => a.concat(b));238const switchContents = oneOf(239    group(IndentToken, switchCases),240    map(cat(anyws, group(BracesToken, switchCases)), a => a[1]),241    map(cat(nbws, lastSwitchCase), ([, c]) => [c]),242);243const switchExpr = ctxify(map(cat(switchIdent, switchContents), ([, m]) => {244    const ex = { type: 'w', matches: m };245    for (const { cond, value } of m) {246        if (cond) cond.parent = ex;247        value.parent = ex;248    }249    return ex;250}));251const closureArg = map(match(x => x instanceof IdentToken && !(x instanceof InfixToken), 'argument name'), x => x.ident);252const closureArgsInner = map(cat(253    many(map(cat(anyws, closureArg, anyws, delim), ([, a]) => a)),254    opt(map(cat(anyws, closureArg), ([, a]) => a)),255    anyws,256), ([a, b]) => a.concat(b));257const closureArgs = oneOf(map(closureArg, arg => [arg]), group(ParensToken, closureArgsInner));258const closureWhereKey = match(x => x instanceof IdentToken && !x.isRaw && x.ident === 'where', 'where keyword');259const closureWhereInner = oneOf(260    group(IndentToken, program),261    map(cat(anyws, group(BracesToken, program)), a => a[1]),262    ctxify(map(cat(nbws, definition), a => {263        const defs = {264            type: 'd',265            defs: new Set([a[1]]),266            floatingExpr: new Set(),267        };268        a[1].parent = defs;269        return defs;270    })),271);272const closureWhere = map(273    opt(map(cat(anyws, closureWhereKey, closureWhereInner), a => a[2])),274    a => a[0],275);276const closureBody = map(cat(expr, closureWhere), ([e, w], tok) => {277    const body = {278        ctx: tok.ctx,279        type: 'd',280        defs: new Set(),281        floatingExpr: new Set(),282    };283    body.defs.add({284        ctx: tok.ctx,285        type: 'ds',286        name: '=',287        expr: e,288    });289    if (w) for (const d of w.defs) body.defs.add(d);290    for (const d of body.defs) d.parent = body;291    return body;292});293const closureExpr = ctxify(map(cat(closureArgs, nbws, arrow, anyws, closureBody), ([p,,,, b]) => ({294    type: 'f',295    params: p,296    body: b,297})));298const minus = match(x => x instanceof InfixToken && x.ident === '-', 'minus sign');299const unaryMinusExpr = ctxify(map(cat(minus, nbws, nonInfixExpr), ([,, e]) => {300    const ex = {301        type: 'c',302        func: { type: 'r', name: '-' },303        args: [{ type: 'n', value: 0 }, e],304    };305    e.parent = ex;306    ex.func.parent = ex;307    return ex;308}));309const _nonInfixExpr = oneOf(310    unaryMinusExpr,311    primitive,312    matrixExpr,313    switchExpr,314    closureExpr,315    groupExpr,316    callExpr,317);318function nonInfixExpr (tok) { // for hoisting319    return _nonInfixExpr(tok);320}321const isInfixOp = x => x instanceof InfixToken && x.ident !== '=' && x.ident !== '->' && x.ident !== '=>';322const mkInfix = a => {323    Object.defineProperty(a, IS_INFIX, {324        value: true,325        enumerable: false,326        configurable: true,327    });328    return a;329};330const mkInfixOp = a => {331    Object.defineProperty(a, IS_INFIX_OP, {332        value: true,333        enumerable: false,334        configurable: true,335    });336    return a;337};338const infixExpr = ctxify(map(339    cat(nonInfixExpr, anyws, match(isInfixOp, 'infix operator'), anyws, expr),340    ([a,, o,, b]) => {341        const iex = mkInfix({342            type: 'c',343            func: mkInfixOp({ type: 'r', name: o.ident }),344            args: [a, b],345            [IS_INFIX]: true,346        });347        iex.func.parent = iex;348        a.parent = iex;349        b.parent = iex;350        return iex;351    },352));353const KNOWN_PREC_OPS = OP_PREC.flatMap(x => x);354function fixPrec (infixExpr) {355    return tok => {356        const expr = infixExpr(tok);357        const parts = [];358        const additionalOps = [];359        const flatten = e => {360            if (e[IS_INFIX]) {361                flatten(e.args[0]);362                parts.push(e.func);363                if (!KNOWN_PREC_OPS.includes(e.func.name)) additionalOps.push(e.func.name);364                flatten(e.args[1]);365            } else parts.push(e);366        };367        flatten(expr);368        const precLevels = OP_PREC.concat([additionalOps]).reverse();369        for (const ops of precLevels) {370            let i = 0;371            while (i < parts.length) {372                const part = parts[i];373                if (part[IS_INFIX_OP] && ops.includes(part.name)) {374                    const pLeft = parts[i - 1];375                    const pRight = parts[i + 1];376                    if (!pLeft || !pRight) tok.throw(`error during precedence sort: lonely operator`);377                    i--;378                    const iex = mkInfix({379                        ctx: tok.ctx,380                        type: 'c',381                        func: part,382                        args: [pLeft, pRight],383                    });384                    pLeft.parent = iex;385                    pRight.parent = iex;386                    parts.splice(i, 3,iex);387                }388                i++;389            }390        }391        if (parts.length !== 1) tok.throw(`error during precedence sort: incomplete reduction`);392        return parts[0];393    };394}395const _expr = oneOf(396    fixPrec(infixExpr),397    nonInfixExpr,398);399function expr (tok) { // for hoisting400    return _expr(tok);401}402const defName = match(x => x instanceof IdentToken, 'definition name');403const _definition = ctxify(map(cat(defName, anyws, equals, anyws, expr), ([n,,,, e]) => {404    const def = {405        type: 'ds',406        name: n.ident,407        expr: e,408    };409    e.parent = def;410    return def;411}));412function definition (tok) {413    return _definition(tok);414}415const _program = map(416    cat(anyws, many(map(cat(definition, bws), ([a]) => a)), opt(definition), anyws),417    ([, a, b], tok) => {418        const defs = new Set();419        const out = { ctx: tok.ctx, type: 'd', defs, floatingExpr: new Set() };420        for (const d of a.concat(b)) {421            defs.add(d);422            d.parent = out;423        }424        return out;425    },426);427function program (tok) { // for hoisting428    return _program(tok);429}430export function parse (tokenStream, ctx) {431    const cursor = new TokenCursor(tokenStream, ctx);432    const defs = program(cursor);433    if (!cursor.topLevelEof()) {434        throw cursor.getCurrentError();435    }436    return defs;...lex.js
Source:lex.js  
...216    const contents = takeUntil(tag(closeTag, 'raw ident close tag'))(str);217    for (let i = 0; i < closeTag.length; i++) str.next();218    return contents;219};220const rawIdent = spanned(map(cat(tag('r#', 'raw ident start tag'), rawIdentInner), (a) => new IdentToken(a[1], true)));221const bareIdent = spanned(map(regex(bareIdentRegex, 'bare ident'), match => new IdentToken(match[1])));222const ident = oneOf(rawIdent, bareIdent);223const infixIdent = spanned(map(regex(infixIdentRegex, 'infix ident'), match => new InfixToken(match[1])));224const number = spanned(map(regex(numberRegex, 'number'), match => new NumberToken(match[1], match[2] || '')));225const bool = spanned(map(oneOf(xtag('yes'), xtag('no'), xtag('true'), xtag('false')), token =>226    new BoolToken(token === 'yes' || token === 'true')));227const nul = spanned(map(xtag('null'), () => new NullToken()));228const ws = spanned(map(regex(/^(\s+)/, 'whitespace'), match => match[1].includes('\n') ? new BreakToken() : new SpaceToken()));229const string = spanned((str) => {230    if (str.next() !== '"') throw new LexError('expected " at beginning of string');231    let c;232    let contents = '';233    let escapeNext = false;234    while ((c = str.next())) {235        if (c === '\\') {236            escapeNext = true;237            continue;238        }239        if (!escapeNext && c === '"') {240            break;241        }242        escapeNext = false;243        contents += c;244    }245    return new StringToken(contents);246});247const treeBracket = spanned(map(wrap('[', ']', tokenStream, '[...]'), inner => new BracketsToken(inner)));248const treeBrace = spanned(map(wrap('{', '}', tokenStream, '{...}'), inner => new BracesToken(inner)));249const treeParens = spanned(map(wrap('(', ')', tokenStream, '(...)'), inner => new ParensToken(inner)));250const delim = spanned(map(tag(','), () => new DelimToken()));251const oneValueToken = oneOf(nul, bool, delim, number, string, ident, infixIdent, treeBracket, treeBrace, treeParens);252const nbws = spanned(map(regex(/^[ \t]+/, 'non-breaking whitespace'), () => new SpaceToken()));253const nbToken = oneOf(nbws, oneValueToken);254const treeIndent = spanned((str) => {255    // find next line break256    while (true) {257        const c = str.peek();258        if (c === '\n') break;259        else if (c.match(/\s/)) str.next();260        else throw new Error(`unexpected ${c}, expected breaking whitespace`);261    }262    const getLineIndentation = str => {263        if (str.eof()) return -1;264        let indent;265        outer:266        while (true) {267            indent = 0;268            while (true) {269                if (str.eof()) return -1;270                const c = str.peek();271                if (c === ' ') indent++;272                else if (c === '\t') indent += 4;273                else if (c === '\n') {274                    str.next();275                    continue outer; // skip empty lines276                }277                else break outer;278                str.next();279            }280        }281        return indent;282    };283    // we're now at the beginning of a line284    // find min indetation level285    const minIndent = getLineIndentation(str);286    const contents = [];287    // we're now at the beginning of the first line's contents288    let atSOL = false;289    while (true) {290        if (str.eof()) break;291        else if (str.peek() === '\n') {292            // line break!293            atSOL = true;294            contents.push(new BreakToken());295            str.next();296            continue;297        } else if (atSOL) {298            atSOL = false;299            // count indentation300            const s = str.clone();301            const currentIndent = getLineIndentation(s);302            if (currentIndent < minIndent) break; // end of block303            str.copyFrom(s); // otherwise continue304        }305        contents.push(...nbTreeToken(str));306    }307    return new IndentToken(contents);308});309const whereClauseKey = spanned(map(xtag('where'), () => new IdentToken('where')));310const switchClauseKey = spanned(map(xtag('switch'), () => new IdentToken('switch')));311// treeIndent will swallow trailing line breaks312const wsWhereClause = map(cat(whereClauseKey, treeIndent), ([a, b]) => [a, b, new BreakToken()]);313const wsSwitchClause = map(cat(switchClauseKey, treeIndent), ([a, b]) => [a, b, new BreakToken()]);314const indentClause = oneOf(315    wsWhereClause,316    wsSwitchClause,317);318const _nbTreeToken = oneOf(319    indentClause,320    map(nbToken, x => [x]),321);322function nbTreeToken (str) { // for hoisting323    return _nbTreeToken(str);324}...cst_visitor.js
Source:cst_visitor.js  
1const selectLexer = require("./lexer")2const parser = require("./parser")3const SelectParser = parser.SelectParser4const parserInstance = new SelectParser([], { outputCst: true })5const BaseSQLVisitor = parserInstance.getBaseCstVisitorConstructor()6class SQLtoAstVisitor extends BaseSQLVisitor {7    constructor() {8      super()9      this.validateVisitor()10    }11    selectStatement(ctx) {12      let select = this.visit(ctx.selectClause)13      let from = this.visit(ctx.fromClause)14      let where = this.visit(ctx.whereClause)15      return {16        type: "SELECT_STATEMENT",17        selectClause: select,18        fromClause: from,19        whereCLause: where20      }21    }22    selectClause(ctx) {23      const columns = ctx.Identifier.map(identToken => identToken.image)24      return {25        type: "SELECT_CLAUSE",26        columns: columns27      }28    }29    fromClause(ctx) {30      const tableName = ctx.Identifier[0].image31      return { type: "FROM_CLAUSE", table: tableName }32    }33    whereClause(ctx) {34      const condition = this.visit(ctx.expression)35      return { type: "WHERE_CLAUSE", condition: condition}36    }37    expression(ctx) {38      const lhs = this.visit(ctx.lhs[0])39      const op = this.visit(ctx.relationalOperator)40      const rhs = this.visit(ctx.rhs[0])41      return {42        type: "EXPRESSION",43        lhs: lhs,44        op: op,45        rhs: rhs46      }47    }48    atomicExpression(ctx) {49      if(ctx.Integer)50        return ctx.Integer[0].image51      else52        return ctx.Identifier[0].image53    }54    relationalOperator(ctx) {55      if(ctx.GreaterThan)56        return ctx.GreaterThan[0].image57      else58        return ctx.LessThan[0].image59    }60}61const toAstVisitorInstance = new SQLtoAstVisitor()62module.exports = {63  toAst: function(inputText) {64    const lexingResult = selectLexer.lexer(inputText)65    parserInstance.input = lexingResult.tokens66    const cst = parserInstance.selectStatement()67    console.log(JSON.stringify(parserInstance.errors, null, 2))68    if(parserInstance.errors.length > 0) {69      throw Error("Semantics Parsing errors detected\n" + parserInstance.errors[0].message)70    }71    const ast = toAstVisitorInstance.visit(cst)72    return ast73  }...step3-visitor.js
Source:step3-visitor.js  
1'use strict';2// Written Docs for this tutorial step can be found here:3// https://chevrotain.io/docs/tutorial/step3a_adding_actions_visitor.html4// Tutorial Step 3a:5// Adding Actions(semantics) to our grammar using a CST Visitor.6import sumLexer from './step1';7// re-using the parser implemented in step two.8import parser from './step2';9const SumParser = parser.SumParser;10// A new parser instance with CST output (enabled by default).11const parserInstance = new SumParser();12// The base visitor class can be accessed via the a parser instance.13const BaseSQLVisitor = parserInstance.getBaseCstVisitorConstructor();14class SQLToAstVisitor extends BaseSQLVisitor {15  constructor() {16    super();17    this.validateVisitor();18  }19  sumStatement(ctx) {20    // "this.visit" can be used to visit none-terminals and will invoke the correct visit method for the CstNode passed.21    const sum = this.visit(ctx.sumClause);22    return {23      type: 'SUM_STMT',24      sumClause: sum,25    };26  }27  sumClause(ctx) {28    // Each Terminal or Non-Terminal in a grammar rule are collected into29    // an array with the same name(key) in the ctx object.30    const columns = ctx.Identifier.map((identToken) => identToken.image);31    return {32      type: 'SUM_CLAUSE',33      columns: columns,34    };35  }36}37// Our visitor has no state, so a single instance is sufficient.38const toAstVisitorInstance = new SQLToAstVisitor();39const toAst = function (inputText) {40  const lexResult = sumLexer.lex(inputText);41  // ".input" is a setter which will reset the parser's internal's state.42  parserInstance.input = lexResult.tokens;43  // Automatic CST created when parsing44  const cst = parserInstance.sumStatement();45  if (parserInstance.errors.length > 0) {46    throw Error(47      'Sad sad panda, parsing errors detected!\n' +48        parserInstance.errors[0].message49    );50  }51  const ast = toAstVisitorInstance.visit(cst);52  return ast;53};...cd.js
Source:cd.js  
1i = require("./iSemanticAction")2module.exports = function(identToken)3{4    let cScope = i.symT.getScope()5    if(!cScope.includes(identToken.Lexeme))6    {7        i.throwError(identToken.linenum, `Constructor ${ident.Lexeme} must match class name ${cScope.split('.').pop()}`, "")8       // <line_number> ": Constructor " <lexeme> " must match class name " <lexeme>9    }...Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  const element = await page.$('input[name="q"]');7  await element.fill('test');8  const token = await element._internalId();9  console.log(token);10  await browser.close();11})();12const { chromium } = require('playwright');13(async () => {14  const browser = await chromium.launch();15  const context = await browser.newContext();16  const page = await context.newPage();17  const element = await page.$('input[name="q"]');18  await element.fill('test');19  const token = await element._internalId();20  console.log(token);21  await browser.close();22})();23const { chromium } = require('playwright');24(async () => {25  const browser = await chromium.launch();26  const context = await browser.newContext();27  const page = await context.newPage();28  const element = await page.$('input[name="q"]');29  await element.fill('test');30  const token = await element._internalId();31  console.log(token);32  await browser.close();33})();34const { chromium } = require('playwright');35(async () => {36  const browser = await chromium.launch();37  const context = await browser.newContext();38  const page = await context.newPage();39  const element = await page.$('input[name="q"]');40  await element.fill('test');41  const token = await element._internalId();42  console.log(token);43  await browser.close();44})();45const { chromium } = require('playwright');46(async () => {47  const browser = await chromium.launch();48  const context = await browser.newContext();49  const page = await context.newPage();50  const element = await page.$('input[name="q"]');51  await element.fill('test');Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch({ headless: false });4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.click('text=Sign in');7  await page.waitForSelector('input[type="email"]');8  const emailInput = await page.$('input[type="email"]');9  const emailInputId = await emailInput.evaluateHandle((node) => node.id);10  const emailInputIdToken = await emailInputId.evaluateHandle((node) => node.token);11  console.log(emailInputIdToken);12  await browser.close();13})();14await page.fill('input#'+emailInputIdToken, 'Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.fill('[name="q"]', 'Hello World!');7  await page.click('text=Google Search');8  await page.screenshot({ path: `example.png` });9  await browser.close();10})();11const { chromium } = require('playwright');12(async () => {13  const browser = await chromium.launch();14  const context = await browser.newContext();15  const page = await context.newPage();16  await page.fill('[name="q"]', 'Hello World!');17  await page.click('text=Google Search');18  await page.screenshot({ path: `example.png` });19  await browser.close();20})();21const { chromium } = require('playwright');22(async () => {23  const browser = await chromium.launch();24  const context = await browser.newContext();25  const page = await context.newPage();26  await page.fill('[name="q"]', 'Hello World!');27  await page.click('text=Google Search');28  await page.screenshot({ path: `example.png` });29  await browser.close();30})();31const { chromium } = require('playwright');32(async () => {33  const browser = await chromium.launch();34  const context = await browser.newContext();35  const page = await context.newPage();36  await page.fill('[name="q"]', 'Hello World!');37  await page.click('text=Google Search');38  await page.screenshot({ path: `example.png` });39  await browser.close();40})();41const { chromium } = require('playwright');42(async () => {43  const browser = await chromium.launch();44  const context = await browser.newContext();45  const page = await context.newPage();46  await page.fill('[name="q"]', 'Hello World!');47  await page.click('text=Google Search');Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.click('text=Get started');Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  console.log(response.status());7  await browser.close();8})();9const { chromium } = require('playwright');10(async () => {11  const browser = await chromium.launch();12  const context = await browser.newContext();13  const page = await context.newPage();14  console.log(response.status());15  await browser.close();16})();17const { chromium } = require('playwright');18(async () => {19  const browser = await chromium.launch();20  const context = await browser.newContext();21  const page = await context.newPage();22  console.log(response.status());23  await browser.close();24})();25const { chromium } = require('playwright');26(async () => {27  const browser = await chromium.launch();28  const context = await browser.newContext();29  const page = await context.newPage();30  console.log(response.status());31  await browser.close();32})();33const { chromium } = require('playwright');34(async () => {35  const browser = await chromium.launch();36  const context = await browser.newContext();37  const page = await context.newPage();38  console.log(response.status());39  await browser.close();40})();41const { chromium } = require('playwright');42(async () => {43  const browser = await chromium.launch();44  const context = await browser.newContext();45  const page = await context.newPage();46  console.log(response.status());47  await browser.close();48})();49const { chromium } = require('playwright');50(async () => {51  const browser = await chromium.launch();Using AI Code Generation
1const { chromium, webkit, firefox, devices } = require('playwright');2(async () => {3  const browser = await chromium.launch({ headless: false });4  const context = await browser.newContext();5  const page = await context.newPage();6  const element = await page.$('input[name="q"]');7  await element.identToken('hello');8  await browser.close();9})();Using AI Code Generation
1const playwright = require('playwright');2(async () => {3  const browser = await playwright.chromium.launch({ headless: false });4  const page = await browser.newPage();5  const token = await page.evaluate(() => {6    return window.playwright._internal.selectors._selectorEngine._identToken;7  });8  console.log(token);9  await browser.close();10})();11const playwright = require('playwright');12(async () => {13  const browser = await playwright.chromium.launch({ headless: false });14  const page = await browser.newPage();15  const token = await page.evaluate(() => {16    return window.playwright._internal.selectors._selectorEngine._identToken;17  });18  console.log(token);19  await browser.close();20})();21const playwright = require('playwright');22(async () => {23  const browser = await playwright.chromium.launch({ headless: false });24  const page = await browser.newPage();25  const token = await page.evaluate(() => {26    return window.playwright._internal.selectors._selectorEngine._identToken;27  });28  console.log(token);29  await browser.close();30})();31const playwright = require('playwright');32(async () => {33  const browser = await playwright.chromium.launch({ headless: false });34  const page = await browser.newPage();35  const token = await page.evaluate(() => {36    return window.playwright._internal.selectors._selectorEngine._identToken;37  });38  console.log(token);39  await browser.close();40})();41const playwright = require('playwright');42(async () => {43  const browser = await playwright.chromium.launch({ headless: false });44  const page = await browser.newPage();45  const token = await page.evaluate(() => {46    return window.playwright._internal.selectors._selectorEngine._identToken;Using AI Code Generation
1const {IdentToken} = require('playwright/lib/internal/identifiers');2const {IdentToken} = require('playwright/lib/internal/identifiers');3const {IdentToken} = require('playwright/lib/internal/identifiers');4const {IdentToken} = require('playwright/lib/internal/identifiers');5const {IdentToken} = require('playwright/lib/internal/identifiers');6const {IdentToken} = require('playwright/lib/internal/identifiers');7const {IdentToken} = require('playwright/lib/internal/identifiers');8const {IdentToken} = require('playwright/lib/internal/identifiers');9const {IdentToken} = require('playwright/lib/internal/identifiers');10const {IdentToken} = require('playwright/lib/internal/identifiers');11const {IdentToken} = require('playwright/lib/internal/identifiers');Using AI Code Generation
1const { InternalAPI } = require('playwright');2const token = await InternalAPI.identToken();3console.log(token);4const { InternalAPI } = require('playwright');5const token = await InternalAPI.identToken();6console.log(token);7const { InternalAPI } = require('playwright');8const token = await InternalAPI.identToken();9console.log(token);10const { InternalAPI } = require('playwright');11const token = await InternalAPI.identToken();12console.log(token);13const { InternalAPI } = require('playwright');14const token = await InternalAPI.identToken();15console.log(token);16const { InternalAPI } = require('playwright');17const token = await InternalAPI.identToken();18console.log(token);19const { InternalAPI } = require('playwright');20const token = await InternalAPI.identToken();21console.log(token);22const { InternalAPI } = require('playwright');23const token = await InternalAPI.identToken();24console.log(token);25const { InternalAPI } = require('playwright');26const token = await InternalAPI.identToken();27console.log(token);28const { InternalAPI } = require('playwright');29const token = await InternalAPI.identToken();30console.log(token);31const { InternalAPI } = require('playwright');32const token = await InternalAPI.identToken();33console.log(token);34const { InternalAPI } = require('playwright');35const token = await InternalAPI.identToken();36console.log(token);LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!
