Skip to content

Commit

Permalink
Handle conditional expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
ihordiachenko committed May 9, 2021
1 parent b9c37c0 commit aa9f674
Show file tree
Hide file tree
Showing 4 changed files with 445 additions and 432 deletions.
85 changes: 62 additions & 23 deletions lib/rules/no-unused-expressions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@
// Rule Definition
//------------------------------------------------------------------------------

/**
* Returns `true`.
* @returns {boolean} `true`.
*/
function alwaysTrue() {
return true;
}

/**
* Returns `false`.
* @returns {boolean} `false`.
*/
function alwaysFalse() {
return false;
}


module.exports = {
meta: {
type: "suggestion",
Expand All @@ -35,7 +52,11 @@ module.exports = {
},
additionalProperties: false
}
]
],

messages: {
unusedExpression: "Expected an assignment or function call and instead saw an expression."
}
},
create: function (context) {
var config = context.options[0] || {},
Expand Down Expand Up @@ -90,31 +111,49 @@ module.exports = {
}

/**
* Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
* @param {ASTNode} node - any node
* @returns {boolean} whether the given node is a valid expression
* The member functions return `true` if the type has no side-effects.
* Unknown nodes are handled as `false`, then this rule ignores those.
*/
function isValidExpression(node) {
if (allowTernary) {

// Recursive check for ternary and logical expressions
if (node.type === "ConditionalExpression") {
return isValidExpression(node.consequent) && isValidExpression(node.alternate);
const Checker = Object.assign(Object.create(null), {
isDisallowed(node) {
return (Checker[node.type] || alwaysFalse)(node);
},

ArrayExpression: alwaysTrue,
ArrowFunctionExpression: alwaysTrue,
BinaryExpression: alwaysTrue,
ChainExpression(node) {
return Checker.isDisallowed(node.expression);
},
ClassExpression: alwaysTrue,
ConditionalExpression(node) {
if (allowTernary) {
return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate);
}
}
if (allowShortCircuit) {
if (node.type === "LogicalExpression") {
return isValidExpression(node.right);
return true;
},
FunctionExpression: alwaysTrue,
Identifier: alwaysTrue,
Literal: alwaysTrue,
LogicalExpression(node) {
if (allowShortCircuit) {
return Checker.isDisallowed(node.right);
}
}

if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
return true;
},
MemberExpression: alwaysTrue,
MetaProperty: alwaysTrue,
ObjectExpression: alwaysTrue,
SequenceExpression: alwaysTrue,
TaggedTemplateExpression() {
return !allowTaggedTemplates;
},
TemplateLiteral: alwaysTrue,
ThisExpression: alwaysTrue,
UnaryExpression(node) {
return node.operator !== "void" && node.operator !== "delete";
}

return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/.test(node.type) ||
(node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
}
});

/**
* Determines whether or not a given node is a chai's expect statement.
Expand Down Expand Up @@ -199,12 +238,12 @@ module.exports = {

return {
ExpressionStatement: function(node) {
var valid = isValidExpression(node.expression)
var valid = !Checker.isDisallowed(node.expression)
|| isDirective(node, context.getAncestors())
|| isChaiExpectCall(node)
|| isChaiShouldCall(node);
if (!valid) {
context.report(node, "Expected an assignment or function call and instead saw an expression.");
context.report({node, messageId: "unusedExpression"});
}
}
};
Expand Down
Loading

0 comments on commit aa9f674

Please sign in to comment.