├── .npmrc ├── .gitignore ├── .eslintignore ├── .babelrc.json ├── tests ├── fixtures │ ├── forLoop.js │ ├── bigArray.js │ ├── whileLoop.js │ ├── simpleProgram.js │ ├── simpleFunction.js │ ├── conditional.js │ ├── nestedFunctions.js │ ├── literal.js │ ├── switchStatement.js │ ├── unknownNodeTypeAST.js │ ├── conditionalLong.js │ ├── customNodes.js │ ├── customNodesWithKind.js │ └── allClasses.js ├── unknownNodeType.js ├── queryCompound.js ├── parser.js ├── queryDescendant.js ├── traverse.js ├── queryField.js ├── queryHas.js ├── queryComplex.js ├── queryNot.js ├── match.js ├── queryWildcard.js ├── queryMatches.js ├── queryType.js ├── queryClass.js ├── querySubject.js ├── matches.js ├── queryPseudoChild.js └── queryAttribute.js ├── .editorconfig ├── testRunner.html ├── .eslintrc.js ├── .github └── workflows │ └── NodeCI.yml ├── license.txt ├── rollup.config.js ├── README.md ├── package.json ├── grammar.pegjs ├── esquery.js └── parser.js /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.nyc_output 2 | /node_modules 3 | /coverage 4 | /dist 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | parser.js 4 | 5 | !.eslintrc.js 6 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ], 5 | "plugins": [ 6 | ["transform-es2017-object-entries"] 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/fixtures/forLoop.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse('for (i = 0; i < foo.length; i++) { foo[i](); }'); 4 | 5 | export default parsed; 6 | -------------------------------------------------------------------------------- /tests/fixtures/bigArray.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse( 4 | '[1, 2, 3, foo, bar, 4, 5, baz, qux, 6]' 5 | ); 6 | 7 | export default parsed; 8 | -------------------------------------------------------------------------------- /tests/fixtures/whileLoop.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | x = 10; 5 | while (x > 0) { x--; } 6 | `); 7 | 8 | export default parsed; 9 | -------------------------------------------------------------------------------- /tests/fixtures/simpleProgram.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | var x = 1; 5 | var y = 'y'; 6 | x = x * 2; 7 | if (y) { y += 'z'; } 8 | `); 9 | 10 | export default parsed; 11 | -------------------------------------------------------------------------------- /tests/fixtures/simpleFunction.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | function foo(x, y) { 5 | var z = x + y; 6 | z++; 7 | return z; 8 | } 9 | `); 10 | 11 | export default parsed; 12 | -------------------------------------------------------------------------------- /tests/fixtures/conditional.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | if (x === 1) { foo(); } else { x = 2; } 5 | if (x == 'test' && true || x) { y = -1; } else if (false) { y = 1; } 6 | `); 7 | 8 | export default parsed; 9 | -------------------------------------------------------------------------------- /tests/fixtures/nestedFunctions.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | function foo() { 5 | var x = 1; 6 | function bar() { 7 | x = 2; 8 | } 9 | } 10 | `); 11 | 12 | export default parsed; 13 | -------------------------------------------------------------------------------- /tests/fixtures/literal.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | var y = '\b\f\\n\\r\t\v and just a back\\slash'; 5 | var x = 21.35; 6 | var z = '\\z'; 7 | var a = 'abc\\z'; 8 | `); 9 | 10 | export default parsed; 11 | -------------------------------------------------------------------------------- /tests/fixtures/switchStatement.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | var x = 1; 5 | switch (x) { 6 | case 0: foo1(); break; 7 | case 1: foo2(); break; 8 | default: x = 1; break; 9 | } 10 | `); 11 | 12 | export default parsed; 13 | -------------------------------------------------------------------------------- /tests/unknownNodeType.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import AST from './fixtures/unknownNodeTypeAST.js'; 3 | 4 | describe('Unknown node type', function () { 5 | it('does not throw', function () { 6 | try { 7 | esquery(AST, '*'); 8 | } catch (e) { 9 | assert.fail(); 10 | } 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/fixtures/unknownNodeTypeAST.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This pretends (badly) to be a TypeAlias type node, which would normally be 3 | * generated by flow-parser from the following code: 4 | * 5 | * type aType = {}; 6 | * 7 | */ 8 | import * as esprima from 'esprima'; 9 | 10 | const program = esprima.parse('var x = \'s\''); 11 | program.body[0].type = 'TypeAlias'; 12 | 13 | export default program; 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [*.json] 15 | indent_size = 2 16 | 17 | [*.pegjs] 18 | indent_size = 2 19 | 20 | [*.yml] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /tests/fixtures/conditionalLong.js: -------------------------------------------------------------------------------- 1 | import * as esprima from 'esprima'; 2 | 3 | const parsed = esprima.parse(` 4 | if (x === 1) { foo(1); } 5 | if (x === 2) { foo(2); } 6 | if (x === 3) { foo(3); } 7 | if (x === 4) { foo(4); } 8 | if (x === 5) { foo(5); } 9 | if (x === 6) { foo(6); } 10 | if (x === 7) { foo(7); } 11 | if (x === 8) { foo(8); } 12 | if (x === 9) { foo(9); } 13 | if (x === 10) { foo(10); } 14 | if (x === 11) { foo(11); } 15 | `); 16 | 17 | export default parsed; 18 | -------------------------------------------------------------------------------- /tests/queryCompound.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | 4 | describe('Compound query', function () { 5 | 6 | it('two attributes', function () { 7 | const matches = esquery(conditional, '[left.name="x"][right.value=1]'); 8 | assert.includeMembers(matches, [ 9 | conditional.body[0].test 10 | ]); 11 | }); 12 | 13 | it('type and pseudo', function () { 14 | const matches = esquery(conditional, '[left.name="x"]:matches(*)'); 15 | assert.includeMembers(matches, [ 16 | conditional.body[0].test 17 | ]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/parser.js: -------------------------------------------------------------------------------- 1 | import esquery from "../esquery.js"; 2 | 3 | describe("basic query parsing", function () { 4 | 5 | it("empty query", function () { 6 | assert.equal(void 0, esquery.parse("")); 7 | assert.equal(void 0, esquery.parse(" ")); 8 | }); 9 | 10 | it("leading/trailing whitespace", function () { 11 | assert.notEqual(void 0, esquery.parse(" A")); 12 | assert.notEqual(void 0, esquery.parse(" A")); 13 | assert.notEqual(void 0, esquery.parse("A ")); 14 | assert.notEqual(void 0, esquery.parse("A ")); 15 | assert.notEqual(void 0, esquery.parse(" A ")); 16 | assert.notEqual(void 0, esquery.parse(" A ")); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/fixtures/customNodes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'CustomRoot', 3 | list: [ 4 | { 5 | type: 'CustomChild', 6 | name: 'one', 7 | sublist: [{ type: 'CustomGrandChild' }], 8 | }, 9 | { 10 | type: 'CustomChild', 11 | name: 'two', 12 | sublist: [], 13 | }, 14 | { 15 | type: 'CustomChild', 16 | name: 'three', 17 | sublist: [ 18 | { type: 'CustomGrandChild' }, 19 | { type: 'CustomGrandChild' }, 20 | ], 21 | }, 22 | { 23 | type: 'CustomChild', 24 | name: 'four', 25 | sublist: [ 26 | { type: 'CustomGrandChild' }, 27 | { type: 'CustomGrandChild' }, 28 | { type: 'CustomGrandChild' }, 29 | ], 30 | }, 31 | ], 32 | }; 33 | -------------------------------------------------------------------------------- /tests/queryDescendant.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | 4 | describe('Pseudo matches query', function () { 5 | 6 | it('conditional matches', function () { 7 | const matches = esquery(conditional, 'Program IfStatement'); 8 | assert.includeMembers(matches, [ 9 | conditional.body[0], 10 | conditional.body[1], 11 | conditional.body[1].alternate 12 | ]); 13 | }); 14 | 15 | it('#8: descendant selector includes ancestor in search', function() { 16 | let matches = esquery(conditional, 'Identifier[name=x]'); 17 | assert.equal(4, matches.length); 18 | matches = esquery(conditional, 'Identifier [name=x]'); 19 | assert.equal(0, matches.length); 20 | matches = esquery(conditional, 'BinaryExpression [name=x]'); 21 | assert.equal(2, matches.length); 22 | matches = esquery(conditional, 'AssignmentExpression [name=x]'); 23 | assert.equal(1, matches.length); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /tests/fixtures/customNodesWithKind.js: -------------------------------------------------------------------------------- 1 | export default { 2 | kind: 'CustomRoot', 3 | list: [ 4 | { 5 | kind: 'CustomChild', 6 | name: 'one', 7 | sublist: [{ kind: 'CustomGrandChild' }], 8 | }, 9 | { 10 | kind: 'CustomChild', 11 | name: 'two', 12 | sublist: [], 13 | }, 14 | { 15 | kind: 'CustomChild', 16 | name: 'three', 17 | sublist: [ 18 | { kind: 'CustomGrandChild' }, 19 | { kind: 'CustomGrandChild' }, 20 | ], 21 | }, 22 | { 23 | kind: 'CustomChild', 24 | name: 'four', 25 | sublist: [ 26 | { kind: 'CustomGrandChild' }, 27 | { kind: 'CustomGrandChild' }, 28 | { kind: 'CustomGrandChild' }, 29 | ], 30 | }, 31 | { 32 | kind: 'CustomExpression' 33 | }, 34 | { 35 | kind: 'CustomStatement' 36 | } 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /tests/traverse.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | 4 | describe('traverse', function () { 5 | it('iterates matches', function () { 6 | const matches = []; 7 | const parents = []; 8 | const ancestries = []; 9 | const selector = esquery.parse(':matches(IfStatement)'); 10 | esquery.traverse(conditional, selector, (match, parent, ancestry) => { 11 | parents.push(parent); 12 | matches.push(match); 13 | ancestries.push(ancestry.slice()); 14 | }); 15 | assert.deepEqual(matches, [ 16 | conditional.body[0], 17 | conditional.body[1], 18 | conditional.body[1].alternate 19 | ]); 20 | assert.deepEqual(parents, [ 21 | conditional, 22 | conditional, 23 | conditional.body[1] 24 | ]); 25 | assert.deepEqual(ancestries, [ 26 | [conditional], 27 | [conditional], 28 | [ 29 | conditional.body[1], 30 | conditional 31 | ] 32 | ]); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/queryField.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import simpleProgram from './fixtures/simpleProgram.js'; 4 | 5 | describe('Field query', function () { 6 | 7 | it('single field', function () { 8 | const matches = esquery(conditional, '.test'); 9 | assert.includeMembers(matches, [ 10 | conditional.body[0].test, 11 | conditional.body[1].test, 12 | conditional.body[1].alternate.test 13 | ]); 14 | }); 15 | 16 | it('field sequence', function () { 17 | const matches = esquery(simpleProgram, '.declarations.init'); 18 | assert.includeMembers(matches, [ 19 | simpleProgram.body[0].declarations[0].init, 20 | simpleProgram.body[1].declarations[0].init 21 | ]); 22 | }); 23 | 24 | it('field sequence (long)', function () { 25 | const matches = esquery(simpleProgram, '.body.declarations.init'); 26 | assert.includeMembers(matches, [ 27 | simpleProgram.body[0].declarations[0].init, 28 | simpleProgram.body[1].declarations[0].init 29 | ]); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /testRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Runner 5 | 6 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | env: { 4 | browser: true, 5 | commonjs: true, 6 | es6: true, 7 | node: true 8 | }, 9 | extends: 'eslint:recommended', 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly' 13 | }, 14 | overrides: [{ 15 | files: '.eslintrc.js', 16 | parserOptions: { 17 | sourceType: 'script' 18 | }, 19 | rules: { 20 | strict: 'error' 21 | } 22 | }, { 23 | files: 'tests/**', 24 | globals: { 25 | assert: true 26 | }, 27 | env: { 28 | mocha: true 29 | } 30 | }], 31 | parserOptions: { 32 | sourceType: 'module', 33 | ecmaVersion: 2018 34 | }, 35 | rules: { 36 | semi: ['error'], 37 | indent: ['error', 4, { SwitchCase: 1 }], 38 | 'prefer-const': ['error'], 39 | 'no-var': ['error'], 40 | 'prefer-destructuring': ['error'], 41 | 'object-shorthand': ['error'], 42 | 'object-curly-spacing': ['error', 'always'], 43 | quotes: ['error', 'single'], 44 | 'quote-props': ['error', 'as-needed'], 45 | 'brace-style': ['error', '1tbs', { allowSingleLine: true }], 46 | 'prefer-template': ['error'] 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /.github/workflows/NodeCI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Use Node.js 12 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: 12 18 | - name: Install Packages 19 | run: npm install 20 | - name: Lint 21 | run: npm run lint 22 | test: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | node-version: [10, 12, 18] 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Use Node.js ${{ matrix.node-version }} 30 | uses: actions/setup-node@v2 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | - name: Install Packages 34 | run: npm install 35 | - name: Build 36 | run: npm run build 37 | - name: Test 38 | run: npm run test:ci 39 | test-with-node8: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v2 43 | - name: Use Node.js 8 44 | uses: actions/setup-node@v2 45 | with: 46 | node-version: 8 47 | - name: Install Packages 48 | run: |+ 49 | npm install 50 | npm install --no-save "eslint@5" 51 | - name: Build 52 | run: npm run build 53 | - name: Test 54 | run: npm run test:ci 55 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Joel Feenstra 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the ESQuery nor the names of its contributors may 12 | be used to endorse or promote products derived from this software without 13 | specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /tests/queryHas.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | 4 | describe('Parent selector query', function () { 5 | 6 | it('conditional', function () { 7 | const matches = esquery(conditional, 'ExpressionStatement:has([name="foo"][type="Identifier"])'); 8 | assert.equal(1, matches.length); 9 | }); 10 | 11 | it('one of', function () { 12 | const matches = esquery(conditional, 'IfStatement:has(LogicalExpression [name="foo"], LogicalExpression [name="x"])'); 13 | assert.equal(1, matches.length); 14 | }); 15 | 16 | it('chaining', function () { 17 | const matches = esquery(conditional, 'BinaryExpression:has(Identifier[name="x"]):has(Literal[value="test"])'); 18 | assert.equal(1, matches.length); 19 | }); 20 | 21 | it('nesting', function () { 22 | const matches = esquery(conditional, 'Program:has(IfStatement:has(Literal[value=true], Literal[value=false]))'); 23 | assert.equal(1, matches.length); 24 | }); 25 | 26 | it('non-matching', function () { 27 | const matches = esquery(conditional, ':has([value="impossible"])'); 28 | assert.equal(0, matches.length); 29 | }); 30 | 31 | it('binary op', function () { 32 | const deepChildMatches = esquery(conditional, 'IfStatement:has(> Identifier[name="x"])'); 33 | assert.equal(0, deepChildMatches.length); 34 | 35 | const shallowChildMatches = esquery(conditional, 'IfStatement:has(> LogicalExpression.test, > Identifier[name="x"])'); 36 | assert.equal(1, shallowChildMatches.length); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/queryComplex.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import simpleProgram from './fixtures/simpleProgram.js'; 4 | 5 | describe('Complex selector query', function () { 6 | 7 | it('two types child', function () { 8 | const matches = esquery(conditional, 'IfStatement > BinaryExpression'); 9 | assert.includeMembers(matches, [ 10 | conditional.body[0].test 11 | ]); 12 | }); 13 | 14 | it('three types child', function () { 15 | const matches = esquery(conditional, 'IfStatement > BinaryExpression > Identifier'); 16 | assert.includeMembers(matches, [ 17 | conditional.body[0].test.left 18 | ]); 19 | }); 20 | 21 | it('two types descendant', function () { 22 | const matches = esquery(conditional, 'IfStatement BinaryExpression'); 23 | assert.includeMembers(matches, [ 24 | conditional.body[0].test 25 | ]); 26 | }); 27 | 28 | it('two types sibling', function () { 29 | const matches = esquery(simpleProgram, 'VariableDeclaration ~ IfStatement'); 30 | assert.includeMembers(matches, [ 31 | simpleProgram.body[3] 32 | ]); 33 | }); 34 | 35 | it('two types adjacent', function () { 36 | const matches = esquery(simpleProgram, 'VariableDeclaration + ExpressionStatement'); 37 | assert.includeMembers(matches, [ 38 | simpleProgram.body[2] 39 | ]); 40 | }); 41 | 42 | it('can not match a top level node', function () { 43 | // Test fix for issue #135: half of a child selector matches a top-level node. 44 | const matches = esquery(simpleProgram, 'NonExistingNodeType > *'); 45 | assert.isEmpty(matches); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/queryNot.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import forLoop from './fixtures/forLoop.js'; 4 | import simpleFunction from './fixtures/simpleFunction.js'; 5 | import simpleProgram from './fixtures/simpleProgram.js'; 6 | 7 | describe('Pseudo matches query', function () { 8 | 9 | it('conditional', function () { 10 | const matches = esquery(conditional, ':not(Literal)'); 11 | assert.equal(28, matches.length); 12 | }); 13 | 14 | it('for loop', function () { 15 | const matches = esquery(forLoop, ':not([name="x"])'); 16 | assert.equal(18, matches.length); 17 | }); 18 | 19 | it('simple function', function () { 20 | const matches = esquery(simpleFunction, ':not(*)'); 21 | assert.equal(0, matches.length); 22 | }); 23 | 24 | it('simple program', function () { 25 | const matches = esquery(simpleProgram, ':not(Identifier, IfStatement)'); 26 | assert.equal(15, matches.length); 27 | }); 28 | 29 | it('small program', function () { 30 | const program = { 31 | type: 'Program', 32 | body: [{ 33 | type: 'VariableDeclaration', 34 | declarations: [{ 35 | type: 'VariableDeclarator', 36 | id: { type: 'Identifier', name: 'x' }, 37 | init: { type: 'Literal', value: 1, raw: '1' } 38 | }], 39 | kind: 'var' 40 | }] 41 | }; 42 | const matches = esquery(program, ':not([value=1])'); 43 | 44 | assert.includeMembers(matches, [ 45 | program, 46 | program.body[0], 47 | program.body[0].declarations[0], 48 | program.body[0].declarations[0].id 49 | ]); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/match.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import forLoop from './fixtures/forLoop.js'; 3 | import ast from './fixtures/allClasses.js'; 4 | 5 | describe('match', function () { 6 | 7 | it('unknown selector type', function () { 8 | assert.throws(function () { 9 | esquery.match(forLoop, { 10 | type: 'badType' 11 | }); 12 | }, Error); 13 | }); 14 | 15 | it('unknown selector type', function () { 16 | assert.throws(function () { 17 | esquery.match(forLoop, { 18 | type: 'class', 19 | name: 'badName', 20 | value: { type: 'foobar' } }); 21 | }, Error); 22 | }); 23 | 24 | it('unknown class name', function () { 25 | assert.throws(function () { 26 | esquery.match(ast, { 27 | type: 'class', 28 | name: 'badName', 29 | value: { type: 'foobar' } }); 30 | }, Error); 31 | }); 32 | 33 | it('unknown type', function () { 34 | assert.throws(function () { 35 | esquery.match(forLoop, { 36 | type: 'attribute', 37 | name: 'foo', 38 | operator: '=', 39 | value: { type: 'foobar' } }); 40 | }, Error); 41 | 42 | assert.throws(function () { 43 | esquery.match(forLoop, { 44 | type: 'attribute', 45 | name: 'foo', 46 | operator: '!=', 47 | value: { type: 'foobar' } }); 48 | }, Error); 49 | }); 50 | 51 | it('unknown operator', function () { 52 | assert.throws(function () { 53 | esquery.match(forLoop, { 54 | type: 'attribute', 55 | name: 'foo', 56 | operator: 'badOperator', 57 | value: { type: 'foobar' } }); 58 | }, Error); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/queryWildcard.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import forLoop from './fixtures/forLoop.js'; 4 | import simpleFunction from './fixtures/simpleFunction.js'; 5 | import simpleProgram from './fixtures/simpleProgram.js'; 6 | 7 | describe('Wildcard query', function () { 8 | 9 | it('empty', function () { 10 | const matches = esquery(conditional, ''); 11 | assert.equal(0, matches.length); 12 | }); 13 | 14 | it('conditional', function () { 15 | const matches = esquery(conditional, '*'); 16 | assert.equal(35, matches.length); 17 | }); 18 | 19 | it('for loop', function () { 20 | const matches = esquery(forLoop, '*'); 21 | assert.equal(18, matches.length); 22 | }); 23 | 24 | it('simple function', function () { 25 | const matches = esquery(simpleFunction, '*'); 26 | assert.equal(17, matches.length); 27 | }); 28 | 29 | it('simple program', function () { 30 | const matches = esquery(simpleProgram, '*'); 31 | assert.equal(22, matches.length); 32 | }); 33 | 34 | it('small program', function () { 35 | const program = { 36 | type: 'Program', 37 | body: [{ 38 | type: 'VariableDeclaration', 39 | declarations: [{ 40 | type: 'VariableDeclarator', 41 | id: { type: 'Identifier', name: 'x' }, 42 | init: { type: 'Literal', value: 1, raw: '1' } 43 | }], 44 | kind: 'var' 45 | }] 46 | }; 47 | const matches = esquery(program, '*'); 48 | 49 | assert.includeMembers(matches, [ 50 | program, 51 | program.body[0], 52 | program.body[0].declarations[0], 53 | program.body[0].declarations[0].id, 54 | program.body[0].declarations[0].init 55 | ]); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { terser } from 'rollup-plugin-terser'; 2 | 3 | import nodeResolve from '@rollup/plugin-node-resolve'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import json from '@rollup/plugin-json'; 6 | 7 | import babel from 'rollup-plugin-babel'; 8 | import packageJson from './package.json'; 9 | 10 | /** 11 | * @external RollupConfig 12 | * @type {PlainObject} 13 | * @see {@link https://rollupjs.org/guide/en#big-list-of-options} 14 | */ 15 | 16 | /** 17 | * @param {PlainObject} [config= {}] 18 | * @param {boolean} [config.minifying=false] 19 | * @param {string} [config.format='umd'] 20 | * @param {boolean} [config.lite=false] 21 | * @returns {external:RollupConfig} 22 | */ 23 | function getRollupObject ({ minifying = false, format = 'umd', lite = false } = {}) { 24 | const nonMinified = { 25 | input: 'esquery.js', 26 | output: { 27 | format, 28 | sourcemap: minifying, 29 | file: [ 30 | 'dist/esquery', 31 | lite ? '.lite' : '', 32 | format === 'umd' ? '' : `.${format}`, 33 | minifying ? '.min' : '', 34 | '.js' 35 | ].join(''), 36 | name: 'esquery', 37 | globals: { 38 | estraverse: 'estraverse' 39 | } 40 | }, 41 | plugins: [ 42 | json(), 43 | nodeResolve(), 44 | commonjs(), 45 | babel() 46 | ] 47 | }; 48 | if (lite) { 49 | nonMinified.external = Object.keys(packageJson.dependencies); 50 | } 51 | if (minifying) { 52 | nonMinified.plugins.push(terser()); 53 | } 54 | return nonMinified; 55 | } 56 | 57 | export default [ 58 | getRollupObject({ minifying: true, format: 'umd' }), 59 | getRollupObject({ minifying: false, format: 'umd' }), 60 | getRollupObject({ minifying: true, format: 'esm' }), 61 | getRollupObject({ minifying: false, format: 'esm' }), 62 | getRollupObject({ minifying: true, format: 'umd', lite: true }), 63 | getRollupObject({ minifying: false, format: 'umd', lite: true }) 64 | ]; 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ESQuery is a library for querying the AST output by Esprima for patterns of syntax using a CSS style selector system. Check out the demo: 2 | 3 | [demo](https://estools.github.io/esquery/) 4 | 5 | The following selectors are supported: 6 | * AST node type: `ForStatement` 7 | * [wildcard](http://dev.w3.org/csswg/selectors4/#universal-selector): `*` 8 | * [attribute existence](http://dev.w3.org/csswg/selectors4/#attribute-selectors): `[attr]` 9 | * [attribute value](http://dev.w3.org/csswg/selectors4/#attribute-selectors): `[attr="foo"]` or `[attr=123]` 10 | * attribute regex: `[attr=/foo.*/]` or (with flags) `[attr=/foo.*/is]` 11 | * attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` 12 | * nested attribute: `[attr.level2="foo"]` 13 | * field: `FunctionDeclaration > Identifier.id` 14 | * [First](http://dev.w3.org/csswg/selectors4/#the-first-child-pseudo) or [last](http://dev.w3.org/csswg/selectors4/#the-last-child-pseudo) child: `:first-child` or `:last-child` 15 | * [nth-child](http://dev.w3.org/csswg/selectors4/#the-nth-child-pseudo) (no ax+b support): `:nth-child(2)` 16 | * [nth-last-child](http://dev.w3.org/csswg/selectors4/#the-nth-last-child-pseudo) (no ax+b support): `:nth-last-child(1)` 17 | * [descendant](http://dev.w3.org/csswg/selectors4/#descendant-combinators): `ancestor descendant` 18 | * [child](http://dev.w3.org/csswg/selectors4/#child-combinators): `parent > child` 19 | * [following sibling](http://dev.w3.org/csswg/selectors4/#general-sibling-combinators): `node ~ sibling` 20 | * [adjacent sibling](http://dev.w3.org/csswg/selectors4/#adjacent-sibling-combinators): `node + adjacent` 21 | * [negation](http://dev.w3.org/csswg/selectors4/#negation-pseudo): `:not(ForStatement)` 22 | * [has](https://drafts.csswg.org/selectors-4/#has-pseudo): `:has(ForStatement)`, `:has(> ForStatement)` 23 | * [matches-any](http://dev.w3.org/csswg/selectors4/#matches): `:is([attr] > :first-child, :last-child)` 24 | * [subject indicator](http://dev.w3.org/csswg/selectors4/#subject): `!IfStatement > [name="foo"]` 25 | * class of AST node: `:statement`, `:expression`, `:declaration`, `:function`, or `:pattern` 26 | 27 | [![Build Status](https://travis-ci.org/estools/esquery.png?branch=master)](https://travis-ci.org/estools/esquery) 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esquery", 3 | "version": "1.6.0", 4 | "author": "Joel Feenstra ", 5 | "contributors": [], 6 | "description": "A query library for ECMAScript AST using a CSS selector like query language.", 7 | "main": "dist/esquery.min.js", 8 | "module": "dist/esquery.esm.min.js", 9 | "files": [ 10 | "dist/*.js", 11 | "dist/*.map", 12 | "parser.js", 13 | "license.txt", 14 | "README.md" 15 | ], 16 | "nyc": { 17 | "branches": 100, 18 | "lines": 100, 19 | "functions": 100, 20 | "statements": 100, 21 | "reporter": [ 22 | "html", 23 | "text" 24 | ], 25 | "exclude": [ 26 | "parser.js", 27 | "dist", 28 | "tests" 29 | ] 30 | }, 31 | "scripts": { 32 | "prepublishOnly": "npm run build && npm test", 33 | "build:parser": "rm parser.js && pegjs --cache --format umd -o \"parser.js\" \"grammar.pegjs\"", 34 | "build:browser": "rollup -c", 35 | "build": "npm run build:parser && npm run build:browser", 36 | "mocha": "mocha --require chai/register-assert --require @babel/register tests", 37 | "test": "nyc npm run mocha && npm run lint", 38 | "test:ci": "npm run mocha", 39 | "lint": "eslint ." 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/estools/esquery.git" 44 | }, 45 | "bugs": "https://github.com/estools/esquery/issues", 46 | "homepage": "https://github.com/estools/esquery/", 47 | "keywords": [ 48 | "ast", 49 | "ecmascript", 50 | "javascript", 51 | "query" 52 | ], 53 | "devDependencies": { 54 | "@babel/core": "^7.9.0", 55 | "@babel/preset-env": "^7.9.5", 56 | "@babel/register": "^7.9.0", 57 | "@rollup/plugin-commonjs": "^11.1.0", 58 | "@rollup/plugin-json": "^4.0.2", 59 | "@rollup/plugin-node-resolve": "^7.1.3", 60 | "babel-plugin-transform-es2017-object-entries": "0.0.5", 61 | "chai": "4.2.0", 62 | "eslint": "^6.8.0", 63 | "esprima": "~4.0.1", 64 | "mocha": "7.1.1", 65 | "nyc": "^15.0.1", 66 | "pegjs": "~0.10.0", 67 | "rollup": "^1.32.1", 68 | "rollup-plugin-babel": "^4.4.0", 69 | "rollup-plugin-terser": "^5.3.0" 70 | }, 71 | "license": "BSD-3-Clause", 72 | "engines": { 73 | "node": ">=0.10" 74 | }, 75 | "dependencies": { 76 | "estraverse": "^5.1.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/queryMatches.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import forLoop from './fixtures/forLoop.js'; 4 | import simpleFunction from './fixtures/simpleFunction.js'; 5 | import simpleProgram from './fixtures/simpleProgram.js'; 6 | 7 | describe('Pseudo matches query', function () { 8 | 9 | it('conditional matches', function () { 10 | const matches = esquery(conditional, ':matches(IfStatement)'); 11 | assert.includeMembers(matches, [ 12 | conditional.body[0], 13 | conditional.body[1].alternate 14 | ]); 15 | }); 16 | 17 | it('for loop matches', function () { 18 | const matches = esquery(forLoop, ':matches(BinaryExpression, MemberExpression)'); 19 | assert.includeMembers(matches, [ 20 | forLoop.body[0].test, 21 | forLoop.body[0].body.body[0].expression.callee 22 | ]); 23 | }); 24 | 25 | it('simple function matches', function () { 26 | const matches = esquery(simpleFunction, ':matches([name="foo"], ReturnStatement)'); 27 | assert.includeMembers(matches, [ 28 | simpleFunction.body[0].id, 29 | simpleFunction.body[0].body.body[2] 30 | ]); 31 | }); 32 | 33 | it('simple program matches', function () { 34 | const matches = esquery(simpleProgram, ':matches(AssignmentExpression, BinaryExpression)'); 35 | assert.includeMembers(matches, [ 36 | simpleProgram.body[2].expression, 37 | simpleProgram.body[3].consequent.body[0].expression, 38 | simpleProgram.body[2].expression.right 39 | ]); 40 | }); 41 | 42 | it('implicit matches', function () { 43 | const matches = esquery(simpleProgram, 'AssignmentExpression, BinaryExpression, NonExistant'); 44 | assert.includeMembers(matches, [ 45 | simpleProgram.body[2].expression, 46 | simpleProgram.body[3].consequent.body[0].expression, 47 | simpleProgram.body[2].expression.right 48 | ]); 49 | }); 50 | 51 | it('simple function is (alias)', function () { 52 | const matches = esquery(simpleFunction, ':is([name="foo"], ReturnStatement)'); 53 | assert.includeMembers(matches, [ 54 | simpleFunction.body[0].id, 55 | simpleFunction.body[0].body.body[2] 56 | ]); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /tests/fixtures/allClasses.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: 'Program', 3 | body: [ 4 | { 5 | type: 'FunctionDeclaration', 6 | id: { 7 | type: 'Identifier', 8 | name: 'a' 9 | }, 10 | params: [], 11 | defaults: [], 12 | body: { 13 | type: 'BlockStatement', 14 | body: [ 15 | { 16 | type: 'ExpressionStatement', 17 | expression: { 18 | type: 'AssignmentExpression', 19 | operator: '=', 20 | left: { 21 | type: 'ArrayPattern', 22 | elements: [ 23 | { 24 | type: 'Identifier', 25 | name: 'a' 26 | } 27 | ] 28 | }, 29 | right: { 30 | type: 'ArrowFunctionExpression', 31 | params: [], 32 | defaults: [], 33 | rest: null, 34 | body: { 35 | type: 'Literal', 36 | value: 0, 37 | raw: '0' 38 | }, 39 | generator: false, 40 | expression: false 41 | } 42 | } 43 | }, 44 | { 45 | type: 'ExpressionStatement', 46 | expression: { 47 | type: 'MetaProperty', 48 | meta: { 49 | type: 'Identifier', 50 | name: 'new', 51 | }, 52 | property: { 53 | type: 'Identifier', 54 | name: 'target', 55 | }, 56 | }, 57 | }, 58 | { 59 | type: 'ExpressionStatement', 60 | expression: { 61 | type: 'TemplateLiteral', 62 | quasis: [ 63 | { 64 | type: 'TemplateElement', 65 | value: { 66 | raw: 'test', 67 | cooked: 'test' 68 | }, 69 | tail: true, 70 | } 71 | ], 72 | expressions: [], 73 | }, 74 | }, 75 | { 76 | type: 'ExpressionStatement', 77 | expression: { 78 | type: 'TemplateLiteral', 79 | quasis: [ 80 | { 81 | type: 'TemplateElement', 82 | value: { 83 | raw: 'hello,', 84 | cooked: 'hello,' 85 | }, 86 | tail: false, 87 | }, 88 | { 89 | type: 'TemplateElement', 90 | value: { 91 | raw: '', 92 | cooked: '' 93 | }, 94 | tail: true, 95 | } 96 | ], 97 | expressions: [ 98 | { 99 | type: 'Identifier', 100 | name: 'name', 101 | } 102 | ], 103 | }, 104 | } 105 | ] 106 | }, 107 | rest: null, 108 | generator: false, 109 | expression: false 110 | } 111 | ] 112 | }; 113 | -------------------------------------------------------------------------------- /grammar.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | function nth(n) { return { type: 'nth-child', index: { type: 'literal', value: n } }; } 3 | function nthLast(n) { return { type: 'nth-last-child', index: { type: 'literal', value: n } }; } 4 | function strUnescape(s) { 5 | return s.replace(/\\(.)/g, function(match, ch) { 6 | switch(ch) { 7 | case 'b': return '\b'; 8 | case 'f': return '\f'; 9 | case 'n': return '\n'; 10 | case 'r': return '\r'; 11 | case 't': return '\t'; 12 | case 'v': return '\v'; 13 | default: return ch; 14 | } 15 | }); 16 | } 17 | } 18 | 19 | start 20 | = _ ss:selectors _ { 21 | return ss.length === 1 ? ss[0] : { type: 'matches', selectors: ss }; 22 | } 23 | / _ { return void 0; } 24 | 25 | _ = " "* 26 | identifierName = i:[^ [\],():#!=><~+.]+ { return i.join(''); } 27 | binaryOp 28 | = _ ">" _ { return 'child'; } 29 | / _ "~" _ { return 'sibling'; } 30 | / _ "+" _ { return 'adjacent'; } 31 | / " " _ { return 'descendant'; } 32 | 33 | hasSelectors = s:hasSelector ss:(_ "," _ hasSelector)* { 34 | return [s].concat(ss.map(function (s) { return s[3]; })); 35 | } 36 | 37 | selectors = s:selector ss:(_ "," _ selector)* { 38 | return [s].concat(ss.map(function (s) { return s[3]; })); 39 | } 40 | 41 | 42 | hasSelector 43 | = op:binaryOp? s:selector { 44 | if (!op) return s; 45 | return { type: op, left: { type: 'exactNode' }, right: s }; 46 | } 47 | 48 | selector 49 | = a:sequence ops:(binaryOp sequence)* { 50 | return ops.reduce(function (memo, rhs) { 51 | return { type: rhs[0], left: memo, right: rhs[1] }; 52 | }, a); 53 | } 54 | 55 | sequence 56 | = subject:"!"? as:atom+ { 57 | const b = as.length === 1 ? as[0] : { type: 'compound', selectors: as }; 58 | if(subject) b.subject = true; 59 | return b; 60 | } 61 | 62 | atom 63 | = wildcard / identifier / attr / field / negation / matches / is 64 | / has / firstChild / lastChild / nthChild / nthLastChild / class 65 | 66 | wildcard = a:"*" { return { type: 'wildcard', value: a }; } 67 | identifier = "#"? i:identifierName { return { type: 'identifier', value: i }; } 68 | 69 | attr 70 | = "[" _ v:attrValue _ "]" { return v; } 71 | attrOps = a:[><] 72 | attrEqOps = a:"!"? "=" { return (a || '') + '='; } 73 | attrName = a:identifierName as:("." identifierName)* { 74 | return [].concat.apply([a], as).join(''); 75 | } 76 | attrValue 77 | = name:attrName _ op:attrEqOps _ value:(type / regex) { 78 | return { type: 'attribute', name: name, operator: op, value: value }; 79 | } 80 | / name:attrName _ op:attrOps _ value:(string / number / path) { 81 | return { type: 'attribute', name: name, operator: op, value: value }; 82 | } 83 | / name:attrName { return { type: 'attribute', name: name }; } 84 | string 85 | = "\"" d:([^\\"] / a:"\\" b:. { return a + b; })* "\"" { 86 | return { type: 'literal', value: strUnescape(d.join('')) }; 87 | } 88 | / "'" d:([^\\'] / a:"\\" b:. { return a + b; })* "'" { 89 | return { type: 'literal', value: strUnescape(d.join('')) }; 90 | } 91 | number 92 | = a:([0-9]* ".")? b:[0-9]+ { 93 | // Can use `a.flat().join('')` once supported 94 | const leadingDecimals = a ? [].concat.apply([], a).join('') : ''; 95 | return { type: 'literal', value: parseFloat(leadingDecimals + b.join('')) }; 96 | } 97 | path = i:identifierName { return { type: 'literal', value: i }; } 98 | type = "type(" _ t:[^ )]+ _ ")" { return { type: 'type', value: t.join('') }; } 99 | flags = [imsu]+ 100 | regex = "/" d:[^/]+ "/" flgs:flags? { return { 101 | type: 'regexp', value: new RegExp(d.join(''), flgs ? flgs.join('') : '') }; 102 | } 103 | 104 | field = "." i:identifierName is:("." identifierName)* { 105 | return { type: 'field', name: is.reduce(function(memo, p){ return memo + p[0] + p[1]; }, i)}; 106 | } 107 | 108 | negation = ":not(" _ ss:selectors _ ")" { return { type: 'not', selectors: ss }; } 109 | matches = ":matches(" _ ss:selectors _ ")" { return { type: 'matches', selectors: ss }; } 110 | is = ":is(" _ ss:selectors _ ")" { return { type: 'matches', selectors: ss }; } 111 | has = ":has(" _ ss:hasSelectors _ ")" { return { type: 'has', selectors: ss }; } 112 | 113 | firstChild = ":first-child" { return nth(1); } 114 | lastChild = ":last-child" { return nthLast(1); } 115 | nthChild = ":nth-child(" _ n:[0-9]+ _ ")" { return nth(parseInt(n.join(''), 10)); } 116 | nthLastChild = ":nth-last-child(" _ n:[0-9]+ _ ")" { return nthLast(parseInt(n.join(''), 10)); } 117 | 118 | 119 | class = ":" c:identifierName { 120 | return { type: 'class', name: c }; 121 | } 122 | -------------------------------------------------------------------------------- /tests/queryType.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import forLoop from './fixtures/forLoop.js'; 4 | import simpleFunction from './fixtures/simpleFunction.js'; 5 | import simpleProgram from './fixtures/simpleProgram.js'; 6 | 7 | describe('Type query', function () { 8 | 9 | it('conditional', function () { 10 | let matches = esquery(conditional, 'Program'); 11 | assert.includeMembers(matches, [conditional]); 12 | 13 | matches = esquery(conditional, 'IfStatement'); 14 | assert.includeMembers(matches, [ 15 | conditional.body[0], 16 | conditional.body[1], 17 | conditional.body[1].alternate 18 | ]); 19 | 20 | matches = esquery(conditional, 'LogicalExpression'); 21 | assert.includeMembers(matches, [ 22 | conditional.body[1].test, 23 | conditional.body[1].test.left 24 | ]); 25 | 26 | matches = esquery(conditional, 'ExpressionStatement'); 27 | assert.includeMembers(matches, [ 28 | conditional.body[0].consequent.body[0], 29 | conditional.body[0].alternate.body[0], 30 | conditional.body[1].consequent.body[0], 31 | conditional.body[1].alternate.consequent.body[0] 32 | ]); 33 | }); 34 | 35 | it('for loop', function () { 36 | let matches = esquery(forLoop, 'Program'); 37 | assert.includeMembers(matches, [forLoop]); 38 | 39 | matches = esquery(forLoop, 'ForStatement'); 40 | assert.includeMembers(matches, [ 41 | forLoop.body[0] 42 | ]); 43 | 44 | matches = esquery(forLoop, 'BinaryExpression'); 45 | assert.includeMembers(matches, [ 46 | forLoop.body[0].test 47 | ]); 48 | }); 49 | 50 | it('simple function', function () { 51 | let matches = esquery(simpleFunction, 'Program'); 52 | assert.includeMembers(matches, [simpleFunction]); 53 | 54 | matches = esquery(simpleFunction, 'VariableDeclaration'); 55 | assert.includeMembers(matches, [ 56 | simpleFunction.body[0].body.body[0] 57 | ]); 58 | 59 | matches = esquery(simpleFunction, 'FunctionDeclaration'); 60 | assert.includeMembers(matches, [ 61 | simpleFunction.body[0] 62 | ]); 63 | 64 | matches = esquery(simpleFunction, 'ReturnStatement'); 65 | assert.includeMembers(matches, [ 66 | simpleFunction.body[0].body.body[2] 67 | ]); 68 | }); 69 | 70 | it('simple program', function () { 71 | let matches = esquery(simpleProgram, 'Program'); 72 | assert.includeMembers(matches, [simpleProgram]); 73 | 74 | matches = esquery(simpleProgram, 'VariableDeclaration'); 75 | assert.includeMembers(matches, [ 76 | simpleProgram.body[0], 77 | simpleProgram.body[1] 78 | ]); 79 | 80 | matches = esquery(simpleProgram, 'AssignmentExpression'); 81 | assert.includeMembers(matches, [ 82 | simpleProgram.body[2].expression, 83 | simpleProgram.body[3].consequent.body[0].expression 84 | ]); 85 | 86 | matches = esquery(simpleProgram, 'Identifier'); 87 | assert.includeMembers(matches, [ 88 | simpleProgram.body[0].declarations[0].id, 89 | simpleProgram.body[1].declarations[0].id, 90 | simpleProgram.body[2].expression.left, 91 | simpleProgram.body[2].expression.right.left, 92 | simpleProgram.body[3].test, 93 | simpleProgram.body[3].consequent.body[0].expression.left 94 | ]); 95 | }); 96 | 97 | it('# type', function () { 98 | let matches = esquery(forLoop, '#Program'); 99 | assert.includeMembers(matches, [ 100 | forLoop 101 | ]); 102 | 103 | matches = esquery(forLoop, '#ForStatement'); 104 | assert.includeMembers(matches, [ 105 | forLoop.body[0] 106 | ]); 107 | 108 | matches = esquery(forLoop, '#BinaryExpression'); 109 | assert.includeMembers(matches, [ 110 | forLoop.body[0].test 111 | ]); 112 | }); 113 | 114 | it('case insensitive type', function () { 115 | let matches = esquery(forLoop, 'Program'); 116 | assert.includeMembers(matches, [ 117 | forLoop 118 | ]); 119 | 120 | matches = esquery(forLoop, 'forStatement'); 121 | assert.includeMembers(matches, [ 122 | forLoop.body[0] 123 | ]); 124 | 125 | matches = esquery(forLoop, 'binaryexpression'); 126 | assert.includeMembers(matches, [ 127 | forLoop.body[0].test 128 | ]); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /tests/queryClass.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import ast from './fixtures/allClasses.js'; 3 | import customNodesWithKind from './fixtures/customNodesWithKind.js'; 4 | 5 | describe('Class query', function () { 6 | 7 | it(':statement', function () { 8 | const matches = esquery(ast, ':statement'); 9 | assert.includeMembers(matches, [ 10 | ast.body[0], 11 | ast.body[0].body, 12 | ast.body[0].body.body[0], 13 | ast.body[0].body.body[1], 14 | ast.body[0].body.body[2], 15 | ast.body[0].body.body[3] 16 | ]); 17 | assert.equal(6, matches.length); 18 | }); 19 | 20 | it(':expression', function () { 21 | const matches = esquery(ast, ':Expression'); 22 | assert.includeMembers(matches, [ 23 | ast.body[0].id, 24 | ast.body[0].body.body[0].expression, 25 | ast.body[0].body.body[0].expression.left.elements[0], 26 | ast.body[0].body.body[0].expression.right, 27 | ast.body[0].body.body[0].expression.right.body, 28 | ast.body[0].body.body[1].expression, 29 | ast.body[0].body.body[2].expression, 30 | ast.body[0].body.body[3].expression, 31 | ast.body[0].body.body[3].expression.expressions[0] 32 | ]); 33 | assert.equal(9, matches.length); 34 | }); 35 | 36 | it(':function', function () { 37 | const matches = esquery(ast, ':FUNCTION'); 38 | assert.includeMembers(matches, [ 39 | ast.body[0], 40 | ast.body[0].body.body[0].expression.right 41 | ]); 42 | assert.equal(2, matches.length); 43 | }); 44 | 45 | it(':declaration', function () { 46 | const matches = esquery(ast, ':declaratioN'); 47 | assert.includeMembers(matches, [ 48 | ast.body[0] 49 | ]); 50 | assert.equal(1, matches.length); 51 | }); 52 | 53 | it(':pattern', function () { 54 | const matches = esquery(ast, ':paTTern'); 55 | assert.includeMembers(matches, [ 56 | ast.body[0].id, 57 | ast.body[0].body.body[0].expression, 58 | ast.body[0].body.body[0].expression.left, 59 | ast.body[0].body.body[0].expression.left.elements[0], 60 | ast.body[0].body.body[0].expression.right, 61 | ast.body[0].body.body[0].expression.right.body, 62 | ast.body[0].body.body[1].expression, 63 | ast.body[0].body.body[2].expression, 64 | ast.body[0].body.body[3].expression, 65 | ast.body[0].body.body[3].expression.expressions[0] 66 | ]); 67 | assert.equal(10, matches.length); 68 | }); 69 | 70 | it(':expression as custom matcher', function () { 71 | const matches = esquery(ast, ':expression', { 72 | matchClass(className, node, ancestry) { 73 | if (className !== 'expression') return false; 74 | 75 | return node.type.slice(-10) === 'Expression' || 76 | node.type.slice(-7) === 'Literal' || 77 | ( 78 | node.type === 'Identifier' && 79 | (ancestry.length === 0 || ancestry[0].type !== 'MetaProperty') 80 | ) || 81 | node.type === 'MetaProperty'; 82 | 83 | } 84 | }); 85 | 86 | assert.includeMembers(matches, [ 87 | ast.body[0].id, 88 | ast.body[0].body.body[0].expression, 89 | ast.body[0].body.body[0].expression.left.elements[0], 90 | ast.body[0].body.body[0].expression.right, 91 | ast.body[0].body.body[0].expression.right.body, 92 | ast.body[0].body.body[1].expression, 93 | ast.body[0].body.body[2].expression, 94 | ast.body[0].body.body[3].expression, 95 | ast.body[0].body.body[3].expression.expressions[0] 96 | ]); 97 | assert.equal(9, matches.length); 98 | }); 99 | 100 | it('custom nodes with :expression, :statement', function () { 101 | const options = { 102 | visitorKeys: { 103 | CustomRoot: ['list'], 104 | CustomChild: ['sublist'], 105 | CustomGrandChild: [], 106 | CustomStatement: [], 107 | CustomExpression: [] 108 | }, 109 | nodeTypeKey: 'kind' 110 | }; 111 | 112 | const matches1 = esquery(customNodesWithKind, ':expression', options); 113 | assert.equal(0, matches1.length); 114 | 115 | const matches2 = esquery(customNodesWithKind, ':statement', options); 116 | assert.equal(0, matches2.length); 117 | }); 118 | 119 | it('custom nodes with custom class matcher', function () { 120 | const options = { 121 | visitorKeys: { 122 | CustomRoot: ['list'], 123 | CustomChild: ['sublist'], 124 | CustomGrandChild: [], 125 | CustomStatement: [], 126 | CustomExpression: [] 127 | }, 128 | nodeTypeKey: 'kind', 129 | matchClass(className, node) { 130 | return className === 'root' && node.kind === 'CustomRoot'; 131 | } 132 | }; 133 | 134 | const matches = esquery(customNodesWithKind, ':root', options); 135 | assert.equal(1, matches.length); 136 | assert.strictEqual(customNodesWithKind, matches[0]); 137 | }); 138 | 139 | }); 140 | -------------------------------------------------------------------------------- /tests/querySubject.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import forLoop from './fixtures/forLoop.js'; 4 | import simpleFunction from './fixtures/simpleFunction.js'; 5 | import simpleProgram from './fixtures/simpleProgram.js'; 6 | 7 | import nestedFunctions from './fixtures/nestedFunctions.js'; 8 | import bigArray from './fixtures/bigArray.js'; 9 | import customNodes from './fixtures/customNodes.js'; 10 | 11 | describe('Query subject', function () { 12 | 13 | it('type subject', function () { 14 | const matches = esquery(conditional, '!IfStatement Identifier'); 15 | assert.includeMembers(matches, [ 16 | conditional.body[0], 17 | conditional.body[1], 18 | conditional.body[1].alternate 19 | ]); 20 | }); 21 | 22 | it('* subject', function () { 23 | const matches = esquery(forLoop, '!* > [name="foo"]'); 24 | assert.includeMembers(matches, [ 25 | forLoop.body[0].test.right, 26 | forLoop.body[0].body.body[0].expression.callee 27 | ]); 28 | }); 29 | 30 | it(':nth-child subject', function () { 31 | const matches = esquery(simpleFunction, '!:nth-child(1) [name="y"]'); 32 | assert.includeMembers(matches, [ 33 | simpleFunction.body[0], 34 | simpleFunction.body[0].body.body[0], 35 | simpleFunction.body[0].body.body[0].declarations[0] 36 | ]); 37 | }); 38 | 39 | it(':nth-last-child subject', function () { 40 | const matches = esquery(simpleProgram, '!:nth-last-child(1) [name="y"]'); 41 | assert.includeMembers(matches, [ 42 | simpleProgram.body[3], 43 | simpleProgram.body[1].declarations[0], 44 | simpleProgram.body[3].consequent.body[0] 45 | ]); 46 | }); 47 | 48 | it('attribute literal subject', function () { 49 | const matches = esquery(simpleProgram, '![test] [name="y"]'); 50 | assert.includeMembers(matches, [ 51 | simpleProgram.body[3] 52 | ]); 53 | }); 54 | 55 | it('attribute type subject', function () { 56 | const matches = esquery(nestedFunctions, '![generator=type(boolean)] > BlockStatement'); 57 | assert.includeMembers(matches, [ 58 | nestedFunctions.body[0], 59 | nestedFunctions.body[0].body.body[1] 60 | ]); 61 | }); 62 | 63 | it('attribute regexp subject', function () { 64 | const matches = esquery(conditional, '![operator=/=+/] > [name="x"]'); 65 | assert.includeMembers(matches, [ 66 | conditional.body[0].test, 67 | conditional.body[0].alternate.body[0].expression, 68 | conditional.body[1].test.left.left 69 | ]); 70 | }); 71 | 72 | it('field subject', function () { 73 | const matches = esquery(forLoop, '!.test'); 74 | assert.includeMembers(matches, [ 75 | forLoop.body[0].test 76 | ]); 77 | }); 78 | 79 | it(':matches subject', function () { 80 | const matches = esquery(forLoop, '!:matches(*) > [name="foo"]'); 81 | assert.includeMembers(matches, [ 82 | forLoop.body[0].test.right, 83 | forLoop.body[0].body.body[0].expression.callee 84 | ]); 85 | }); 86 | 87 | it(':not subject', function () { 88 | const matches = esquery(nestedFunctions, '!:not(BlockStatement) > [name="foo"]'); 89 | assert.includeMembers(matches, [ 90 | nestedFunctions.body[0] 91 | ]); 92 | }); 93 | 94 | it('compound attributes subject', function () { 95 | const matches = esquery(conditional, '![left.name="x"][right.value=1]'); 96 | assert.includeMembers(matches, [ 97 | conditional.body[0].test 98 | ]); 99 | }); 100 | 101 | it('descendant right subject', function () { 102 | const matches = esquery(forLoop, '* !AssignmentExpression'); 103 | assert.includeMembers(matches, [ 104 | forLoop.body[0].init 105 | ]); 106 | }); 107 | 108 | it('child right subject', function () { 109 | const matches = esquery(forLoop, '* > !AssignmentExpression'); 110 | assert.includeMembers(matches, [ 111 | forLoop.body[0].init 112 | ]); 113 | }); 114 | 115 | it('sibling left subject', function () { 116 | const matches = esquery(simpleProgram, '!VariableDeclaration ~ IfStatement'); 117 | assert.includeMembers(matches, [ 118 | simpleProgram.body[0], 119 | simpleProgram.body[1] 120 | ]); 121 | }); 122 | 123 | it('sibling right subject', function () { 124 | const matches = esquery(simpleProgram, '!VariableDeclaration ~ !IfStatement'); 125 | assert.includeMembers(matches, [ 126 | simpleProgram.body[0], 127 | simpleProgram.body[1], 128 | simpleProgram.body[3] 129 | ]); 130 | }); 131 | 132 | it('adjacent right subject', function () { 133 | const matches = esquery(simpleProgram, '!VariableDeclaration + !ExpressionStatement'); 134 | assert.includeMembers(matches, [ 135 | simpleProgram.body[1], 136 | simpleProgram.body[2] 137 | ]); 138 | }); 139 | 140 | it('multiple adjacent siblings', function () { 141 | const matches = esquery(bigArray, 'Identifier + Identifier'); 142 | assert.includeMembers(matches, [ 143 | bigArray.body[0].expression.elements[4], 144 | bigArray.body[0].expression.elements[8] 145 | ]); 146 | assert.equal(2, matches.length); 147 | }); 148 | 149 | it('multiple siblings', function () { 150 | const matches = esquery(bigArray, 'Identifier ~ Identifier'); 151 | assert.includeMembers(matches, [ 152 | bigArray.body[0].expression.elements[4], 153 | bigArray.body[0].expression.elements[7], 154 | bigArray.body[0].expression.elements[8] 155 | ]); 156 | assert.equal(3, matches.length); 157 | }); 158 | }); 159 | 160 | describe('Query subject with custom ast', function () { 161 | const visitorKeys = { 162 | CustomRoot: ['list'], 163 | CustomChild: ['sublist'], 164 | CustomGrandChild: [] 165 | }; 166 | 167 | it('sibling', function () { 168 | const matches = esquery(customNodes, 'CustomChild[name=two] ~ CustomChild', { visitorKeys }); 169 | assert.includeMembers(matches, [ 170 | customNodes.list[2], 171 | customNodes.list[3], 172 | ]); 173 | }); 174 | 175 | 176 | it('sibling with fallback', function () { 177 | const matches = esquery(customNodes, 'CustomChild[name=two] ~ CustomChild', { 178 | fallback (node) { 179 | return node.type === 'CustomRoot' ? ['list'] : node.type === 'CustomChild' ? ['sublist'] : []; 180 | } 181 | }); 182 | assert.includeMembers(matches, [ 183 | customNodes.list[2], 184 | customNodes.list[3], 185 | ]); 186 | }); 187 | 188 | it('sibling with default fallback', function () { 189 | const matches = esquery(customNodes, 'CustomChild[name=two] ~ CustomChild'); 190 | assert.includeMembers(matches, [ 191 | customNodes.list[2], 192 | customNodes.list[3], 193 | ]); 194 | }); 195 | 196 | it('adjacent', function () { 197 | const matches = esquery(customNodes, 'CustomChild[name=two] + CustomChild', { visitorKeys }); 198 | assert.includeMembers(matches, [ 199 | customNodes.list[2], 200 | ]); 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /tests/matches.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import forLoop from './fixtures/forLoop.js'; 3 | import simpleProgram from './fixtures/simpleProgram.js'; 4 | import conditional from './fixtures/conditional.js'; 5 | import customNodes from './fixtures/customNodes.js'; 6 | import customNodesWithKind from './fixtures/customNodesWithKind.js'; 7 | 8 | describe('matches', function () { 9 | it('falsey node', function () { 10 | const selector = esquery.parse('*'); 11 | 12 | assert.equal(false, esquery.matches( 13 | null, 14 | selector, 15 | [] 16 | )); 17 | 18 | assert.equal(false, esquery.matches( 19 | '', 20 | selector, 21 | [] 22 | )); 23 | 24 | assert.equal(false, esquery.matches( 25 | false, 26 | selector, 27 | [] 28 | )); 29 | }); 30 | 31 | it('falsey selector', function () { 32 | assert.equal(true, esquery.matches( 33 | forLoop, 34 | null, 35 | [] 36 | )); 37 | 38 | assert.equal(true, esquery.matches( 39 | forLoop, 40 | '', 41 | [] 42 | )); 43 | 44 | assert.equal(true, esquery.matches( 45 | forLoop, 46 | false, 47 | [] 48 | )); 49 | }); 50 | 51 | it('falsey ancestry', function () { 52 | const selector = esquery.parse('*'); 53 | 54 | assert.doesNotThrow(() => { 55 | esquery.matches( 56 | forLoop, 57 | selector, 58 | null 59 | ); 60 | }); 61 | 62 | assert.doesNotThrow(() => { 63 | esquery.matches( 64 | forLoop, 65 | selector, 66 | '' 67 | ); 68 | }); 69 | 70 | assert.doesNotThrow(() => { 71 | esquery.matches( 72 | forLoop, 73 | selector, 74 | false 75 | ); 76 | }); 77 | }); 78 | 79 | it('missing parent', function () { 80 | let selector = esquery.parse('!VariableDeclaration + !ExpressionStatement'); 81 | assert.doesNotThrow(() => { 82 | esquery.matches( 83 | simpleProgram.body[2], 84 | selector, 85 | [] 86 | ); 87 | }); 88 | 89 | selector = esquery.parse('!VariableDeclaration ~ IfStatement'); 90 | assert.doesNotThrow(() => { 91 | esquery.matches( 92 | simpleProgram.body[3], 93 | selector, 94 | [] 95 | ); 96 | }); 97 | }); 98 | 99 | it('adjacent/sibling', function () { 100 | let selector = esquery.parse('!VariableDeclaration + !ExpressionStatement'); 101 | assert.doesNotThrow(() => { 102 | esquery.matches( 103 | simpleProgram.body[2], 104 | selector, 105 | simpleProgram.body 106 | ); 107 | }); 108 | 109 | selector = esquery.parse('!VariableDeclaration ~ IfStatement'); 110 | assert.doesNotThrow(() => { 111 | esquery.matches( 112 | simpleProgram.body[3], 113 | selector, 114 | simpleProgram.body 115 | ); 116 | }); 117 | }); 118 | 119 | it('Non-array list prop', function () { 120 | let selector = esquery.parse('!IfStatement ~ IfStatement'); 121 | assert.doesNotThrow(() => { 122 | esquery.matches( 123 | conditional.body[1], 124 | selector, 125 | conditional.body 126 | ); 127 | }); 128 | 129 | selector = esquery.parse('!IfStatement + IfStatement'); 130 | assert.doesNotThrow(() => { 131 | esquery.matches( 132 | conditional.body[1], 133 | selector, 134 | conditional.body 135 | ); 136 | }); 137 | }); 138 | }); 139 | 140 | describe('matches with custom AST and custom visitor keys', function () { 141 | it('adjacent/sibling', function () { 142 | const options = { 143 | visitorKeys: { 144 | CustomRoot: ['list'], 145 | CustomChild: ['sublist'], 146 | CustomGrandChild: [] 147 | } 148 | }; 149 | let selector = esquery.parse('CustomChild + CustomChild'); 150 | assert.doesNotThrow(() => { 151 | esquery.matches( 152 | customNodes.list[1], 153 | selector, 154 | [customNodes], 155 | options 156 | ); 157 | }); 158 | 159 | selector = esquery.parse('CustomChild ~ CustomChild'); 160 | assert.doesNotThrow(() => { 161 | esquery.matches( 162 | customNodes.list[1], 163 | selector, 164 | [customNodes], 165 | options 166 | ); 167 | }); 168 | }); 169 | }); 170 | 171 | describe('matches with custom AST and nodeTypeKey and custom visitor keys', function () { 172 | it('adjacent/sibling', function () { 173 | const options = { 174 | visitorKeys: { 175 | CustomRoot: ['list'], 176 | CustomChild: ['sublist'], 177 | CustomGrandChild: [], 178 | CustomStatement: [], 179 | CustomExpression: [] 180 | }, 181 | nodeTypeKey: 'kind' 182 | }; 183 | 184 | let selector = esquery.parse('CustomChild + CustomChild'); 185 | assert.doesNotThrow(() => { 186 | esquery.matches( 187 | customNodesWithKind.list[1], 188 | selector, 189 | [customNodesWithKind], 190 | options 191 | ); 192 | }); 193 | 194 | selector = esquery.parse('CustomChild ~ CustomChild'); 195 | assert.doesNotThrow(() => { 196 | esquery.matches( 197 | customNodesWithKind.list[1], 198 | selector, 199 | [customNodesWithKind], 200 | options 201 | ); 202 | }); 203 | }); 204 | }); 205 | 206 | describe('matches with custom AST and fallback option', function () { 207 | it('adjacent/sibling', function () { 208 | const options = { 209 | fallback (node) { 210 | return node.type === 'CustomRoot' ? ['list'] : node.type === 'CustomChild' ? ['sublist'] : []; 211 | } 212 | }; 213 | let selector = esquery.parse('CustomChild + CustomChild'); 214 | assert.doesNotThrow(() => { 215 | esquery.matches( 216 | customNodes.list[1], 217 | selector, 218 | [customNodes], 219 | options 220 | ); 221 | }); 222 | 223 | selector = esquery.parse('CustomChild ~ CustomChild'); 224 | assert.doesNotThrow(() => { 225 | esquery.matches( 226 | customNodes.list[1], 227 | selector, 228 | [customNodes], 229 | options 230 | ); 231 | }); 232 | }); 233 | }); 234 | 235 | describe('matches with custom AST and default fallback', function () { 236 | it('adjacent/sibling', function () { 237 | let selector = esquery.parse('CustomChild + CustomChild'); 238 | assert.doesNotThrow(() => { 239 | esquery.matches( 240 | customNodes.list[1], 241 | selector, 242 | [customNodes], 243 | ); 244 | }); 245 | 246 | selector = esquery.parse('CustomChild ~ CustomChild'); 247 | assert.doesNotThrow(() => { 248 | esquery.matches( 249 | customNodes.list[1], 250 | selector, 251 | [customNodes], 252 | ); 253 | }); 254 | }); 255 | }); 256 | -------------------------------------------------------------------------------- /tests/queryPseudoChild.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import conditional from './fixtures/conditional.js'; 3 | import conditionalLong from './fixtures/conditionalLong.js'; 4 | import forLoop from './fixtures/forLoop.js'; 5 | import simpleFunction from './fixtures/simpleFunction.js'; 6 | import simpleProgram from './fixtures/simpleProgram.js'; 7 | import customNodes from './fixtures/customNodes.js'; 8 | 9 | describe('Pseudo *-child query', function () { 10 | 11 | it('conditional first child', function () { 12 | const matches = esquery(conditional, ':first-child'); 13 | assert.includeMembers(matches, [ 14 | conditional.body[0], 15 | conditional.body[0].consequent.body[0], 16 | conditional.body[0].alternate.body[0], 17 | conditional.body[1].consequent.body[0], 18 | conditional.body[1].alternate.consequent.body[0] 19 | ]); 20 | }); 21 | 22 | it('conditional last child', function () { 23 | const matches = esquery(conditional, ':last-child'); 24 | assert.includeMembers(matches, [ 25 | conditional.body[1], 26 | conditional.body[0].consequent.body[0], 27 | conditional.body[0].alternate.body[0], 28 | conditional.body[1].consequent.body[0], 29 | conditional.body[1].alternate.consequent.body[0] 30 | ]); 31 | }); 32 | 33 | it('conditional nth child', function () { 34 | let matches = esquery(conditional, ':nth-child(2)'); 35 | assert.includeMembers(matches, [ 36 | conditional.body[1] 37 | ]); 38 | 39 | matches = esquery(conditional, ':nth-last-child(2)'); 40 | assert.includeMembers(matches, [ 41 | conditional.body[0] 42 | ]); 43 | }); 44 | 45 | it('conditional nth child (multiple digits)', function () { 46 | const matches = esquery(conditionalLong, ':nth-child(10)'); 47 | assert.includeMembers(matches, [ 48 | conditionalLong.body[9] 49 | ]); 50 | }); 51 | 52 | it('conditional nth-last child (multiple digits)', function () { 53 | const matches = esquery(conditionalLong, ':nth-last-child(10)'); 54 | assert.includeMembers(matches, [ 55 | conditionalLong.body[1] 56 | ]); 57 | }); 58 | 59 | it('for loop first child', function () { 60 | const matches = esquery(forLoop, ':first-child'); 61 | assert.includeMembers(matches, [ 62 | forLoop.body[0], 63 | forLoop.body[0].body.body[0] 64 | ]); 65 | }); 66 | 67 | it('for loop last child', function () { 68 | const matches = esquery(forLoop, ':last-child'); 69 | assert.includeMembers(matches, [ 70 | forLoop.body[0], 71 | forLoop.body[0].body.body[0] 72 | ]); 73 | }); 74 | 75 | it('for loop nth child', function () { 76 | const matches = esquery(forLoop, ':nth-last-child(1)'); 77 | assert.includeMembers(matches, [ 78 | forLoop.body[0], 79 | forLoop.body[0].body.body[0] 80 | ]); 81 | }); 82 | 83 | it('simple function first child', function () { 84 | const matches = esquery(simpleFunction, ':first-child'); 85 | assert.includeMembers(matches, [ 86 | simpleFunction.body[0], 87 | simpleFunction.body[0].params[0], 88 | simpleFunction.body[0].body.body[0], 89 | simpleFunction.body[0].body.body[0].declarations[0] 90 | ]); 91 | }); 92 | 93 | it('simple function last child', function () { 94 | const matches = esquery(simpleFunction, ':last-child'); 95 | assert.includeMembers(matches, [ 96 | simpleFunction.body[0], 97 | simpleFunction.body[0].params[1], 98 | simpleFunction.body[0].body.body[2], 99 | simpleFunction.body[0].body.body[0].declarations[0] 100 | ]); 101 | }); 102 | 103 | it('simple function nth child', function () { 104 | let matches = esquery(simpleFunction, ':nth-child(2)'); 105 | assert.includeMembers(matches, [ 106 | simpleFunction.body[0].params[1], 107 | simpleFunction.body[0].body.body[1] 108 | ]); 109 | 110 | matches = esquery(simpleFunction, ':nth-child(3)'); 111 | assert.includeMembers(matches, [ 112 | simpleFunction.body[0].body.body[2] 113 | ]); 114 | 115 | matches = esquery(simpleFunction, ':nth-last-child(2)'); 116 | assert.includeMembers(matches, [ 117 | simpleFunction.body[0].params[0], 118 | simpleFunction.body[0].body.body[1] 119 | ]); 120 | }); 121 | 122 | it('simple program first child', function () { 123 | const matches = esquery(simpleProgram, ':first-child'); 124 | assert.includeMembers(matches, [ 125 | simpleProgram.body[0], 126 | simpleProgram.body[0].declarations[0], 127 | simpleProgram.body[1].declarations[0], 128 | simpleProgram.body[3].consequent.body[0] 129 | ]); 130 | }); 131 | 132 | it('simple program last child', function () { 133 | const matches = esquery(simpleProgram, ':last-child'); 134 | assert.includeMembers(matches, [ 135 | simpleProgram.body[3], 136 | simpleProgram.body[0].declarations[0], 137 | simpleProgram.body[1].declarations[0], 138 | simpleProgram.body[3].consequent.body[0] 139 | ]); 140 | }); 141 | 142 | it('simple program nth child', function () { 143 | let matches = esquery(simpleProgram, ':nth-child(2)'); 144 | assert.includeMembers(matches, [ 145 | simpleProgram.body[1] 146 | ]); 147 | 148 | matches = esquery(simpleProgram, ':nth-child(3)'); 149 | assert.includeMembers(matches, [ 150 | simpleProgram.body[2] 151 | ]); 152 | 153 | matches = esquery(simpleProgram, ':nth-last-child(2)'); 154 | assert.includeMembers(matches, [ 155 | simpleProgram.body[2] 156 | ]); 157 | }); 158 | }); 159 | 160 | describe('Pseudo *-child query with custom ast', function () { 161 | const visitorKeys = { 162 | CustomRoot: ['list'], 163 | CustomChild: ['sublist'], 164 | CustomGrandChild: [] 165 | }; 166 | 167 | it('conditional first child', function () { 168 | const matches = esquery(customNodes, ':first-child', { visitorKeys }); 169 | assert.includeMembers(matches, [ 170 | customNodes.list[0], 171 | customNodes.list[0].sublist[0], 172 | customNodes.list[2].sublist[0], 173 | customNodes.list[3].sublist[0], 174 | ]); 175 | }); 176 | 177 | it('conditional first child with fallback', function () { 178 | const matches = esquery(customNodes, ':first-child', { 179 | fallback (node) { 180 | return node.type === 'CustomRoot' ? ['list'] : node.type === 'CustomChild' ? ['sublist'] : []; 181 | } 182 | }); 183 | assert.includeMembers(matches, [ 184 | customNodes.list[0], 185 | customNodes.list[0].sublist[0], 186 | customNodes.list[2].sublist[0], 187 | customNodes.list[3].sublist[0], 188 | ]); 189 | }); 190 | 191 | it('conditional first child with default fallback', function () { 192 | const matches = esquery(customNodes, ':first-child'); 193 | assert.includeMembers(matches, [ 194 | customNodes.list[0], 195 | customNodes.list[0].sublist[0], 196 | customNodes.list[2].sublist[0], 197 | customNodes.list[3].sublist[0], 198 | ]); 199 | }); 200 | 201 | it('conditional last child', function () { 202 | const matches = esquery(customNodes, ':last-child', { visitorKeys }); 203 | assert.includeMembers(matches, [ 204 | customNodes.list[3], 205 | customNodes.list[0].sublist[0], 206 | customNodes.list[2].sublist[1], 207 | customNodes.list[3].sublist[2], 208 | ]); 209 | }); 210 | 211 | it('conditional nth child', function () { 212 | let matches = esquery(customNodes, ':nth-child(2)', { visitorKeys }); 213 | assert.includeMembers(matches, [ 214 | customNodes.list[1], 215 | customNodes.list[2].sublist[1], 216 | customNodes.list[3].sublist[1], 217 | ]); 218 | 219 | matches = esquery(customNodes, ':nth-last-child(2)', { visitorKeys }); 220 | assert.includeMembers(matches, [ 221 | customNodes.list[2], 222 | customNodes.list[2].sublist[0], 223 | customNodes.list[3].sublist[1], 224 | ]); 225 | }); 226 | 227 | it('conditional nth child combination', function () { 228 | let matches = esquery(customNodes, ':matches(:nth-child(2), :nth-last-child(2))', { visitorKeys }); 229 | assert.includeMembers(matches, [ 230 | customNodes.list[1], 231 | customNodes.list[2], 232 | customNodes.list[2].sublist[0], 233 | customNodes.list[2].sublist[1], 234 | customNodes.list[3].sublist[1], 235 | ]); 236 | 237 | matches = esquery(customNodes, ':not(:nth-child(2)):nth-last-child(2)', { visitorKeys }); 238 | assert.includeMembers(matches, [ 239 | customNodes.list[2], 240 | customNodes.list[2].sublist[0], 241 | ]); 242 | 243 | 244 | matches = esquery(customNodes, ':nth-last-child(2) > :nth-child(2)', { visitorKeys }); 245 | assert.includeMembers(matches, [ 246 | customNodes.list[2].sublist[1], 247 | ]); 248 | 249 | matches = esquery(customNodes, ':nth-last-child(2) :nth-child(2)', { visitorKeys }); 250 | assert.includeMembers(matches, [ 251 | customNodes.list[2].sublist[1], 252 | ]); 253 | 254 | matches = esquery(customNodes, '*:has(:nth-child(2))', { visitorKeys }); 255 | assert.includeMembers(matches, [ 256 | customNodes.list[2], 257 | customNodes.list[3], 258 | ]); 259 | }); 260 | }); 261 | -------------------------------------------------------------------------------- /tests/queryAttribute.js: -------------------------------------------------------------------------------- 1 | import esquery from '../esquery.js'; 2 | import literal from './fixtures/literal.js'; 3 | import conditional from './fixtures/conditional.js'; 4 | import forLoop from './fixtures/forLoop.js'; 5 | import simpleFunction from './fixtures/simpleFunction.js'; 6 | import simpleProgram from './fixtures/simpleProgram.js'; 7 | 8 | describe('Attribute query', function () { 9 | 10 | it('conditional', function () { 11 | let matches = esquery(conditional, '[name="x"]'); 12 | assert.includeMembers(matches, [ 13 | conditional.body[0].test.left, 14 | conditional.body[0].alternate.body[0].expression.left, 15 | conditional.body[1].test.left.left.left, 16 | conditional.body[1].test.right 17 | ]); 18 | 19 | matches = esquery(conditional, '[callee.name="foo"]'); 20 | assert.includeMembers(matches, [ 21 | conditional.body[0].consequent.body[0].expression 22 | ]); 23 | 24 | matches = esquery(conditional, '[operator]'); 25 | assert.includeMembers(matches, [ 26 | conditional.body[0].test, 27 | conditional.body[0].alternate.body[0].expression, 28 | conditional.body[1].test, 29 | conditional.body[1].test.left, 30 | conditional.body[1].test.left.left 31 | ]); 32 | 33 | matches = esquery(conditional, '[prefix=true]'); 34 | assert.includeMembers(matches, [ 35 | conditional.body[1].consequent.body[0].expression.right 36 | ]); 37 | }); 38 | 39 | it('literal with special escapes', function () { 40 | const matches = esquery(literal, 'Literal[value=\'\\b\\f\\n\\r\\t\\v and just a \\ back\\slash\']'); 41 | assert.includeMembers(matches, [ 42 | literal.body[0].declarations[0].init 43 | ]); 44 | }); 45 | 46 | it('literal with decimal', function () { 47 | const matches = esquery(literal, 'Literal[value=21.35]'); 48 | assert.includeMembers(matches, [ 49 | literal.body[1].declarations[0].init 50 | ]); 51 | }); 52 | 53 | it('literal with extra whitespace', function () { 54 | const matches = esquery(literal, 'Literal[value = 21.35]'); 55 | assert.includeMembers(matches, [ 56 | literal.body[1].declarations[0].init 57 | ]); 58 | }); 59 | 60 | it('literal with backslashes', function () { 61 | const matches = esquery(literal, 'Literal[value="\\z"]'); 62 | assert.includeMembers(matches, [ 63 | literal.body[2].declarations[0].init 64 | ]); 65 | }); 66 | 67 | it('literal with backslash after beginning', function () { 68 | const matches = esquery(literal, 'Literal[value="abc\\z"]'); 69 | assert.includeMembers(matches, [ 70 | literal.body[3].declarations[0].init 71 | ]); 72 | }); 73 | 74 | it('for loop', function () { 75 | let matches = esquery(forLoop, '[operator="="]'); 76 | assert.includeMembers(matches, [ 77 | forLoop.body[0].init 78 | ]); 79 | 80 | matches = esquery(forLoop, '[object.name="foo"]'); 81 | assert.includeMembers(matches, [ 82 | forLoop.body[0].test.right 83 | ]); 84 | 85 | matches = esquery(forLoop, '[operator]'); 86 | assert.includeMembers(matches, [ 87 | forLoop.body[0].init, 88 | forLoop.body[0].test, 89 | forLoop.body[0].update 90 | ]); 91 | }); 92 | 93 | it('simple function', function () { 94 | let matches = esquery(simpleFunction, '[kind="var"]'); 95 | assert.includeMembers(matches, [ 96 | simpleFunction.body[0].body.body[0] 97 | ]); 98 | 99 | matches = esquery(simpleFunction, '[id.name="foo"]'); 100 | assert.includeMembers(matches, [ 101 | simpleFunction.body[0] 102 | ]); 103 | 104 | matches = esquery(simpleFunction, '[left]'); 105 | assert.includeMembers(matches, [ 106 | simpleFunction.body[0].body.body[0].declarations[0].init 107 | ]); 108 | }); 109 | 110 | it('simple program', function () { 111 | let matches = esquery(simpleProgram, '[kind="var"]'); 112 | assert.includeMembers(matches, [ 113 | simpleProgram.body[0], 114 | simpleProgram.body[1] 115 | ]); 116 | 117 | matches = esquery(simpleProgram, '[id.name="y"]'); 118 | assert.includeMembers(matches, [ 119 | simpleProgram.body[1].declarations[0] 120 | ]); 121 | 122 | matches = esquery(simpleProgram, '[body]'); 123 | assert.includeMembers(matches, [ 124 | simpleProgram, 125 | simpleProgram.body[3].consequent 126 | ]); 127 | }); 128 | 129 | it('conditional regexp', function () { 130 | const matches = esquery(conditional, '[name=/x|foo/]'); 131 | assert.includeMembers(matches, [ 132 | conditional.body[0].test.left, 133 | conditional.body[0].consequent.body[0].expression.callee, 134 | conditional.body[0].alternate.body[0].expression.left 135 | ]); 136 | }); 137 | 138 | it('simple function regexp', function () { 139 | const matches = esquery(simpleFunction, '[name=/x|foo/]'); 140 | assert.includeMembers(matches, [ 141 | simpleFunction.body[0].id, 142 | simpleFunction.body[0].params[0], 143 | simpleFunction.body[0].body.body[0].declarations[0].init.left 144 | ]); 145 | }); 146 | 147 | it('simple function numeric', function () { 148 | const matches = esquery(simpleFunction, 'FunctionDeclaration[params.0.name=x]'); 149 | assert.includeMembers(matches, [ 150 | simpleFunction.body[0] 151 | ]); 152 | }); 153 | 154 | it('simple program regexp', function () { 155 | const matches = esquery(simpleProgram, '[name=/[asdfy]/]'); 156 | assert.includeMembers(matches, [ 157 | simpleProgram.body[1].declarations[0].id, 158 | simpleProgram.body[3].test, 159 | simpleProgram.body[3].consequent.body[0].expression.left 160 | ]); 161 | }); 162 | 163 | it('multiple regexp flags (i and u)', function () { 164 | const matches = esquery(simpleProgram, '[name=/\\u{61}|[SDFY]/iu]'); 165 | assert.includeMembers(matches, [ 166 | simpleProgram.body[1].declarations[0].id, 167 | simpleProgram.body[3].test, 168 | simpleProgram.body[3].consequent.body[0].expression.left 169 | ]); 170 | }); 171 | 172 | if (parseInt(process.version) >= 8) { 173 | it('regexp flag (s)', function () { 174 | const matches = esquery(literal, '[value=/\f.\r/s]'); 175 | assert.includeMembers(matches, [ 176 | literal.body[0].declarations[0].init 177 | ]); 178 | }); 179 | } 180 | 181 | it('regexp flag (m)', function () { 182 | const matches = esquery(literal, '[value=/^\r/m]'); 183 | assert.includeMembers(matches, [ 184 | literal.body[0].declarations[0].init 185 | ]); 186 | }); 187 | 188 | it('for loop regexp', function () { 189 | const matches = esquery(forLoop, '[name=/i|foo/]'); 190 | assert.includeMembers(matches, [ 191 | forLoop.body[0].init.left, 192 | forLoop.body[0].test.left, 193 | forLoop.body[0].test.right.object, 194 | forLoop.body[0].update.argument, 195 | forLoop.body[0].body.body[0].expression.callee.object, 196 | forLoop.body[0].body.body[0].expression.callee.property 197 | ]); 198 | }); 199 | 200 | it('nonexistent attribute regexp', function () { 201 | const matches = esquery(conditional, '[foobar=/./]'); 202 | assert.lengthOf(matches, 0); 203 | }); 204 | 205 | it('not string', function () { 206 | const matches = esquery(conditional, '[name!="x"]'); 207 | assert.includeMembers(matches, [ 208 | conditional.body[0].consequent.body[0].expression.callee, 209 | conditional.body[1].consequent.body[0].expression.left 210 | ]); 211 | }); 212 | 213 | it('not type', function () { 214 | const matches = esquery(conditional, '[value!=type(number)]'); 215 | assert.includeMembers(matches, [ 216 | conditional.body[1].test.left.left.right, 217 | conditional.body[1].test.left.right, 218 | conditional.body[1].alternate 219 | ]); 220 | }); 221 | 222 | it('not regexp', function () { 223 | const matches = esquery(conditional, '[name!=/x|y/]'); 224 | assert.includeMembers(matches, [ 225 | conditional.body[0].consequent.body[0].expression.callee 226 | ]); 227 | }); 228 | 229 | it('less than', function () { 230 | const matches = esquery(conditional, '[body.length<2]'); 231 | assert.includeMembers(matches, [ 232 | conditional.body[0].consequent, 233 | conditional.body[0].alternate, 234 | conditional.body[1].consequent, 235 | conditional.body[1].alternate.consequent 236 | ]); 237 | }); 238 | 239 | it('greater than', function () { 240 | const matches = esquery(conditional, '[body.length>1]'); 241 | assert.includeMembers(matches, [ 242 | conditional 243 | ]); 244 | }); 245 | 246 | it('less than or equal', function () { 247 | const matches = esquery(conditional, '[body.length<=2]'); 248 | assert.includeMembers(matches, [ 249 | conditional, 250 | conditional.body[0].consequent, 251 | conditional.body[0].alternate, 252 | conditional.body[1].consequent, 253 | conditional.body[1].alternate.consequent 254 | ]); 255 | }); 256 | 257 | it('greater than or equal', function () { 258 | const matches = esquery(conditional, '[body.length>=1]'); 259 | assert.includeMembers(matches, [ 260 | conditional, 261 | conditional.body[0].consequent, 262 | conditional.body[0].alternate, 263 | conditional.body[1].consequent, 264 | conditional.body[1].alternate.consequent 265 | ]); 266 | }); 267 | 268 | it('attribute type', function () { 269 | let matches = esquery(conditional, '[test=type(object)]'); 270 | assert.includeMembers(matches, [ 271 | conditional.body[0], 272 | conditional.body[1], 273 | conditional.body[1].alternate 274 | ]); 275 | 276 | matches = esquery(conditional, '[value=type(boolean)]'); 277 | assert.includeMembers(matches, [ 278 | conditional.body[1].test.left.right, 279 | conditional.body[1].alternate.test 280 | ]); 281 | }); 282 | 283 | }); 284 | -------------------------------------------------------------------------------- /esquery.js: -------------------------------------------------------------------------------- 1 | /* vim: set sw=4 sts=4 : */ 2 | import estraverse from 'estraverse'; 3 | import parser from './parser.js'; 4 | 5 | /** 6 | * @typedef {"LEFT_SIDE"|"RIGHT_SIDE"} Side 7 | */ 8 | 9 | const LEFT_SIDE = 'LEFT_SIDE'; 10 | const RIGHT_SIDE = 'RIGHT_SIDE'; 11 | 12 | /** 13 | * @external AST 14 | * @see https://esprima.readthedocs.io/en/latest/syntax-tree-format.html 15 | */ 16 | 17 | /** 18 | * One of the rules of `grammar.pegjs` 19 | * @typedef {PlainObject} SelectorAST 20 | * @see grammar.pegjs 21 | */ 22 | 23 | /** 24 | * The `sequence` production of `grammar.pegjs` 25 | * @typedef {PlainObject} SelectorSequenceAST 26 | */ 27 | 28 | /** 29 | * Get the value of a property which may be multiple levels down 30 | * in the object. 31 | * @param {?PlainObject} obj 32 | * @param {string[]} keys 33 | * @returns {undefined|boolean|string|number|external:AST} 34 | */ 35 | function getPath(obj, keys) { 36 | for (let i = 0; i < keys.length; ++i) { 37 | if (obj == null) { return obj; } 38 | obj = obj[keys[i]]; 39 | } 40 | return obj; 41 | } 42 | 43 | /** 44 | * Determine whether `node` can be reached by following `path`, 45 | * starting at `ancestor`. 46 | * @param {?external:AST} node 47 | * @param {?external:AST} ancestor 48 | * @param {string[]} path 49 | * @param {Integer} fromPathIndex 50 | * @returns {boolean} 51 | */ 52 | function inPath(node, ancestor, path, fromPathIndex) { 53 | let current = ancestor; 54 | for (let i = fromPathIndex; i < path.length; ++i) { 55 | if (current == null) { 56 | return false; 57 | } 58 | const field = current[path[i]]; 59 | if (Array.isArray(field)) { 60 | for (let k = 0; k < field.length; ++k) { 61 | if (inPath(node, field[k], path, i + 1)) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | } 67 | current = field; 68 | } 69 | return node === current; 70 | } 71 | 72 | /** 73 | * A generated matcher function for a selector. 74 | * @callback SelectorMatcher 75 | * @param {?SelectorAST} selector 76 | * @param {external:AST[]} [ancestry=[]] 77 | * @param {ESQueryOptions} [options] 78 | * @returns {void} 79 | */ 80 | 81 | /** 82 | * A WeakMap for holding cached matcher functions for selectors. 83 | * @type {WeakMap} 84 | */ 85 | const MATCHER_CACHE = typeof WeakMap === 'function' ? new WeakMap : null; 86 | 87 | /** 88 | * Look up a matcher function for `selector` in the cache. 89 | * If it does not exist, generate it with `generateMatcher` and add it to the cache. 90 | * In engines without WeakMap, the caching is skipped and matchers are generated with every call. 91 | * @param {?SelectorAST} selector 92 | * @returns {SelectorMatcher} 93 | */ 94 | function getMatcher(selector) { 95 | if (selector == null) { 96 | return () => true; 97 | } 98 | 99 | if (MATCHER_CACHE != null) { 100 | let matcher = MATCHER_CACHE.get(selector); 101 | if (matcher != null) { 102 | return matcher; 103 | } 104 | matcher = generateMatcher(selector); 105 | MATCHER_CACHE.set(selector, matcher); 106 | return matcher; 107 | } 108 | 109 | return generateMatcher(selector); 110 | } 111 | 112 | /** 113 | * Create a matcher function for `selector`, 114 | * @param {?SelectorAST} selector 115 | * @returns {SelectorMatcher} 116 | */ 117 | function generateMatcher(selector) { 118 | switch(selector.type) { 119 | case 'wildcard': 120 | return () => true; 121 | 122 | case 'identifier': { 123 | const value = selector.value.toLowerCase(); 124 | return (node, ancestry, options) => { 125 | const nodeTypeKey = (options && options.nodeTypeKey) || 'type'; 126 | return value === node[nodeTypeKey].toLowerCase(); 127 | }; 128 | } 129 | 130 | case 'exactNode': 131 | return (node, ancestry) => { 132 | return ancestry.length === 0; 133 | }; 134 | 135 | case 'field': { 136 | const path = selector.name.split('.'); 137 | return (node, ancestry) => { 138 | const ancestor = ancestry[path.length - 1]; 139 | return inPath(node, ancestor, path, 0); 140 | }; 141 | } 142 | 143 | case 'matches': { 144 | const matchers = selector.selectors.map(getMatcher); 145 | return (node, ancestry, options) => { 146 | for (let i = 0; i < matchers.length; ++i) { 147 | if (matchers[i](node, ancestry, options)) { return true; } 148 | } 149 | return false; 150 | }; 151 | } 152 | 153 | case 'compound': { 154 | const matchers = selector.selectors.map(getMatcher); 155 | return (node, ancestry, options) => { 156 | for (let i = 0; i < matchers.length; ++i) { 157 | if (!matchers[i](node, ancestry, options)) { return false; } 158 | } 159 | return true; 160 | }; 161 | } 162 | 163 | case 'not': { 164 | const matchers = selector.selectors.map(getMatcher); 165 | return (node, ancestry, options) => { 166 | for (let i = 0; i < matchers.length; ++i) { 167 | if (matchers[i](node, ancestry, options)) { return false; } 168 | } 169 | return true; 170 | }; 171 | } 172 | 173 | case 'has': { 174 | const matchers = selector.selectors.map(getMatcher); 175 | return (node, ancestry, options) => { 176 | let result = false; 177 | 178 | const a = []; 179 | estraverse.traverse(node, { 180 | enter (node, parent) { 181 | if (parent != null) { a.unshift(parent); } 182 | 183 | for (let i = 0; i < matchers.length; ++i) { 184 | if (matchers[i](node, a, options)) { 185 | result = true; 186 | this.break(); 187 | return; 188 | } 189 | } 190 | }, 191 | leave () { a.shift(); }, 192 | keys: options && options.visitorKeys, 193 | fallback: options && options.fallback || 'iteration' 194 | }); 195 | 196 | return result; 197 | }; 198 | } 199 | 200 | case 'child': { 201 | const left = getMatcher(selector.left); 202 | const right = getMatcher(selector.right); 203 | return (node, ancestry, options) => { 204 | if (ancestry.length > 0 && right(node, ancestry, options)) { 205 | return left(ancestry[0], ancestry.slice(1), options); 206 | } 207 | return false; 208 | }; 209 | } 210 | 211 | case 'descendant': { 212 | const left = getMatcher(selector.left); 213 | const right = getMatcher(selector.right); 214 | return (node, ancestry, options) => { 215 | if (right(node, ancestry, options)) { 216 | for (let i = 0, l = ancestry.length; i < l; ++i) { 217 | if (left(ancestry[i], ancestry.slice(i + 1), options)) { 218 | return true; 219 | } 220 | } 221 | } 222 | return false; 223 | }; 224 | } 225 | 226 | case 'attribute': { 227 | const path = selector.name.split('.'); 228 | switch (selector.operator) { 229 | case void 0: 230 | return (node) => getPath(node, path) != null; 231 | case '=': 232 | switch (selector.value.type) { 233 | case 'regexp': 234 | return (node) => { 235 | const p = getPath(node, path); 236 | return typeof p === 'string' && selector.value.value.test(p); 237 | }; 238 | case 'literal': { 239 | const literal = `${selector.value.value}`; 240 | return (node) => literal === `${getPath(node, path)}`; 241 | } 242 | case 'type': 243 | return (node) => selector.value.value === typeof getPath(node, path); 244 | } 245 | throw new Error(`Unknown selector value type: ${selector.value.type}`); 246 | case '!=': 247 | switch (selector.value.type) { 248 | case 'regexp': 249 | return (node) => !selector.value.value.test(getPath(node, path)); 250 | case 'literal': { 251 | const literal = `${selector.value.value}`; 252 | return (node) => literal !== `${getPath(node, path)}`; 253 | } 254 | case 'type': 255 | return (node) => selector.value.value !== typeof getPath(node, path); 256 | } 257 | throw new Error(`Unknown selector value type: ${selector.value.type}`); 258 | case '<=': 259 | return (node) => getPath(node, path) <= selector.value.value; 260 | case '<': 261 | return (node) => getPath(node, path) < selector.value.value; 262 | case '>': 263 | return (node) => getPath(node, path) > selector.value.value; 264 | case '>=': 265 | return (node) => getPath(node, path) >= selector.value.value; 266 | } 267 | throw new Error(`Unknown operator: ${selector.operator}`); 268 | } 269 | 270 | case 'sibling': { 271 | const left = getMatcher(selector.left); 272 | const right = getMatcher(selector.right); 273 | return (node, ancestry, options) => 274 | right(node, ancestry, options) && 275 | sibling(node, left, ancestry, LEFT_SIDE, options) || 276 | selector.left.subject && 277 | left(node, ancestry, options) && 278 | sibling(node, right, ancestry, RIGHT_SIDE, options); 279 | } 280 | 281 | case 'adjacent': { 282 | const left = getMatcher(selector.left); 283 | const right = getMatcher(selector.right); 284 | return (node, ancestry, options) => 285 | right(node, ancestry, options) && 286 | adjacent(node, left, ancestry, LEFT_SIDE, options) || 287 | selector.right.subject && 288 | left(node, ancestry, options) && 289 | adjacent(node, right, ancestry, RIGHT_SIDE, options); 290 | } 291 | 292 | case 'nth-child': { 293 | const nth = selector.index.value; 294 | const right = getMatcher(selector.right); 295 | return (node, ancestry, options) => 296 | right(node, ancestry, options) && 297 | nthChild(node, ancestry, nth, options); 298 | } 299 | 300 | case 'nth-last-child': { 301 | const nth = -selector.index.value; 302 | const right = getMatcher(selector.right); 303 | return (node, ancestry, options) => 304 | right(node, ancestry, options) && 305 | nthChild(node, ancestry, nth, options); 306 | } 307 | 308 | case 'class': { 309 | 310 | const name = selector.name.toLowerCase(); 311 | 312 | return (node, ancestry, options) => { 313 | 314 | if (options && options.matchClass) { 315 | return options.matchClass(selector.name, node, ancestry); 316 | } 317 | 318 | if (options && options.nodeTypeKey) return false; 319 | 320 | switch(name){ 321 | case 'statement': 322 | if(node.type.slice(-9) === 'Statement') return true; 323 | // fallthrough: interface Declaration <: Statement { } 324 | case 'declaration': 325 | return node.type.slice(-11) === 'Declaration'; 326 | case 'pattern': 327 | if(node.type.slice(-7) === 'Pattern') return true; 328 | // fallthrough: interface Expression <: Node, Pattern { } 329 | case 'expression': 330 | return node.type.slice(-10) === 'Expression' || 331 | node.type.slice(-7) === 'Literal' || 332 | ( 333 | node.type === 'Identifier' && 334 | (ancestry.length === 0 || ancestry[0].type !== 'MetaProperty') 335 | ) || 336 | node.type === 'MetaProperty'; 337 | case 'function': 338 | return node.type === 'FunctionDeclaration' || 339 | node.type === 'FunctionExpression' || 340 | node.type === 'ArrowFunctionExpression'; 341 | } 342 | throw new Error(`Unknown class name: ${selector.name}`); 343 | }; 344 | } 345 | } 346 | 347 | throw new Error(`Unknown selector type: ${selector.type}`); 348 | } 349 | 350 | /** 351 | * @callback TraverseOptionFallback 352 | * @param {external:AST} node The given node. 353 | * @returns {string[]} An array of visitor keys for the given node. 354 | */ 355 | 356 | /** 357 | * @callback ClassMatcher 358 | * @param {string} className The name of the class to match. 359 | * @param {external:AST} node The node to match against. 360 | * @param {Array} ancestry The ancestry of the node. 361 | * @returns {boolean} True if the node matches the class, false if not. 362 | */ 363 | 364 | /** 365 | * @typedef {object} ESQueryOptions 366 | * @property {string} [nodeTypeKey="type"] By passing `nodeTypeKey`, we can allow other ASTs to use ESQuery. 367 | * @property { { [nodeType: string]: string[] } } [visitorKeys] By passing `visitorKeys` mapping, we can extend the properties of the nodes that traverse the node. 368 | * @property {TraverseOptionFallback} [fallback] By passing `fallback` option, we can control the properties of traversing nodes when encountering unknown nodes. 369 | * @property {ClassMatcher} [matchClass] By passing `matchClass` option, we can customize the interpretation of classes. 370 | */ 371 | 372 | /** 373 | * Given a `node` and its ancestors, determine if `node` is matched 374 | * by `selector`. 375 | * @param {?external:AST} node 376 | * @param {?SelectorAST} selector 377 | * @param {external:AST[]} [ancestry=[]] 378 | * @param {ESQueryOptions} [options] 379 | * @throws {Error} Unknowns (operator, class name, selector type, or 380 | * selector value type) 381 | * @returns {boolean} 382 | */ 383 | function matches(node, selector, ancestry, options) { 384 | if (!selector) { return true; } 385 | if (!node) { return false; } 386 | if (!ancestry) { ancestry = []; } 387 | 388 | return getMatcher(selector)(node, ancestry, options); 389 | } 390 | 391 | /** 392 | * Get visitor keys of a given node. 393 | * @param {external:AST} node The AST node to get keys. 394 | * @param {ESQueryOptions|undefined} options 395 | * @returns {string[]} Visitor keys of the node. 396 | */ 397 | function getVisitorKeys(node, options) { 398 | const nodeTypeKey = (options && options.nodeTypeKey) || 'type'; 399 | 400 | const nodeType = node[nodeTypeKey]; 401 | if (options && options.visitorKeys && options.visitorKeys[nodeType]) { 402 | return options.visitorKeys[nodeType]; 403 | } 404 | if (estraverse.VisitorKeys[nodeType]) { 405 | return estraverse.VisitorKeys[nodeType]; 406 | } 407 | if (options && typeof options.fallback === 'function') { 408 | return options.fallback(node); 409 | } 410 | // 'iteration' fallback 411 | return Object.keys(node).filter(function (key) { 412 | return key !== nodeTypeKey; 413 | }); 414 | } 415 | 416 | 417 | /** 418 | * Check whether the given value is an ASTNode or not. 419 | * @param {any} node The value to check. 420 | * @param {ESQueryOptions|undefined} options The options to use. 421 | * @returns {boolean} `true` if the value is an ASTNode. 422 | */ 423 | function isNode(node, options) { 424 | const nodeTypeKey = (options && options.nodeTypeKey) || 'type'; 425 | return node !== null && typeof node === 'object' && typeof node[nodeTypeKey] === 'string'; 426 | } 427 | 428 | /** 429 | * Determines if the given node has a sibling that matches the 430 | * given selector matcher. 431 | * @param {external:AST} node 432 | * @param {SelectorMatcher} matcher 433 | * @param {external:AST[]} ancestry 434 | * @param {Side} side 435 | * @param {ESQueryOptions|undefined} options 436 | * @returns {boolean} 437 | */ 438 | function sibling(node, matcher, ancestry, side, options) { 439 | const [parent] = ancestry; 440 | if (!parent) { return false; } 441 | const keys = getVisitorKeys(parent, options); 442 | for (let i = 0; i < keys.length; ++i) { 443 | const listProp = parent[keys[i]]; 444 | if (Array.isArray(listProp)) { 445 | const startIndex = listProp.indexOf(node); 446 | if (startIndex < 0) { continue; } 447 | let lowerBound, upperBound; 448 | if (side === LEFT_SIDE) { 449 | lowerBound = 0; 450 | upperBound = startIndex; 451 | } else { 452 | lowerBound = startIndex + 1; 453 | upperBound = listProp.length; 454 | } 455 | for (let k = lowerBound; k < upperBound; ++k) { 456 | if (isNode(listProp[k], options) && matcher(listProp[k], ancestry, options)) { 457 | return true; 458 | } 459 | } 460 | } 461 | } 462 | return false; 463 | } 464 | 465 | /** 466 | * Determines if the given node has an adjacent sibling that matches 467 | * the given selector matcher. 468 | * @param {external:AST} node 469 | * @param {SelectorMatcher} matcher 470 | * @param {external:AST[]} ancestry 471 | * @param {Side} side 472 | * @param {ESQueryOptions|undefined} options 473 | * @returns {boolean} 474 | */ 475 | function adjacent(node, matcher, ancestry, side, options) { 476 | const [parent] = ancestry; 477 | if (!parent) { return false; } 478 | const keys = getVisitorKeys(parent, options); 479 | for (let i = 0; i < keys.length; ++i) { 480 | const listProp = parent[keys[i]]; 481 | if (Array.isArray(listProp)) { 482 | const idx = listProp.indexOf(node); 483 | if (idx < 0) { continue; } 484 | if (side === LEFT_SIDE && idx > 0 && isNode(listProp[idx - 1], options) && matcher(listProp[idx - 1], ancestry, options)) { 485 | return true; 486 | } 487 | if (side === RIGHT_SIDE && idx < listProp.length - 1 && isNode(listProp[idx + 1], options) && matcher(listProp[idx + 1], ancestry, options)) { 488 | return true; 489 | } 490 | } 491 | } 492 | return false; 493 | } 494 | 495 | /** 496 | * Determines if the given node is the `nth` child. 497 | * If `nth` is negative then the position is counted 498 | * from the end of the list of children. 499 | * @param {external:AST} node 500 | * @param {external:AST[]} ancestry 501 | * @param {Integer} nth 502 | * @param {ESQueryOptions|undefined} options 503 | * @returns {boolean} 504 | */ 505 | function nthChild(node, ancestry, nth, options) { 506 | if (nth === 0) { return false; } 507 | const [parent] = ancestry; 508 | if (!parent) { return false; } 509 | const keys = getVisitorKeys(parent, options); 510 | for (let i = 0; i < keys.length; ++i) { 511 | const listProp = parent[keys[i]]; 512 | if (Array.isArray(listProp)){ 513 | const idx = nth < 0 ? listProp.length + nth : nth - 1; 514 | if (idx >= 0 && idx < listProp.length && listProp[idx] === node) { 515 | return true; 516 | } 517 | } 518 | } 519 | return false; 520 | } 521 | 522 | /** 523 | * For each selector node marked as a subject, find the portion of the 524 | * selector that the subject must match. 525 | * @param {SelectorAST} selector 526 | * @param {SelectorAST} [ancestor] Defaults to `selector` 527 | * @returns {SelectorAST[]} 528 | */ 529 | function subjects(selector, ancestor) { 530 | if (selector == null || typeof selector != 'object') { return []; } 531 | if (ancestor == null) { ancestor = selector; } 532 | const results = selector.subject ? [ancestor] : []; 533 | const keys = Object.keys(selector); 534 | for (let i = 0; i < keys.length; ++i) { 535 | const p = keys[i]; 536 | const sel = selector[p]; 537 | results.push(...subjects(sel, p === 'left' ? sel : ancestor)); 538 | } 539 | return results; 540 | } 541 | 542 | /** 543 | * @callback TraverseVisitor 544 | * @param {?external:AST} node 545 | * @param {?external:AST} parent 546 | * @param {external:AST[]} ancestry 547 | */ 548 | 549 | /** 550 | * From a JS AST and a selector AST, collect all JS AST nodes that 551 | * match the selector. 552 | * @param {external:AST} ast 553 | * @param {?SelectorAST} selector 554 | * @param {TraverseVisitor} visitor 555 | * @param {ESQueryOptions} [options] 556 | * @returns {external:AST[]} 557 | */ 558 | function traverse(ast, selector, visitor, options) { 559 | if (!selector) { return; } 560 | const ancestry = []; 561 | const matcher = getMatcher(selector); 562 | const altSubjects = subjects(selector).map(getMatcher); 563 | estraverse.traverse(ast, { 564 | enter (node, parent) { 565 | if (parent != null) { ancestry.unshift(parent); } 566 | if (matcher(node, ancestry, options)) { 567 | if (altSubjects.length) { 568 | for (let i = 0, l = altSubjects.length; i < l; ++i) { 569 | if (altSubjects[i](node, ancestry, options)) { 570 | visitor(node, parent, ancestry); 571 | } 572 | for (let k = 0, m = ancestry.length; k < m; ++k) { 573 | const succeedingAncestry = ancestry.slice(k + 1); 574 | if (altSubjects[i](ancestry[k], succeedingAncestry, options)) { 575 | visitor(ancestry[k], parent, succeedingAncestry); 576 | } 577 | } 578 | } 579 | } else { 580 | visitor(node, parent, ancestry); 581 | } 582 | } 583 | }, 584 | leave () { ancestry.shift(); }, 585 | keys: options && options.visitorKeys, 586 | fallback: options && options.fallback || 'iteration' 587 | }); 588 | } 589 | 590 | 591 | /** 592 | * From a JS AST and a selector AST, collect all JS AST nodes that 593 | * match the selector. 594 | * @param {external:AST} ast 595 | * @param {?SelectorAST} selector 596 | * @param {ESQueryOptions} [options] 597 | * @returns {external:AST[]} 598 | */ 599 | function match(ast, selector, options) { 600 | const results = []; 601 | traverse(ast, selector, function (node) { 602 | results.push(node); 603 | }, options); 604 | return results; 605 | } 606 | 607 | /** 608 | * Parse a selector string and return its AST. 609 | * @param {string} selector 610 | * @returns {SelectorAST} 611 | */ 612 | function parse(selector) { 613 | return parser.parse(selector); 614 | } 615 | 616 | /** 617 | * Query the code AST using the selector string. 618 | * @param {external:AST} ast 619 | * @param {string} selector 620 | * @param {ESQueryOptions} [options] 621 | * @returns {external:AST[]} 622 | */ 623 | function query(ast, selector, options) { 624 | return match(ast, parse(selector), options); 625 | } 626 | 627 | query.parse = parse; 628 | query.match = match; 629 | query.traverse = traverse; 630 | query.matches = matches; 631 | query.query = query; 632 | 633 | export default query; 634 | -------------------------------------------------------------------------------- /parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Generated by PEG.js 0.10.0. 3 | * 4 | * http://pegjs.org/ 5 | */ 6 | (function(root, factory) { 7 | if (typeof define === "function" && define.amd) { 8 | define([], factory); 9 | } else if (typeof module === "object" && module.exports) { 10 | module.exports = factory(); 11 | } 12 | })(this, function() { 13 | "use strict"; 14 | 15 | function peg$subclass(child, parent) { 16 | function ctor() { this.constructor = child; } 17 | ctor.prototype = parent.prototype; 18 | child.prototype = new ctor(); 19 | } 20 | 21 | function peg$SyntaxError(message, expected, found, location) { 22 | this.message = message; 23 | this.expected = expected; 24 | this.found = found; 25 | this.location = location; 26 | this.name = "SyntaxError"; 27 | 28 | if (typeof Error.captureStackTrace === "function") { 29 | Error.captureStackTrace(this, peg$SyntaxError); 30 | } 31 | } 32 | 33 | peg$subclass(peg$SyntaxError, Error); 34 | 35 | peg$SyntaxError.buildMessage = function(expected, found) { 36 | var DESCRIBE_EXPECTATION_FNS = { 37 | literal: function(expectation) { 38 | return "\"" + literalEscape(expectation.text) + "\""; 39 | }, 40 | 41 | "class": function(expectation) { 42 | var escapedParts = "", 43 | i; 44 | 45 | for (i = 0; i < expectation.parts.length; i++) { 46 | escapedParts += expectation.parts[i] instanceof Array 47 | ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) 48 | : classEscape(expectation.parts[i]); 49 | } 50 | 51 | return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; 52 | }, 53 | 54 | any: function(expectation) { 55 | return "any character"; 56 | }, 57 | 58 | end: function(expectation) { 59 | return "end of input"; 60 | }, 61 | 62 | other: function(expectation) { 63 | return expectation.description; 64 | } 65 | }; 66 | 67 | function hex(ch) { 68 | return ch.charCodeAt(0).toString(16).toUpperCase(); 69 | } 70 | 71 | function literalEscape(s) { 72 | return s 73 | .replace(/\\/g, '\\\\') 74 | .replace(/"/g, '\\"') 75 | .replace(/\0/g, '\\0') 76 | .replace(/\t/g, '\\t') 77 | .replace(/\n/g, '\\n') 78 | .replace(/\r/g, '\\r') 79 | .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) 80 | .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); 81 | } 82 | 83 | function classEscape(s) { 84 | return s 85 | .replace(/\\/g, '\\\\') 86 | .replace(/\]/g, '\\]') 87 | .replace(/\^/g, '\\^') 88 | .replace(/-/g, '\\-') 89 | .replace(/\0/g, '\\0') 90 | .replace(/\t/g, '\\t') 91 | .replace(/\n/g, '\\n') 92 | .replace(/\r/g, '\\r') 93 | .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) 94 | .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); 95 | } 96 | 97 | function describeExpectation(expectation) { 98 | return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); 99 | } 100 | 101 | function describeExpected(expected) { 102 | var descriptions = new Array(expected.length), 103 | i, j; 104 | 105 | for (i = 0; i < expected.length; i++) { 106 | descriptions[i] = describeExpectation(expected[i]); 107 | } 108 | 109 | descriptions.sort(); 110 | 111 | if (descriptions.length > 0) { 112 | for (i = 1, j = 1; i < descriptions.length; i++) { 113 | if (descriptions[i - 1] !== descriptions[i]) { 114 | descriptions[j] = descriptions[i]; 115 | j++; 116 | } 117 | } 118 | descriptions.length = j; 119 | } 120 | 121 | switch (descriptions.length) { 122 | case 1: 123 | return descriptions[0]; 124 | 125 | case 2: 126 | return descriptions[0] + " or " + descriptions[1]; 127 | 128 | default: 129 | return descriptions.slice(0, -1).join(", ") 130 | + ", or " 131 | + descriptions[descriptions.length - 1]; 132 | } 133 | } 134 | 135 | function describeFound(found) { 136 | return found ? "\"" + literalEscape(found) + "\"" : "end of input"; 137 | } 138 | 139 | return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; 140 | }; 141 | 142 | function peg$parse(input, options) { 143 | options = options !== void 0 ? options : {}; 144 | 145 | var peg$FAILED = {}, 146 | 147 | peg$startRuleFunctions = { start: peg$parsestart }, 148 | peg$startRuleFunction = peg$parsestart, 149 | 150 | peg$c0 = function(ss) { 151 | return ss.length === 1 ? ss[0] : { type: 'matches', selectors: ss }; 152 | }, 153 | peg$c1 = function() { return void 0; }, 154 | peg$c2 = " ", 155 | peg$c3 = peg$literalExpectation(" ", false), 156 | peg$c4 = /^[^ [\],():#!=><~+.]/, 157 | peg$c5 = peg$classExpectation([" ", "[", "]", ",", "(", ")", ":", "#", "!", "=", ">", "<", "~", "+", "."], true, false), 158 | peg$c6 = function(i) { return i.join(''); }, 159 | peg$c7 = ">", 160 | peg$c8 = peg$literalExpectation(">", false), 161 | peg$c9 = function() { return 'child'; }, 162 | peg$c10 = "~", 163 | peg$c11 = peg$literalExpectation("~", false), 164 | peg$c12 = function() { return 'sibling'; }, 165 | peg$c13 = "+", 166 | peg$c14 = peg$literalExpectation("+", false), 167 | peg$c15 = function() { return 'adjacent'; }, 168 | peg$c16 = function() { return 'descendant'; }, 169 | peg$c17 = ",", 170 | peg$c18 = peg$literalExpectation(",", false), 171 | peg$c19 = function(s, ss) { 172 | return [s].concat(ss.map(function (s) { return s[3]; })); 173 | }, 174 | peg$c20 = function(op, s) { 175 | if (!op) return s; 176 | return { type: op, left: { type: 'exactNode' }, right: s }; 177 | }, 178 | peg$c21 = function(a, ops) { 179 | return ops.reduce(function (memo, rhs) { 180 | return { type: rhs[0], left: memo, right: rhs[1] }; 181 | }, a); 182 | }, 183 | peg$c22 = "!", 184 | peg$c23 = peg$literalExpectation("!", false), 185 | peg$c24 = function(subject, as) { 186 | const b = as.length === 1 ? as[0] : { type: 'compound', selectors: as }; 187 | if(subject) b.subject = true; 188 | return b; 189 | }, 190 | peg$c25 = "*", 191 | peg$c26 = peg$literalExpectation("*", false), 192 | peg$c27 = function(a) { return { type: 'wildcard', value: a }; }, 193 | peg$c28 = "#", 194 | peg$c29 = peg$literalExpectation("#", false), 195 | peg$c30 = function(i) { return { type: 'identifier', value: i }; }, 196 | peg$c31 = "[", 197 | peg$c32 = peg$literalExpectation("[", false), 198 | peg$c33 = "]", 199 | peg$c34 = peg$literalExpectation("]", false), 200 | peg$c35 = function(v) { return v; }, 201 | peg$c36 = /^[>", "<", "!"], false, false), 203 | peg$c38 = "=", 204 | peg$c39 = peg$literalExpectation("=", false), 205 | peg$c40 = function(a) { return (a || '') + '='; }, 206 | peg$c41 = /^[><]/, 207 | peg$c42 = peg$classExpectation([">", "<"], false, false), 208 | peg$c43 = ".", 209 | peg$c44 = peg$literalExpectation(".", false), 210 | peg$c45 = function(a, as) { 211 | return [].concat.apply([a], as).join(''); 212 | }, 213 | peg$c46 = function(name, op, value) { 214 | return { type: 'attribute', name: name, operator: op, value: value }; 215 | }, 216 | peg$c47 = function(name) { return { type: 'attribute', name: name }; }, 217 | peg$c48 = "\"", 218 | peg$c49 = peg$literalExpectation("\"", false), 219 | peg$c50 = /^[^\\"]/, 220 | peg$c51 = peg$classExpectation(["\\", "\""], true, false), 221 | peg$c52 = "\\", 222 | peg$c53 = peg$literalExpectation("\\", false), 223 | peg$c54 = peg$anyExpectation(), 224 | peg$c55 = function(a, b) { return a + b; }, 225 | peg$c56 = function(d) { 226 | return { type: 'literal', value: strUnescape(d.join('')) }; 227 | }, 228 | peg$c57 = "'", 229 | peg$c58 = peg$literalExpectation("'", false), 230 | peg$c59 = /^[^\\']/, 231 | peg$c60 = peg$classExpectation(["\\", "'"], true, false), 232 | peg$c61 = /^[0-9]/, 233 | peg$c62 = peg$classExpectation([["0", "9"]], false, false), 234 | peg$c63 = function(a, b) { 235 | // Can use `a.flat().join('')` once supported 236 | const leadingDecimals = a ? [].concat.apply([], a).join('') : ''; 237 | return { type: 'literal', value: parseFloat(leadingDecimals + b.join('')) }; 238 | }, 239 | peg$c64 = function(i) { return { type: 'literal', value: i }; }, 240 | peg$c65 = "type(", 241 | peg$c66 = peg$literalExpectation("type(", false), 242 | peg$c67 = /^[^ )]/, 243 | peg$c68 = peg$classExpectation([" ", ")"], true, false), 244 | peg$c69 = ")", 245 | peg$c70 = peg$literalExpectation(")", false), 246 | peg$c71 = function(t) { return { type: 'type', value: t.join('') }; }, 247 | peg$c72 = /^[imsu]/, 248 | peg$c73 = peg$classExpectation(["i", "m", "s", "u"], false, false), 249 | peg$c74 = "/", 250 | peg$c75 = peg$literalExpectation("/", false), 251 | peg$c76 = /^[^\/]/, 252 | peg$c77 = peg$classExpectation(["/"], true, false), 253 | peg$c78 = function(d, flgs) { return { 254 | type: 'regexp', value: new RegExp(d.join(''), flgs ? flgs.join('') : '') }; 255 | }, 256 | peg$c79 = function(i, is) { 257 | return { type: 'field', name: is.reduce(function(memo, p){ return memo + p[0] + p[1]; }, i)}; 258 | }, 259 | peg$c80 = ":not(", 260 | peg$c81 = peg$literalExpectation(":not(", false), 261 | peg$c82 = function(ss) { return { type: 'not', selectors: ss }; }, 262 | peg$c83 = ":matches(", 263 | peg$c84 = peg$literalExpectation(":matches(", false), 264 | peg$c85 = function(ss) { return { type: 'matches', selectors: ss }; }, 265 | peg$c86 = ":is(", 266 | peg$c87 = peg$literalExpectation(":is(", false), 267 | peg$c88 = ":has(", 268 | peg$c89 = peg$literalExpectation(":has(", false), 269 | peg$c90 = function(ss) { return { type: 'has', selectors: ss }; }, 270 | peg$c91 = ":first-child", 271 | peg$c92 = peg$literalExpectation(":first-child", false), 272 | peg$c93 = function() { return nth(1); }, 273 | peg$c94 = ":last-child", 274 | peg$c95 = peg$literalExpectation(":last-child", false), 275 | peg$c96 = function() { return nthLast(1); }, 276 | peg$c97 = ":nth-child(", 277 | peg$c98 = peg$literalExpectation(":nth-child(", false), 278 | peg$c99 = function(n) { return nth(parseInt(n.join(''), 10)); }, 279 | peg$c100 = ":nth-last-child(", 280 | peg$c101 = peg$literalExpectation(":nth-last-child(", false), 281 | peg$c102 = function(n) { return nthLast(parseInt(n.join(''), 10)); }, 282 | peg$c103 = ":", 283 | peg$c104 = peg$literalExpectation(":", false), 284 | peg$c105 = function(c) { 285 | return { type: 'class', name: c }; 286 | }, 287 | 288 | peg$currPos = 0, 289 | peg$savedPos = 0, 290 | peg$posDetailsCache = [{ line: 1, column: 1 }], 291 | peg$maxFailPos = 0, 292 | peg$maxFailExpected = [], 293 | peg$silentFails = 0, 294 | 295 | peg$resultsCache = {}, 296 | 297 | peg$result; 298 | 299 | if ("startRule" in options) { 300 | if (!(options.startRule in peg$startRuleFunctions)) { 301 | throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); 302 | } 303 | 304 | peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; 305 | } 306 | 307 | function text() { 308 | return input.substring(peg$savedPos, peg$currPos); 309 | } 310 | 311 | function location() { 312 | return peg$computeLocation(peg$savedPos, peg$currPos); 313 | } 314 | 315 | function expected(description, location) { 316 | location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) 317 | 318 | throw peg$buildStructuredError( 319 | [peg$otherExpectation(description)], 320 | input.substring(peg$savedPos, peg$currPos), 321 | location 322 | ); 323 | } 324 | 325 | function error(message, location) { 326 | location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) 327 | 328 | throw peg$buildSimpleError(message, location); 329 | } 330 | 331 | function peg$literalExpectation(text, ignoreCase) { 332 | return { type: "literal", text: text, ignoreCase: ignoreCase }; 333 | } 334 | 335 | function peg$classExpectation(parts, inverted, ignoreCase) { 336 | return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; 337 | } 338 | 339 | function peg$anyExpectation() { 340 | return { type: "any" }; 341 | } 342 | 343 | function peg$endExpectation() { 344 | return { type: "end" }; 345 | } 346 | 347 | function peg$otherExpectation(description) { 348 | return { type: "other", description: description }; 349 | } 350 | 351 | function peg$computePosDetails(pos) { 352 | var details = peg$posDetailsCache[pos], p; 353 | 354 | if (details) { 355 | return details; 356 | } else { 357 | p = pos - 1; 358 | while (!peg$posDetailsCache[p]) { 359 | p--; 360 | } 361 | 362 | details = peg$posDetailsCache[p]; 363 | details = { 364 | line: details.line, 365 | column: details.column 366 | }; 367 | 368 | while (p < pos) { 369 | if (input.charCodeAt(p) === 10) { 370 | details.line++; 371 | details.column = 1; 372 | } else { 373 | details.column++; 374 | } 375 | 376 | p++; 377 | } 378 | 379 | peg$posDetailsCache[pos] = details; 380 | return details; 381 | } 382 | } 383 | 384 | function peg$computeLocation(startPos, endPos) { 385 | var startPosDetails = peg$computePosDetails(startPos), 386 | endPosDetails = peg$computePosDetails(endPos); 387 | 388 | return { 389 | start: { 390 | offset: startPos, 391 | line: startPosDetails.line, 392 | column: startPosDetails.column 393 | }, 394 | end: { 395 | offset: endPos, 396 | line: endPosDetails.line, 397 | column: endPosDetails.column 398 | } 399 | }; 400 | } 401 | 402 | function peg$fail(expected) { 403 | if (peg$currPos < peg$maxFailPos) { return; } 404 | 405 | if (peg$currPos > peg$maxFailPos) { 406 | peg$maxFailPos = peg$currPos; 407 | peg$maxFailExpected = []; 408 | } 409 | 410 | peg$maxFailExpected.push(expected); 411 | } 412 | 413 | function peg$buildSimpleError(message, location) { 414 | return new peg$SyntaxError(message, null, null, location); 415 | } 416 | 417 | function peg$buildStructuredError(expected, found, location) { 418 | return new peg$SyntaxError( 419 | peg$SyntaxError.buildMessage(expected, found), 420 | expected, 421 | found, 422 | location 423 | ); 424 | } 425 | 426 | function peg$parsestart() { 427 | var s0, s1, s2, s3; 428 | 429 | var key = peg$currPos * 33 + 0, 430 | cached = peg$resultsCache[key]; 431 | 432 | if (cached) { 433 | peg$currPos = cached.nextPos; 434 | 435 | return cached.result; 436 | } 437 | 438 | s0 = peg$currPos; 439 | s1 = peg$parse_(); 440 | if (s1 !== peg$FAILED) { 441 | s2 = peg$parseselectors(); 442 | if (s2 !== peg$FAILED) { 443 | s3 = peg$parse_(); 444 | if (s3 !== peg$FAILED) { 445 | peg$savedPos = s0; 446 | s1 = peg$c0(s2); 447 | s0 = s1; 448 | } else { 449 | peg$currPos = s0; 450 | s0 = peg$FAILED; 451 | } 452 | } else { 453 | peg$currPos = s0; 454 | s0 = peg$FAILED; 455 | } 456 | } else { 457 | peg$currPos = s0; 458 | s0 = peg$FAILED; 459 | } 460 | if (s0 === peg$FAILED) { 461 | s0 = peg$currPos; 462 | s1 = peg$parse_(); 463 | if (s1 !== peg$FAILED) { 464 | peg$savedPos = s0; 465 | s1 = peg$c1(); 466 | } 467 | s0 = s1; 468 | } 469 | 470 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 471 | 472 | return s0; 473 | } 474 | 475 | function peg$parse_() { 476 | var s0, s1; 477 | 478 | var key = peg$currPos * 33 + 1, 479 | cached = peg$resultsCache[key]; 480 | 481 | if (cached) { 482 | peg$currPos = cached.nextPos; 483 | 484 | return cached.result; 485 | } 486 | 487 | s0 = []; 488 | if (input.charCodeAt(peg$currPos) === 32) { 489 | s1 = peg$c2; 490 | peg$currPos++; 491 | } else { 492 | s1 = peg$FAILED; 493 | if (peg$silentFails === 0) { peg$fail(peg$c3); } 494 | } 495 | while (s1 !== peg$FAILED) { 496 | s0.push(s1); 497 | if (input.charCodeAt(peg$currPos) === 32) { 498 | s1 = peg$c2; 499 | peg$currPos++; 500 | } else { 501 | s1 = peg$FAILED; 502 | if (peg$silentFails === 0) { peg$fail(peg$c3); } 503 | } 504 | } 505 | 506 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 507 | 508 | return s0; 509 | } 510 | 511 | function peg$parseidentifierName() { 512 | var s0, s1, s2; 513 | 514 | var key = peg$currPos * 33 + 2, 515 | cached = peg$resultsCache[key]; 516 | 517 | if (cached) { 518 | peg$currPos = cached.nextPos; 519 | 520 | return cached.result; 521 | } 522 | 523 | s0 = peg$currPos; 524 | s1 = []; 525 | if (peg$c4.test(input.charAt(peg$currPos))) { 526 | s2 = input.charAt(peg$currPos); 527 | peg$currPos++; 528 | } else { 529 | s2 = peg$FAILED; 530 | if (peg$silentFails === 0) { peg$fail(peg$c5); } 531 | } 532 | if (s2 !== peg$FAILED) { 533 | while (s2 !== peg$FAILED) { 534 | s1.push(s2); 535 | if (peg$c4.test(input.charAt(peg$currPos))) { 536 | s2 = input.charAt(peg$currPos); 537 | peg$currPos++; 538 | } else { 539 | s2 = peg$FAILED; 540 | if (peg$silentFails === 0) { peg$fail(peg$c5); } 541 | } 542 | } 543 | } else { 544 | s1 = peg$FAILED; 545 | } 546 | if (s1 !== peg$FAILED) { 547 | peg$savedPos = s0; 548 | s1 = peg$c6(s1); 549 | } 550 | s0 = s1; 551 | 552 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 553 | 554 | return s0; 555 | } 556 | 557 | function peg$parsebinaryOp() { 558 | var s0, s1, s2, s3; 559 | 560 | var key = peg$currPos * 33 + 3, 561 | cached = peg$resultsCache[key]; 562 | 563 | if (cached) { 564 | peg$currPos = cached.nextPos; 565 | 566 | return cached.result; 567 | } 568 | 569 | s0 = peg$currPos; 570 | s1 = peg$parse_(); 571 | if (s1 !== peg$FAILED) { 572 | if (input.charCodeAt(peg$currPos) === 62) { 573 | s2 = peg$c7; 574 | peg$currPos++; 575 | } else { 576 | s2 = peg$FAILED; 577 | if (peg$silentFails === 0) { peg$fail(peg$c8); } 578 | } 579 | if (s2 !== peg$FAILED) { 580 | s3 = peg$parse_(); 581 | if (s3 !== peg$FAILED) { 582 | peg$savedPos = s0; 583 | s1 = peg$c9(); 584 | s0 = s1; 585 | } else { 586 | peg$currPos = s0; 587 | s0 = peg$FAILED; 588 | } 589 | } else { 590 | peg$currPos = s0; 591 | s0 = peg$FAILED; 592 | } 593 | } else { 594 | peg$currPos = s0; 595 | s0 = peg$FAILED; 596 | } 597 | if (s0 === peg$FAILED) { 598 | s0 = peg$currPos; 599 | s1 = peg$parse_(); 600 | if (s1 !== peg$FAILED) { 601 | if (input.charCodeAt(peg$currPos) === 126) { 602 | s2 = peg$c10; 603 | peg$currPos++; 604 | } else { 605 | s2 = peg$FAILED; 606 | if (peg$silentFails === 0) { peg$fail(peg$c11); } 607 | } 608 | if (s2 !== peg$FAILED) { 609 | s3 = peg$parse_(); 610 | if (s3 !== peg$FAILED) { 611 | peg$savedPos = s0; 612 | s1 = peg$c12(); 613 | s0 = s1; 614 | } else { 615 | peg$currPos = s0; 616 | s0 = peg$FAILED; 617 | } 618 | } else { 619 | peg$currPos = s0; 620 | s0 = peg$FAILED; 621 | } 622 | } else { 623 | peg$currPos = s0; 624 | s0 = peg$FAILED; 625 | } 626 | if (s0 === peg$FAILED) { 627 | s0 = peg$currPos; 628 | s1 = peg$parse_(); 629 | if (s1 !== peg$FAILED) { 630 | if (input.charCodeAt(peg$currPos) === 43) { 631 | s2 = peg$c13; 632 | peg$currPos++; 633 | } else { 634 | s2 = peg$FAILED; 635 | if (peg$silentFails === 0) { peg$fail(peg$c14); } 636 | } 637 | if (s2 !== peg$FAILED) { 638 | s3 = peg$parse_(); 639 | if (s3 !== peg$FAILED) { 640 | peg$savedPos = s0; 641 | s1 = peg$c15(); 642 | s0 = s1; 643 | } else { 644 | peg$currPos = s0; 645 | s0 = peg$FAILED; 646 | } 647 | } else { 648 | peg$currPos = s0; 649 | s0 = peg$FAILED; 650 | } 651 | } else { 652 | peg$currPos = s0; 653 | s0 = peg$FAILED; 654 | } 655 | if (s0 === peg$FAILED) { 656 | s0 = peg$currPos; 657 | if (input.charCodeAt(peg$currPos) === 32) { 658 | s1 = peg$c2; 659 | peg$currPos++; 660 | } else { 661 | s1 = peg$FAILED; 662 | if (peg$silentFails === 0) { peg$fail(peg$c3); } 663 | } 664 | if (s1 !== peg$FAILED) { 665 | s2 = peg$parse_(); 666 | if (s2 !== peg$FAILED) { 667 | peg$savedPos = s0; 668 | s1 = peg$c16(); 669 | s0 = s1; 670 | } else { 671 | peg$currPos = s0; 672 | s0 = peg$FAILED; 673 | } 674 | } else { 675 | peg$currPos = s0; 676 | s0 = peg$FAILED; 677 | } 678 | } 679 | } 680 | } 681 | 682 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 683 | 684 | return s0; 685 | } 686 | 687 | function peg$parsehasSelectors() { 688 | var s0, s1, s2, s3, s4, s5, s6, s7; 689 | 690 | var key = peg$currPos * 33 + 4, 691 | cached = peg$resultsCache[key]; 692 | 693 | if (cached) { 694 | peg$currPos = cached.nextPos; 695 | 696 | return cached.result; 697 | } 698 | 699 | s0 = peg$currPos; 700 | s1 = peg$parsehasSelector(); 701 | if (s1 !== peg$FAILED) { 702 | s2 = []; 703 | s3 = peg$currPos; 704 | s4 = peg$parse_(); 705 | if (s4 !== peg$FAILED) { 706 | if (input.charCodeAt(peg$currPos) === 44) { 707 | s5 = peg$c17; 708 | peg$currPos++; 709 | } else { 710 | s5 = peg$FAILED; 711 | if (peg$silentFails === 0) { peg$fail(peg$c18); } 712 | } 713 | if (s5 !== peg$FAILED) { 714 | s6 = peg$parse_(); 715 | if (s6 !== peg$FAILED) { 716 | s7 = peg$parsehasSelector(); 717 | if (s7 !== peg$FAILED) { 718 | s4 = [s4, s5, s6, s7]; 719 | s3 = s4; 720 | } else { 721 | peg$currPos = s3; 722 | s3 = peg$FAILED; 723 | } 724 | } else { 725 | peg$currPos = s3; 726 | s3 = peg$FAILED; 727 | } 728 | } else { 729 | peg$currPos = s3; 730 | s3 = peg$FAILED; 731 | } 732 | } else { 733 | peg$currPos = s3; 734 | s3 = peg$FAILED; 735 | } 736 | while (s3 !== peg$FAILED) { 737 | s2.push(s3); 738 | s3 = peg$currPos; 739 | s4 = peg$parse_(); 740 | if (s4 !== peg$FAILED) { 741 | if (input.charCodeAt(peg$currPos) === 44) { 742 | s5 = peg$c17; 743 | peg$currPos++; 744 | } else { 745 | s5 = peg$FAILED; 746 | if (peg$silentFails === 0) { peg$fail(peg$c18); } 747 | } 748 | if (s5 !== peg$FAILED) { 749 | s6 = peg$parse_(); 750 | if (s6 !== peg$FAILED) { 751 | s7 = peg$parsehasSelector(); 752 | if (s7 !== peg$FAILED) { 753 | s4 = [s4, s5, s6, s7]; 754 | s3 = s4; 755 | } else { 756 | peg$currPos = s3; 757 | s3 = peg$FAILED; 758 | } 759 | } else { 760 | peg$currPos = s3; 761 | s3 = peg$FAILED; 762 | } 763 | } else { 764 | peg$currPos = s3; 765 | s3 = peg$FAILED; 766 | } 767 | } else { 768 | peg$currPos = s3; 769 | s3 = peg$FAILED; 770 | } 771 | } 772 | if (s2 !== peg$FAILED) { 773 | peg$savedPos = s0; 774 | s1 = peg$c19(s1, s2); 775 | s0 = s1; 776 | } else { 777 | peg$currPos = s0; 778 | s0 = peg$FAILED; 779 | } 780 | } else { 781 | peg$currPos = s0; 782 | s0 = peg$FAILED; 783 | } 784 | 785 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 786 | 787 | return s0; 788 | } 789 | 790 | function peg$parseselectors() { 791 | var s0, s1, s2, s3, s4, s5, s6, s7; 792 | 793 | var key = peg$currPos * 33 + 5, 794 | cached = peg$resultsCache[key]; 795 | 796 | if (cached) { 797 | peg$currPos = cached.nextPos; 798 | 799 | return cached.result; 800 | } 801 | 802 | s0 = peg$currPos; 803 | s1 = peg$parseselector(); 804 | if (s1 !== peg$FAILED) { 805 | s2 = []; 806 | s3 = peg$currPos; 807 | s4 = peg$parse_(); 808 | if (s4 !== peg$FAILED) { 809 | if (input.charCodeAt(peg$currPos) === 44) { 810 | s5 = peg$c17; 811 | peg$currPos++; 812 | } else { 813 | s5 = peg$FAILED; 814 | if (peg$silentFails === 0) { peg$fail(peg$c18); } 815 | } 816 | if (s5 !== peg$FAILED) { 817 | s6 = peg$parse_(); 818 | if (s6 !== peg$FAILED) { 819 | s7 = peg$parseselector(); 820 | if (s7 !== peg$FAILED) { 821 | s4 = [s4, s5, s6, s7]; 822 | s3 = s4; 823 | } else { 824 | peg$currPos = s3; 825 | s3 = peg$FAILED; 826 | } 827 | } else { 828 | peg$currPos = s3; 829 | s3 = peg$FAILED; 830 | } 831 | } else { 832 | peg$currPos = s3; 833 | s3 = peg$FAILED; 834 | } 835 | } else { 836 | peg$currPos = s3; 837 | s3 = peg$FAILED; 838 | } 839 | while (s3 !== peg$FAILED) { 840 | s2.push(s3); 841 | s3 = peg$currPos; 842 | s4 = peg$parse_(); 843 | if (s4 !== peg$FAILED) { 844 | if (input.charCodeAt(peg$currPos) === 44) { 845 | s5 = peg$c17; 846 | peg$currPos++; 847 | } else { 848 | s5 = peg$FAILED; 849 | if (peg$silentFails === 0) { peg$fail(peg$c18); } 850 | } 851 | if (s5 !== peg$FAILED) { 852 | s6 = peg$parse_(); 853 | if (s6 !== peg$FAILED) { 854 | s7 = peg$parseselector(); 855 | if (s7 !== peg$FAILED) { 856 | s4 = [s4, s5, s6, s7]; 857 | s3 = s4; 858 | } else { 859 | peg$currPos = s3; 860 | s3 = peg$FAILED; 861 | } 862 | } else { 863 | peg$currPos = s3; 864 | s3 = peg$FAILED; 865 | } 866 | } else { 867 | peg$currPos = s3; 868 | s3 = peg$FAILED; 869 | } 870 | } else { 871 | peg$currPos = s3; 872 | s3 = peg$FAILED; 873 | } 874 | } 875 | if (s2 !== peg$FAILED) { 876 | peg$savedPos = s0; 877 | s1 = peg$c19(s1, s2); 878 | s0 = s1; 879 | } else { 880 | peg$currPos = s0; 881 | s0 = peg$FAILED; 882 | } 883 | } else { 884 | peg$currPos = s0; 885 | s0 = peg$FAILED; 886 | } 887 | 888 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 889 | 890 | return s0; 891 | } 892 | 893 | function peg$parsehasSelector() { 894 | var s0, s1, s2; 895 | 896 | var key = peg$currPos * 33 + 6, 897 | cached = peg$resultsCache[key]; 898 | 899 | if (cached) { 900 | peg$currPos = cached.nextPos; 901 | 902 | return cached.result; 903 | } 904 | 905 | s0 = peg$currPos; 906 | s1 = peg$parsebinaryOp(); 907 | if (s1 === peg$FAILED) { 908 | s1 = null; 909 | } 910 | if (s1 !== peg$FAILED) { 911 | s2 = peg$parseselector(); 912 | if (s2 !== peg$FAILED) { 913 | peg$savedPos = s0; 914 | s1 = peg$c20(s1, s2); 915 | s0 = s1; 916 | } else { 917 | peg$currPos = s0; 918 | s0 = peg$FAILED; 919 | } 920 | } else { 921 | peg$currPos = s0; 922 | s0 = peg$FAILED; 923 | } 924 | 925 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 926 | 927 | return s0; 928 | } 929 | 930 | function peg$parseselector() { 931 | var s0, s1, s2, s3, s4, s5; 932 | 933 | var key = peg$currPos * 33 + 7, 934 | cached = peg$resultsCache[key]; 935 | 936 | if (cached) { 937 | peg$currPos = cached.nextPos; 938 | 939 | return cached.result; 940 | } 941 | 942 | s0 = peg$currPos; 943 | s1 = peg$parsesequence(); 944 | if (s1 !== peg$FAILED) { 945 | s2 = []; 946 | s3 = peg$currPos; 947 | s4 = peg$parsebinaryOp(); 948 | if (s4 !== peg$FAILED) { 949 | s5 = peg$parsesequence(); 950 | if (s5 !== peg$FAILED) { 951 | s4 = [s4, s5]; 952 | s3 = s4; 953 | } else { 954 | peg$currPos = s3; 955 | s3 = peg$FAILED; 956 | } 957 | } else { 958 | peg$currPos = s3; 959 | s3 = peg$FAILED; 960 | } 961 | while (s3 !== peg$FAILED) { 962 | s2.push(s3); 963 | s3 = peg$currPos; 964 | s4 = peg$parsebinaryOp(); 965 | if (s4 !== peg$FAILED) { 966 | s5 = peg$parsesequence(); 967 | if (s5 !== peg$FAILED) { 968 | s4 = [s4, s5]; 969 | s3 = s4; 970 | } else { 971 | peg$currPos = s3; 972 | s3 = peg$FAILED; 973 | } 974 | } else { 975 | peg$currPos = s3; 976 | s3 = peg$FAILED; 977 | } 978 | } 979 | if (s2 !== peg$FAILED) { 980 | peg$savedPos = s0; 981 | s1 = peg$c21(s1, s2); 982 | s0 = s1; 983 | } else { 984 | peg$currPos = s0; 985 | s0 = peg$FAILED; 986 | } 987 | } else { 988 | peg$currPos = s0; 989 | s0 = peg$FAILED; 990 | } 991 | 992 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 993 | 994 | return s0; 995 | } 996 | 997 | function peg$parsesequence() { 998 | var s0, s1, s2, s3; 999 | 1000 | var key = peg$currPos * 33 + 8, 1001 | cached = peg$resultsCache[key]; 1002 | 1003 | if (cached) { 1004 | peg$currPos = cached.nextPos; 1005 | 1006 | return cached.result; 1007 | } 1008 | 1009 | s0 = peg$currPos; 1010 | if (input.charCodeAt(peg$currPos) === 33) { 1011 | s1 = peg$c22; 1012 | peg$currPos++; 1013 | } else { 1014 | s1 = peg$FAILED; 1015 | if (peg$silentFails === 0) { peg$fail(peg$c23); } 1016 | } 1017 | if (s1 === peg$FAILED) { 1018 | s1 = null; 1019 | } 1020 | if (s1 !== peg$FAILED) { 1021 | s2 = []; 1022 | s3 = peg$parseatom(); 1023 | if (s3 !== peg$FAILED) { 1024 | while (s3 !== peg$FAILED) { 1025 | s2.push(s3); 1026 | s3 = peg$parseatom(); 1027 | } 1028 | } else { 1029 | s2 = peg$FAILED; 1030 | } 1031 | if (s2 !== peg$FAILED) { 1032 | peg$savedPos = s0; 1033 | s1 = peg$c24(s1, s2); 1034 | s0 = s1; 1035 | } else { 1036 | peg$currPos = s0; 1037 | s0 = peg$FAILED; 1038 | } 1039 | } else { 1040 | peg$currPos = s0; 1041 | s0 = peg$FAILED; 1042 | } 1043 | 1044 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1045 | 1046 | return s0; 1047 | } 1048 | 1049 | function peg$parseatom() { 1050 | var s0; 1051 | 1052 | var key = peg$currPos * 33 + 9, 1053 | cached = peg$resultsCache[key]; 1054 | 1055 | if (cached) { 1056 | peg$currPos = cached.nextPos; 1057 | 1058 | return cached.result; 1059 | } 1060 | 1061 | s0 = peg$parsewildcard(); 1062 | if (s0 === peg$FAILED) { 1063 | s0 = peg$parseidentifier(); 1064 | if (s0 === peg$FAILED) { 1065 | s0 = peg$parseattr(); 1066 | if (s0 === peg$FAILED) { 1067 | s0 = peg$parsefield(); 1068 | if (s0 === peg$FAILED) { 1069 | s0 = peg$parsenegation(); 1070 | if (s0 === peg$FAILED) { 1071 | s0 = peg$parsematches(); 1072 | if (s0 === peg$FAILED) { 1073 | s0 = peg$parseis(); 1074 | if (s0 === peg$FAILED) { 1075 | s0 = peg$parsehas(); 1076 | if (s0 === peg$FAILED) { 1077 | s0 = peg$parsefirstChild(); 1078 | if (s0 === peg$FAILED) { 1079 | s0 = peg$parselastChild(); 1080 | if (s0 === peg$FAILED) { 1081 | s0 = peg$parsenthChild(); 1082 | if (s0 === peg$FAILED) { 1083 | s0 = peg$parsenthLastChild(); 1084 | if (s0 === peg$FAILED) { 1085 | s0 = peg$parseclass(); 1086 | } 1087 | } 1088 | } 1089 | } 1090 | } 1091 | } 1092 | } 1093 | } 1094 | } 1095 | } 1096 | } 1097 | } 1098 | 1099 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1100 | 1101 | return s0; 1102 | } 1103 | 1104 | function peg$parsewildcard() { 1105 | var s0, s1; 1106 | 1107 | var key = peg$currPos * 33 + 10, 1108 | cached = peg$resultsCache[key]; 1109 | 1110 | if (cached) { 1111 | peg$currPos = cached.nextPos; 1112 | 1113 | return cached.result; 1114 | } 1115 | 1116 | s0 = peg$currPos; 1117 | if (input.charCodeAt(peg$currPos) === 42) { 1118 | s1 = peg$c25; 1119 | peg$currPos++; 1120 | } else { 1121 | s1 = peg$FAILED; 1122 | if (peg$silentFails === 0) { peg$fail(peg$c26); } 1123 | } 1124 | if (s1 !== peg$FAILED) { 1125 | peg$savedPos = s0; 1126 | s1 = peg$c27(s1); 1127 | } 1128 | s0 = s1; 1129 | 1130 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1131 | 1132 | return s0; 1133 | } 1134 | 1135 | function peg$parseidentifier() { 1136 | var s0, s1, s2; 1137 | 1138 | var key = peg$currPos * 33 + 11, 1139 | cached = peg$resultsCache[key]; 1140 | 1141 | if (cached) { 1142 | peg$currPos = cached.nextPos; 1143 | 1144 | return cached.result; 1145 | } 1146 | 1147 | s0 = peg$currPos; 1148 | if (input.charCodeAt(peg$currPos) === 35) { 1149 | s1 = peg$c28; 1150 | peg$currPos++; 1151 | } else { 1152 | s1 = peg$FAILED; 1153 | if (peg$silentFails === 0) { peg$fail(peg$c29); } 1154 | } 1155 | if (s1 === peg$FAILED) { 1156 | s1 = null; 1157 | } 1158 | if (s1 !== peg$FAILED) { 1159 | s2 = peg$parseidentifierName(); 1160 | if (s2 !== peg$FAILED) { 1161 | peg$savedPos = s0; 1162 | s1 = peg$c30(s2); 1163 | s0 = s1; 1164 | } else { 1165 | peg$currPos = s0; 1166 | s0 = peg$FAILED; 1167 | } 1168 | } else { 1169 | peg$currPos = s0; 1170 | s0 = peg$FAILED; 1171 | } 1172 | 1173 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1174 | 1175 | return s0; 1176 | } 1177 | 1178 | function peg$parseattr() { 1179 | var s0, s1, s2, s3, s4, s5; 1180 | 1181 | var key = peg$currPos * 33 + 12, 1182 | cached = peg$resultsCache[key]; 1183 | 1184 | if (cached) { 1185 | peg$currPos = cached.nextPos; 1186 | 1187 | return cached.result; 1188 | } 1189 | 1190 | s0 = peg$currPos; 1191 | if (input.charCodeAt(peg$currPos) === 91) { 1192 | s1 = peg$c31; 1193 | peg$currPos++; 1194 | } else { 1195 | s1 = peg$FAILED; 1196 | if (peg$silentFails === 0) { peg$fail(peg$c32); } 1197 | } 1198 | if (s1 !== peg$FAILED) { 1199 | s2 = peg$parse_(); 1200 | if (s2 !== peg$FAILED) { 1201 | s3 = peg$parseattrValue(); 1202 | if (s3 !== peg$FAILED) { 1203 | s4 = peg$parse_(); 1204 | if (s4 !== peg$FAILED) { 1205 | if (input.charCodeAt(peg$currPos) === 93) { 1206 | s5 = peg$c33; 1207 | peg$currPos++; 1208 | } else { 1209 | s5 = peg$FAILED; 1210 | if (peg$silentFails === 0) { peg$fail(peg$c34); } 1211 | } 1212 | if (s5 !== peg$FAILED) { 1213 | peg$savedPos = s0; 1214 | s1 = peg$c35(s3); 1215 | s0 = s1; 1216 | } else { 1217 | peg$currPos = s0; 1218 | s0 = peg$FAILED; 1219 | } 1220 | } else { 1221 | peg$currPos = s0; 1222 | s0 = peg$FAILED; 1223 | } 1224 | } else { 1225 | peg$currPos = s0; 1226 | s0 = peg$FAILED; 1227 | } 1228 | } else { 1229 | peg$currPos = s0; 1230 | s0 = peg$FAILED; 1231 | } 1232 | } else { 1233 | peg$currPos = s0; 1234 | s0 = peg$FAILED; 1235 | } 1236 | 1237 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1238 | 1239 | return s0; 1240 | } 1241 | 1242 | function peg$parseattrOps() { 1243 | var s0, s1, s2; 1244 | 1245 | var key = peg$currPos * 33 + 13, 1246 | cached = peg$resultsCache[key]; 1247 | 1248 | if (cached) { 1249 | peg$currPos = cached.nextPos; 1250 | 1251 | return cached.result; 1252 | } 1253 | 1254 | s0 = peg$currPos; 1255 | if (peg$c36.test(input.charAt(peg$currPos))) { 1256 | s1 = input.charAt(peg$currPos); 1257 | peg$currPos++; 1258 | } else { 1259 | s1 = peg$FAILED; 1260 | if (peg$silentFails === 0) { peg$fail(peg$c37); } 1261 | } 1262 | if (s1 === peg$FAILED) { 1263 | s1 = null; 1264 | } 1265 | if (s1 !== peg$FAILED) { 1266 | if (input.charCodeAt(peg$currPos) === 61) { 1267 | s2 = peg$c38; 1268 | peg$currPos++; 1269 | } else { 1270 | s2 = peg$FAILED; 1271 | if (peg$silentFails === 0) { peg$fail(peg$c39); } 1272 | } 1273 | if (s2 !== peg$FAILED) { 1274 | peg$savedPos = s0; 1275 | s1 = peg$c40(s1); 1276 | s0 = s1; 1277 | } else { 1278 | peg$currPos = s0; 1279 | s0 = peg$FAILED; 1280 | } 1281 | } else { 1282 | peg$currPos = s0; 1283 | s0 = peg$FAILED; 1284 | } 1285 | if (s0 === peg$FAILED) { 1286 | if (peg$c41.test(input.charAt(peg$currPos))) { 1287 | s0 = input.charAt(peg$currPos); 1288 | peg$currPos++; 1289 | } else { 1290 | s0 = peg$FAILED; 1291 | if (peg$silentFails === 0) { peg$fail(peg$c42); } 1292 | } 1293 | } 1294 | 1295 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1296 | 1297 | return s0; 1298 | } 1299 | 1300 | function peg$parseattrEqOps() { 1301 | var s0, s1, s2; 1302 | 1303 | var key = peg$currPos * 33 + 14, 1304 | cached = peg$resultsCache[key]; 1305 | 1306 | if (cached) { 1307 | peg$currPos = cached.nextPos; 1308 | 1309 | return cached.result; 1310 | } 1311 | 1312 | s0 = peg$currPos; 1313 | if (input.charCodeAt(peg$currPos) === 33) { 1314 | s1 = peg$c22; 1315 | peg$currPos++; 1316 | } else { 1317 | s1 = peg$FAILED; 1318 | if (peg$silentFails === 0) { peg$fail(peg$c23); } 1319 | } 1320 | if (s1 === peg$FAILED) { 1321 | s1 = null; 1322 | } 1323 | if (s1 !== peg$FAILED) { 1324 | if (input.charCodeAt(peg$currPos) === 61) { 1325 | s2 = peg$c38; 1326 | peg$currPos++; 1327 | } else { 1328 | s2 = peg$FAILED; 1329 | if (peg$silentFails === 0) { peg$fail(peg$c39); } 1330 | } 1331 | if (s2 !== peg$FAILED) { 1332 | peg$savedPos = s0; 1333 | s1 = peg$c40(s1); 1334 | s0 = s1; 1335 | } else { 1336 | peg$currPos = s0; 1337 | s0 = peg$FAILED; 1338 | } 1339 | } else { 1340 | peg$currPos = s0; 1341 | s0 = peg$FAILED; 1342 | } 1343 | 1344 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1345 | 1346 | return s0; 1347 | } 1348 | 1349 | function peg$parseattrName() { 1350 | var s0, s1, s2, s3, s4, s5; 1351 | 1352 | var key = peg$currPos * 33 + 15, 1353 | cached = peg$resultsCache[key]; 1354 | 1355 | if (cached) { 1356 | peg$currPos = cached.nextPos; 1357 | 1358 | return cached.result; 1359 | } 1360 | 1361 | s0 = peg$currPos; 1362 | s1 = peg$parseidentifierName(); 1363 | if (s1 !== peg$FAILED) { 1364 | s2 = []; 1365 | s3 = peg$currPos; 1366 | if (input.charCodeAt(peg$currPos) === 46) { 1367 | s4 = peg$c43; 1368 | peg$currPos++; 1369 | } else { 1370 | s4 = peg$FAILED; 1371 | if (peg$silentFails === 0) { peg$fail(peg$c44); } 1372 | } 1373 | if (s4 !== peg$FAILED) { 1374 | s5 = peg$parseidentifierName(); 1375 | if (s5 !== peg$FAILED) { 1376 | s4 = [s4, s5]; 1377 | s3 = s4; 1378 | } else { 1379 | peg$currPos = s3; 1380 | s3 = peg$FAILED; 1381 | } 1382 | } else { 1383 | peg$currPos = s3; 1384 | s3 = peg$FAILED; 1385 | } 1386 | while (s3 !== peg$FAILED) { 1387 | s2.push(s3); 1388 | s3 = peg$currPos; 1389 | if (input.charCodeAt(peg$currPos) === 46) { 1390 | s4 = peg$c43; 1391 | peg$currPos++; 1392 | } else { 1393 | s4 = peg$FAILED; 1394 | if (peg$silentFails === 0) { peg$fail(peg$c44); } 1395 | } 1396 | if (s4 !== peg$FAILED) { 1397 | s5 = peg$parseidentifierName(); 1398 | if (s5 !== peg$FAILED) { 1399 | s4 = [s4, s5]; 1400 | s3 = s4; 1401 | } else { 1402 | peg$currPos = s3; 1403 | s3 = peg$FAILED; 1404 | } 1405 | } else { 1406 | peg$currPos = s3; 1407 | s3 = peg$FAILED; 1408 | } 1409 | } 1410 | if (s2 !== peg$FAILED) { 1411 | peg$savedPos = s0; 1412 | s1 = peg$c45(s1, s2); 1413 | s0 = s1; 1414 | } else { 1415 | peg$currPos = s0; 1416 | s0 = peg$FAILED; 1417 | } 1418 | } else { 1419 | peg$currPos = s0; 1420 | s0 = peg$FAILED; 1421 | } 1422 | 1423 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1424 | 1425 | return s0; 1426 | } 1427 | 1428 | function peg$parseattrValue() { 1429 | var s0, s1, s2, s3, s4, s5; 1430 | 1431 | var key = peg$currPos * 33 + 16, 1432 | cached = peg$resultsCache[key]; 1433 | 1434 | if (cached) { 1435 | peg$currPos = cached.nextPos; 1436 | 1437 | return cached.result; 1438 | } 1439 | 1440 | s0 = peg$currPos; 1441 | s1 = peg$parseattrName(); 1442 | if (s1 !== peg$FAILED) { 1443 | s2 = peg$parse_(); 1444 | if (s2 !== peg$FAILED) { 1445 | s3 = peg$parseattrEqOps(); 1446 | if (s3 !== peg$FAILED) { 1447 | s4 = peg$parse_(); 1448 | if (s4 !== peg$FAILED) { 1449 | s5 = peg$parsetype(); 1450 | if (s5 === peg$FAILED) { 1451 | s5 = peg$parseregex(); 1452 | } 1453 | if (s5 !== peg$FAILED) { 1454 | peg$savedPos = s0; 1455 | s1 = peg$c46(s1, s3, s5); 1456 | s0 = s1; 1457 | } else { 1458 | peg$currPos = s0; 1459 | s0 = peg$FAILED; 1460 | } 1461 | } else { 1462 | peg$currPos = s0; 1463 | s0 = peg$FAILED; 1464 | } 1465 | } else { 1466 | peg$currPos = s0; 1467 | s0 = peg$FAILED; 1468 | } 1469 | } else { 1470 | peg$currPos = s0; 1471 | s0 = peg$FAILED; 1472 | } 1473 | } else { 1474 | peg$currPos = s0; 1475 | s0 = peg$FAILED; 1476 | } 1477 | if (s0 === peg$FAILED) { 1478 | s0 = peg$currPos; 1479 | s1 = peg$parseattrName(); 1480 | if (s1 !== peg$FAILED) { 1481 | s2 = peg$parse_(); 1482 | if (s2 !== peg$FAILED) { 1483 | s3 = peg$parseattrOps(); 1484 | if (s3 !== peg$FAILED) { 1485 | s4 = peg$parse_(); 1486 | if (s4 !== peg$FAILED) { 1487 | s5 = peg$parsestring(); 1488 | if (s5 === peg$FAILED) { 1489 | s5 = peg$parsenumber(); 1490 | if (s5 === peg$FAILED) { 1491 | s5 = peg$parsepath(); 1492 | } 1493 | } 1494 | if (s5 !== peg$FAILED) { 1495 | peg$savedPos = s0; 1496 | s1 = peg$c46(s1, s3, s5); 1497 | s0 = s1; 1498 | } else { 1499 | peg$currPos = s0; 1500 | s0 = peg$FAILED; 1501 | } 1502 | } else { 1503 | peg$currPos = s0; 1504 | s0 = peg$FAILED; 1505 | } 1506 | } else { 1507 | peg$currPos = s0; 1508 | s0 = peg$FAILED; 1509 | } 1510 | } else { 1511 | peg$currPos = s0; 1512 | s0 = peg$FAILED; 1513 | } 1514 | } else { 1515 | peg$currPos = s0; 1516 | s0 = peg$FAILED; 1517 | } 1518 | if (s0 === peg$FAILED) { 1519 | s0 = peg$currPos; 1520 | s1 = peg$parseattrName(); 1521 | if (s1 !== peg$FAILED) { 1522 | peg$savedPos = s0; 1523 | s1 = peg$c47(s1); 1524 | } 1525 | s0 = s1; 1526 | } 1527 | } 1528 | 1529 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1530 | 1531 | return s0; 1532 | } 1533 | 1534 | function peg$parsestring() { 1535 | var s0, s1, s2, s3, s4, s5; 1536 | 1537 | var key = peg$currPos * 33 + 17, 1538 | cached = peg$resultsCache[key]; 1539 | 1540 | if (cached) { 1541 | peg$currPos = cached.nextPos; 1542 | 1543 | return cached.result; 1544 | } 1545 | 1546 | s0 = peg$currPos; 1547 | if (input.charCodeAt(peg$currPos) === 34) { 1548 | s1 = peg$c48; 1549 | peg$currPos++; 1550 | } else { 1551 | s1 = peg$FAILED; 1552 | if (peg$silentFails === 0) { peg$fail(peg$c49); } 1553 | } 1554 | if (s1 !== peg$FAILED) { 1555 | s2 = []; 1556 | if (peg$c50.test(input.charAt(peg$currPos))) { 1557 | s3 = input.charAt(peg$currPos); 1558 | peg$currPos++; 1559 | } else { 1560 | s3 = peg$FAILED; 1561 | if (peg$silentFails === 0) { peg$fail(peg$c51); } 1562 | } 1563 | if (s3 === peg$FAILED) { 1564 | s3 = peg$currPos; 1565 | if (input.charCodeAt(peg$currPos) === 92) { 1566 | s4 = peg$c52; 1567 | peg$currPos++; 1568 | } else { 1569 | s4 = peg$FAILED; 1570 | if (peg$silentFails === 0) { peg$fail(peg$c53); } 1571 | } 1572 | if (s4 !== peg$FAILED) { 1573 | if (input.length > peg$currPos) { 1574 | s5 = input.charAt(peg$currPos); 1575 | peg$currPos++; 1576 | } else { 1577 | s5 = peg$FAILED; 1578 | if (peg$silentFails === 0) { peg$fail(peg$c54); } 1579 | } 1580 | if (s5 !== peg$FAILED) { 1581 | peg$savedPos = s3; 1582 | s4 = peg$c55(s4, s5); 1583 | s3 = s4; 1584 | } else { 1585 | peg$currPos = s3; 1586 | s3 = peg$FAILED; 1587 | } 1588 | } else { 1589 | peg$currPos = s3; 1590 | s3 = peg$FAILED; 1591 | } 1592 | } 1593 | while (s3 !== peg$FAILED) { 1594 | s2.push(s3); 1595 | if (peg$c50.test(input.charAt(peg$currPos))) { 1596 | s3 = input.charAt(peg$currPos); 1597 | peg$currPos++; 1598 | } else { 1599 | s3 = peg$FAILED; 1600 | if (peg$silentFails === 0) { peg$fail(peg$c51); } 1601 | } 1602 | if (s3 === peg$FAILED) { 1603 | s3 = peg$currPos; 1604 | if (input.charCodeAt(peg$currPos) === 92) { 1605 | s4 = peg$c52; 1606 | peg$currPos++; 1607 | } else { 1608 | s4 = peg$FAILED; 1609 | if (peg$silentFails === 0) { peg$fail(peg$c53); } 1610 | } 1611 | if (s4 !== peg$FAILED) { 1612 | if (input.length > peg$currPos) { 1613 | s5 = input.charAt(peg$currPos); 1614 | peg$currPos++; 1615 | } else { 1616 | s5 = peg$FAILED; 1617 | if (peg$silentFails === 0) { peg$fail(peg$c54); } 1618 | } 1619 | if (s5 !== peg$FAILED) { 1620 | peg$savedPos = s3; 1621 | s4 = peg$c55(s4, s5); 1622 | s3 = s4; 1623 | } else { 1624 | peg$currPos = s3; 1625 | s3 = peg$FAILED; 1626 | } 1627 | } else { 1628 | peg$currPos = s3; 1629 | s3 = peg$FAILED; 1630 | } 1631 | } 1632 | } 1633 | if (s2 !== peg$FAILED) { 1634 | if (input.charCodeAt(peg$currPos) === 34) { 1635 | s3 = peg$c48; 1636 | peg$currPos++; 1637 | } else { 1638 | s3 = peg$FAILED; 1639 | if (peg$silentFails === 0) { peg$fail(peg$c49); } 1640 | } 1641 | if (s3 !== peg$FAILED) { 1642 | peg$savedPos = s0; 1643 | s1 = peg$c56(s2); 1644 | s0 = s1; 1645 | } else { 1646 | peg$currPos = s0; 1647 | s0 = peg$FAILED; 1648 | } 1649 | } else { 1650 | peg$currPos = s0; 1651 | s0 = peg$FAILED; 1652 | } 1653 | } else { 1654 | peg$currPos = s0; 1655 | s0 = peg$FAILED; 1656 | } 1657 | if (s0 === peg$FAILED) { 1658 | s0 = peg$currPos; 1659 | if (input.charCodeAt(peg$currPos) === 39) { 1660 | s1 = peg$c57; 1661 | peg$currPos++; 1662 | } else { 1663 | s1 = peg$FAILED; 1664 | if (peg$silentFails === 0) { peg$fail(peg$c58); } 1665 | } 1666 | if (s1 !== peg$FAILED) { 1667 | s2 = []; 1668 | if (peg$c59.test(input.charAt(peg$currPos))) { 1669 | s3 = input.charAt(peg$currPos); 1670 | peg$currPos++; 1671 | } else { 1672 | s3 = peg$FAILED; 1673 | if (peg$silentFails === 0) { peg$fail(peg$c60); } 1674 | } 1675 | if (s3 === peg$FAILED) { 1676 | s3 = peg$currPos; 1677 | if (input.charCodeAt(peg$currPos) === 92) { 1678 | s4 = peg$c52; 1679 | peg$currPos++; 1680 | } else { 1681 | s4 = peg$FAILED; 1682 | if (peg$silentFails === 0) { peg$fail(peg$c53); } 1683 | } 1684 | if (s4 !== peg$FAILED) { 1685 | if (input.length > peg$currPos) { 1686 | s5 = input.charAt(peg$currPos); 1687 | peg$currPos++; 1688 | } else { 1689 | s5 = peg$FAILED; 1690 | if (peg$silentFails === 0) { peg$fail(peg$c54); } 1691 | } 1692 | if (s5 !== peg$FAILED) { 1693 | peg$savedPos = s3; 1694 | s4 = peg$c55(s4, s5); 1695 | s3 = s4; 1696 | } else { 1697 | peg$currPos = s3; 1698 | s3 = peg$FAILED; 1699 | } 1700 | } else { 1701 | peg$currPos = s3; 1702 | s3 = peg$FAILED; 1703 | } 1704 | } 1705 | while (s3 !== peg$FAILED) { 1706 | s2.push(s3); 1707 | if (peg$c59.test(input.charAt(peg$currPos))) { 1708 | s3 = input.charAt(peg$currPos); 1709 | peg$currPos++; 1710 | } else { 1711 | s3 = peg$FAILED; 1712 | if (peg$silentFails === 0) { peg$fail(peg$c60); } 1713 | } 1714 | if (s3 === peg$FAILED) { 1715 | s3 = peg$currPos; 1716 | if (input.charCodeAt(peg$currPos) === 92) { 1717 | s4 = peg$c52; 1718 | peg$currPos++; 1719 | } else { 1720 | s4 = peg$FAILED; 1721 | if (peg$silentFails === 0) { peg$fail(peg$c53); } 1722 | } 1723 | if (s4 !== peg$FAILED) { 1724 | if (input.length > peg$currPos) { 1725 | s5 = input.charAt(peg$currPos); 1726 | peg$currPos++; 1727 | } else { 1728 | s5 = peg$FAILED; 1729 | if (peg$silentFails === 0) { peg$fail(peg$c54); } 1730 | } 1731 | if (s5 !== peg$FAILED) { 1732 | peg$savedPos = s3; 1733 | s4 = peg$c55(s4, s5); 1734 | s3 = s4; 1735 | } else { 1736 | peg$currPos = s3; 1737 | s3 = peg$FAILED; 1738 | } 1739 | } else { 1740 | peg$currPos = s3; 1741 | s3 = peg$FAILED; 1742 | } 1743 | } 1744 | } 1745 | if (s2 !== peg$FAILED) { 1746 | if (input.charCodeAt(peg$currPos) === 39) { 1747 | s3 = peg$c57; 1748 | peg$currPos++; 1749 | } else { 1750 | s3 = peg$FAILED; 1751 | if (peg$silentFails === 0) { peg$fail(peg$c58); } 1752 | } 1753 | if (s3 !== peg$FAILED) { 1754 | peg$savedPos = s0; 1755 | s1 = peg$c56(s2); 1756 | s0 = s1; 1757 | } else { 1758 | peg$currPos = s0; 1759 | s0 = peg$FAILED; 1760 | } 1761 | } else { 1762 | peg$currPos = s0; 1763 | s0 = peg$FAILED; 1764 | } 1765 | } else { 1766 | peg$currPos = s0; 1767 | s0 = peg$FAILED; 1768 | } 1769 | } 1770 | 1771 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1772 | 1773 | return s0; 1774 | } 1775 | 1776 | function peg$parsenumber() { 1777 | var s0, s1, s2, s3; 1778 | 1779 | var key = peg$currPos * 33 + 18, 1780 | cached = peg$resultsCache[key]; 1781 | 1782 | if (cached) { 1783 | peg$currPos = cached.nextPos; 1784 | 1785 | return cached.result; 1786 | } 1787 | 1788 | s0 = peg$currPos; 1789 | s1 = peg$currPos; 1790 | s2 = []; 1791 | if (peg$c61.test(input.charAt(peg$currPos))) { 1792 | s3 = input.charAt(peg$currPos); 1793 | peg$currPos++; 1794 | } else { 1795 | s3 = peg$FAILED; 1796 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 1797 | } 1798 | while (s3 !== peg$FAILED) { 1799 | s2.push(s3); 1800 | if (peg$c61.test(input.charAt(peg$currPos))) { 1801 | s3 = input.charAt(peg$currPos); 1802 | peg$currPos++; 1803 | } else { 1804 | s3 = peg$FAILED; 1805 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 1806 | } 1807 | } 1808 | if (s2 !== peg$FAILED) { 1809 | if (input.charCodeAt(peg$currPos) === 46) { 1810 | s3 = peg$c43; 1811 | peg$currPos++; 1812 | } else { 1813 | s3 = peg$FAILED; 1814 | if (peg$silentFails === 0) { peg$fail(peg$c44); } 1815 | } 1816 | if (s3 !== peg$FAILED) { 1817 | s2 = [s2, s3]; 1818 | s1 = s2; 1819 | } else { 1820 | peg$currPos = s1; 1821 | s1 = peg$FAILED; 1822 | } 1823 | } else { 1824 | peg$currPos = s1; 1825 | s1 = peg$FAILED; 1826 | } 1827 | if (s1 === peg$FAILED) { 1828 | s1 = null; 1829 | } 1830 | if (s1 !== peg$FAILED) { 1831 | s2 = []; 1832 | if (peg$c61.test(input.charAt(peg$currPos))) { 1833 | s3 = input.charAt(peg$currPos); 1834 | peg$currPos++; 1835 | } else { 1836 | s3 = peg$FAILED; 1837 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 1838 | } 1839 | if (s3 !== peg$FAILED) { 1840 | while (s3 !== peg$FAILED) { 1841 | s2.push(s3); 1842 | if (peg$c61.test(input.charAt(peg$currPos))) { 1843 | s3 = input.charAt(peg$currPos); 1844 | peg$currPos++; 1845 | } else { 1846 | s3 = peg$FAILED; 1847 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 1848 | } 1849 | } 1850 | } else { 1851 | s2 = peg$FAILED; 1852 | } 1853 | if (s2 !== peg$FAILED) { 1854 | peg$savedPos = s0; 1855 | s1 = peg$c63(s1, s2); 1856 | s0 = s1; 1857 | } else { 1858 | peg$currPos = s0; 1859 | s0 = peg$FAILED; 1860 | } 1861 | } else { 1862 | peg$currPos = s0; 1863 | s0 = peg$FAILED; 1864 | } 1865 | 1866 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1867 | 1868 | return s0; 1869 | } 1870 | 1871 | function peg$parsepath() { 1872 | var s0, s1; 1873 | 1874 | var key = peg$currPos * 33 + 19, 1875 | cached = peg$resultsCache[key]; 1876 | 1877 | if (cached) { 1878 | peg$currPos = cached.nextPos; 1879 | 1880 | return cached.result; 1881 | } 1882 | 1883 | s0 = peg$currPos; 1884 | s1 = peg$parseidentifierName(); 1885 | if (s1 !== peg$FAILED) { 1886 | peg$savedPos = s0; 1887 | s1 = peg$c64(s1); 1888 | } 1889 | s0 = s1; 1890 | 1891 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1892 | 1893 | return s0; 1894 | } 1895 | 1896 | function peg$parsetype() { 1897 | var s0, s1, s2, s3, s4, s5; 1898 | 1899 | var key = peg$currPos * 33 + 20, 1900 | cached = peg$resultsCache[key]; 1901 | 1902 | if (cached) { 1903 | peg$currPos = cached.nextPos; 1904 | 1905 | return cached.result; 1906 | } 1907 | 1908 | s0 = peg$currPos; 1909 | if (input.substr(peg$currPos, 5) === peg$c65) { 1910 | s1 = peg$c65; 1911 | peg$currPos += 5; 1912 | } else { 1913 | s1 = peg$FAILED; 1914 | if (peg$silentFails === 0) { peg$fail(peg$c66); } 1915 | } 1916 | if (s1 !== peg$FAILED) { 1917 | s2 = peg$parse_(); 1918 | if (s2 !== peg$FAILED) { 1919 | s3 = []; 1920 | if (peg$c67.test(input.charAt(peg$currPos))) { 1921 | s4 = input.charAt(peg$currPos); 1922 | peg$currPos++; 1923 | } else { 1924 | s4 = peg$FAILED; 1925 | if (peg$silentFails === 0) { peg$fail(peg$c68); } 1926 | } 1927 | if (s4 !== peg$FAILED) { 1928 | while (s4 !== peg$FAILED) { 1929 | s3.push(s4); 1930 | if (peg$c67.test(input.charAt(peg$currPos))) { 1931 | s4 = input.charAt(peg$currPos); 1932 | peg$currPos++; 1933 | } else { 1934 | s4 = peg$FAILED; 1935 | if (peg$silentFails === 0) { peg$fail(peg$c68); } 1936 | } 1937 | } 1938 | } else { 1939 | s3 = peg$FAILED; 1940 | } 1941 | if (s3 !== peg$FAILED) { 1942 | s4 = peg$parse_(); 1943 | if (s4 !== peg$FAILED) { 1944 | if (input.charCodeAt(peg$currPos) === 41) { 1945 | s5 = peg$c69; 1946 | peg$currPos++; 1947 | } else { 1948 | s5 = peg$FAILED; 1949 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 1950 | } 1951 | if (s5 !== peg$FAILED) { 1952 | peg$savedPos = s0; 1953 | s1 = peg$c71(s3); 1954 | s0 = s1; 1955 | } else { 1956 | peg$currPos = s0; 1957 | s0 = peg$FAILED; 1958 | } 1959 | } else { 1960 | peg$currPos = s0; 1961 | s0 = peg$FAILED; 1962 | } 1963 | } else { 1964 | peg$currPos = s0; 1965 | s0 = peg$FAILED; 1966 | } 1967 | } else { 1968 | peg$currPos = s0; 1969 | s0 = peg$FAILED; 1970 | } 1971 | } else { 1972 | peg$currPos = s0; 1973 | s0 = peg$FAILED; 1974 | } 1975 | 1976 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 1977 | 1978 | return s0; 1979 | } 1980 | 1981 | function peg$parseflags() { 1982 | var s0, s1; 1983 | 1984 | var key = peg$currPos * 33 + 21, 1985 | cached = peg$resultsCache[key]; 1986 | 1987 | if (cached) { 1988 | peg$currPos = cached.nextPos; 1989 | 1990 | return cached.result; 1991 | } 1992 | 1993 | s0 = []; 1994 | if (peg$c72.test(input.charAt(peg$currPos))) { 1995 | s1 = input.charAt(peg$currPos); 1996 | peg$currPos++; 1997 | } else { 1998 | s1 = peg$FAILED; 1999 | if (peg$silentFails === 0) { peg$fail(peg$c73); } 2000 | } 2001 | if (s1 !== peg$FAILED) { 2002 | while (s1 !== peg$FAILED) { 2003 | s0.push(s1); 2004 | if (peg$c72.test(input.charAt(peg$currPos))) { 2005 | s1 = input.charAt(peg$currPos); 2006 | peg$currPos++; 2007 | } else { 2008 | s1 = peg$FAILED; 2009 | if (peg$silentFails === 0) { peg$fail(peg$c73); } 2010 | } 2011 | } 2012 | } else { 2013 | s0 = peg$FAILED; 2014 | } 2015 | 2016 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2017 | 2018 | return s0; 2019 | } 2020 | 2021 | function peg$parseregex() { 2022 | var s0, s1, s2, s3, s4; 2023 | 2024 | var key = peg$currPos * 33 + 22, 2025 | cached = peg$resultsCache[key]; 2026 | 2027 | if (cached) { 2028 | peg$currPos = cached.nextPos; 2029 | 2030 | return cached.result; 2031 | } 2032 | 2033 | s0 = peg$currPos; 2034 | if (input.charCodeAt(peg$currPos) === 47) { 2035 | s1 = peg$c74; 2036 | peg$currPos++; 2037 | } else { 2038 | s1 = peg$FAILED; 2039 | if (peg$silentFails === 0) { peg$fail(peg$c75); } 2040 | } 2041 | if (s1 !== peg$FAILED) { 2042 | s2 = []; 2043 | if (peg$c76.test(input.charAt(peg$currPos))) { 2044 | s3 = input.charAt(peg$currPos); 2045 | peg$currPos++; 2046 | } else { 2047 | s3 = peg$FAILED; 2048 | if (peg$silentFails === 0) { peg$fail(peg$c77); } 2049 | } 2050 | if (s3 !== peg$FAILED) { 2051 | while (s3 !== peg$FAILED) { 2052 | s2.push(s3); 2053 | if (peg$c76.test(input.charAt(peg$currPos))) { 2054 | s3 = input.charAt(peg$currPos); 2055 | peg$currPos++; 2056 | } else { 2057 | s3 = peg$FAILED; 2058 | if (peg$silentFails === 0) { peg$fail(peg$c77); } 2059 | } 2060 | } 2061 | } else { 2062 | s2 = peg$FAILED; 2063 | } 2064 | if (s2 !== peg$FAILED) { 2065 | if (input.charCodeAt(peg$currPos) === 47) { 2066 | s3 = peg$c74; 2067 | peg$currPos++; 2068 | } else { 2069 | s3 = peg$FAILED; 2070 | if (peg$silentFails === 0) { peg$fail(peg$c75); } 2071 | } 2072 | if (s3 !== peg$FAILED) { 2073 | s4 = peg$parseflags(); 2074 | if (s4 === peg$FAILED) { 2075 | s4 = null; 2076 | } 2077 | if (s4 !== peg$FAILED) { 2078 | peg$savedPos = s0; 2079 | s1 = peg$c78(s2, s4); 2080 | s0 = s1; 2081 | } else { 2082 | peg$currPos = s0; 2083 | s0 = peg$FAILED; 2084 | } 2085 | } else { 2086 | peg$currPos = s0; 2087 | s0 = peg$FAILED; 2088 | } 2089 | } else { 2090 | peg$currPos = s0; 2091 | s0 = peg$FAILED; 2092 | } 2093 | } else { 2094 | peg$currPos = s0; 2095 | s0 = peg$FAILED; 2096 | } 2097 | 2098 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2099 | 2100 | return s0; 2101 | } 2102 | 2103 | function peg$parsefield() { 2104 | var s0, s1, s2, s3, s4, s5, s6; 2105 | 2106 | var key = peg$currPos * 33 + 23, 2107 | cached = peg$resultsCache[key]; 2108 | 2109 | if (cached) { 2110 | peg$currPos = cached.nextPos; 2111 | 2112 | return cached.result; 2113 | } 2114 | 2115 | s0 = peg$currPos; 2116 | if (input.charCodeAt(peg$currPos) === 46) { 2117 | s1 = peg$c43; 2118 | peg$currPos++; 2119 | } else { 2120 | s1 = peg$FAILED; 2121 | if (peg$silentFails === 0) { peg$fail(peg$c44); } 2122 | } 2123 | if (s1 !== peg$FAILED) { 2124 | s2 = peg$parseidentifierName(); 2125 | if (s2 !== peg$FAILED) { 2126 | s3 = []; 2127 | s4 = peg$currPos; 2128 | if (input.charCodeAt(peg$currPos) === 46) { 2129 | s5 = peg$c43; 2130 | peg$currPos++; 2131 | } else { 2132 | s5 = peg$FAILED; 2133 | if (peg$silentFails === 0) { peg$fail(peg$c44); } 2134 | } 2135 | if (s5 !== peg$FAILED) { 2136 | s6 = peg$parseidentifierName(); 2137 | if (s6 !== peg$FAILED) { 2138 | s5 = [s5, s6]; 2139 | s4 = s5; 2140 | } else { 2141 | peg$currPos = s4; 2142 | s4 = peg$FAILED; 2143 | } 2144 | } else { 2145 | peg$currPos = s4; 2146 | s4 = peg$FAILED; 2147 | } 2148 | while (s4 !== peg$FAILED) { 2149 | s3.push(s4); 2150 | s4 = peg$currPos; 2151 | if (input.charCodeAt(peg$currPos) === 46) { 2152 | s5 = peg$c43; 2153 | peg$currPos++; 2154 | } else { 2155 | s5 = peg$FAILED; 2156 | if (peg$silentFails === 0) { peg$fail(peg$c44); } 2157 | } 2158 | if (s5 !== peg$FAILED) { 2159 | s6 = peg$parseidentifierName(); 2160 | if (s6 !== peg$FAILED) { 2161 | s5 = [s5, s6]; 2162 | s4 = s5; 2163 | } else { 2164 | peg$currPos = s4; 2165 | s4 = peg$FAILED; 2166 | } 2167 | } else { 2168 | peg$currPos = s4; 2169 | s4 = peg$FAILED; 2170 | } 2171 | } 2172 | if (s3 !== peg$FAILED) { 2173 | peg$savedPos = s0; 2174 | s1 = peg$c79(s2, s3); 2175 | s0 = s1; 2176 | } else { 2177 | peg$currPos = s0; 2178 | s0 = peg$FAILED; 2179 | } 2180 | } else { 2181 | peg$currPos = s0; 2182 | s0 = peg$FAILED; 2183 | } 2184 | } else { 2185 | peg$currPos = s0; 2186 | s0 = peg$FAILED; 2187 | } 2188 | 2189 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2190 | 2191 | return s0; 2192 | } 2193 | 2194 | function peg$parsenegation() { 2195 | var s0, s1, s2, s3, s4, s5; 2196 | 2197 | var key = peg$currPos * 33 + 24, 2198 | cached = peg$resultsCache[key]; 2199 | 2200 | if (cached) { 2201 | peg$currPos = cached.nextPos; 2202 | 2203 | return cached.result; 2204 | } 2205 | 2206 | s0 = peg$currPos; 2207 | if (input.substr(peg$currPos, 5) === peg$c80) { 2208 | s1 = peg$c80; 2209 | peg$currPos += 5; 2210 | } else { 2211 | s1 = peg$FAILED; 2212 | if (peg$silentFails === 0) { peg$fail(peg$c81); } 2213 | } 2214 | if (s1 !== peg$FAILED) { 2215 | s2 = peg$parse_(); 2216 | if (s2 !== peg$FAILED) { 2217 | s3 = peg$parseselectors(); 2218 | if (s3 !== peg$FAILED) { 2219 | s4 = peg$parse_(); 2220 | if (s4 !== peg$FAILED) { 2221 | if (input.charCodeAt(peg$currPos) === 41) { 2222 | s5 = peg$c69; 2223 | peg$currPos++; 2224 | } else { 2225 | s5 = peg$FAILED; 2226 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 2227 | } 2228 | if (s5 !== peg$FAILED) { 2229 | peg$savedPos = s0; 2230 | s1 = peg$c82(s3); 2231 | s0 = s1; 2232 | } else { 2233 | peg$currPos = s0; 2234 | s0 = peg$FAILED; 2235 | } 2236 | } else { 2237 | peg$currPos = s0; 2238 | s0 = peg$FAILED; 2239 | } 2240 | } else { 2241 | peg$currPos = s0; 2242 | s0 = peg$FAILED; 2243 | } 2244 | } else { 2245 | peg$currPos = s0; 2246 | s0 = peg$FAILED; 2247 | } 2248 | } else { 2249 | peg$currPos = s0; 2250 | s0 = peg$FAILED; 2251 | } 2252 | 2253 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2254 | 2255 | return s0; 2256 | } 2257 | 2258 | function peg$parsematches() { 2259 | var s0, s1, s2, s3, s4, s5; 2260 | 2261 | var key = peg$currPos * 33 + 25, 2262 | cached = peg$resultsCache[key]; 2263 | 2264 | if (cached) { 2265 | peg$currPos = cached.nextPos; 2266 | 2267 | return cached.result; 2268 | } 2269 | 2270 | s0 = peg$currPos; 2271 | if (input.substr(peg$currPos, 9) === peg$c83) { 2272 | s1 = peg$c83; 2273 | peg$currPos += 9; 2274 | } else { 2275 | s1 = peg$FAILED; 2276 | if (peg$silentFails === 0) { peg$fail(peg$c84); } 2277 | } 2278 | if (s1 !== peg$FAILED) { 2279 | s2 = peg$parse_(); 2280 | if (s2 !== peg$FAILED) { 2281 | s3 = peg$parseselectors(); 2282 | if (s3 !== peg$FAILED) { 2283 | s4 = peg$parse_(); 2284 | if (s4 !== peg$FAILED) { 2285 | if (input.charCodeAt(peg$currPos) === 41) { 2286 | s5 = peg$c69; 2287 | peg$currPos++; 2288 | } else { 2289 | s5 = peg$FAILED; 2290 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 2291 | } 2292 | if (s5 !== peg$FAILED) { 2293 | peg$savedPos = s0; 2294 | s1 = peg$c85(s3); 2295 | s0 = s1; 2296 | } else { 2297 | peg$currPos = s0; 2298 | s0 = peg$FAILED; 2299 | } 2300 | } else { 2301 | peg$currPos = s0; 2302 | s0 = peg$FAILED; 2303 | } 2304 | } else { 2305 | peg$currPos = s0; 2306 | s0 = peg$FAILED; 2307 | } 2308 | } else { 2309 | peg$currPos = s0; 2310 | s0 = peg$FAILED; 2311 | } 2312 | } else { 2313 | peg$currPos = s0; 2314 | s0 = peg$FAILED; 2315 | } 2316 | 2317 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2318 | 2319 | return s0; 2320 | } 2321 | 2322 | function peg$parseis() { 2323 | var s0, s1, s2, s3, s4, s5; 2324 | 2325 | var key = peg$currPos * 33 + 26, 2326 | cached = peg$resultsCache[key]; 2327 | 2328 | if (cached) { 2329 | peg$currPos = cached.nextPos; 2330 | 2331 | return cached.result; 2332 | } 2333 | 2334 | s0 = peg$currPos; 2335 | if (input.substr(peg$currPos, 4) === peg$c86) { 2336 | s1 = peg$c86; 2337 | peg$currPos += 4; 2338 | } else { 2339 | s1 = peg$FAILED; 2340 | if (peg$silentFails === 0) { peg$fail(peg$c87); } 2341 | } 2342 | if (s1 !== peg$FAILED) { 2343 | s2 = peg$parse_(); 2344 | if (s2 !== peg$FAILED) { 2345 | s3 = peg$parseselectors(); 2346 | if (s3 !== peg$FAILED) { 2347 | s4 = peg$parse_(); 2348 | if (s4 !== peg$FAILED) { 2349 | if (input.charCodeAt(peg$currPos) === 41) { 2350 | s5 = peg$c69; 2351 | peg$currPos++; 2352 | } else { 2353 | s5 = peg$FAILED; 2354 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 2355 | } 2356 | if (s5 !== peg$FAILED) { 2357 | peg$savedPos = s0; 2358 | s1 = peg$c85(s3); 2359 | s0 = s1; 2360 | } else { 2361 | peg$currPos = s0; 2362 | s0 = peg$FAILED; 2363 | } 2364 | } else { 2365 | peg$currPos = s0; 2366 | s0 = peg$FAILED; 2367 | } 2368 | } else { 2369 | peg$currPos = s0; 2370 | s0 = peg$FAILED; 2371 | } 2372 | } else { 2373 | peg$currPos = s0; 2374 | s0 = peg$FAILED; 2375 | } 2376 | } else { 2377 | peg$currPos = s0; 2378 | s0 = peg$FAILED; 2379 | } 2380 | 2381 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2382 | 2383 | return s0; 2384 | } 2385 | 2386 | function peg$parsehas() { 2387 | var s0, s1, s2, s3, s4, s5; 2388 | 2389 | var key = peg$currPos * 33 + 27, 2390 | cached = peg$resultsCache[key]; 2391 | 2392 | if (cached) { 2393 | peg$currPos = cached.nextPos; 2394 | 2395 | return cached.result; 2396 | } 2397 | 2398 | s0 = peg$currPos; 2399 | if (input.substr(peg$currPos, 5) === peg$c88) { 2400 | s1 = peg$c88; 2401 | peg$currPos += 5; 2402 | } else { 2403 | s1 = peg$FAILED; 2404 | if (peg$silentFails === 0) { peg$fail(peg$c89); } 2405 | } 2406 | if (s1 !== peg$FAILED) { 2407 | s2 = peg$parse_(); 2408 | if (s2 !== peg$FAILED) { 2409 | s3 = peg$parsehasSelectors(); 2410 | if (s3 !== peg$FAILED) { 2411 | s4 = peg$parse_(); 2412 | if (s4 !== peg$FAILED) { 2413 | if (input.charCodeAt(peg$currPos) === 41) { 2414 | s5 = peg$c69; 2415 | peg$currPos++; 2416 | } else { 2417 | s5 = peg$FAILED; 2418 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 2419 | } 2420 | if (s5 !== peg$FAILED) { 2421 | peg$savedPos = s0; 2422 | s1 = peg$c90(s3); 2423 | s0 = s1; 2424 | } else { 2425 | peg$currPos = s0; 2426 | s0 = peg$FAILED; 2427 | } 2428 | } else { 2429 | peg$currPos = s0; 2430 | s0 = peg$FAILED; 2431 | } 2432 | } else { 2433 | peg$currPos = s0; 2434 | s0 = peg$FAILED; 2435 | } 2436 | } else { 2437 | peg$currPos = s0; 2438 | s0 = peg$FAILED; 2439 | } 2440 | } else { 2441 | peg$currPos = s0; 2442 | s0 = peg$FAILED; 2443 | } 2444 | 2445 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2446 | 2447 | return s0; 2448 | } 2449 | 2450 | function peg$parsefirstChild() { 2451 | var s0, s1; 2452 | 2453 | var key = peg$currPos * 33 + 28, 2454 | cached = peg$resultsCache[key]; 2455 | 2456 | if (cached) { 2457 | peg$currPos = cached.nextPos; 2458 | 2459 | return cached.result; 2460 | } 2461 | 2462 | s0 = peg$currPos; 2463 | if (input.substr(peg$currPos, 12) === peg$c91) { 2464 | s1 = peg$c91; 2465 | peg$currPos += 12; 2466 | } else { 2467 | s1 = peg$FAILED; 2468 | if (peg$silentFails === 0) { peg$fail(peg$c92); } 2469 | } 2470 | if (s1 !== peg$FAILED) { 2471 | peg$savedPos = s0; 2472 | s1 = peg$c93(); 2473 | } 2474 | s0 = s1; 2475 | 2476 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2477 | 2478 | return s0; 2479 | } 2480 | 2481 | function peg$parselastChild() { 2482 | var s0, s1; 2483 | 2484 | var key = peg$currPos * 33 + 29, 2485 | cached = peg$resultsCache[key]; 2486 | 2487 | if (cached) { 2488 | peg$currPos = cached.nextPos; 2489 | 2490 | return cached.result; 2491 | } 2492 | 2493 | s0 = peg$currPos; 2494 | if (input.substr(peg$currPos, 11) === peg$c94) { 2495 | s1 = peg$c94; 2496 | peg$currPos += 11; 2497 | } else { 2498 | s1 = peg$FAILED; 2499 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2500 | } 2501 | if (s1 !== peg$FAILED) { 2502 | peg$savedPos = s0; 2503 | s1 = peg$c96(); 2504 | } 2505 | s0 = s1; 2506 | 2507 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2508 | 2509 | return s0; 2510 | } 2511 | 2512 | function peg$parsenthChild() { 2513 | var s0, s1, s2, s3, s4, s5; 2514 | 2515 | var key = peg$currPos * 33 + 30, 2516 | cached = peg$resultsCache[key]; 2517 | 2518 | if (cached) { 2519 | peg$currPos = cached.nextPos; 2520 | 2521 | return cached.result; 2522 | } 2523 | 2524 | s0 = peg$currPos; 2525 | if (input.substr(peg$currPos, 11) === peg$c97) { 2526 | s1 = peg$c97; 2527 | peg$currPos += 11; 2528 | } else { 2529 | s1 = peg$FAILED; 2530 | if (peg$silentFails === 0) { peg$fail(peg$c98); } 2531 | } 2532 | if (s1 !== peg$FAILED) { 2533 | s2 = peg$parse_(); 2534 | if (s2 !== peg$FAILED) { 2535 | s3 = []; 2536 | if (peg$c61.test(input.charAt(peg$currPos))) { 2537 | s4 = input.charAt(peg$currPos); 2538 | peg$currPos++; 2539 | } else { 2540 | s4 = peg$FAILED; 2541 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 2542 | } 2543 | if (s4 !== peg$FAILED) { 2544 | while (s4 !== peg$FAILED) { 2545 | s3.push(s4); 2546 | if (peg$c61.test(input.charAt(peg$currPos))) { 2547 | s4 = input.charAt(peg$currPos); 2548 | peg$currPos++; 2549 | } else { 2550 | s4 = peg$FAILED; 2551 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 2552 | } 2553 | } 2554 | } else { 2555 | s3 = peg$FAILED; 2556 | } 2557 | if (s3 !== peg$FAILED) { 2558 | s4 = peg$parse_(); 2559 | if (s4 !== peg$FAILED) { 2560 | if (input.charCodeAt(peg$currPos) === 41) { 2561 | s5 = peg$c69; 2562 | peg$currPos++; 2563 | } else { 2564 | s5 = peg$FAILED; 2565 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 2566 | } 2567 | if (s5 !== peg$FAILED) { 2568 | peg$savedPos = s0; 2569 | s1 = peg$c99(s3); 2570 | s0 = s1; 2571 | } else { 2572 | peg$currPos = s0; 2573 | s0 = peg$FAILED; 2574 | } 2575 | } else { 2576 | peg$currPos = s0; 2577 | s0 = peg$FAILED; 2578 | } 2579 | } else { 2580 | peg$currPos = s0; 2581 | s0 = peg$FAILED; 2582 | } 2583 | } else { 2584 | peg$currPos = s0; 2585 | s0 = peg$FAILED; 2586 | } 2587 | } else { 2588 | peg$currPos = s0; 2589 | s0 = peg$FAILED; 2590 | } 2591 | 2592 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2593 | 2594 | return s0; 2595 | } 2596 | 2597 | function peg$parsenthLastChild() { 2598 | var s0, s1, s2, s3, s4, s5; 2599 | 2600 | var key = peg$currPos * 33 + 31, 2601 | cached = peg$resultsCache[key]; 2602 | 2603 | if (cached) { 2604 | peg$currPos = cached.nextPos; 2605 | 2606 | return cached.result; 2607 | } 2608 | 2609 | s0 = peg$currPos; 2610 | if (input.substr(peg$currPos, 16) === peg$c100) { 2611 | s1 = peg$c100; 2612 | peg$currPos += 16; 2613 | } else { 2614 | s1 = peg$FAILED; 2615 | if (peg$silentFails === 0) { peg$fail(peg$c101); } 2616 | } 2617 | if (s1 !== peg$FAILED) { 2618 | s2 = peg$parse_(); 2619 | if (s2 !== peg$FAILED) { 2620 | s3 = []; 2621 | if (peg$c61.test(input.charAt(peg$currPos))) { 2622 | s4 = input.charAt(peg$currPos); 2623 | peg$currPos++; 2624 | } else { 2625 | s4 = peg$FAILED; 2626 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 2627 | } 2628 | if (s4 !== peg$FAILED) { 2629 | while (s4 !== peg$FAILED) { 2630 | s3.push(s4); 2631 | if (peg$c61.test(input.charAt(peg$currPos))) { 2632 | s4 = input.charAt(peg$currPos); 2633 | peg$currPos++; 2634 | } else { 2635 | s4 = peg$FAILED; 2636 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 2637 | } 2638 | } 2639 | } else { 2640 | s3 = peg$FAILED; 2641 | } 2642 | if (s3 !== peg$FAILED) { 2643 | s4 = peg$parse_(); 2644 | if (s4 !== peg$FAILED) { 2645 | if (input.charCodeAt(peg$currPos) === 41) { 2646 | s5 = peg$c69; 2647 | peg$currPos++; 2648 | } else { 2649 | s5 = peg$FAILED; 2650 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 2651 | } 2652 | if (s5 !== peg$FAILED) { 2653 | peg$savedPos = s0; 2654 | s1 = peg$c102(s3); 2655 | s0 = s1; 2656 | } else { 2657 | peg$currPos = s0; 2658 | s0 = peg$FAILED; 2659 | } 2660 | } else { 2661 | peg$currPos = s0; 2662 | s0 = peg$FAILED; 2663 | } 2664 | } else { 2665 | peg$currPos = s0; 2666 | s0 = peg$FAILED; 2667 | } 2668 | } else { 2669 | peg$currPos = s0; 2670 | s0 = peg$FAILED; 2671 | } 2672 | } else { 2673 | peg$currPos = s0; 2674 | s0 = peg$FAILED; 2675 | } 2676 | 2677 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2678 | 2679 | return s0; 2680 | } 2681 | 2682 | function peg$parseclass() { 2683 | var s0, s1, s2; 2684 | 2685 | var key = peg$currPos * 33 + 32, 2686 | cached = peg$resultsCache[key]; 2687 | 2688 | if (cached) { 2689 | peg$currPos = cached.nextPos; 2690 | 2691 | return cached.result; 2692 | } 2693 | 2694 | s0 = peg$currPos; 2695 | if (input.charCodeAt(peg$currPos) === 58) { 2696 | s1 = peg$c103; 2697 | peg$currPos++; 2698 | } else { 2699 | s1 = peg$FAILED; 2700 | if (peg$silentFails === 0) { peg$fail(peg$c104); } 2701 | } 2702 | if (s1 !== peg$FAILED) { 2703 | s2 = peg$parseidentifierName(); 2704 | if (s2 !== peg$FAILED) { 2705 | peg$savedPos = s0; 2706 | s1 = peg$c105(s2); 2707 | s0 = s1; 2708 | } else { 2709 | peg$currPos = s0; 2710 | s0 = peg$FAILED; 2711 | } 2712 | } else { 2713 | peg$currPos = s0; 2714 | s0 = peg$FAILED; 2715 | } 2716 | 2717 | peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; 2718 | 2719 | return s0; 2720 | } 2721 | 2722 | 2723 | function nth(n) { return { type: 'nth-child', index: { type: 'literal', value: n } }; } 2724 | function nthLast(n) { return { type: 'nth-last-child', index: { type: 'literal', value: n } }; } 2725 | function strUnescape(s) { 2726 | return s.replace(/\\(.)/g, function(match, ch) { 2727 | switch(ch) { 2728 | case 'b': return '\b'; 2729 | case 'f': return '\f'; 2730 | case 'n': return '\n'; 2731 | case 'r': return '\r'; 2732 | case 't': return '\t'; 2733 | case 'v': return '\v'; 2734 | default: return ch; 2735 | } 2736 | }); 2737 | } 2738 | 2739 | 2740 | peg$result = peg$startRuleFunction(); 2741 | 2742 | if (peg$result !== peg$FAILED && peg$currPos === input.length) { 2743 | return peg$result; 2744 | } else { 2745 | if (peg$result !== peg$FAILED && peg$currPos < input.length) { 2746 | peg$fail(peg$endExpectation()); 2747 | } 2748 | 2749 | throw peg$buildStructuredError( 2750 | peg$maxFailExpected, 2751 | peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, 2752 | peg$maxFailPos < input.length 2753 | ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) 2754 | : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) 2755 | ); 2756 | } 2757 | } 2758 | 2759 | return { 2760 | SyntaxError: peg$SyntaxError, 2761 | parse: peg$parse 2762 | }; 2763 | }); 2764 | --------------------------------------------------------------------------------