123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- /* 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 typeof p === 'string' && 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.slice(-7) === 'Literal' ||
- (
- node.type === 'Identifier' &&
- (ancestry.length === 0 || ancestry[0].type !== 'MetaProperty')
- ) ||
- node.type === 'MetaProperty';
- 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;
- }
-
-
- if (typeof define === "function" && define.amd) {
- define(esqueryModule);
- } else if (typeof module !== 'undefined' && module.exports) {
- module.exports = esqueryModule();
- } else {
- this.esquery = esqueryModule();
- }
-
- })();
|