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!!