Skip to content

Commit d36a064

Browse files
author
Casey Visco
committed
Update: Refactor no-conditional-require rule
* Replace stack-based conditional tracking with `ancestor` function * Improve code-coverage * Add `docs` meta
1 parent dda9314 commit d36a064

File tree

3 files changed

+79
-56
lines changed

3 files changed

+79
-56
lines changed

lib/rules/no-conditional-require.js

Lines changed: 32 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,50 @@
11
/**
2-
* @fileoverview Disallow use of conditional `require` calls
2+
* @fileoverview Rule to disallow use of conditional `require` calls
33
* @author Casey Visco
44
*/
55

66
"use strict";
77

8-
const util = require("../util");
8+
const isRequireCall = require("../util").isRequireCall;
9+
const ancestor = require("../utils/ast").ancestor;
10+
11+
// -----------------------------------------------------------------------------
12+
// Helpers
13+
// -----------------------------------------------------------------------------
14+
15+
/**
16+
* Test if supplied `node` represents a conditional—either an `if` statement
17+
* or a ternary expression.
18+
* @private
19+
* @param {ASTNode} node - node to test
20+
* @returns {Boolean} true if node represents a conditional
21+
*/
22+
function isConditional(node) {
23+
return node.type === "IfStatement" ||
24+
node.type === "ConditionalExpression";
25+
}
26+
27+
// -----------------------------------------------------------------------------
28+
// Rule Definition
29+
// -----------------------------------------------------------------------------
30+
31+
const ERROR_MSG = "Conditional `require` calls are not allowed.";
932

1033
module.exports = {
1134
meta: {
12-
docs: {},
35+
docs: {
36+
description: "Disallow use of conditional `require` calls",
37+
category: "Stylistic Choices",
38+
recommended: true
39+
},
1340
schema: []
1441
},
1542

1643
create: function (context) {
17-
const MESSAGE = "Conditional `require` calls are not allowed.";
18-
const condStack = [];
19-
20-
/**
21-
* Determine if we are currently inside of a conditional.
22-
*
23-
* @private
24-
* @returns {Boolean} true if inside at least one conditional
25-
*/
26-
function isInsideConditional() {
27-
return condStack.length > 0;
28-
}
29-
30-
/**
31-
* Add provided conditional `node` to the stack.
32-
*
33-
* @private
34-
* @param {ASTNode} node - ConditionalExpression or IfStatement node
35-
* @returns {void}
36-
*/
37-
function pushConditional(node) {
38-
condStack.push(node);
39-
}
40-
41-
/**
42-
* Remove provided conditional `node` from the stack if it is the last in
43-
* the list.
44-
*
45-
* @private
46-
* @param {ASTNode} node - ConditionalExpression or IfStatement node
47-
* @returns {void}
48-
*/
49-
function popConditional(node) {
50-
const len = condStack.length;
51-
52-
if (len && condStack[len - 1] === node) {
53-
condStack.pop();
54-
}
55-
}
56-
5744
return {
58-
59-
// Standard "If" block
60-
"IfStatement": pushConditional,
61-
"IfStatement:exit": popConditional,
62-
63-
// Ternary Expression (?:)
64-
"ConditionalExpression": pushConditional,
65-
"ConditionalExpression:exit": popConditional,
66-
6745
"CallExpression": function (node) {
68-
if (util.isRequireCall(node) && isInsideConditional()) {
69-
context.report(node, MESSAGE);
46+
if (isRequireCall(node) && ancestor(isConditional, node)) {
47+
context.report(node, ERROR_MSG);
7048
}
7149
}
7250
};

lib/utils/ast.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @fileoverview Predicates for testing AST Nodes
2+
* @fileoverview Helpers for testing AST Nodes
33
* @author Casey Visco
44
*/
55

@@ -129,6 +129,22 @@ function hasCallback(node) {
129129
node.arguments.some(isFunctionExpr);
130130
}
131131

132+
/**
133+
* Traverse parent nodes until one is found that satisfies `predicate` is found.
134+
* @param {Function} predicate - predicate to test each ancestor against
135+
* @param {ASTNode} node - child node to begin search at
136+
* @returns {Boolean} true if an ancestor satisfies `predicate`
137+
*/
138+
function ancestor(predicate, node) {
139+
while ((node = node.parent)) {
140+
if (predicate(node)) {
141+
return true;
142+
}
143+
}
144+
145+
return false;
146+
}
147+
132148
module.exports = {
133149
isIdentifier,
134150
isLiteral,
@@ -140,5 +156,6 @@ module.exports = {
140156
isExprStatement,
141157
isStringLiteralArray,
142158
hasParams,
143-
hasCallback
159+
hasCallback,
160+
ancestor
144161
};

tests/lib/utils/ast.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,3 +419,31 @@ describe("ast.hasCallback", function () {
419419
});
420420

421421
});
422+
423+
describe("ast.ancestor", function () {
424+
const sampleNode = {
425+
parent: {
426+
parent: {
427+
parent: {
428+
type: "bar"
429+
},
430+
type: "foo"
431+
}
432+
}
433+
};
434+
435+
it("should return `true` if an ancestor satisfies the predicate", function () {
436+
const actual = ast.ancestor((node) => node.type === "foo", sampleNode);
437+
const expected = true;
438+
439+
assert.equal(actual, expected);
440+
});
441+
442+
it("should return `false` if no ancestor satisfies the predicate", function () {
443+
const actual = ast.ancestor((node) => node.type === "baz", sampleNode);
444+
const expected = false;
445+
446+
assert.equal(actual, expected);
447+
});
448+
449+
});

0 commit comments

Comments
 (0)