no-unused-expressions.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /**
  2. * @fileoverview Flag expressions in statement position that do not side effect
  3. * @author Michael Ficarra
  4. */
  5. "use strict";
  6. const astUtils = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. /**
  11. * Returns `true`.
  12. * @returns {boolean} `true`.
  13. */
  14. function alwaysTrue() {
  15. return true;
  16. }
  17. /**
  18. * Returns `false`.
  19. * @returns {boolean} `false`.
  20. */
  21. function alwaysFalse() {
  22. return false;
  23. }
  24. /** @type {import('../types').Rule.RuleModule} */
  25. module.exports = {
  26. meta: {
  27. dialects: ["javascript", "typescript"],
  28. language: "javascript",
  29. type: "suggestion",
  30. docs: {
  31. description: "Disallow unused expressions",
  32. recommended: false,
  33. url: "https://eslint.org/docs/latest/rules/no-unused-expressions",
  34. },
  35. schema: [
  36. {
  37. type: "object",
  38. properties: {
  39. allowShortCircuit: {
  40. type: "boolean",
  41. },
  42. allowTernary: {
  43. type: "boolean",
  44. },
  45. allowTaggedTemplates: {
  46. type: "boolean",
  47. },
  48. enforceForJSX: {
  49. type: "boolean",
  50. },
  51. ignoreDirectives: {
  52. type: "boolean",
  53. },
  54. },
  55. additionalProperties: false,
  56. },
  57. ],
  58. defaultOptions: [
  59. {
  60. allowShortCircuit: false,
  61. allowTernary: false,
  62. allowTaggedTemplates: false,
  63. enforceForJSX: false,
  64. ignoreDirectives: false,
  65. },
  66. ],
  67. messages: {
  68. unusedExpression:
  69. "Expected an assignment or function call and instead saw an expression.",
  70. },
  71. },
  72. create(context) {
  73. const [
  74. {
  75. allowShortCircuit,
  76. allowTernary,
  77. allowTaggedTemplates,
  78. enforceForJSX,
  79. ignoreDirectives,
  80. },
  81. ] = context.options;
  82. /**
  83. * Has AST suggesting a directive.
  84. * @param {ASTNode} node any node
  85. * @returns {boolean} whether the given node structurally represents a directive
  86. */
  87. function looksLikeDirective(node) {
  88. return (
  89. node.type === "ExpressionStatement" &&
  90. node.expression.type === "Literal" &&
  91. typeof node.expression.value === "string"
  92. );
  93. }
  94. /**
  95. * Gets the leading sequence of members in a list that pass the predicate.
  96. * @param {Function} predicate ([a] -> Boolean) the function used to make the determination
  97. * @param {a[]} list the input list
  98. * @returns {a[]} the leading sequence of members in the given list that pass the given predicate
  99. */
  100. function takeWhile(predicate, list) {
  101. for (let i = 0; i < list.length; ++i) {
  102. if (!predicate(list[i])) {
  103. return list.slice(0, i);
  104. }
  105. }
  106. return list.slice();
  107. }
  108. /**
  109. * Gets leading directives nodes in a Node body.
  110. * @param {ASTNode} node a Program or BlockStatement node
  111. * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body
  112. */
  113. function directives(node) {
  114. return takeWhile(looksLikeDirective, node.body);
  115. }
  116. /**
  117. * Detect if a Node is a directive.
  118. * @param {ASTNode} node any node
  119. * @returns {boolean} whether the given node is considered a directive in its current position
  120. */
  121. function isDirective(node) {
  122. /**
  123. * https://tc39.es/ecma262/#directive-prologue
  124. *
  125. * Only `FunctionBody`, `ScriptBody` and `ModuleBody` can have directive prologue.
  126. * Class static blocks do not have directive prologue.
  127. */
  128. return (
  129. astUtils.isTopLevelExpressionStatement(node) &&
  130. directives(node.parent).includes(node)
  131. );
  132. }
  133. /**
  134. * The member functions return `true` if the type has no side-effects.
  135. * Unknown nodes are handled as `false`, then this rule ignores those.
  136. */
  137. const Checker = Object.assign(Object.create(null), {
  138. isDisallowed(node) {
  139. return (Checker[node.type] || alwaysFalse)(node);
  140. },
  141. ArrayExpression: alwaysTrue,
  142. ArrowFunctionExpression: alwaysTrue,
  143. BinaryExpression: alwaysTrue,
  144. ChainExpression(node) {
  145. return Checker.isDisallowed(node.expression);
  146. },
  147. ClassExpression: alwaysTrue,
  148. ConditionalExpression(node) {
  149. if (allowTernary) {
  150. return (
  151. Checker.isDisallowed(node.consequent) ||
  152. Checker.isDisallowed(node.alternate)
  153. );
  154. }
  155. return true;
  156. },
  157. FunctionExpression: alwaysTrue,
  158. Identifier: alwaysTrue,
  159. JSXElement() {
  160. return enforceForJSX;
  161. },
  162. JSXFragment() {
  163. return enforceForJSX;
  164. },
  165. Literal: alwaysTrue,
  166. LogicalExpression(node) {
  167. if (allowShortCircuit) {
  168. return Checker.isDisallowed(node.right);
  169. }
  170. return true;
  171. },
  172. MemberExpression: alwaysTrue,
  173. MetaProperty: alwaysTrue,
  174. ObjectExpression: alwaysTrue,
  175. SequenceExpression: alwaysTrue,
  176. TaggedTemplateExpression() {
  177. return !allowTaggedTemplates;
  178. },
  179. TemplateLiteral: alwaysTrue,
  180. ThisExpression: alwaysTrue,
  181. UnaryExpression(node) {
  182. return node.operator !== "void" && node.operator !== "delete";
  183. },
  184. // TypeScript-specific node types
  185. TSAsExpression(node) {
  186. return Checker.isDisallowed(node.expression);
  187. },
  188. TSTypeAssertion(node) {
  189. return Checker.isDisallowed(node.expression);
  190. },
  191. TSNonNullExpression(node) {
  192. return Checker.isDisallowed(node.expression);
  193. },
  194. TSInstantiationExpression(node) {
  195. return Checker.isDisallowed(node.expression);
  196. },
  197. });
  198. return {
  199. ExpressionStatement(node) {
  200. if (
  201. Checker.isDisallowed(node.expression) &&
  202. !astUtils.isDirective(node) &&
  203. !(ignoreDirectives && isDirective(node))
  204. ) {
  205. context.report({ node, messageId: "unusedExpression" });
  206. }
  207. },
  208. };
  209. },
  210. };