diff --git a/API usage.md b/API usage.md new file mode 100644 index 0000000..c95cb43 --- /dev/null +++ b/API usage.md @@ -0,0 +1,295 @@ +## API and usage + +*Install (via npm for Node.js)* + +`npm install esquery --save` + +*Quick start* + +```js +const esquery = require('esquery'); + +const conditional = 'if (x === 1) { foo(); } else { x = 2; }' + +var matches = esquery(conditional, '[name='x']') +console.log(matches); +``` + +The following examples are taken from the test cases in `/tests` folder. + +### Attribute query + +*for loop* + +`for (i = 0; i < foo.length; i++) { foo[i](); }` + +- `[operator="="]` - matches `i = 0` +- `[object.name="foo"]` - object named `foo` ie. `foo.length` +- `[name=/i|foo/]` - where name of node matches `i` or `foo` + +*simple function* + +```js +function foo(x, y) { + var z = x + y; + z++; + return z; +} +``` + +- `[kind="var"]` - a `var` +- `[id.name="foo"]` - `foo` declaration, fx a function or variable +- `[left]` - left expression, such as `var z` +- `FunctionDeclaration[params.0.name=x]` - where first argument of function is named `x` + +*simple program* + +```js +var x = 1; +var y = 'y'; +x = x * 2; +if (y) { y += 'z'; } +``` + +- `[body]` - full body, such as function/scope body +- `[body.length<2]` - body has less than 2 nodes +- `[body.length>1]` - body has more than 1 node +- `[body.length<=2]` - body has 2 or less nodes +- `[body.length>=1]` - body has 1 or more nodes +- `[name=/[asdfy]/]` - name matches the characters `asdfy` + +*conditional* + +```js +if (x === 1) { foo(); } else { x = 2; } +if (x == 'test' && true || x) { y = -1; } else if (false) { y = 1; } +``` + +- `[name="x"]` - node named `x` +- `[name!="x"]` - node not named `x` +- `[name=/x|foo/]` - node matches `x` or `foo` +- `[name!=/x|y/]` - node does not match `x` or `y` +- `[callee.name="foo"]` - Where `callee` is named `foo` (ie. function call) +- `[operator]` - That is any type of operator (such as `==`, `||` etc) +- `[prefix=true]` - node that has `prefix` set to true, such as `++c` +- `[test=type(object)]` - where subject of condition is an object, such as `x` in `|| x` +- `[value=type(boolean)]` - where value is a boolean, such +as `&& true` + +Example: `prefix` + +AST node representing unary operators such as `++`, `~`, `typeof` and `delete`" + +`++c` has `prefix: true` and `c++` has `prefix: false` + +```js +interface UnaryExpression <: Expression { + type: "UnaryExpression"; + operator: UnaryOperator; + prefix: boolean; + argument: Expression; +} +``` + +### Classes + +`:statement` - a statement +`:expression` - an expression +`:function` - a function +`:declaration` - a declaration +`:pattern` - a pattern + +Examples: + +`[name="x"]:function` - function named `x` +`[name="foo"]:declaration` - declaration named `foo` + +## Combinators + +- descendant selector (` ` space) +- child selector (`>`) +- adjacent sibling selector (`+`) +- general sibling selector (`~`) + +Please see [ESTree specification](https://github.com/estree/estree) and [CSS selectors spec](https://www.w3.org/TR/css3-selectors/) + +`IfStatement > BinaryExpression` - [binary expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators) fx `3+4;` or `x*y;` + +A `BinaryExpression` nested directly within an `IfStatement` + +Valid: `if (2 === 2)` + +`IfStatement > BinaryExpression > Identifier` + +Valid: `if (x === 2)` + +`IfStatement BinaryExpression` + +A binary expression nested arbitrarily deeply within an `if` statement. + +`VariableDeclaration ~ IfStatement` + +`if` statement with sibling `var` declaration. The `if` statement is the target. + +Valid: siblings of same body +```js +var x = 1; +if (x === 2) +``` + +`VariableDeclaration + ExpressionStatement` + +Valid: variable declaration followed by next sibling which is an expression statement (see [javascript statements and declarations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements)) + +```js +var y = 'y'; +x = x * 2; +``` + +*Shorthands* + +There are also shorthands such as: + +`@If > @Binary` equivalent to `IfStatement > BinaryExpression` + +See `translateInput` function in `parser.js` for the full list of supported shorthands and translations. + +- `@If`: `IfExpression` +- `@Id`: `Identifier` +- `@Var`: `VariableDeclaration` +- `@Expr` +- `@Member` +- `@Return` +- `@Block` +- `@ForIn` +- `@ForOf` +- `@For` +- `@Empty` +- `@Labeled` +- `@Break` +- `@Continue` +- `@Switch` +- `@Throw` +- `@Try` +- `@While` +- `@DoWhile` +- `@Let` +- `@This` +- `@Array` +- `@Object` +- `@FunDecl` +- `@Fun` +- `@Arrow` +- `@Seq` +- `@Cond` +- `@New` +- `@Member` +- `@Yield` +- `@Gen` +- `@UnaryOp` +- `@Unary` +- `@BinaryOp` +- `@Binary` +- `@LogicalOp` +- `@Logical` +- `@AssignOp` +- `@Assign` + +### Compound + +`[left.name="x"][right.value=1]` - where the left side is a node name `x` and the right has the value `1` + +Valid: `x = 1` + +`[left.name="x"]:matches(*)` any type of node where the left side is named `x` + +### Descendant + +`Program IfStatement` - Any program with an `if` statement + +`Identifier[name=x]` - identified named `x` such as `const x`. Identifiers are used to name variables and functions and to provide labels for certain loops... + +`Identifier [name=x]` - any identifier with named `x`, such as `const x` + +`BinaryExpression [name=x]` a binary expression where one side is named `x`, such as `x !== 3` + +`AssignmentExpression [name=x]` an assignment where one side is named `x`, such as `x = 2` or `y = x` + +### Fields + +You can also query on the [ESTree API](https://github.com/estree/estree) fields directly + +Example: + +```js +interface IfStatement <: Statement { + type: "IfStatement"; + test: Expression; + consequent: Statement; + alternate: Statement | null; +} +``` + +`.test` - node (object) that has a `test` field set +`.declarations.init` - node that has `.declarations.init` set + +`init` means initialised, such as `var x = 1`, where `x` is initialised to value `1` + +```js +interface VariableDeclaration <: Declaration { + type: "VariableDeclaration"; + declarations: [ VariableDeclarator ]; + kind: "var" | "let" | "const"; +} + +interface VariableDeclarator <: Node { + type: "VariableDeclarator"; + id: Pattern; + init: Expression | null; +} +``` + +### :has + +`ExpressionStatement:has([name="foo"][type="Identifier"])` + +Valid: `const foo = 2` + +### Matches + +`,` means OR (ie. any of) + +- `:matches(IfStatement)` +- `:matches(BinaryExpression, MemberExpression)` +- `:matches([name="foo"], ReturnStatement)` +- `:matches(AssignmentExpression, BinaryExpression)` +- `AssignmentExpression, BinaryExpression, NonExistant` + +### Not + +- `:not(Literal)` not a literal +- `:not([name="x"])` not a node named `x` +- `:not(*)` - not anything! +- `:not(Identifier, IfStatement)` +- `:not([value=1])` not a node set to value of `1` + +### Pseudo child + +`:first-child` - first child node +`:last-child` - last child node +`:nth-child(2)` - 2nd child +`:nth-last-child(2)` - 2nd last child + +### Subject + +`!IfStatement Identifier` - any `if` statement with one or more nested `Identifier`, such as `const x = 3` but not `if (x == 2)` + +`!* > [name="foo"]` all nodes but those where the immediate child is a node named `foo` + +### Types + +- `LogicalExpression` +- `ForStatement` +- `FunctionDeclaration` +- `ReturnStatement` +- `AssignmentExpression` diff --git a/README.md b/README.md index a3ee334..a010be0 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,6 @@ The following selectors are supported: * [subject indicator](http://dev.w3.org/csswg/selectors4/#subject): `!IfStatement > [name="foo"]` * class of AST node: `:statement`, `:expression`, `:declaration`, `:function`, or `:pattern` -[![Build Status](https://travis-ci.org/estools/esquery.png?branch=master)](https://travis-ci.org/estools/esquery) +See `API usage.md` document for API usage examples and explanations. + +[![Build Status](https://travis-ci.org/estools/esquery.png?branch=master)](https://travis-ci.org/estools/esquery) \ No newline at end of file diff --git a/esquery.js b/esquery.js index cad6640..887b7de 100644 --- a/esquery.js +++ b/esquery.js @@ -80,6 +80,22 @@ } return true; + case 'has': + var a, collector = []; + for (i = 0, l = selector.selectors.length; i < l; ++i) { + a = []; + estraverse.traverse(node, { + enter: function (node, parent) { + if (parent != null) { a.unshift(parent); } + if (matches(node, selector.selectors[i], a)) { + collector.push(node); + } + }, + leave: function () { a.shift(); } + }); + } + return collector.length !== 0; + case 'child': if (matches(node, selector.right, ancestry)) { return matches(ancestry[0], selector.left, ancestry.slice(1)); diff --git a/grammar.pegjs b/grammar.pegjs index d124c51..34e1c59 100644 --- a/grammar.pegjs +++ b/grammar.pegjs @@ -49,7 +49,7 @@ sequence atom = wildcard / identifier / attr / field / negation / matches - / firstChild / lastChild / nthChild / nthLastChild / class + / has / firstChild / lastChild / nthChild / nthLastChild / class wildcard = a:"*" { return { type: 'wildcard', value: a }; } identifier = "#"? i:identifierName { return { type: 'identifier', value: i }; } @@ -88,12 +88,14 @@ field = "." i:identifierName is:("." identifierName)* { negation = ":not(" _ ss:selectors _ ")" { return { type: 'not', selectors: ss }; } matches = ":matches(" _ ss:selectors _ ")" { return { type: 'matches', selectors: ss }; } +has = ":has(" _ ss:selectors _ ")" { return { type: 'has', selectors: ss }; } firstChild = ":first-child" { return nth(1); } lastChild = ":last-child" { return nthLast(1); } nthChild = ":nth-child(" _ n:[0-9]+ _ ")" { return nth(parseInt(n.join(''), 10)); } nthLastChild = ":nth-last-child(" _ n:[0-9]+ _ ")" { return nthLast(parseInt(n.join(''), 10)); } + class = ":" c:("statement"i / "expression"i / "declaration"i / "function"i / "pattern"i) { return { type: 'class', name: c }; } diff --git a/package.json b/package.json index b138264..bc8ae5f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "esquery", "preferGlobal": false, - "version": "0.4.0", + "version": "0.4.1", "author": "Joel Feenstra ", "description": "A query library for ECMAScript AST using a CSS selector like query language.", "main": "esquery.js", @@ -25,16 +25,16 @@ "query" ], "devDependencies": { - "jstestr": ">=0.4", - "pegjs": "~0.7.0", - "commonjs-everywhere": "~0.9.4", - "esprima": "~1.1.1" + "commonjs-everywhere": "~0.9.7", + "esprima": "^3.1.0", + "jstestr": "^0.4.2", + "pegjs": "^0.10.0" }, "license": "BSD", "engines": { - "node": ">=0.6" + "node": ">=5.0.0" }, "dependencies": { - "estraverse": "^4.0.0" + "estraverse": "^4.2.0" } } diff --git a/parser.js b/parser.js index 2ce8134..f4c2532 100644 --- a/parser.js +++ b/parser.js @@ -1,10 +1,10 @@ -var result = (function(){ +var result = (function () { /* * Generated by PEG.js 0.7.0. * * http://pegjs.majda.cz/ */ - + function quote(s) { /* * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a @@ -16,18 +16,65 @@ var result = (function(){ * characters. Note that "\0" and "\v" escape sequences are not used * because JSHint does not like the first and IE the second. */ - return '"' + s - .replace(/\\/g, '\\\\') // backslash - .replace(/"/g, '\\"') // closing quote character + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character .replace(/\x08/g, '\\b') // backspace - .replace(/\t/g, '\\t') // horizontal tab - .replace(/\n/g, '\\n') // line feed - .replace(/\f/g, '\\f') // form feed - .replace(/\r/g, '\\r') // carriage return - .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) - + '"'; + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) + + '"'; } - + + function translateInput(input) { + input = input.replace(/@If\b/, 'IfStatement ') + input = input.replace(/@Id\b/, 'Identifier ') + input = input.replace(/@Var\b/, 'VariableDeclaration ') + input = input.replace(/@Expr\b/, 'ExpressionStatement ') + input = input.replace(/@Member\b/, 'MemberExpression ') + input = input.replace(/@Return\b/, 'ReturnStatement ') + input = input.replace(/@Block\b/, 'BlockStatement ') + input = input.replace(/@ForIn\b/, 'ForInStatement ') + input = input.replace(/@ForOf\b/, 'ForOfStatement ') + input = input.replace(/@For\b/, 'ForStatement ') + input = input.replace(/@Empty\b/, 'EmptyStatement ') + input = input.replace(/@Labeled\b/, 'LabeledStatement ') + input = input.replace(/@Break\b/, 'BreakStatement ') + input = input.replace(/@Continue\b/, 'ContinueStatement ') + input = input.replace(/@With\b/, 'WithStatement ') + input = input.replace(/@Switch\b/, 'SwitchStatement ') + input = input.replace(/@Throw\b/, 'ThrowStatement ') + input = input.replace(/@Try\b/, 'TryStatement ') + input = input.replace(/@While\b/, 'WhileStatement ') + input = input.replace(/@DoWhile\b/, 'DoWhileStatement ') + input = input.replace(/@Let\b/, 'LetStatement ') + input = input.replace(/@This\b/, 'ThisExpression ') + input = input.replace(/@Array\b/, 'ArrayExpression ') + input = input.replace(/@Object\b/, 'ObjectExpression ') + input = input.replace(/@FunDecl\b/, 'FunctionDeclaration ') + input = input.replace(/@Fun\b/, 'FunctionExpression ') + input = input.replace(/@Arrow\b/, 'ArrowExpression ') + input = input.replace(/@Seq\b/, 'SequenceExpression ') + input = input.replace(/@Cond\b/, 'ConditionalExpression ') + input = input.replace(/@New\b/, 'NewExpression ') + input = input.replace(/@Call\b/, 'CallExpression ') + input = input.replace(/@Member\b/, 'MemberExpression ') + input = input.replace(/@Yield\b/, 'YieldExpression ') + input = input.replace(/@Gen\b/, 'GeneratorExpression ') + input = input.replace(/@UnaryOp\b/, 'UnaryOperator ') + input = input.replace(/@Unary\b/, 'UnaryExpression ') + input = input.replace(/@BinaryOp\b/, 'BinaryOperator ') + input = input.replace(/@Binary\b/, 'BinaryExpression ') + input = input.replace(/@LogicalOp\b/, 'LogicalOperator ') + input = input.replace(/@Logical\b/, 'LogicalExpression ') + input = input.replace(/@AssignOp\b/, 'AssignmentOperator ') + input = input.replace(/@Assign\b/, 'AssignmentExpression ') + + return input + } + var result = { /* * Parses the input with a generated parser. If the parsing is successfull, @@ -35,7 +82,11 @@ var result = (function(){ * which the parser was generated (see |PEG.buildParser|). If the parsing is * unsuccessful, throws |PEG.parser.SyntaxError| describing the error. */ - parse: function(input, startRule) { + parse: function (input, startRule) { + // console.log('parse', input, startRule) + input = translateInput(input) + // console.log('translated input for parse', input) + var parseFunctions = { "start": parse_start, "_": parse__, @@ -60,13 +111,14 @@ var result = (function(){ "field": parse_field, "negation": parse_negation, "matches": parse_matches, + "has": parse_has, "firstChild": parse_firstChild, "lastChild": parse_lastChild, "nthChild": parse_nthChild, "nthLastChild": parse_nthLastChild, "class": parse_class }; - + if (startRule !== undefined) { if (parseFunctions[startRule] === undefined) { throw new Error("Invalid rule name: " + quote(startRule) + "."); @@ -74,29 +126,29 @@ var result = (function(){ } else { startRule = "start"; } - + var pos = 0; var reportFailures = 0; var rightmostFailuresPos = 0; var rightmostFailuresExpected = []; var cache = {}; - + function padLeft(input, padding, length) { var result = input; - + var padLength = length - input.length; for (var i = 0; i < padLength; i++) { result = padding + result; } - + return result; } - + function escape(ch) { var charCode = ch.charCodeAt(0); var escapeChar; var length; - + if (charCode <= 0xFF) { escapeChar = 'x'; length = 2; @@ -104,23 +156,23 @@ var result = (function(){ escapeChar = 'u'; length = 4; } - + return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); } - + function matchFailed(failure) { if (pos < rightmostFailuresPos) { return; } - + if (pos > rightmostFailuresPos) { rightmostFailuresPos = pos; rightmostFailuresExpected = []; } - + rightmostFailuresExpected.push(failure); } - + function parse_start() { var cacheKey = "start@" + pos; var cachedResult = cache[cacheKey]; @@ -128,10 +180,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1, result2; var pos0, pos1; - + pos0 = pos; pos1 = pos; result0 = parse__(); @@ -154,7 +206,12 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset, ss) { return ss.length === 1 ? ss[0] : { type: 'matches', selectors: ss }; })(pos0, result0[1]); + result0 = (function (offset, ss) { + return ss.length === 1 ? ss[0] : { + type: 'matches', + selectors: ss + }; + })(pos0, result0[1]); } if (result0 === null) { pos = pos0; @@ -163,20 +220,22 @@ var result = (function(){ pos0 = pos; result0 = parse__(); if (result0 !== null) { - result0 = (function(offset) { return void 0; })(pos0); + result0 = (function (offset) { + return void 0; + })(pos0); } if (result0 === null) { pos = pos0; } } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse__() { var cacheKey = "_@" + pos; var cachedResult = cache[cacheKey]; @@ -184,9 +243,9 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1; - + result0 = []; if (input.charCodeAt(pos) === 32) { result1 = " "; @@ -209,14 +268,14 @@ var result = (function(){ } } } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_identifierName() { var cacheKey = "identifierName@" + pos; var cachedResult = cache[cacheKey]; @@ -224,10 +283,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1; var pos0; - + pos0 = pos; if (/^[^ [\],():#!=><~+.]/.test(input.charAt(pos))) { result1 = input.charAt(pos); @@ -256,19 +315,21 @@ var result = (function(){ result0 = null; } if (result0 !== null) { - result0 = (function(offset, i) { return i.join(''); })(pos0, result0); + result0 = (function (offset, i) { + return i.join(''); + })(pos0, result0); } if (result0 === null) { pos = pos0; } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_binaryOp() { var cacheKey = "binaryOp@" + pos; var cachedResult = cache[cacheKey]; @@ -276,10 +337,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1, result2; var pos0, pos1; - + pos0 = pos; pos1 = pos; result0 = parse__(); @@ -310,7 +371,9 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset) { return 'child'; })(pos0); + result0 = (function (offset) { + return 'child'; + })(pos0); } if (result0 === null) { pos = pos0; @@ -346,7 +409,9 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset) { return 'sibling'; })(pos0); + result0 = (function (offset) { + return 'sibling'; + })(pos0); } if (result0 === null) { pos = pos0; @@ -382,7 +447,9 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset) { return 'adjacent'; })(pos0); + result0 = (function (offset) { + return 'adjacent'; + })(pos0); } if (result0 === null) { pos = pos0; @@ -412,7 +479,9 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset) { return 'descendant'; })(pos0); + result0 = (function (offset) { + return 'descendant'; + })(pos0); } if (result0 === null) { pos = pos0; @@ -420,14 +489,14 @@ var result = (function(){ } } } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_selectors() { var cacheKey = "selectors@" + pos; var cachedResult = cache[cacheKey]; @@ -435,10 +504,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1, result2, result3, result4, result5; var pos0, pos1, pos2; - + pos0 = pos; pos1 = pos; result0 = parse_selector(); @@ -526,21 +595,23 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset, s, ss) { - return [s].concat(ss.map(function (s) { return s[3]; })); - })(pos0, result0[0], result0[1]); + result0 = (function (offset, s, ss) { + return [s].concat(ss.map(function (s) { + return s[3]; + })); + })(pos0, result0[0], result0[1]); } if (result0 === null) { pos = pos0; } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_selector() { var cacheKey = "selector@" + pos; var cachedResult = cache[cacheKey]; @@ -548,10 +619,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1, result2, result3; var pos0, pos1, pos2; - + pos0 = pos; pos1 = pos; result0 = parse_sequence(); @@ -599,23 +670,27 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset, a, ops) { + result0 = (function (offset, a, ops) { return ops.reduce(function (memo, rhs) { - return { type: rhs[0], left: memo, right: rhs[1] }; + return { + type: rhs[0], + left: memo, + right: rhs[1] + }; }, a); })(pos0, result0[0], result0[1]); } if (result0 === null) { pos = pos0; } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_sequence() { var cacheKey = "sequence@" + pos; var cachedResult = cache[cacheKey]; @@ -623,10 +698,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1, result2; var pos0, pos1; - + pos0 = pos; pos1 = pos; if (input.charCodeAt(pos) === 33) { @@ -661,23 +736,26 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset, subject, as) { - var b = as.length === 1 ? as[0] : { type: 'compound', selectors: as }; - if(subject) b.subject = true; + result0 = (function (offset, subject, as) { + var b = as.length === 1 ? as[0] : { + type: 'compound', + selectors: as + }; + if (subject) b.subject = true; return b; })(pos0, result0[0], result0[1]); } if (result0 === null) { pos = pos0; } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_atom() { var cacheKey = "atom@" + pos; var cachedResult = cache[cacheKey]; @@ -685,9 +763,9 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0; - + result0 = parse_wildcard(); if (result0 === null) { result0 = parse_identifier(); @@ -700,15 +778,18 @@ var result = (function(){ if (result0 === null) { result0 = parse_matches(); if (result0 === null) { - result0 = parse_firstChild(); + result0 = parse_has(); if (result0 === null) { - result0 = parse_lastChild(); + result0 = parse_firstChild(); if (result0 === null) { - result0 = parse_nthChild(); + result0 = parse_lastChild(); if (result0 === null) { - result0 = parse_nthLastChild(); + result0 = parse_nthChild(); if (result0 === null) { - result0 = parse_class(); + result0 = parse_nthLastChild(); + if (result0 === null) { + result0 = parse_class(); + } } } } @@ -719,14 +800,14 @@ var result = (function(){ } } } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_wildcard() { var cacheKey = "wildcard@" + pos; var cachedResult = cache[cacheKey]; @@ -734,10 +815,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0; var pos0; - + pos0 = pos; if (input.charCodeAt(pos) === 42) { result0 = "*"; @@ -749,19 +830,24 @@ var result = (function(){ } } if (result0 !== null) { - result0 = (function(offset, a) { return { type: 'wildcard', value: a }; })(pos0, result0); + result0 = (function (offset, a) { + return { + type: 'wildcard', + value: a + }; + })(pos0, result0); } if (result0 === null) { pos = pos0; } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_identifier() { var cacheKey = "identifier@" + pos; var cachedResult = cache[cacheKey]; @@ -769,10 +855,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1; var pos0, pos1; - + pos0 = pos; pos1 = pos; if (input.charCodeAt(pos) === 35) { @@ -798,19 +884,24 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset, i) { return { type: 'identifier', value: i }; })(pos0, result0[1]); + result0 = (function (offset, i) { + return { + type: 'identifier', + value: i + }; + })(pos0, result0[1]); } if (result0 === null) { pos = pos0; } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_attr() { var cacheKey = "attr@" + pos; var cachedResult = cache[cacheKey]; @@ -818,10 +909,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1, result2, result3, result4; var pos0, pos1; - + pos0 = pos; pos1 = pos; if (input.charCodeAt(pos) === 91) { @@ -872,19 +963,21 @@ var result = (function(){ pos = pos1; } if (result0 !== null) { - result0 = (function(offset, v) { return v; })(pos0, result0[2]); + result0 = (function (offset, v) { + return v; + })(pos0, result0[2]); } if (result0 === null) { pos = pos0; } - + cache[cacheKey] = { nextPos: pos, - result: result0 + result: result0 }; return result0; } - + function parse_attrOps() { var cacheKey = "attrOps@" + pos; var cachedResult = cache[cacheKey]; @@ -892,10 +985,10 @@ var result = (function(){ pos = cachedResult.nextPos; return cachedResult.result; } - + var result0, result1; var pos0, pos1; - + pos0 = pos; pos1 = pos; if (/^[> @Binary"); + assert.contains([ + conditional.body[0].test + ], matches); + }, + "three types child": function () { var matches = esquery(conditional, "IfStatement > BinaryExpression > Identifier"); assert.contains([ @@ -26,6 +33,14 @@ define([ ], matches); }, + "three types child (shorthand)": function () { + var matches = esquery(conditional, "@If > @Binary > @Id"); + assert.contains([ + conditional.body[0].test.left + ], matches); + }, + + "two types descendant": function () { var matches = esquery(conditional, "IfStatement BinaryExpression"); assert.contains([ @@ -33,6 +48,13 @@ define([ ], matches); }, + "two types descendant (shorthand)": function () { + var matches = esquery(conditional, "@If @Binary"); + assert.contains([ + conditional.body[0].test + ], matches); + }, + "two types sibling": function () { var matches = esquery(simpleProgram, "VariableDeclaration ~ IfStatement"); assert.contains([ @@ -40,11 +62,25 @@ define([ ], matches); }, + "two types sibling (shorthand)": function () { + var matches = esquery(simpleProgram, "@Var ~ @If"); + assert.contains([ + simpleProgram.body[3] + ], matches); + }, + "two types adjacent": function () { var matches = esquery(simpleProgram, "VariableDeclaration + ExpressionStatement"); assert.contains([ simpleProgram.body[2] ], matches); + }, + + "two types adjacent (shorthand)": function () { + var matches = esquery(simpleProgram, "@Variable + @Expr"); + assert.contains([ + simpleProgram.body[2] + ], matches); } }); }); diff --git a/tests/queryHas.js b/tests/queryHas.js new file mode 100644 index 0000000..041f18a --- /dev/null +++ b/tests/queryHas.js @@ -0,0 +1,17 @@ + +define([ + "esquery", + "jstestr/assert", + "jstestr/test", + "./fixtures/conditional" +], function (esquery, assert, test, conditional) { + + test.defineSuite("Parent selector query", { + + "conditional": function () { + var matches = esquery(conditional, 'ExpressionStatement:has([name="foo"][type="Identifier"])'); + assert.isEqual(1, matches.length); + } + + }); +});