no-unused-vars.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. /**
  2. * @fileoverview Rule to flag declared but unused variables
  3. * @author Ilya Volodin
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Typedefs
  12. //------------------------------------------------------------------------------
  13. /**
  14. * A simple name for the types of variables that this rule supports
  15. * @typedef {'array-destructure'|'catch-clause'|'parameter'|'variable'} VariableType
  16. */
  17. /**
  18. * Bag of data used for formatting the `unusedVar` lint message.
  19. * @typedef {Object} UnusedVarMessageData
  20. * @property {string} varName The name of the unused var.
  21. * @property {'defined'|'assigned a value'} action Description of the vars state.
  22. * @property {string} additional Any additional info to be appended at the end.
  23. */
  24. /**
  25. * Bag of data used for formatting the `usedIgnoredVar` lint message.
  26. * @typedef {Object} UsedIgnoredVarMessageData
  27. * @property {string} varName The name of the unused var.
  28. * @property {string} additional Any additional info to be appended at the end.
  29. */
  30. //------------------------------------------------------------------------------
  31. // Rule Definition
  32. //------------------------------------------------------------------------------
  33. /** @type {import('../shared/types').Rule} */
  34. module.exports = {
  35. meta: {
  36. type: "problem",
  37. docs: {
  38. description: "Disallow unused variables",
  39. recommended: true,
  40. url: "https://eslint.org/docs/latest/rules/no-unused-vars"
  41. },
  42. schema: [
  43. {
  44. oneOf: [
  45. {
  46. enum: ["all", "local"]
  47. },
  48. {
  49. type: "object",
  50. properties: {
  51. vars: {
  52. enum: ["all", "local"]
  53. },
  54. varsIgnorePattern: {
  55. type: "string"
  56. },
  57. args: {
  58. enum: ["all", "after-used", "none"]
  59. },
  60. ignoreRestSiblings: {
  61. type: "boolean"
  62. },
  63. argsIgnorePattern: {
  64. type: "string"
  65. },
  66. caughtErrors: {
  67. enum: ["all", "none"]
  68. },
  69. caughtErrorsIgnorePattern: {
  70. type: "string"
  71. },
  72. destructuredArrayIgnorePattern: {
  73. type: "string"
  74. },
  75. ignoreClassWithStaticInitBlock: {
  76. type: "boolean"
  77. },
  78. reportUsedIgnorePattern: {
  79. type: "boolean"
  80. }
  81. },
  82. additionalProperties: false
  83. }
  84. ]
  85. }
  86. ],
  87. messages: {
  88. unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.",
  89. usedIgnoredVar: "'{{varName}}' is marked as ignored but is used{{additional}}."
  90. }
  91. },
  92. create(context) {
  93. const sourceCode = context.sourceCode;
  94. const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/u;
  95. const config = {
  96. vars: "all",
  97. args: "after-used",
  98. ignoreRestSiblings: false,
  99. caughtErrors: "all",
  100. ignoreClassWithStaticInitBlock: false,
  101. reportUsedIgnorePattern: false
  102. };
  103. const firstOption = context.options[0];
  104. if (firstOption) {
  105. if (typeof firstOption === "string") {
  106. config.vars = firstOption;
  107. } else {
  108. config.vars = firstOption.vars || config.vars;
  109. config.args = firstOption.args || config.args;
  110. config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
  111. config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
  112. config.ignoreClassWithStaticInitBlock = firstOption.ignoreClassWithStaticInitBlock || config.ignoreClassWithStaticInitBlock;
  113. config.reportUsedIgnorePattern = firstOption.reportUsedIgnorePattern || config.reportUsedIgnorePattern;
  114. if (firstOption.varsIgnorePattern) {
  115. config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u");
  116. }
  117. if (firstOption.argsIgnorePattern) {
  118. config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, "u");
  119. }
  120. if (firstOption.caughtErrorsIgnorePattern) {
  121. config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u");
  122. }
  123. if (firstOption.destructuredArrayIgnorePattern) {
  124. config.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, "u");
  125. }
  126. }
  127. }
  128. /**
  129. * Determines what variable type a def is.
  130. * @param {Object} def the declaration to check
  131. * @returns {VariableType} a simple name for the types of variables that this rule supports
  132. */
  133. function defToVariableType(def) {
  134. /*
  135. * This `destructuredArrayIgnorePattern` error report works differently from the catch
  136. * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the
  137. * `destructuredArrayIgnorePattern` will be checked for array destructuring. However,
  138. * for the purposes of the report, the currently defined behavior is to only inform the
  139. * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact
  140. * that the `varsIgnorePattern` would also apply). If it's not present, the user will be
  141. * informed of the `varsIgnorePattern`, assuming that's present.
  142. */
  143. if (config.destructuredArrayIgnorePattern && def.name.parent.type === "ArrayPattern") {
  144. return "array-destructure";
  145. }
  146. switch (def.type) {
  147. case "CatchClause":
  148. return "catch-clause";
  149. case "Parameter":
  150. return "parameter";
  151. default:
  152. return "variable";
  153. }
  154. }
  155. /**
  156. * Gets a given variable's description and configured ignore pattern
  157. * based on the provided variableType
  158. * @param {VariableType} variableType a simple name for the types of variables that this rule supports
  159. * @throws {Error} (Unreachable)
  160. * @returns {[string | undefined, string | undefined]} the given variable's description and
  161. * ignore pattern
  162. */
  163. function getVariableDescription(variableType) {
  164. let pattern;
  165. let variableDescription;
  166. switch (variableType) {
  167. case "array-destructure":
  168. pattern = config.destructuredArrayIgnorePattern;
  169. variableDescription = "elements of array destructuring";
  170. break;
  171. case "catch-clause":
  172. pattern = config.caughtErrorsIgnorePattern;
  173. variableDescription = "caught errors";
  174. break;
  175. case "parameter":
  176. pattern = config.argsIgnorePattern;
  177. variableDescription = "args";
  178. break;
  179. case "variable":
  180. pattern = config.varsIgnorePattern;
  181. variableDescription = "vars";
  182. break;
  183. default:
  184. throw new Error(`Unexpected variable type: ${variableType}`);
  185. }
  186. if (pattern) {
  187. pattern = pattern.toString();
  188. }
  189. return [variableDescription, pattern];
  190. }
  191. /**
  192. * Generates the message data about the variable being defined and unused,
  193. * including the ignore pattern if configured.
  194. * @param {Variable} unusedVar eslint-scope variable object.
  195. * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
  196. */
  197. function getDefinedMessageData(unusedVar) {
  198. const def = unusedVar.defs && unusedVar.defs[0];
  199. let additionalMessageData = "";
  200. if (def) {
  201. const [variableDescription, pattern] = getVariableDescription(defToVariableType(def));
  202. if (pattern && variableDescription) {
  203. additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
  204. }
  205. }
  206. return {
  207. varName: unusedVar.name,
  208. action: "defined",
  209. additional: additionalMessageData
  210. };
  211. }
  212. /**
  213. * Generate the warning message about the variable being
  214. * assigned and unused, including the ignore pattern if configured.
  215. * @param {Variable} unusedVar eslint-scope variable object.
  216. * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
  217. */
  218. function getAssignedMessageData(unusedVar) {
  219. const def = unusedVar.defs && unusedVar.defs[0];
  220. let additionalMessageData = "";
  221. if (def) {
  222. const [variableDescription, pattern] = getVariableDescription(defToVariableType(def));
  223. if (pattern && variableDescription) {
  224. additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
  225. }
  226. }
  227. return {
  228. varName: unusedVar.name,
  229. action: "assigned a value",
  230. additional: additionalMessageData
  231. };
  232. }
  233. /**
  234. * Generate the warning message about a variable being used even though
  235. * it is marked as being ignored.
  236. * @param {Variable} variable eslint-scope variable object
  237. * @param {VariableType} variableType a simple name for the types of variables that this rule supports
  238. * @returns {UsedIgnoredVarMessageData} The message data to be used with
  239. * this used ignored variable.
  240. */
  241. function getUsedIgnoredMessageData(variable, variableType) {
  242. const [variableDescription, pattern] = getVariableDescription(variableType);
  243. let additionalMessageData = "";
  244. if (pattern && variableDescription) {
  245. additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`;
  246. }
  247. return {
  248. varName: variable.name,
  249. additional: additionalMessageData
  250. };
  251. }
  252. //--------------------------------------------------------------------------
  253. // Helpers
  254. //--------------------------------------------------------------------------
  255. const STATEMENT_TYPE = /(?:Statement|Declaration)$/u;
  256. /**
  257. * Determines if a given variable is being exported from a module.
  258. * @param {Variable} variable eslint-scope variable object.
  259. * @returns {boolean} True if the variable is exported, false if not.
  260. * @private
  261. */
  262. function isExported(variable) {
  263. const definition = variable.defs[0];
  264. if (definition) {
  265. let node = definition.node;
  266. if (node.type === "VariableDeclarator") {
  267. node = node.parent;
  268. } else if (definition.type === "Parameter") {
  269. return false;
  270. }
  271. return node.parent.type.indexOf("Export") === 0;
  272. }
  273. return false;
  274. }
  275. /**
  276. * Checks whether a node is a sibling of the rest property or not.
  277. * @param {ASTNode} node a node to check
  278. * @returns {boolean} True if the node is a sibling of the rest property, otherwise false.
  279. */
  280. function hasRestSibling(node) {
  281. return node.type === "Property" &&
  282. node.parent.type === "ObjectPattern" &&
  283. REST_PROPERTY_TYPE.test(node.parent.properties.at(-1).type);
  284. }
  285. /**
  286. * Determines if a variable has a sibling rest property
  287. * @param {Variable} variable eslint-scope variable object.
  288. * @returns {boolean} True if the variable has a sibling rest property, false if not.
  289. * @private
  290. */
  291. function hasRestSpreadSibling(variable) {
  292. if (config.ignoreRestSiblings) {
  293. const hasRestSiblingDefinition = variable.defs.some(def => hasRestSibling(def.name.parent));
  294. const hasRestSiblingReference = variable.references.some(ref => hasRestSibling(ref.identifier.parent));
  295. return hasRestSiblingDefinition || hasRestSiblingReference;
  296. }
  297. return false;
  298. }
  299. /**
  300. * Determines if a reference is a read operation.
  301. * @param {Reference} ref An eslint-scope Reference
  302. * @returns {boolean} whether the given reference represents a read operation
  303. * @private
  304. */
  305. function isReadRef(ref) {
  306. return ref.isRead();
  307. }
  308. /**
  309. * Determine if an identifier is referencing an enclosing function name.
  310. * @param {Reference} ref The reference to check.
  311. * @param {ASTNode[]} nodes The candidate function nodes.
  312. * @returns {boolean} True if it's a self-reference, false if not.
  313. * @private
  314. */
  315. function isSelfReference(ref, nodes) {
  316. let scope = ref.from;
  317. while (scope) {
  318. if (nodes.includes(scope.block)) {
  319. return true;
  320. }
  321. scope = scope.upper;
  322. }
  323. return false;
  324. }
  325. /**
  326. * Gets a list of function definitions for a specified variable.
  327. * @param {Variable} variable eslint-scope variable object.
  328. * @returns {ASTNode[]} Function nodes.
  329. * @private
  330. */
  331. function getFunctionDefinitions(variable) {
  332. const functionDefinitions = [];
  333. variable.defs.forEach(def => {
  334. const { type, node } = def;
  335. // FunctionDeclarations
  336. if (type === "FunctionName") {
  337. functionDefinitions.push(node);
  338. }
  339. // FunctionExpressions
  340. if (type === "Variable" && node.init &&
  341. (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) {
  342. functionDefinitions.push(node.init);
  343. }
  344. });
  345. return functionDefinitions;
  346. }
  347. /**
  348. * Checks the position of given nodes.
  349. * @param {ASTNode} inner A node which is expected as inside.
  350. * @param {ASTNode} outer A node which is expected as outside.
  351. * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
  352. * @private
  353. */
  354. function isInside(inner, outer) {
  355. return (
  356. inner.range[0] >= outer.range[0] &&
  357. inner.range[1] <= outer.range[1]
  358. );
  359. }
  360. /**
  361. * Checks whether a given node is unused expression or not.
  362. * @param {ASTNode} node The node itself
  363. * @returns {boolean} The node is an unused expression.
  364. * @private
  365. */
  366. function isUnusedExpression(node) {
  367. const parent = node.parent;
  368. if (parent.type === "ExpressionStatement") {
  369. return true;
  370. }
  371. if (parent.type === "SequenceExpression") {
  372. const isLastExpression = parent.expressions.at(-1) === node;
  373. if (!isLastExpression) {
  374. return true;
  375. }
  376. return isUnusedExpression(parent);
  377. }
  378. return false;
  379. }
  380. /**
  381. * If a given reference is left-hand side of an assignment, this gets
  382. * the right-hand side node of the assignment.
  383. *
  384. * In the following cases, this returns null.
  385. *
  386. * - The reference is not the LHS of an assignment expression.
  387. * - The reference is inside of a loop.
  388. * - The reference is inside of a function scope which is different from
  389. * the declaration.
  390. * @param {eslint-scope.Reference} ref A reference to check.
  391. * @param {ASTNode} prevRhsNode The previous RHS node.
  392. * @returns {ASTNode|null} The RHS node or null.
  393. * @private
  394. */
  395. function getRhsNode(ref, prevRhsNode) {
  396. const id = ref.identifier;
  397. const parent = id.parent;
  398. const refScope = ref.from.variableScope;
  399. const varScope = ref.resolved.scope.variableScope;
  400. const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
  401. /*
  402. * Inherits the previous node if this reference is in the node.
  403. * This is for `a = a + a`-like code.
  404. */
  405. if (prevRhsNode && isInside(id, prevRhsNode)) {
  406. return prevRhsNode;
  407. }
  408. if (parent.type === "AssignmentExpression" &&
  409. isUnusedExpression(parent) &&
  410. id === parent.left &&
  411. !canBeUsedLater
  412. ) {
  413. return parent.right;
  414. }
  415. return null;
  416. }
  417. /**
  418. * Checks whether a given function node is stored to somewhere or not.
  419. * If the function node is stored, the function can be used later.
  420. * @param {ASTNode} funcNode A function node to check.
  421. * @param {ASTNode} rhsNode The RHS node of the previous assignment.
  422. * @returns {boolean} `true` if under the following conditions:
  423. * - the funcNode is assigned to a variable.
  424. * - the funcNode is bound as an argument of a function call.
  425. * - the function is bound to a property and the object satisfies above conditions.
  426. * @private
  427. */
  428. function isStorableFunction(funcNode, rhsNode) {
  429. let node = funcNode;
  430. let parent = funcNode.parent;
  431. while (parent && isInside(parent, rhsNode)) {
  432. switch (parent.type) {
  433. case "SequenceExpression":
  434. if (parent.expressions.at(-1) !== node) {
  435. return false;
  436. }
  437. break;
  438. case "CallExpression":
  439. case "NewExpression":
  440. return parent.callee !== node;
  441. case "AssignmentExpression":
  442. case "TaggedTemplateExpression":
  443. case "YieldExpression":
  444. return true;
  445. default:
  446. if (STATEMENT_TYPE.test(parent.type)) {
  447. /*
  448. * If it encountered statements, this is a complex pattern.
  449. * Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
  450. */
  451. return true;
  452. }
  453. }
  454. node = parent;
  455. parent = parent.parent;
  456. }
  457. return false;
  458. }
  459. /**
  460. * Checks whether a given Identifier node exists inside of a function node which can be used later.
  461. *
  462. * "can be used later" means:
  463. * - the function is assigned to a variable.
  464. * - the function is bound to a property and the object can be used later.
  465. * - the function is bound as an argument of a function call.
  466. *
  467. * If a reference exists in a function which can be used later, the reference is read when the function is called.
  468. * @param {ASTNode} id An Identifier node to check.
  469. * @param {ASTNode} rhsNode The RHS node of the previous assignment.
  470. * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
  471. * @private
  472. */
  473. function isInsideOfStorableFunction(id, rhsNode) {
  474. const funcNode = astUtils.getUpperFunction(id);
  475. return (
  476. funcNode &&
  477. isInside(funcNode, rhsNode) &&
  478. isStorableFunction(funcNode, rhsNode)
  479. );
  480. }
  481. /**
  482. * Checks whether a given reference is a read to update itself or not.
  483. * @param {eslint-scope.Reference} ref A reference to check.
  484. * @param {ASTNode} rhsNode The RHS node of the previous assignment.
  485. * @returns {boolean} The reference is a read to update itself.
  486. * @private
  487. */
  488. function isReadForItself(ref, rhsNode) {
  489. const id = ref.identifier;
  490. const parent = id.parent;
  491. return ref.isRead() && (
  492. // self update. e.g. `a += 1`, `a++`
  493. (
  494. (
  495. parent.type === "AssignmentExpression" &&
  496. parent.left === id &&
  497. isUnusedExpression(parent) &&
  498. !astUtils.isLogicalAssignmentOperator(parent.operator)
  499. ) ||
  500. (
  501. parent.type === "UpdateExpression" &&
  502. isUnusedExpression(parent)
  503. )
  504. ) ||
  505. // in RHS of an assignment for itself. e.g. `a = a + 1`
  506. (
  507. rhsNode &&
  508. isInside(id, rhsNode) &&
  509. !isInsideOfStorableFunction(id, rhsNode)
  510. )
  511. );
  512. }
  513. /**
  514. * Determine if an identifier is used either in for-in or for-of loops.
  515. * @param {Reference} ref The reference to check.
  516. * @returns {boolean} whether reference is used in the for-in loops
  517. * @private
  518. */
  519. function isForInOfRef(ref) {
  520. let target = ref.identifier.parent;
  521. // "for (var ...) { return; }"
  522. if (target.type === "VariableDeclarator") {
  523. target = target.parent.parent;
  524. }
  525. if (target.type !== "ForInStatement" && target.type !== "ForOfStatement") {
  526. return false;
  527. }
  528. // "for (...) { return; }"
  529. if (target.body.type === "BlockStatement") {
  530. target = target.body.body[0];
  531. // "for (...) return;"
  532. } else {
  533. target = target.body;
  534. }
  535. // For empty loop body
  536. if (!target) {
  537. return false;
  538. }
  539. return target.type === "ReturnStatement";
  540. }
  541. /**
  542. * Determines if the variable is used.
  543. * @param {Variable} variable The variable to check.
  544. * @returns {boolean} True if the variable is used
  545. * @private
  546. */
  547. function isUsedVariable(variable) {
  548. if (variable.eslintUsed) {
  549. return true;
  550. }
  551. const functionNodes = getFunctionDefinitions(variable);
  552. const isFunctionDefinition = functionNodes.length > 0;
  553. let rhsNode = null;
  554. return variable.references.some(ref => {
  555. if (isForInOfRef(ref)) {
  556. return true;
  557. }
  558. const forItself = isReadForItself(ref, rhsNode);
  559. rhsNode = getRhsNode(ref, rhsNode);
  560. return (
  561. isReadRef(ref) &&
  562. !forItself &&
  563. !(isFunctionDefinition && isSelfReference(ref, functionNodes))
  564. );
  565. });
  566. }
  567. /**
  568. * Checks whether the given variable is after the last used parameter.
  569. * @param {eslint-scope.Variable} variable The variable to check.
  570. * @returns {boolean} `true` if the variable is defined after the last
  571. * used parameter.
  572. */
  573. function isAfterLastUsedArg(variable) {
  574. const def = variable.defs[0];
  575. const params = sourceCode.getDeclaredVariables(def.node);
  576. const posteriorParams = params.slice(params.indexOf(variable) + 1);
  577. // If any used parameters occur after this parameter, do not report.
  578. return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed);
  579. }
  580. /**
  581. * Gets an array of variables without read references.
  582. * @param {Scope} scope an eslint-scope Scope object.
  583. * @param {Variable[]} unusedVars an array that saving result.
  584. * @returns {Variable[]} unused variables of the scope and descendant scopes.
  585. * @private
  586. */
  587. function collectUnusedVariables(scope, unusedVars) {
  588. const variables = scope.variables;
  589. const childScopes = scope.childScopes;
  590. let i, l;
  591. if (scope.type !== "global" || config.vars === "all") {
  592. for (i = 0, l = variables.length; i < l; ++i) {
  593. const variable = variables[i];
  594. // skip a variable of class itself name in the class scope
  595. if (scope.type === "class" && scope.block.id === variable.identifiers[0]) {
  596. continue;
  597. }
  598. // skip function expression names
  599. if (scope.functionExpressionScope) {
  600. continue;
  601. }
  602. // skip variables marked with markVariableAsUsed()
  603. if (!config.reportUsedIgnorePattern && variable.eslintUsed) {
  604. continue;
  605. }
  606. // skip implicit "arguments" variable
  607. if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) {
  608. continue;
  609. }
  610. // explicit global variables don't have definitions.
  611. const def = variable.defs[0];
  612. if (def) {
  613. const type = def.type;
  614. const refUsedInArrayPatterns = variable.references.some(ref => ref.identifier.parent.type === "ArrayPattern");
  615. // skip elements of array destructuring patterns
  616. if (
  617. (
  618. def.name.parent.type === "ArrayPattern" ||
  619. refUsedInArrayPatterns
  620. ) &&
  621. config.destructuredArrayIgnorePattern &&
  622. config.destructuredArrayIgnorePattern.test(def.name.name)
  623. ) {
  624. if (config.reportUsedIgnorePattern && isUsedVariable(variable)) {
  625. context.report({
  626. node: def.name,
  627. messageId: "usedIgnoredVar",
  628. data: getUsedIgnoredMessageData(variable, "array-destructure")
  629. });
  630. }
  631. continue;
  632. }
  633. if (type === "ClassName") {
  634. const hasStaticBlock = def.node.body.body.some(node => node.type === "StaticBlock");
  635. if (config.ignoreClassWithStaticInitBlock && hasStaticBlock) {
  636. continue;
  637. }
  638. }
  639. // skip catch variables
  640. if (type === "CatchClause") {
  641. if (config.caughtErrors === "none") {
  642. continue;
  643. }
  644. // skip ignored parameters
  645. if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) {
  646. if (config.reportUsedIgnorePattern && isUsedVariable(variable)) {
  647. context.report({
  648. node: def.name,
  649. messageId: "usedIgnoredVar",
  650. data: getUsedIgnoredMessageData(variable, "catch-clause")
  651. });
  652. }
  653. continue;
  654. }
  655. } else if (type === "Parameter") {
  656. // skip any setter argument
  657. if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") {
  658. continue;
  659. }
  660. // if "args" option is "none", skip any parameter
  661. if (config.args === "none") {
  662. continue;
  663. }
  664. // skip ignored parameters
  665. if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) {
  666. if (config.reportUsedIgnorePattern && isUsedVariable(variable)) {
  667. context.report({
  668. node: def.name,
  669. messageId: "usedIgnoredVar",
  670. data: getUsedIgnoredMessageData(variable, "parameter")
  671. });
  672. }
  673. continue;
  674. }
  675. // if "args" option is "after-used", skip used variables
  676. if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) {
  677. continue;
  678. }
  679. } else {
  680. // skip ignored variables
  681. if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) {
  682. if (config.reportUsedIgnorePattern && isUsedVariable(variable)) {
  683. context.report({
  684. node: def.name,
  685. messageId: "usedIgnoredVar",
  686. data: getUsedIgnoredMessageData(variable, "variable")
  687. });
  688. }
  689. continue;
  690. }
  691. }
  692. }
  693. if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
  694. unusedVars.push(variable);
  695. }
  696. }
  697. }
  698. for (i = 0, l = childScopes.length; i < l; ++i) {
  699. collectUnusedVariables(childScopes[i], unusedVars);
  700. }
  701. return unusedVars;
  702. }
  703. //--------------------------------------------------------------------------
  704. // Public
  705. //--------------------------------------------------------------------------
  706. return {
  707. "Program:exit"(programNode) {
  708. const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []);
  709. for (let i = 0, l = unusedVars.length; i < l; ++i) {
  710. const unusedVar = unusedVars[i];
  711. // Report the first declaration.
  712. if (unusedVar.defs.length > 0) {
  713. // report last write reference, https://github.com/eslint/eslint/issues/14324
  714. const writeReferences = unusedVar.references.filter(ref => ref.isWrite() && ref.from.variableScope === unusedVar.scope.variableScope);
  715. let referenceToReport;
  716. if (writeReferences.length > 0) {
  717. referenceToReport = writeReferences.at(-1);
  718. }
  719. context.report({
  720. node: referenceToReport ? referenceToReport.identifier : unusedVar.identifiers[0],
  721. messageId: "unusedVar",
  722. data: unusedVar.references.some(ref => ref.isWrite())
  723. ? getAssignedMessageData(unusedVar)
  724. : getDefinedMessageData(unusedVar)
  725. });
  726. // If there are no regular declaration, report the first `/*globals*/` comment directive.
  727. } else if (unusedVar.eslintExplicitGlobalComments) {
  728. const directiveComment = unusedVar.eslintExplicitGlobalComments[0];
  729. context.report({
  730. node: programNode,
  731. loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name),
  732. messageId: "unusedVar",
  733. data: getDefinedMessageData(unusedVar)
  734. });
  735. }
  736. }
  737. }
  738. };
  739. }
  740. };