1 | /**
|
2 | * @fileoverview Flag expressions in statement position that do not side effect
|
3 | * @author Michael Ficarra
|
4 | */
|
5 | ;
|
6 |
|
7 | //------------------------------------------------------------------------------
|
8 | // Rule Definition
|
9 | //------------------------------------------------------------------------------
|
10 |
|
11 | module.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 | };
|