Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update deps and adds some useful API documentation #49

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
295 changes: 295 additions & 0 deletions API usage.md
Original file line number Diff line number Diff line change
@@ -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`
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