Skip to content

Commit

Permalink
jsdocs and minor refactoring (#90)
Browse files Browse the repository at this point in the history
- Refactoring: Change internally-used constants to strings for simpler enum-like type referencing in jsdocs
- Refactoring: Switch from `hasOwnProperty` check (with test) to Object.entries + for..of loop
- Refactoring: Prefer spread operator over `apply`
- Refactoring: Use `const` in pegjs
  • Loading branch information
brettz9 committed Mar 20, 2020
1 parent 6792194 commit bd870d6
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 37 deletions.
105 changes: 88 additions & 17 deletions esquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@
import estraverse from 'estraverse';
import parser from './parser.js';

const LEFT_SIDE = {};
const RIGHT_SIDE = {};
/**
* @typedef {"LEFT_SIDE"|"RIGHT_SIDE"} Side
*/

const LEFT_SIDE = 'LEFT_SIDE';
const RIGHT_SIDE = 'RIGHT_SIDE';

/**
* Get the value of a property which may be multiple levels down in the object.
* @external AST
* @see https://esprima.readthedocs.io/en/latest/syntax-tree-format.html
*/

/**
* One of the rules of `grammar.pegjs`
* @typedef {PlainObject} SelectorAST
* @see grammar.pegjs
*/

/**
* The `sequence` production of `grammar.pegjs`
* @typedef {PlainObject} SelectorSequenceAST
*/

/**
* Get the value of a property which may be multiple levels down
* in the object.
* @param {?PlainObject} obj
* @param {string} key
* @returns {undefined|boolean|string|number|external:AST}
*/
function getPath(obj, key) {
const keys = key.split('.');
Expand All @@ -18,7 +42,12 @@ function getPath(obj, key) {
}

/**
* Determine whether `node` can be reached by following `path`, starting at `ancestor`.
* Determine whether `node` can be reached by following `path`,
* starting at `ancestor`.
* @param {?external:AST} node
* @param {?external:AST} ancestor
* @param {string[]} path
* @returns {boolean}
*/
function inPath(node, ancestor, path) {
if (path.length === 0) { return node === ancestor; }
Expand All @@ -36,7 +65,14 @@ function inPath(node, ancestor, path) {
}

/**
* Given a `node` and its ancestors, determine if `node` is matched by `selector`.
* Given a `node` and its ancestors, determine if `node` is matched
* by `selector`.
* @param {?external:AST} node
* @param {?SelectorAST} selector
* @param {external:AST[]} [ancestry=[]]
* @throws {Error} Unknowns (operator, class name, selector type, or
* selector value type)
* @returns {boolean}
*/
function matches(node, selector, ancestry) {
if (!selector) { return true; }
Expand Down Expand Up @@ -188,8 +224,14 @@ function matches(node, selector, ancestry) {
throw new Error(`Unknown selector type: ${selector.type}`);
}

/*
* Determines if the given node has a sibling that matches the given selector.
/**
* Determines if the given node has a sibling that matches the
* given selector.
* @param {external:AST} node
* @param {SelectorSequenceAST} selector
* @param {external:AST[]} ancestry
* @param {Side} side
* @returns {boolean}
*/
function sibling(node, selector, ancestry, side) {
const [parent] = ancestry;
Expand Down Expand Up @@ -218,8 +260,14 @@ function sibling(node, selector, ancestry, side) {
return false;
}

/*
* Determines if the given node has an adjacent sibling that matches the given selector.
/**
* Determines if the given node has an adjacent sibling that matches
* the given selector.
* @param {external:AST} node
* @param {SelectorSequenceAST} selector
* @param {external:AST[]} ancestry
* @param {Side} side
* @returns {boolean}
*/
function adjacent(node, selector, ancestry, side) {
const [parent] = ancestry;
Expand All @@ -241,8 +289,19 @@ function adjacent(node, selector, ancestry, side) {
return false;
}

/*
* Determines if the given node is the nth child, determined by idxFn, which is given the containing list's length.
/**
* @callback IndexFunction
* @param {Integer} len Containing list's length
* @returns {Integer}
*/

/**
* Determines if the given node is the nth child, determined by
* `idxFn`, which is given the containing list's length.
* @param {external:AST} node
* @param {external:AST[]} ancestry
* @param {IndexFunction} idxFn
* @returns {boolean}
*/
function nthChild(node, ancestry, idxFn) {
const [parent] = ancestry;
Expand All @@ -258,22 +317,29 @@ function nthChild(node, ancestry, idxFn) {
return false;
}

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

/**
* From a JS AST and a selector AST, collect all JS AST nodes that match the selector.
* From a JS AST and a selector AST, collect all JS AST nodes that
* match the selector.
* @param {external:AST} ast
* @param {?SelectorAST} selector
* @returns {external:AST[]}
*/
function match(ast, selector) {
const ancestry = [], results = [];
Expand Down Expand Up @@ -305,13 +371,18 @@ function match(ast, selector) {

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

/**
* Query the code AST using the selector string.
* @param {external:AST} ast
* @param {string} selector
* @returns {external:AST[]}
*/
function query(ast, selector) {
return match(ast, parse(selector));
Expand Down
8 changes: 5 additions & 3 deletions grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
}

start
= _ ss:selectors _ { return ss.length === 1 ? ss[0] : { type: 'matches', selectors: ss }; }
= _ ss:selectors _ {
return ss.length === 1 ? ss[0] : { type: 'matches', selectors: ss };
}
/ _ { return void 0; }

_ = " "*
Expand All @@ -41,7 +43,7 @@ selector

sequence
= subject:"!"? as:atom+ {
var b = as.length === 1 ? as[0] : { type: 'compound', selectors: as };
const b = as.length === 1 ? as[0] : { type: 'compound', selectors: as };
if(subject) b.subject = true;
return b;
}
Expand Down Expand Up @@ -76,7 +78,7 @@ attr
number
= a:([0-9]* ".")? b:[0-9]+ {
// Can use `a.flat().join('')` once supported
var leadingDecimals = a ? [].concat.apply([], a).join('') : '';
const leadingDecimals = a ? [].concat.apply([], a).join('') : '';
return { type: 'literal', value: parseFloat(leadingDecimals + b.join('')) };
}
path = i:identifierName { return { type: 'literal', value: i }; }
Expand Down
8 changes: 5 additions & 3 deletions parser.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 0 additions & 14 deletions tests/match.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,4 @@ describe('match', function () {
value: { type: 'foobar' } });
}, Error);
});

// Can remove need for this if remove `subjects`' `hasOwnProperty`
// check in favor of for-of (Node >=6.9.2) or use `Object.keys`
it('selector with non-own properties', function () {
assert.throws(function () {
function Selector () {}
Selector.prototype.inheritedMethod = function () {};
const sel = new Selector();
sel.type = 'class';
sel.name = 'badName';
sel.value = { type: 'foobar' };
esquery.match(ast, sel);
}, Error);
});
});

0 comments on commit bd870d6

Please sign in to comment.