1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | function alwaysTrue() {
|
16 | return true;
|
17 | }
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | function alwaysFalse() {
|
24 | return false;
|
25 | }
|
26 |
|
27 | module.exports = {
|
28 | meta: {
|
29 | type: "suggestion",
|
30 |
|
31 | docs: {
|
32 | description: "disallow unused expressions",
|
33 | category: "Best Practices",
|
34 | recommended: false,
|
35 | url: "https://eslint.org/docs/rules/no-unused-expressions"
|
36 | },
|
37 |
|
38 | schema: [
|
39 | {
|
40 | type: "object",
|
41 | properties: {
|
42 | allowShortCircuit: {
|
43 | type: "boolean",
|
44 | default: false
|
45 | },
|
46 | allowTernary: {
|
47 | type: "boolean",
|
48 | default: false
|
49 | },
|
50 | allowTaggedTemplates: {
|
51 | type: "boolean",
|
52 | default: false
|
53 | },
|
54 | enforceForJSX: {
|
55 | type: "boolean",
|
56 | default: false
|
57 | }
|
58 | },
|
59 | additionalProperties: false
|
60 | }
|
61 | ],
|
62 |
|
63 | messages: {
|
64 | unusedExpression: "Expected an assignment or function call and instead saw an expression."
|
65 | }
|
66 | },
|
67 |
|
68 | create(context) {
|
69 | const config = context.options[0] || {},
|
70 | allowShortCircuit = config.allowShortCircuit || false,
|
71 | allowTernary = config.allowTernary || false,
|
72 | allowTaggedTemplates = config.allowTaggedTemplates || false,
|
73 | enforceForJSX = config.enforceForJSX || false;
|
74 |
|
75 |
|
76 | |
77 |
|
78 |
|
79 |
|
80 | function looksLikeDirective(node) {
|
81 | return node.type === "ExpressionStatement" &&
|
82 | node.expression.type === "Literal" && typeof node.expression.value === "string";
|
83 | }
|
84 |
|
85 |
|
86 | |
87 |
|
88 |
|
89 |
|
90 |
|
91 | function takeWhile(predicate, list) {
|
92 | for (let i = 0; i < list.length; ++i) {
|
93 | if (!predicate(list[i])) {
|
94 | return list.slice(0, i);
|
95 | }
|
96 | }
|
97 | return list.slice();
|
98 | }
|
99 |
|
100 |
|
101 | |
102 |
|
103 |
|
104 |
|
105 | function directives(node) {
|
106 | return takeWhile(looksLikeDirective, node.body);
|
107 | }
|
108 |
|
109 |
|
110 | |
111 |
|
112 |
|
113 |
|
114 |
|
115 | function isDirective(node, ancestors) {
|
116 | const parent = ancestors[ancestors.length - 1],
|
117 | grandparent = ancestors[ancestors.length - 2];
|
118 |
|
119 | return (parent.type === "Program" || parent.type === "BlockStatement" &&
|
120 | (/Function/u.test(grandparent.type))) &&
|
121 | directives(parent).indexOf(node) >= 0;
|
122 | }
|
123 |
|
124 | |
125 |
|
126 |
|
127 |
|
128 | const Checker = Object.assign(Object.create(null), {
|
129 | isDisallowed(node) {
|
130 | return (Checker[node.type] || alwaysFalse)(node);
|
131 | },
|
132 |
|
133 | ArrayExpression: alwaysTrue,
|
134 | ArrowFunctionExpression: alwaysTrue,
|
135 | BinaryExpression: alwaysTrue,
|
136 | ChainExpression(node) {
|
137 | return Checker.isDisallowed(node.expression);
|
138 | },
|
139 | ClassExpression: alwaysTrue,
|
140 | ConditionalExpression(node) {
|
141 | if (allowTernary) {
|
142 | return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate);
|
143 | }
|
144 | return true;
|
145 | },
|
146 | FunctionExpression: alwaysTrue,
|
147 | Identifier: alwaysTrue,
|
148 | JSXElement() {
|
149 | return enforceForJSX;
|
150 | },
|
151 | JSXFragment() {
|
152 | return enforceForJSX;
|
153 | },
|
154 | Literal: alwaysTrue,
|
155 | LogicalExpression(node) {
|
156 | if (allowShortCircuit) {
|
157 | return Checker.isDisallowed(node.right);
|
158 | }
|
159 | return true;
|
160 | },
|
161 | MemberExpression: alwaysTrue,
|
162 | MetaProperty: alwaysTrue,
|
163 | ObjectExpression: alwaysTrue,
|
164 | SequenceExpression: alwaysTrue,
|
165 | TaggedTemplateExpression() {
|
166 | return !allowTaggedTemplates;
|
167 | },
|
168 | TemplateLiteral: alwaysTrue,
|
169 | ThisExpression: alwaysTrue,
|
170 | UnaryExpression(node) {
|
171 | return node.operator !== "void" && node.operator !== "delete";
|
172 | }
|
173 | });
|
174 |
|
175 | return {
|
176 | ExpressionStatement(node) {
|
177 | if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) {
|
178 | context.report({ node, messageId: "unusedExpression" });
|
179 | }
|
180 | }
|
181 | };
|
182 | }
|
183 | };
|