diff --git a/docs/developer-guide.rst b/docs/developer-guide.rst index 9200a2a..9bc9a26 100644 --- a/docs/developer-guide.rst +++ b/docs/developer-guide.rst @@ -240,17 +240,19 @@ The functions exposed by SourceCode object are as follows: 7. ``getParent (node)`` - get the parent node of the specified node -8. ``getNextChar (node)`` - get 1 character after the code of specified node +8. ``getNthParent (node)`` - get the Nth parent node of the specified node -9. ``getPrevChar (node)`` - get 1 character before the code of specified node +9. ``getNextChar (node)`` - get 1 character after the code of specified node -10. ``getNextChars (node, charCount)`` - get charCount no. of characters after the code of specified node +10. ``getPrevChar (node)`` - get 1 character before the code of specified node -11. ``getPrevChars (node, charCount)`` - get charCount no. of characters befre the code of specified node +11. ``getNextChars (node, charCount)`` - get charCount no. of characters after the code of specified node -12. ``isASTNode (arg)`` - Returns true if the given argument is a valid (Spider-Monkey compliant) AST Node +12. ``getPrevChars (node, charCount)`` - get charCount no. of characters befre the code of specified node -13. ``getStringBetweenNodes (prevNode, nextNode)`` - get the complete code between 2 specified nodes. (The code ranges from prevNode.end (inclusive) to nextNode.start (exclusive) ) +13. ``isASTNode (arg)`` - Returns true if the given argument is a valid (Spider-Monkey compliant) AST Node + +14. ``getStringBetweenNodes (prevNode, nextNode)`` - get the complete code between 2 specified nodes. (The code ranges from prevNode.end (inclusive) to nextNode.start (exclusive) ) - ``context.report()`` - Lastly, the context object provides you with a clean interface to report lint issues: diff --git a/lib/solium.js b/lib/solium.js index 9a2711e..1585e00 100644 --- a/lib/solium.js +++ b/lib/solium.js @@ -160,16 +160,20 @@ module.exports = (function() { commentDirectiveParser = new CommentDirectiveParser(AST.comments, AST); + // Set parent reference in all nodes + solExplore.traverse(AST, { + enter(node, parent) { + node.parent = parent; + } + }); /** * Perform depth-first traversal of the AST and notify rules upon entering & leaving nodes * Each node has a type property which serves as the Event's name. * This allows rules to listen to the type of node they wish to test. */ solExplore.traverse(AST, { - enter(node, parent) { - node.parent = parent; //allow the rules to access immediate parent of current node + enter(node) { nodeEventGenerator.enterNode(node); - delete node.parent; }, leave(node) { diff --git a/lib/utils/ast-utils.js b/lib/utils/ast-utils.js index 3fb86a3..2784425 100644 --- a/lib/utils/ast-utils.js +++ b/lib/utils/ast-utils.js @@ -119,6 +119,25 @@ exports.getParent = function(node) { return node.parent; }; +/** + * Get the Nth parent node of the specified node. 0th parent is the node itself. 1st parent + * is an immediate parent of a node. + * @param {Object} node The AST Node to retrieve the parent of + * @param {Integer} n number of a parent node starting from zero + * @returns {Object} nodeParent Nth parent node of the given node + */ +exports.getNthParent = function(node, n) { + if (n < 0) { + throw new Error("Parent number should be greater of equal to zero"); + } + let parent = node; + for (let i = 0; i < n; i++) { + parent = exports.getParent(parent); + } + throwIfInvalidNode(parent); + return parent; +}; + /** * Retrieve the line number on which the code for provided node STARTS * @param {Object} node The AST Node to retrieve the line number of diff --git a/lib/utils/source-code-utils.js b/lib/utils/source-code-utils.js index 2b58afb..5b1da16 100644 --- a/lib/utils/source-code-utils.js +++ b/lib/utils/source-code-utils.js @@ -9,6 +9,7 @@ const { EOL } = require("os"), astUtils = require("./ast-utils"); const INHERITABLE_METHODS = [ "isASTNode", "getParent", + "getNthParent", "getColumn", "getEndingColumn", "getLine", diff --git a/test/lib/solium.js b/test/lib/solium.js index 1275261..4b0a139 100644 --- a/test/lib/solium.js +++ b/test/lib/solium.js @@ -26,7 +26,7 @@ describe("Checking Exported Solium API", function() { Solium.should.be.type("object"); Solium.should.be.instanceof(EventEmitter); Solium.should.have.size(11); - + Solium.should.have.ownProperty("reset"); Solium.reset.should.be.type("function"); Solium.should.have.ownProperty("lint"); @@ -109,13 +109,16 @@ describe("Checking Exported Solium API", function() { sourceCode.should.have.property("getEndingColumn"); sourceCode.getColumn.should.be.type("function"); - + sourceCode.should.have.property("getParent"); sourceCode.getParent.should.be.type("function"); + sourceCode.should.have.property("getNthParent"); + sourceCode.getNthParent.should.be.type("function"); + sourceCode.should.have.property("getEndingLine"); sourceCode.getParent.should.be.type("function"); - + sourceCode.should.have.property("isASTNode"); sourceCode.getParent.should.be.type("function"); @@ -124,19 +127,19 @@ describe("Checking Exported Solium API", function() { sourceCode.should.have.property("getTextOnLine"); sourceCode.getText.should.be.type("function"); - + sourceCode.should.have.property("getStringBetweenNodes"); sourceCode.getText.should.be.type("function"); sourceCode.should.have.property("getNextChar"); sourceCode.getNextChar.should.be.type("function"); - + sourceCode.should.have.property("getPrevChar"); sourceCode.getPrevChar.should.be.type("function"); - + sourceCode.should.have.property("getNextChars"); sourceCode.getNextChars.should.be.type("function"); - + sourceCode.should.have.property("getPrevChars"); sourceCode.getPrevChars.should.be.type("function"); diff --git a/test/lib/utils/ast-utils.js b/test/lib/utils/ast-utils.js index 5e4df5a..2d7a3f8 100644 --- a/test/lib/utils/ast-utils.js +++ b/test/lib/utils/ast-utils.js @@ -133,6 +133,83 @@ describe("Testing astUtils Object", function() { done(); }); + it("should return node itself when getting 0th parent of a node", function(done) { + let node = { + type: "TestNode", + start: 0, end: 190, + parent: {type: "TestParentNode", start: 21, end: 981} + }; + + let parent = astUtils.getNthParent(node, 0); + + parent.should.be.type("object"); + parent.should.have.ownProperty("type"); + parent.type.should.equal("TestNode"); + + done(); + }); + + it("should return immediate parent when getting 1st parent of a node", function(done) { + let node = { + type: "TestNode", + start: 0, end: 190, + parent: {type: "TestParentNode", start: 21, end: 981} + }; + + let parent = astUtils.getNthParent(node, 1); + + parent.should.be.type("object"); + parent.should.have.ownProperty("type"); + parent.type.should.equal("TestParentNode"); + + done(); + }); + + it("should return 2nd parent of a node", function(done) { + let node = { + type: "TestNode", + start: 0, end: 190, + parent: { + type: "TestParentNode", + start: 21, + end: 981, + parent: {type: "RootNode", start: 0, end: 1000} + } + }; + + let parent = astUtils.getNthParent(node, 2); + + parent.should.be.type("object"); + parent.should.have.ownProperty("type"); + parent.type.should.equal("RootNode"); + + done(); + }); + + it("throw an exception if getting a non-existing parent", function(done) { + let node = { + type: "TestNode", + start: 0, end: 190, + parent: {type: "TestParentNode", start: 21, end: 981} + }; + + astUtils.getNthParent.bind(node, 2).should.throw(); + + done(); + }); + + it("should handle invalid parent number", function(done) { + let node = { + type: "TestNode", + start: 0, end: 190, + parent: {type: "TestParentNode", start: 21, end: 981} + }; + + astUtils.getNthParent.bind(node, -1).should.throw(); + + done(); + }); + it("should handle invalid argument(s) passed to utility functions", function(done) { astUtils.isIfStatement.bind(astUtils).should.throw(); astUtils.isIfStatement.bind(astUtils, null).should.throw();