UNPKG

4.66 kBJavaScriptView Raw
1/**
2 * @fileoverview Flag expressions in statement position that do not side effect
3 * @author Michael Ficarra
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11module.exports = {
12 meta: {
13 docs: {
14 description: "disallow unused expressions",
15 category: "Best Practices",
16 recommended: false,
17 url: "https://eslint.org/docs/rules/no-unused-expressions"
18 },
19
20 schema: [
21 {
22 type: "object",
23 properties: {
24 allowShortCircuit: {
25 type: "boolean"
26 },
27 allowTernary: {
28 type: "boolean"
29 },
30 allowTaggedTemplates: {
31 type: "boolean"
32 }
33 },
34 additionalProperties: false
35 }
36 ]
37 },
38
39 create(context) {
40 const config = context.options[0] || {},
41 allowShortCircuit = config.allowShortCircuit || false,
42 allowTernary = config.allowTernary || false,
43 allowTaggedTemplates = config.allowTaggedTemplates || false;
44
45 /**
46 * @param {ASTNode} node - any node
47 * @returns {boolean} whether the given node structurally represents a directive
48 */
49 function looksLikeDirective(node) {
50 return node.type === "ExpressionStatement" &&
51 node.expression.type === "Literal" && typeof node.expression.value === "string";
52 }
53
54 /**
55 * @param {Function} predicate - ([a] -> Boolean) the function used to make the determination
56 * @param {a[]} list - the input list
57 * @returns {a[]} the leading sequence of members in the given list that pass the given predicate
58 */
59 function takeWhile(predicate, list) {
60 for (let i = 0; i < list.length; ++i) {
61 if (!predicate(list[i])) {
62 return list.slice(0, i);
63 }
64 }
65 return list.slice();
66 }
67
68 /**
69 * @param {ASTNode} node - a Program or BlockStatement node
70 * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body
71 */
72 function directives(node) {
73 return takeWhile(looksLikeDirective, node.body);
74 }
75
76 /**
77 * @param {ASTNode} node - any node
78 * @param {ASTNode[]} ancestors - the given node's ancestors
79 * @returns {boolean} whether the given node is considered a directive in its current position
80 */
81 function isDirective(node, ancestors) {
82 const parent = ancestors[ancestors.length - 1],
83 grandparent = ancestors[ancestors.length - 2];
84
85 return (parent.type === "Program" || parent.type === "BlockStatement" &&
86 (/Function/.test(grandparent.type))) &&
87 directives(parent).indexOf(node) >= 0;
88 }
89
90 /**
91 * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
92 * @param {ASTNode} node - any node
93 * @returns {boolean} whether the given node is a valid expression
94 */
95 function isValidExpression(node) {
96 if (allowTernary) {
97
98 // Recursive check for ternary and logical expressions
99 if (node.type === "ConditionalExpression") {
100 return isValidExpression(node.consequent) && isValidExpression(node.alternate);
101 }
102 }
103
104 if (allowShortCircuit) {
105 if (node.type === "LogicalExpression") {
106 return isValidExpression(node.right);
107 }
108 }
109
110 if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
111 return true;
112 }
113
114 return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/.test(node.type) ||
115 (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
116 }
117
118 return {
119 ExpressionStatement(node) {
120 if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) {
121 context.report({ node, message: "Expected an assignment or function call and instead saw an expression." });
122 }
123 }
124 };
125
126 }
127};