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

Add optional JSX-Support while keeping original behaviour the default #57

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,14 @@ 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`

For JSX-Support use:

`var esquery = require("esquery/jsx");`

instead of:

`var esquery = require("esquery");`

(Note the '/jsx' part)

[![Build Status](https://travis-ci.org/estools/esquery.png?branch=master)](https://travis-ci.org/estools/esquery)
330 changes: 5 additions & 325 deletions esquery.js
Original file line number Diff line number Diff line change
@@ -1,336 +1,16 @@
/* vim: set sw=4 sts=4 : */
(function () {

var estraverse = require('estraverse');
var parser = require('./parser');

var isArray = Array.isArray || function isArray(array) {
return {}.toString.call(array) === '[object Array]';
};

var LEFT_SIDE = {};
var RIGHT_SIDE = {};

function esqueryModule() {

/**
* Get the value of a property which may be multiple levels down in the object.
*/
function getPath(obj, key) {
var i, keys = key.split(".");
for (i = 0; i < keys.length; i++) {
if (obj == null) { return obj; }
obj = obj[keys[i]];
}
return obj;
}

/**
* Determine whether `node` can be reached by following `path`, starting at `ancestor`.
*/
function inPath(node, ancestor, path) {
var field, remainingPath, i;
if (path.length === 0) { return node === ancestor; }
if (ancestor == null) { return false; }
field = ancestor[path[0]];
remainingPath = path.slice(1);
if (isArray(field)) {
for (i = 0, l = field.length; i < l; ++i) {
if (inPath(node, field[i], remainingPath)) { return true; }
}
return false;
} else {
return inPath(node, field, remainingPath);
}
}

/**
* Given a `node` and its ancestors, determine if `node` is matched by `selector`.
*/
function matches(node, selector, ancestry) {
var path, ancestor, i, l, p;
if (!selector) { return true; }
if (!node) { return false; }
if (!ancestry) { ancestry = []; }

switch(selector.type) {
case 'wildcard':
return true;

case 'identifier':
return selector.value.toLowerCase() === node.type.toLowerCase();

case 'field':
path = selector.name.split('.');
ancestor = ancestry[path.length - 1];
return inPath(node, ancestor, path);

case 'matches':
for (i = 0, l = selector.selectors.length; i < l; ++i) {
if (matches(node, selector.selectors[i], ancestry)) { return true; }
}
return false;

case 'compound':
for (i = 0, l = selector.selectors.length; i < l; ++i) {
if (!matches(node, selector.selectors[i], ancestry)) { return false; }
}
return true;

case 'not':
for (i = 0, l = selector.selectors.length; i < l; ++i) {
if (matches(node, selector.selectors[i], ancestry)) { return false; }
}
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));
}
return false;

case 'descendant':
if (matches(node, selector.right, ancestry)) {
for (i = 0, l = ancestry.length; i < l; ++i) {
if (matches(ancestry[i], selector.left, ancestry.slice(i + 1))) {
return true;
}
}
}
return false;

case 'attribute':
p = getPath(node, selector.name);
switch (selector.operator) {
case null:
case void 0:
return p != null;
case '=':
switch (selector.value.type) {
case 'regexp': return selector.value.value.test(p);
case 'literal': return '' + selector.value.value === '' + p;
case 'type': return selector.value.value === typeof p;
}
case '!=':
switch (selector.value.type) {
case 'regexp': return !selector.value.value.test(p);
case 'literal': return '' + selector.value.value !== '' + p;
case 'type': return selector.value.value !== typeof p;
}
case '<=': return p <= selector.value.value;
case '<': return p < selector.value.value;
case '>': return p > selector.value.value;
case '>=': return p >= selector.value.value;
}

case 'sibling':
return matches(node, selector.right, ancestry) &&
sibling(node, selector.left, ancestry, LEFT_SIDE) ||
selector.left.subject &&
matches(node, selector.left, ancestry) &&
sibling(node, selector.right, ancestry, RIGHT_SIDE);

case 'adjacent':
return matches(node, selector.right, ancestry) &&
adjacent(node, selector.left, ancestry, LEFT_SIDE) ||
selector.right.subject &&
matches(node, selector.left, ancestry) &&
adjacent(node, selector.right, ancestry, RIGHT_SIDE);

case 'nth-child':
return matches(node, selector.right, ancestry) &&
nthChild(node, ancestry, function (length) {
return selector.index.value - 1;
});

case 'nth-last-child':
return matches(node, selector.right, ancestry) &&
nthChild(node, ancestry, function (length) {
return length - selector.index.value;
});

case 'class':
if(!node.type) return false;
switch(selector.name.toLowerCase()){
case 'statement':
if(node.type.slice(-9) === 'Statement') return true;
// fallthrough: interface Declaration <: Statement { }
case 'declaration':
return node.type.slice(-11) === 'Declaration';
case 'pattern':
if(node.type.slice(-7) === 'Pattern') return true;
// fallthrough: interface Expression <: Node, Pattern { }
case 'expression':
return node.type.slice(-10) === 'Expression' ||
node.type === 'Literal' ||
node.type === 'Identifier';
case 'function':
return node.type.slice(0, 8) === 'Function' ||
node.type === 'ArrowFunctionExpression';
}
throw new Error('Unknown class name: ' + selector.name);
}

throw new Error('Unknown selector type: ' + selector.type);
}

/*
* Determines if the given node has a sibling that matches the given selector.
*/
function sibling(node, selector, ancestry, side) {
var parent = ancestry[0], listProp, startIndex, keys, i, l, k, lowerBound, upperBound;
if (!parent) { return false; }
keys = estraverse.VisitorKeys[parent.type];
for (i = 0, l = keys.length; i < l; ++i) {
listProp = parent[keys[i]];
if (isArray(listProp)) {
startIndex = listProp.indexOf(node);
if (startIndex < 0) { continue; }
if (side === LEFT_SIDE) {
lowerBound = 0;
upperBound = startIndex;
} else {
lowerBound = startIndex + 1;
upperBound = listProp.length;
}
for (k = lowerBound; k < upperBound; ++k) {
if (matches(listProp[k], selector, ancestry)) {
return true;
}
}
}
}
return false;
}

/*
* Determines if the given node has an asjacent sibling that matches the given selector.
*/
function adjacent(node, selector, ancestry, side) {
var parent = ancestry[0], listProp, keys, i, l, idx;
if (!parent) { return false; }
keys = estraverse.VisitorKeys[parent.type];
for (i = 0, l = keys.length; i < l; ++i) {
listProp = parent[keys[i]];
if (isArray(listProp)) {
idx = listProp.indexOf(node);
if (idx < 0) { continue; }
if (side === LEFT_SIDE && idx > 0 && matches(listProp[idx - 1], selector, ancestry)) {
return true;
}
if (side === RIGHT_SIDE && idx < listProp.length - 1 && matches(listProp[idx + 1], selector, ancestry)) {
return true;
}
}
}
return false;
}

/*
* Determines if the given node is the nth child, determined by idxFn, which is given the containing list's length.
*/
function nthChild(node, ancestry, idxFn) {
var parent = ancestry[0], listProp, keys, i, l, idx;
if (!parent) { return false; }
keys = estraverse.VisitorKeys[parent.type];
for (i = 0, l = keys.length; i < l; ++i) {
listProp = parent[keys[i]];
if (isArray(listProp)) {
idx = listProp.indexOf(node);
if (idx >= 0 && idx === idxFn(listProp.length)) { return true; }
}
}
return false;
}

/*
* For each selector node marked as a subject, find the portion of the selector that the subject must match.
*/
function subjects(selector, ancestor) {
var results, p;
if (selector == null || typeof selector != 'object') { return []; }
if (ancestor == null) { ancestor = selector; }
results = selector.subject ? [ancestor] : [];
for(p in selector) {
if(!{}.hasOwnProperty.call(selector, p)) { continue; }
[].push.apply(results, subjects(selector[p], p === 'left' ? selector[p] : ancestor));
}
return results;
}

/**
* From a JS AST and a selector AST, collect all JS AST nodes that match the selector.
*/
function match(ast, selector) {
var ancestry = [], results = [], altSubjects, i, l, k, m;
if (!selector) { return results; }
altSubjects = subjects(selector);
estraverse.traverse(ast, {
enter: function (node, parent) {
if (parent != null) { ancestry.unshift(parent); }
if (matches(node, selector, ancestry)) {
if (altSubjects.length) {
for (i = 0, l = altSubjects.length; i < l; ++i) {
if (matches(node, altSubjects[i], ancestry)) { results.push(node); }
for (k = 0, m = ancestry.length; k < m; ++k) {
if (matches(ancestry[k], altSubjects[i], ancestry.slice(k + 1))) {
results.push(ancestry[k]);
}
}
}
} else {
results.push(node);
}
}
},
leave: function () { ancestry.shift(); }
});
return results;
}

/**
* Parse a selector string and return its AST.
*/
function parse(selector) {
return parser.parse(selector);
}

/**
* Query the code AST using the selector string.
*/
function query(ast, selector) {
return match(ast, parse(selector));
}

query.parse = parse;
query.match = match;
query.matches = matches;
return query.query = query;
}
var esquery = require("./esquery_init");
var estraverse = require("estraverse");


if (typeof define === "function" && define.amd) {
define(esqueryModule);
define(function(){return esquery(estraverse);});
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = esqueryModule();
module.exports = esquery(estraverse);
} else {
this.esquery = esqueryModule();
this.esquery = esquery(estraverse);
}

})();