Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 256 additions & 0 deletions API usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
## API and usage

*Install (via npm for Node.js)*

`npm i esquery --save`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly prefer installation documentation to use npm install .... There's no reason to use shorthand in documentation.


*Quick start*

```js
const esquery = require('esquery');

const conditional = "if (x === 1) { foo(); } else { x = 2; }"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use single quotes or double quotes, but not both.


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`

## Complex
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combinators


- descendant selector (` ` space)
- child selector (`>`)
- adjacent sibling selector (`+`)
- general sibling selector (`~`)

Please see [javascript: expressions-vs-statements](http://www.2ality.com/2012/09/expressions-vs-statements.html)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how this is related. Instead, one should see the estree specification.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also want to point them to the CSS documentation because these combinators are both syntactically and semantically identical.


`IfStatement > BinaryExpression` - `if` statement followed by a [binary expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators) fx `3+4` or `x*y`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a BinaryExpression nested directly within an IfStatement.


Valid: `if (2 === 2)`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These examples are not valid JavaScript. You can make it valid by adding a semicolon.


`IfStatement > BinaryExpression > Identifier`

Valid: `if (x === 2)`

`IfStatement BinaryExpression`

An `if` statement with any binary expression below it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A binary expression nested arbitrarily deeply within an if statement.


`VariableDeclaration ~ IfStatement`

`var` declaration with sibling `if` statement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
```

### 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 [Mozilla Parser API](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API) fields directly
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link should be updated to estree.


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 not an If statement with an Identifier under, such as `const x = 3` but not `if (x == 2)`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any not an If statement with an Identifier under

any if statement with one or more nested Identifier


`!* > [name="foo"]` all nodes but those where the immediate child is a node namded `foo`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namded -> named


More examples:

- `![test] [name="y"]`
- `![generator=type(boolean)] > BlockStatement`
- `![operator=/=+/] > [name="x"]`
- `!:matches(*) > [name="foo"]`
- `!:not(BlockStatement) > [name="foo"]`
- `![left.name="x"][right.value=1]`
- `* !AssignmentExpression`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a productive example. What is it trying to show off?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do whatever it takes to add sufficient docs for how to use the API. I tried my best using the tests cases for inspiration.

- `!VariableDeclaration ~ IfStatement`
- `!VariableDeclaration + !ExpressionStatement`

### Types

- `LogicalExpression`
- `ForStatement`
- `FunctionDeclaration`
- `ReturnStatement`
- `AssignmentExpression`
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
16 changes: 16 additions & 0 deletions esquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
4 changes: 3 additions & 1 deletion grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }; }
Expand Down Expand Up @@ -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 };
}
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "esquery",
"preferGlobal": false,
"version": "0.4.0",
"version": "0.4.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't bump the version. We'll bump it appropriately during a release.

"author": "Joel Feenstra <[email protected]>",
"description": "A query library for ECMAScript AST using a CSS selector like query language.",
"main": "esquery.js",
Expand All @@ -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"
}
}
Loading