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