no-unused-vars.js 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739
  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('../types').Rule.RuleModule} */
  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. hasSuggestions: true,
  43. schema: [
  44. {
  45. oneOf: [
  46. {
  47. enum: ["all", "local"],
  48. },
  49. {
  50. type: "object",
  51. properties: {
  52. vars: {
  53. enum: ["all", "local"],
  54. },
  55. varsIgnorePattern: {
  56. type: "string",
  57. },
  58. args: {
  59. enum: ["all", "after-used", "none"],
  60. },
  61. ignoreRestSiblings: {
  62. type: "boolean",
  63. },
  64. argsIgnorePattern: {
  65. type: "string",
  66. },
  67. caughtErrors: {
  68. enum: ["all", "none"],
  69. },
  70. caughtErrorsIgnorePattern: {
  71. type: "string",
  72. },
  73. destructuredArrayIgnorePattern: {
  74. type: "string",
  75. },
  76. ignoreClassWithStaticInitBlock: {
  77. type: "boolean",
  78. },
  79. ignoreUsingDeclarations: {
  80. type: "boolean",
  81. },
  82. reportUsedIgnorePattern: {
  83. type: "boolean",
  84. },
  85. },
  86. additionalProperties: false,
  87. },
  88. ],
  89. },
  90. ],
  91. messages: {
  92. unusedVar:
  93. "'{{varName}}' is {{action}} but never used{{additional}}.",
  94. usedIgnoredVar:
  95. "'{{varName}}' is marked as ignored but is used{{additional}}.",
  96. removeVar: "Remove unused variable '{{varName}}'.",
  97. },
  98. },
  99. create(context) {
  100. const sourceCode = context.sourceCode;
  101. const REST_PROPERTY_TYPE =
  102. /^(?:RestElement|(?:Experimental)?RestProperty)$/u;
  103. const config = {
  104. vars: "all",
  105. args: "after-used",
  106. ignoreRestSiblings: false,
  107. caughtErrors: "all",
  108. ignoreClassWithStaticInitBlock: false,
  109. ignoreUsingDeclarations: false,
  110. reportUsedIgnorePattern: false,
  111. };
  112. const firstOption = context.options[0];
  113. if (firstOption) {
  114. if (typeof firstOption === "string") {
  115. config.vars = firstOption;
  116. } else {
  117. config.vars = firstOption.vars || config.vars;
  118. config.args = firstOption.args || config.args;
  119. config.ignoreRestSiblings =
  120. firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
  121. config.caughtErrors =
  122. firstOption.caughtErrors || config.caughtErrors;
  123. config.ignoreClassWithStaticInitBlock =
  124. firstOption.ignoreClassWithStaticInitBlock ||
  125. config.ignoreClassWithStaticInitBlock;
  126. config.ignoreUsingDeclarations =
  127. firstOption.ignoreUsingDeclarations ||
  128. config.ignoreUsingDeclarations;
  129. config.reportUsedIgnorePattern =
  130. firstOption.reportUsedIgnorePattern ||
  131. config.reportUsedIgnorePattern;
  132. if (firstOption.varsIgnorePattern) {
  133. config.varsIgnorePattern = new RegExp(
  134. firstOption.varsIgnorePattern,
  135. "u",
  136. );
  137. }
  138. if (firstOption.argsIgnorePattern) {
  139. config.argsIgnorePattern = new RegExp(
  140. firstOption.argsIgnorePattern,
  141. "u",
  142. );
  143. }
  144. if (firstOption.caughtErrorsIgnorePattern) {
  145. config.caughtErrorsIgnorePattern = new RegExp(
  146. firstOption.caughtErrorsIgnorePattern,
  147. "u",
  148. );
  149. }
  150. if (firstOption.destructuredArrayIgnorePattern) {
  151. config.destructuredArrayIgnorePattern = new RegExp(
  152. firstOption.destructuredArrayIgnorePattern,
  153. "u",
  154. );
  155. }
  156. }
  157. }
  158. /**
  159. * Determines what variable type a def is.
  160. * @param {Object} def the declaration to check
  161. * @returns {VariableType} a simple name for the types of variables that this rule supports
  162. */
  163. function defToVariableType(def) {
  164. /*
  165. * This `destructuredArrayIgnorePattern` error report works differently from the catch
  166. * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the
  167. * `destructuredArrayIgnorePattern` will be checked for array destructuring. However,
  168. * for the purposes of the report, the currently defined behavior is to only inform the
  169. * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact
  170. * that the `varsIgnorePattern` would also apply). If it's not present, the user will be
  171. * informed of the `varsIgnorePattern`, assuming that's present.
  172. */
  173. if (
  174. config.destructuredArrayIgnorePattern &&
  175. def.name.parent.type === "ArrayPattern"
  176. ) {
  177. return "array-destructure";
  178. }
  179. switch (def.type) {
  180. case "CatchClause":
  181. return "catch-clause";
  182. case "Parameter":
  183. return "parameter";
  184. default:
  185. return "variable";
  186. }
  187. }
  188. /**
  189. * Gets a given variable's description and configured ignore pattern
  190. * based on the provided variableType
  191. * @param {VariableType} variableType a simple name for the types of variables that this rule supports
  192. * @throws {Error} (Unreachable)
  193. * @returns {[string | undefined, string | undefined]} the given variable's description and
  194. * ignore pattern
  195. */
  196. function getVariableDescription(variableType) {
  197. let pattern;
  198. let variableDescription;
  199. switch (variableType) {
  200. case "array-destructure":
  201. pattern = config.destructuredArrayIgnorePattern;
  202. variableDescription = "elements of array destructuring";
  203. break;
  204. case "catch-clause":
  205. pattern = config.caughtErrorsIgnorePattern;
  206. variableDescription = "caught errors";
  207. break;
  208. case "parameter":
  209. pattern = config.argsIgnorePattern;
  210. variableDescription = "args";
  211. break;
  212. case "variable":
  213. pattern = config.varsIgnorePattern;
  214. variableDescription = "vars";
  215. break;
  216. default:
  217. throw new Error(
  218. `Unexpected variable type: ${variableType}`,
  219. );
  220. }
  221. if (pattern) {
  222. pattern = pattern.toString();
  223. }
  224. return [variableDescription, pattern];
  225. }
  226. /**
  227. * Generates the message data about the variable being defined and unused,
  228. * including the ignore pattern if configured.
  229. * @param {Variable} unusedVar eslint-scope variable object.
  230. * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
  231. */
  232. function getDefinedMessageData(unusedVar) {
  233. const def = unusedVar.defs && unusedVar.defs[0];
  234. let additionalMessageData = "";
  235. if (def) {
  236. const [variableDescription, pattern] = getVariableDescription(
  237. defToVariableType(def),
  238. );
  239. if (pattern && variableDescription) {
  240. additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
  241. }
  242. }
  243. return {
  244. varName: unusedVar.name,
  245. action: "defined",
  246. additional: additionalMessageData,
  247. };
  248. }
  249. /**
  250. * Generate the warning message about the variable being
  251. * assigned and unused, including the ignore pattern if configured.
  252. * @param {Variable} unusedVar eslint-scope variable object.
  253. * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
  254. */
  255. function getAssignedMessageData(unusedVar) {
  256. const def = unusedVar.defs && unusedVar.defs[0];
  257. let additionalMessageData = "";
  258. if (def) {
  259. const [variableDescription, pattern] = getVariableDescription(
  260. defToVariableType(def),
  261. );
  262. if (pattern && variableDescription) {
  263. additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
  264. }
  265. }
  266. return {
  267. varName: unusedVar.name,
  268. action: "assigned a value",
  269. additional: additionalMessageData,
  270. };
  271. }
  272. /**
  273. * Generate the warning message about a variable being used even though
  274. * it is marked as being ignored.
  275. * @param {Variable} variable eslint-scope variable object
  276. * @param {VariableType} variableType a simple name for the types of variables that this rule supports
  277. * @returns {UsedIgnoredVarMessageData} The message data to be used with
  278. * this used ignored variable.
  279. */
  280. function getUsedIgnoredMessageData(variable, variableType) {
  281. const [variableDescription, pattern] =
  282. getVariableDescription(variableType);
  283. let additionalMessageData = "";
  284. if (pattern && variableDescription) {
  285. additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`;
  286. }
  287. return {
  288. varName: variable.name,
  289. additional: additionalMessageData,
  290. };
  291. }
  292. //--------------------------------------------------------------------------
  293. // Helpers
  294. //--------------------------------------------------------------------------
  295. const STATEMENT_TYPE = /(?:Statement|Declaration)$/u;
  296. /**
  297. * Determines if a given variable is being exported from a module.
  298. * @param {Variable} variable eslint-scope variable object.
  299. * @returns {boolean} True if the variable is exported, false if not.
  300. * @private
  301. */
  302. function isExported(variable) {
  303. const definition = variable.defs[0];
  304. if (definition) {
  305. let node = definition.node;
  306. if (node.type === "VariableDeclarator") {
  307. node = node.parent;
  308. } else if (definition.type === "Parameter") {
  309. return false;
  310. }
  311. return node.parent.type.indexOf("Export") === 0;
  312. }
  313. return false;
  314. }
  315. /**
  316. * Determines if a given variable uses the explicit resource management protocol.
  317. * @param {Variable} variable eslint-scope variable object.
  318. * @returns {boolean} True if the variable is declared with "using" or "await using"
  319. * @private
  320. */
  321. function usesExplicitResourceManagement(variable) {
  322. const [definition] = variable.defs;
  323. return (
  324. definition?.type === "Variable" &&
  325. (definition.parent.kind === "using" ||
  326. definition.parent.kind === "await using")
  327. );
  328. }
  329. /**
  330. * Checks whether a node is a sibling of the rest property or not.
  331. * @param {ASTNode} node a node to check
  332. * @returns {boolean} True if the node is a sibling of the rest property, otherwise false.
  333. */
  334. function hasRestSibling(node) {
  335. return (
  336. node.type === "Property" &&
  337. node.parent.type === "ObjectPattern" &&
  338. REST_PROPERTY_TYPE.test(node.parent.properties.at(-1).type)
  339. );
  340. }
  341. /**
  342. * Determines if a variable has a sibling rest property
  343. * @param {Variable} variable eslint-scope variable object.
  344. * @returns {boolean} True if the variable has a sibling rest property, false if not.
  345. * @private
  346. */
  347. function hasRestSpreadSibling(variable) {
  348. if (config.ignoreRestSiblings) {
  349. const hasRestSiblingDefinition = variable.defs.some(def =>
  350. hasRestSibling(def.name.parent),
  351. );
  352. const hasRestSiblingReference = variable.references.some(ref =>
  353. hasRestSibling(ref.identifier.parent),
  354. );
  355. return hasRestSiblingDefinition || hasRestSiblingReference;
  356. }
  357. return false;
  358. }
  359. /**
  360. * Determines if a reference is a read operation.
  361. * @param {Reference} ref An eslint-scope Reference
  362. * @returns {boolean} whether the given reference represents a read operation
  363. * @private
  364. */
  365. function isReadRef(ref) {
  366. return ref.isRead();
  367. }
  368. /**
  369. * Determine if an identifier is referencing an enclosing function name.
  370. * @param {Reference} ref The reference to check.
  371. * @param {ASTNode[]} nodes The candidate function nodes.
  372. * @returns {boolean} True if it's a self-reference, false if not.
  373. * @private
  374. */
  375. function isSelfReference(ref, nodes) {
  376. let scope = ref.from;
  377. while (scope) {
  378. if (nodes.includes(scope.block)) {
  379. return true;
  380. }
  381. scope = scope.upper;
  382. }
  383. return false;
  384. }
  385. /**
  386. * Gets a list of function definitions for a specified variable.
  387. * @param {Variable} variable eslint-scope variable object.
  388. * @returns {ASTNode[]} Function nodes.
  389. * @private
  390. */
  391. function getFunctionDefinitions(variable) {
  392. const functionDefinitions = [];
  393. variable.defs.forEach(def => {
  394. const { type, node } = def;
  395. // FunctionDeclarations
  396. if (type === "FunctionName") {
  397. functionDefinitions.push(node);
  398. }
  399. // FunctionExpressions
  400. if (
  401. type === "Variable" &&
  402. node.init &&
  403. (node.init.type === "FunctionExpression" ||
  404. node.init.type === "ArrowFunctionExpression")
  405. ) {
  406. functionDefinitions.push(node.init);
  407. }
  408. });
  409. return functionDefinitions;
  410. }
  411. /**
  412. * Checks the position of given nodes.
  413. * @param {ASTNode} inner A node which is expected as inside.
  414. * @param {ASTNode} outer A node which is expected as outside.
  415. * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
  416. * @private
  417. */
  418. function isInside(inner, outer) {
  419. return (
  420. inner.range[0] >= outer.range[0] &&
  421. inner.range[1] <= outer.range[1]
  422. );
  423. }
  424. /**
  425. * Checks whether a given node is unused expression or not.
  426. * @param {ASTNode} node The node itself
  427. * @returns {boolean} The node is an unused expression.
  428. * @private
  429. */
  430. function isUnusedExpression(node) {
  431. const parent = node.parent;
  432. if (parent.type === "ExpressionStatement") {
  433. return true;
  434. }
  435. if (parent.type === "SequenceExpression") {
  436. const isLastExpression = parent.expressions.at(-1) === node;
  437. if (!isLastExpression) {
  438. return true;
  439. }
  440. return isUnusedExpression(parent);
  441. }
  442. return false;
  443. }
  444. /**
  445. * If a given reference is left-hand side of an assignment, this gets
  446. * the right-hand side node of the assignment.
  447. *
  448. * In the following cases, this returns null.
  449. *
  450. * - The reference is not the LHS of an assignment expression.
  451. * - The reference is inside of a loop.
  452. * - The reference is inside of a function scope which is different from
  453. * the declaration.
  454. * @param {eslint-scope.Reference} ref A reference to check.
  455. * @param {ASTNode} prevRhsNode The previous RHS node.
  456. * @returns {ASTNode|null} The RHS node or null.
  457. * @private
  458. */
  459. function getRhsNode(ref, prevRhsNode) {
  460. const id = ref.identifier;
  461. const parent = id.parent;
  462. const refScope = ref.from.variableScope;
  463. const varScope = ref.resolved.scope.variableScope;
  464. const canBeUsedLater =
  465. refScope !== varScope || astUtils.isInLoop(id);
  466. /*
  467. * Inherits the previous node if this reference is in the node.
  468. * This is for `a = a + a`-like code.
  469. */
  470. if (prevRhsNode && isInside(id, prevRhsNode)) {
  471. return prevRhsNode;
  472. }
  473. if (
  474. parent.type === "AssignmentExpression" &&
  475. isUnusedExpression(parent) &&
  476. id === parent.left &&
  477. !canBeUsedLater
  478. ) {
  479. return parent.right;
  480. }
  481. return null;
  482. }
  483. /**
  484. * Checks whether a given function node is stored to somewhere or not.
  485. * If the function node is stored, the function can be used later.
  486. * @param {ASTNode} funcNode A function node to check.
  487. * @param {ASTNode} rhsNode The RHS node of the previous assignment.
  488. * @returns {boolean} `true` if under the following conditions:
  489. * - the funcNode is assigned to a variable.
  490. * - the funcNode is bound as an argument of a function call.
  491. * - the function is bound to a property and the object satisfies above conditions.
  492. * @private
  493. */
  494. function isStorableFunction(funcNode, rhsNode) {
  495. let node = funcNode;
  496. let parent = funcNode.parent;
  497. while (parent && isInside(parent, rhsNode)) {
  498. switch (parent.type) {
  499. case "SequenceExpression":
  500. if (parent.expressions.at(-1) !== node) {
  501. return false;
  502. }
  503. break;
  504. case "CallExpression":
  505. case "NewExpression":
  506. return parent.callee !== node;
  507. case "AssignmentExpression":
  508. case "TaggedTemplateExpression":
  509. case "YieldExpression":
  510. return true;
  511. default:
  512. if (STATEMENT_TYPE.test(parent.type)) {
  513. /*
  514. * If it encountered statements, this is a complex pattern.
  515. * Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
  516. */
  517. return true;
  518. }
  519. }
  520. node = parent;
  521. parent = parent.parent;
  522. }
  523. return false;
  524. }
  525. /**
  526. * Checks whether a given Identifier node exists inside of a function node which can be used later.
  527. *
  528. * "can be used later" means:
  529. * - the function is assigned to a variable.
  530. * - the function is bound to a property and the object can be used later.
  531. * - the function is bound as an argument of a function call.
  532. *
  533. * If a reference exists in a function which can be used later, the reference is read when the function is called.
  534. * @param {ASTNode} id An Identifier node to check.
  535. * @param {ASTNode} rhsNode The RHS node of the previous assignment.
  536. * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
  537. * @private
  538. */
  539. function isInsideOfStorableFunction(id, rhsNode) {
  540. const funcNode = astUtils.getUpperFunction(id);
  541. return (
  542. funcNode &&
  543. isInside(funcNode, rhsNode) &&
  544. isStorableFunction(funcNode, rhsNode)
  545. );
  546. }
  547. /**
  548. * Checks whether a given reference is a read to update itself or not.
  549. * @param {eslint-scope.Reference} ref A reference to check.
  550. * @param {ASTNode} rhsNode The RHS node of the previous assignment.
  551. * @returns {boolean} The reference is a read to update itself.
  552. * @private
  553. */
  554. function isReadForItself(ref, rhsNode) {
  555. const id = ref.identifier;
  556. const parent = id.parent;
  557. return (
  558. ref.isRead() &&
  559. // self update. e.g. `a += 1`, `a++`
  560. ((parent.type === "AssignmentExpression" &&
  561. parent.left === id &&
  562. isUnusedExpression(parent) &&
  563. !astUtils.isLogicalAssignmentOperator(parent.operator)) ||
  564. (parent.type === "UpdateExpression" &&
  565. isUnusedExpression(parent)) ||
  566. // in RHS of an assignment for itself. e.g. `a = a + 1`
  567. (rhsNode &&
  568. isInside(id, rhsNode) &&
  569. !isInsideOfStorableFunction(id, rhsNode)))
  570. );
  571. }
  572. /**
  573. * Determine if an identifier is used either in for-in or for-of loops.
  574. * @param {Reference} ref The reference to check.
  575. * @returns {boolean} whether reference is used in the for-in loops
  576. * @private
  577. */
  578. function isForInOfRef(ref) {
  579. let target = ref.identifier.parent;
  580. // "for (var ...) { return; }"
  581. if (target.type === "VariableDeclarator") {
  582. target = target.parent.parent;
  583. }
  584. if (
  585. target.type !== "ForInStatement" &&
  586. target.type !== "ForOfStatement"
  587. ) {
  588. return false;
  589. }
  590. // "for (...) { return; }"
  591. if (target.body.type === "BlockStatement") {
  592. target = target.body.body[0];
  593. // "for (...) return;"
  594. } else {
  595. target = target.body;
  596. }
  597. // For empty loop body
  598. if (!target) {
  599. return false;
  600. }
  601. return target.type === "ReturnStatement";
  602. }
  603. /**
  604. * Determines if the variable is used.
  605. * @param {Variable} variable The variable to check.
  606. * @returns {boolean} True if the variable is used
  607. * @private
  608. */
  609. function isUsedVariable(variable) {
  610. if (variable.eslintUsed) {
  611. return true;
  612. }
  613. const functionNodes = getFunctionDefinitions(variable);
  614. const isFunctionDefinition = functionNodes.length > 0;
  615. let rhsNode = null;
  616. return variable.references.some(ref => {
  617. if (isForInOfRef(ref)) {
  618. return true;
  619. }
  620. const forItself = isReadForItself(ref, rhsNode);
  621. rhsNode = getRhsNode(ref, rhsNode);
  622. return (
  623. isReadRef(ref) &&
  624. !forItself &&
  625. !(
  626. isFunctionDefinition &&
  627. isSelfReference(ref, functionNodes)
  628. )
  629. );
  630. });
  631. }
  632. /**
  633. * Checks whether the given variable is after the last used parameter.
  634. * @param {eslint-scope.Variable} variable The variable to check.
  635. * @returns {boolean} `true` if the variable is defined after the last
  636. * used parameter.
  637. */
  638. function isAfterLastUsedArg(variable) {
  639. const def = variable.defs[0];
  640. const params = sourceCode.getDeclaredVariables(def.node);
  641. const posteriorParams = params.slice(params.indexOf(variable) + 1);
  642. // If any used parameters occur after this parameter, do not report.
  643. return !posteriorParams.some(
  644. v => v.references.length > 0 || v.eslintUsed,
  645. );
  646. }
  647. /**
  648. * Gets an array of variables without read references.
  649. * @param {Scope} scope an eslint-scope Scope object.
  650. * @param {Variable[]} unusedVars an array that saving result.
  651. * @returns {Variable[]} unused variables of the scope and descendant scopes.
  652. * @private
  653. */
  654. function collectUnusedVariables(scope, unusedVars) {
  655. const variables = scope.variables;
  656. const childScopes = scope.childScopes;
  657. let i, l;
  658. if (scope.type !== "global" || config.vars === "all") {
  659. for (i = 0, l = variables.length; i < l; ++i) {
  660. const variable = variables[i];
  661. // skip a variable of class itself name in the class scope
  662. if (
  663. scope.type === "class" &&
  664. scope.block.id === variable.identifiers[0]
  665. ) {
  666. continue;
  667. }
  668. // skip function expression names
  669. if (scope.functionExpressionScope) {
  670. continue;
  671. }
  672. // skip variables marked with markVariableAsUsed()
  673. if (
  674. !config.reportUsedIgnorePattern &&
  675. variable.eslintUsed
  676. ) {
  677. continue;
  678. }
  679. // skip implicit "arguments" variable
  680. if (
  681. scope.type === "function" &&
  682. variable.name === "arguments" &&
  683. variable.identifiers.length === 0
  684. ) {
  685. continue;
  686. }
  687. // explicit global variables don't have definitions.
  688. const def = variable.defs[0];
  689. if (def) {
  690. const type = def.type;
  691. const refUsedInArrayPatterns = variable.references.some(
  692. ref =>
  693. ref.identifier.parent.type === "ArrayPattern",
  694. );
  695. // skip elements of array destructuring patterns
  696. if (
  697. (def.name.parent.type === "ArrayPattern" ||
  698. refUsedInArrayPatterns) &&
  699. config.destructuredArrayIgnorePattern &&
  700. config.destructuredArrayIgnorePattern.test(
  701. def.name.name,
  702. )
  703. ) {
  704. if (
  705. config.reportUsedIgnorePattern &&
  706. isUsedVariable(variable)
  707. ) {
  708. context.report({
  709. node: def.name,
  710. messageId: "usedIgnoredVar",
  711. data: getUsedIgnoredMessageData(
  712. variable,
  713. "array-destructure",
  714. ),
  715. });
  716. }
  717. continue;
  718. }
  719. if (type === "ClassName") {
  720. const hasStaticBlock = def.node.body.body.some(
  721. node => node.type === "StaticBlock",
  722. );
  723. if (
  724. config.ignoreClassWithStaticInitBlock &&
  725. hasStaticBlock
  726. ) {
  727. continue;
  728. }
  729. }
  730. // skip catch variables
  731. if (type === "CatchClause") {
  732. if (config.caughtErrors === "none") {
  733. continue;
  734. }
  735. // skip ignored parameters
  736. if (
  737. config.caughtErrorsIgnorePattern &&
  738. config.caughtErrorsIgnorePattern.test(
  739. def.name.name,
  740. )
  741. ) {
  742. if (
  743. config.reportUsedIgnorePattern &&
  744. isUsedVariable(variable)
  745. ) {
  746. context.report({
  747. node: def.name,
  748. messageId: "usedIgnoredVar",
  749. data: getUsedIgnoredMessageData(
  750. variable,
  751. "catch-clause",
  752. ),
  753. });
  754. }
  755. continue;
  756. }
  757. } else if (type === "Parameter") {
  758. // skip any setter argument
  759. if (
  760. (def.node.parent.type === "Property" ||
  761. def.node.parent.type ===
  762. "MethodDefinition") &&
  763. def.node.parent.kind === "set"
  764. ) {
  765. continue;
  766. }
  767. // if "args" option is "none", skip any parameter
  768. if (config.args === "none") {
  769. continue;
  770. }
  771. // skip ignored parameters
  772. if (
  773. config.argsIgnorePattern &&
  774. config.argsIgnorePattern.test(def.name.name)
  775. ) {
  776. if (
  777. config.reportUsedIgnorePattern &&
  778. isUsedVariable(variable)
  779. ) {
  780. context.report({
  781. node: def.name,
  782. messageId: "usedIgnoredVar",
  783. data: getUsedIgnoredMessageData(
  784. variable,
  785. "parameter",
  786. ),
  787. });
  788. }
  789. continue;
  790. }
  791. // if "args" option is "after-used", skip used variables
  792. if (
  793. config.args === "after-used" &&
  794. astUtils.isFunction(def.name.parent) &&
  795. !isAfterLastUsedArg(variable)
  796. ) {
  797. continue;
  798. }
  799. } else {
  800. // skip ignored variables
  801. if (
  802. config.varsIgnorePattern &&
  803. config.varsIgnorePattern.test(def.name.name)
  804. ) {
  805. if (
  806. config.reportUsedIgnorePattern &&
  807. isUsedVariable(variable)
  808. ) {
  809. context.report({
  810. node: def.name,
  811. messageId: "usedIgnoredVar",
  812. data: getUsedIgnoredMessageData(
  813. variable,
  814. "variable",
  815. ),
  816. });
  817. }
  818. continue;
  819. }
  820. }
  821. }
  822. if (
  823. !isUsedVariable(variable) &&
  824. !isExported(variable) &&
  825. !(
  826. config.ignoreUsingDeclarations &&
  827. usesExplicitResourceManagement(variable)
  828. ) &&
  829. !hasRestSpreadSibling(variable)
  830. ) {
  831. unusedVars.push(variable);
  832. }
  833. }
  834. }
  835. for (i = 0, l = childScopes.length; i < l; ++i) {
  836. collectUnusedVariables(childScopes[i], unusedVars);
  837. }
  838. return unusedVars;
  839. }
  840. /**
  841. * fixes unused variables
  842. * @param {Object} fixer fixer object
  843. * @param {Object} unusedVar unused variable to fix
  844. * @returns {Object} fixer object
  845. */
  846. function handleFixes(fixer, unusedVar) {
  847. const id = unusedVar.identifiers[0];
  848. const parent = id.parent;
  849. const parentType = parent.type;
  850. const tokenBefore = sourceCode.getTokenBefore(id);
  851. const tokenAfter = sourceCode.getTokenAfter(id);
  852. const isFunction = astUtils.isFunction;
  853. const isLoop = astUtils.isLoop;
  854. const allWriteReferences = unusedVar.references.filter(ref =>
  855. ref.isWrite(),
  856. );
  857. /**
  858. * get range from token before of a given node
  859. * @param {ASTNode} node node of identifier
  860. * @param {number} skips number of token to skip
  861. * @returns {number} start range of token before the identifier
  862. */
  863. function getPreviousTokenStart(node, skips) {
  864. return sourceCode.getTokenBefore(node, skips).range[0];
  865. }
  866. /**
  867. * get range to token after of a given node
  868. * @param {ASTNode} node node of identifier
  869. * @param {number} skips number of token to skip
  870. * @returns {number} end range of token after the identifier
  871. */
  872. function getNextTokenEnd(node, skips) {
  873. return sourceCode.getTokenAfter(node, skips).range[1];
  874. }
  875. /**
  876. * get the value of token before of a given node
  877. * @param {ASTNode} node node of identifier
  878. * @returns {string} value of token before the identifier
  879. */
  880. function getTokenBeforeValue(node) {
  881. return sourceCode.getTokenBefore(node).value;
  882. }
  883. /**
  884. * get the value of token after of a given node
  885. * @param {ASTNode} node node of identifier
  886. * @returns {string} value of token after the identifier
  887. */
  888. function getTokenAfterValue(node) {
  889. return sourceCode.getTokenAfter(node).value;
  890. }
  891. /**
  892. * Check if an array has a single element with null as other element.
  893. * @param {ASTNode} node ArrayPattern node
  894. * @returns {boolean} true if array has single element with other null elements
  895. */
  896. function hasSingleElement(node) {
  897. return node.elements.filter(e => e !== null).length === 1;
  898. }
  899. /**
  900. * check whether import specifier has an import of particular type
  901. * @param {ASTNode} node ImportDeclaration node
  902. * @param {string} type type of import to check
  903. * @returns {boolean} true if import specifier has import of specified type
  904. */
  905. function hasImportOfCertainType(node, type) {
  906. return node.specifiers.some(e => e.type === type);
  907. }
  908. /**
  909. * Check whether declaration is safe to remove or not
  910. * @param {ASTNode} nextToken next token of unused variable
  911. * @param {ASTNode} prevToken previous token of unused variable
  912. * @returns {boolean} true if declaration is not safe to remove
  913. */
  914. function isDeclarationNotSafeToRemove(nextToken, prevToken) {
  915. return (
  916. nextToken.type === "String" ||
  917. (prevToken &&
  918. !astUtils.isSemicolonToken(prevToken) &&
  919. !astUtils.isOpeningBraceToken(prevToken))
  920. );
  921. }
  922. /**
  923. * give fixes for unused variables in function parameters
  924. * @param {ASTNode} node node to check
  925. * @returns {Object} fixer object
  926. */
  927. function fixFunctionParameters(node) {
  928. const parentNode = node.parent;
  929. if (isFunction(parentNode)) {
  930. // remove unused function parameter if there is only a single parameter
  931. if (parentNode.params.length === 1) {
  932. return fixer.removeRange(node.range);
  933. }
  934. // remove first unused function parameter when there are multiple parameters
  935. if (
  936. getTokenBeforeValue(node) === "(" &&
  937. getTokenAfterValue(node) === ","
  938. ) {
  939. return fixer.removeRange([
  940. node.range[0],
  941. getNextTokenEnd(node),
  942. ]);
  943. }
  944. // remove unused function parameters except first one when there are multiple parameters
  945. return fixer.removeRange([
  946. getPreviousTokenStart(node),
  947. node.range[1],
  948. ]);
  949. }
  950. return null;
  951. }
  952. /**
  953. * fix unused variable declarations and function parameters
  954. * @param {ASTNode} node parent node to identifier
  955. * @returns {Object} fixer object
  956. */
  957. function fixVariables(node) {
  958. const parentNode = node.parent;
  959. // remove unused declared variables such as var a = b; or var a = b, c;
  960. if (parentNode.type === "VariableDeclarator") {
  961. // skip variable in for (const [ foo ] of bar);
  962. if (isLoop(parentNode.parent.parent)) {
  963. return null;
  964. }
  965. /*
  966. * remove unused declared variable with single declaration such as 'var a = b;'
  967. * remove complete declaration when there is an unused variable in 'const { a } = foo;', same for arrays.
  968. */
  969. if (parentNode.parent.declarations.length === 1) {
  970. // if next token is a string it could become a directive if node is removed -> no suggestion.
  971. const nextToken = sourceCode.getTokenAfter(
  972. parentNode.parent,
  973. );
  974. // if previous token exists and is not ";" or "{" not sure about ASI rules -> no suggestion.
  975. const prevToken = sourceCode.getTokenBefore(
  976. parentNode.parent,
  977. );
  978. if (
  979. nextToken &&
  980. isDeclarationNotSafeToRemove(nextToken, prevToken)
  981. ) {
  982. return null;
  983. }
  984. return fixer.removeRange(parentNode.parent.range);
  985. }
  986. /*
  987. * remove unused declared variable with multiple declaration except first one such as 'var a = b, c = d;'
  988. * fix 'let bar = "hello", { a } = foo;' to 'let bar = "hello";' if 'a' is unused, same for arrays.
  989. */
  990. if (getTokenBeforeValue(parentNode) === ",") {
  991. return fixer.removeRange([
  992. getPreviousTokenStart(parentNode),
  993. parentNode.range[1],
  994. ]);
  995. }
  996. /*
  997. * remove first unused declared variable when there are multiple declarations
  998. * fix 'let { a } = foo, bar = "hello";' to 'let bar = "hello";' if 'a' is unused, same for arrays.
  999. */
  1000. return fixer.removeRange([
  1001. parentNode.range[0],
  1002. getNextTokenEnd(parentNode),
  1003. ]);
  1004. }
  1005. // fixes [{a: {k}}], [{a: [k]}]
  1006. if (getTokenBeforeValue(node) === ":") {
  1007. if (parentNode.parent.type === "ObjectPattern") {
  1008. // eslint-disable-next-line no-use-before-define -- due to interdependency of functions
  1009. return fixObjectWithValueSeparator(node);
  1010. }
  1011. }
  1012. // fix unused function parameters
  1013. return fixFunctionParameters(node);
  1014. }
  1015. /**
  1016. * fix nested object like { a: { b } }
  1017. * @param {ASTNode} node parent node to check
  1018. * @returns {Object} fixer object
  1019. */
  1020. function fixNestedObjectVariable(node) {
  1021. const parentNode = node.parent;
  1022. // fix for { a: { b: { c: { d } } } }
  1023. if (
  1024. parentNode.parent.parent.parent.type === "ObjectPattern" &&
  1025. parentNode.parent.properties.length === 1
  1026. ) {
  1027. return fixNestedObjectVariable(parentNode.parent);
  1028. }
  1029. // fix for { a: { b } }
  1030. if (parentNode.parent.type === "ObjectPattern") {
  1031. // fix for unused variables in destructured object with single property in variable declaration and function parameter
  1032. if (parentNode.parent.properties.length === 1) {
  1033. return fixVariables(parentNode.parent);
  1034. }
  1035. // fix for first unused property when there are multiple properties such as '{ a: { b }, c }'
  1036. if (getTokenBeforeValue(parentNode) === "{") {
  1037. return fixer.removeRange([
  1038. parentNode.range[0],
  1039. getNextTokenEnd(parentNode),
  1040. ]);
  1041. }
  1042. // fix for unused property except first one when there are multiple properties such as '{ k, a: { b } }'
  1043. return fixer.removeRange([
  1044. getPreviousTokenStart(parentNode),
  1045. parentNode.range[1],
  1046. ]);
  1047. }
  1048. return null;
  1049. }
  1050. /**
  1051. * fix unused variables in array and nested array
  1052. * @param {ASTNode} node parent node to check
  1053. * @returns {Object} fixer object
  1054. */
  1055. function fixNestedArrayVariable(node) {
  1056. const parentNode = node.parent;
  1057. // fix for nested arrays [[ a ]]
  1058. if (
  1059. parentNode.parent.type === "ArrayPattern" &&
  1060. hasSingleElement(parentNode)
  1061. ) {
  1062. return fixNestedArrayVariable(parentNode);
  1063. }
  1064. if (hasSingleElement(parentNode)) {
  1065. // fixes { a: [{ b }] } or { a: [[ b ]] }
  1066. if (getTokenBeforeValue(parentNode) === ":") {
  1067. return fixVariables(parentNode);
  1068. }
  1069. // fixes [a, ...[[ b ]]] or [a, ...[{ b }]]
  1070. if (parentNode.parent.type === "RestElement") {
  1071. // eslint-disable-next-line no-use-before-define -- due to interdependency of functions
  1072. return fixRestInPattern(parentNode.parent);
  1073. }
  1074. // fix unused variables in destructured array in variable declaration or function parameter
  1075. return fixVariables(parentNode);
  1076. }
  1077. // remove last unused array element
  1078. if (
  1079. getTokenBeforeValue(node) === "," &&
  1080. getTokenAfterValue(node) === "]"
  1081. ) {
  1082. return fixer.removeRange([
  1083. getPreviousTokenStart(node),
  1084. node.range[1],
  1085. ]);
  1086. }
  1087. // remove unused array element
  1088. return fixer.removeRange(node.range);
  1089. }
  1090. /**
  1091. * fix cases like {a: {k}} or {a: [k]}
  1092. * @param {ASTNode} node parent node to check
  1093. * @returns {Object} fixer object
  1094. */
  1095. function fixObjectWithValueSeparator(node) {
  1096. const parentNode = node.parent.parent;
  1097. // fix cases like [{a : { b }}] or [{a : [ b ]}]
  1098. if (
  1099. parentNode.parent.type === "ArrayPattern" &&
  1100. parentNode.properties.length === 1
  1101. ) {
  1102. return fixNestedArrayVariable(parentNode);
  1103. }
  1104. // fix cases like {a: {k}} or {a: [k]}
  1105. return fixNestedObjectVariable(node);
  1106. }
  1107. /**
  1108. * fix ...[[a]] or ...[{a}] like patterns
  1109. * @param {ASTNode} node parent node to check
  1110. * @returns {Object} fixer object
  1111. */
  1112. function fixRestInPattern(node) {
  1113. const parentNode = node.parent;
  1114. // fix ...[[a]] or ...[{a}] in function parameters
  1115. if (isFunction(parentNode)) {
  1116. if (parentNode.params.length === 1) {
  1117. return fixer.removeRange(node.range);
  1118. }
  1119. return fixer.removeRange([
  1120. getPreviousTokenStart(node),
  1121. node.range[1],
  1122. ]);
  1123. }
  1124. // fix rest in nested array pattern like [[a, ...[b]]]
  1125. if (parentNode.type === "ArrayPattern") {
  1126. // fix [[...[b]]]
  1127. if (hasSingleElement(parentNode)) {
  1128. if (parentNode.parent.type === "ArrayPattern") {
  1129. return fixNestedArrayVariable(parentNode);
  1130. }
  1131. // fix 'const [...[b]] = foo; and function foo([...[b]]) {}
  1132. return fixVariables(parentNode);
  1133. }
  1134. // fix [[a, ...[b]]]
  1135. return fixer.removeRange([
  1136. getPreviousTokenStart(node),
  1137. node.range[1],
  1138. ]);
  1139. }
  1140. return null;
  1141. }
  1142. // skip fix when variable has references that would be left behind
  1143. if (
  1144. allWriteReferences.some(
  1145. ref => ref.identifier.range[0] !== id.range[0],
  1146. )
  1147. ) {
  1148. return null;
  1149. }
  1150. // remove declared variables such as var a; or var a, b;
  1151. if (parentType === "VariableDeclarator") {
  1152. if (parent.parent.declarations.length === 1) {
  1153. // prevent fix of variable in forOf and forIn loops.
  1154. if (
  1155. isLoop(parent.parent.parent) &&
  1156. parent.parent.parent.body !== parent.parent
  1157. ) {
  1158. return null;
  1159. }
  1160. // removes only variable not semicolon in 'if (foo()) var bar;' or in 'loops' or in 'with' statement.
  1161. if (
  1162. parent.parent.parent.type === "IfStatement" ||
  1163. isLoop(parent.parent.parent) ||
  1164. (parent.parent.parent.type === "WithStatement" &&
  1165. parent.parent.parent.body === parent.parent)
  1166. ) {
  1167. return fixer.replaceText(parent.parent, ";");
  1168. }
  1169. // if next token is a string it could become a directive if node is removed -> no suggestion.
  1170. const nextToken = sourceCode.getTokenAfter(parent.parent);
  1171. // if previous token exists and is not ";" or "{" not sure about ASI rules -> no suggestion.
  1172. const prevToken = sourceCode.getTokenBefore(parent.parent);
  1173. if (
  1174. nextToken &&
  1175. isDeclarationNotSafeToRemove(nextToken, prevToken)
  1176. ) {
  1177. return null;
  1178. }
  1179. // remove unused declared variable with single declaration like 'var a = b;'
  1180. return fixer.removeRange(parent.parent.range);
  1181. }
  1182. // remove unused declared variable with multiple declaration except first one like 'var a = b, c = d;'
  1183. if (tokenBefore.value === ",") {
  1184. return fixer.removeRange([
  1185. tokenBefore.range[0],
  1186. parent.range[1],
  1187. ]);
  1188. }
  1189. // remove first unused declared variable when there are multiple declarations
  1190. return fixer.removeRange([
  1191. parent.range[0],
  1192. getNextTokenEnd(parent),
  1193. ]);
  1194. }
  1195. // remove variables in object patterns
  1196. if (parent.parent.type === "ObjectPattern") {
  1197. if (parent.parent.properties.length === 1) {
  1198. // fix [a, ...{b}]
  1199. if (parent.parent.parent.type === "RestElement") {
  1200. return fixRestInPattern(parent.parent.parent);
  1201. }
  1202. // fix [{ a }]
  1203. if (parent.parent.parent.type === "ArrayPattern") {
  1204. return fixNestedArrayVariable(parent.parent);
  1205. }
  1206. /*
  1207. * var {a} = foo;
  1208. * function a({a}) {}
  1209. * fix const { a: { b } } = foo;
  1210. */
  1211. return fixVariables(parent.parent);
  1212. }
  1213. // fix const { a:b } = foo;
  1214. if (tokenBefore.value === ":") {
  1215. // remove first unused variable in const { a:b } = foo;
  1216. if (
  1217. getTokenBeforeValue(parent) === "{" &&
  1218. getTokenAfterValue(parent) === ","
  1219. ) {
  1220. return fixer.removeRange([
  1221. parent.range[0],
  1222. getNextTokenEnd(parent),
  1223. ]);
  1224. }
  1225. // remove unused variables in const { a: b, c: d } = foo; except first one
  1226. return fixer.removeRange([
  1227. getPreviousTokenStart(parent),
  1228. id.range[1],
  1229. ]);
  1230. }
  1231. }
  1232. // remove unused variables inside an array
  1233. if (parentType === "ArrayPattern") {
  1234. if (hasSingleElement(parent)) {
  1235. // fix [a, ...[b]]
  1236. if (parent.parent.type === "RestElement") {
  1237. return fixRestInPattern(parent.parent);
  1238. }
  1239. // fix [ [a] ]
  1240. if (parent.parent.type === "ArrayPattern") {
  1241. return fixNestedArrayVariable(parent);
  1242. }
  1243. /*
  1244. * fix var [a] = foo;
  1245. * fix function foo([a]) {}
  1246. * fix const { a: [b] } = foo;
  1247. */
  1248. return fixVariables(parent);
  1249. }
  1250. // if "a" is unused in [a, b ,c] fixes to [, b, c]
  1251. if (tokenBefore.value === "," && tokenAfter.value === ",") {
  1252. return fixer.removeRange(id.range);
  1253. }
  1254. }
  1255. // remove unused rest elements
  1256. if (parentType === "RestElement") {
  1257. // fix [a, ...rest]
  1258. if (parent.parent.type === "ArrayPattern") {
  1259. if (hasSingleElement(parent.parent)) {
  1260. // fix [[...rest]] when there is only rest element
  1261. if (parent.parent.parent.type === "ArrayPattern") {
  1262. return fixNestedArrayVariable(parent.parent);
  1263. }
  1264. // fix 'const [...rest] = foo;' and 'function foo([...rest]) {}'
  1265. return fixVariables(parent.parent);
  1266. }
  1267. // fix [a, ...rest]
  1268. return fixer.removeRange([
  1269. getPreviousTokenStart(id, 1),
  1270. id.range[1],
  1271. ]);
  1272. }
  1273. // fix { a, ...rest}
  1274. if (parent.parent.type === "ObjectPattern") {
  1275. // fix 'const {...rest} = foo;' and 'function foo({...rest}) {}'
  1276. if (parent.parent.properties.length === 1) {
  1277. return fixVariables(parent.parent);
  1278. }
  1279. // fix { a, ...rest} when there are multiple properties
  1280. return fixer.removeRange([
  1281. getPreviousTokenStart(id, 1),
  1282. id.range[1],
  1283. ]);
  1284. }
  1285. // fix function foo(...rest) {}
  1286. if (isFunction(parent.parent)) {
  1287. // remove unused rest in function parameter if there is only single parameter
  1288. if (parent.parent.params.length === 1) {
  1289. return fixer.removeRange(parent.range);
  1290. }
  1291. // remove unused rest in function parameter if there multiple parameter
  1292. return fixer.removeRange([
  1293. getPreviousTokenStart(parent),
  1294. parent.range[1],
  1295. ]);
  1296. }
  1297. }
  1298. if (parentType === "AssignmentPattern") {
  1299. // fix [a = aDefault]
  1300. if (parent.parent.type === "ArrayPattern") {
  1301. return fixNestedArrayVariable(parent);
  1302. }
  1303. // fix {a = aDefault}
  1304. if (parent.parent.parent.type === "ObjectPattern") {
  1305. if (parent.parent.parent.properties.length === 1) {
  1306. // fixes [{a = aDefault}]
  1307. if (
  1308. parent.parent.parent.parent.type === "ArrayPattern"
  1309. ) {
  1310. return fixNestedArrayVariable(parent.parent.parent);
  1311. }
  1312. // fix 'const {a = aDefault} = foo;' and 'function foo({a = aDefault}) {}'
  1313. return fixVariables(parent.parent.parent);
  1314. }
  1315. // fix unused 'a' in {a = aDefault} if it is the first property
  1316. if (
  1317. getTokenBeforeValue(parent.parent) === "{" &&
  1318. getTokenAfterValue(parent.parent) === ","
  1319. ) {
  1320. return fixer.removeRange([
  1321. parent.parent.range[0],
  1322. getNextTokenEnd(parent.parent),
  1323. ]);
  1324. }
  1325. // fix unused 'b' in {a, b = aDefault} if it is not the first property
  1326. return fixer.removeRange([
  1327. getPreviousTokenStart(parent.parent),
  1328. parent.parent.range[1],
  1329. ]);
  1330. }
  1331. // fix unused assignment patterns in function parameters
  1332. if (isFunction(parent.parent)) {
  1333. return fixFunctionParameters(parent);
  1334. }
  1335. }
  1336. // remove unused functions
  1337. if (parentType === "FunctionDeclaration" && parent.id === id) {
  1338. return fixer.removeRange(parent.range);
  1339. }
  1340. // remove unused default import
  1341. if (parentType === "ImportDefaultSpecifier") {
  1342. // remove unused default import when there are not other imports
  1343. if (
  1344. !hasImportOfCertainType(parent.parent, "ImportSpecifier") &&
  1345. !hasImportOfCertainType(
  1346. parent.parent,
  1347. "ImportNamespaceSpecifier",
  1348. )
  1349. ) {
  1350. return fixer.removeRange([
  1351. parent.range[0],
  1352. parent.parent.source.range[0],
  1353. ]);
  1354. }
  1355. // remove unused default import when there are other imports also
  1356. return fixer.removeRange([id.range[0], tokenAfter.range[1]]);
  1357. }
  1358. if (parentType === "ImportSpecifier") {
  1359. // remove unused imports when there is a single import
  1360. if (
  1361. parent.parent.specifiers.filter(
  1362. e => e.type === "ImportSpecifier",
  1363. ).length === 1
  1364. ) {
  1365. // remove unused import when there is no default import
  1366. if (
  1367. !hasImportOfCertainType(
  1368. parent.parent,
  1369. "ImportDefaultSpecifier",
  1370. )
  1371. ) {
  1372. return fixer.removeRange(parent.parent.range);
  1373. }
  1374. // fixes "import foo from 'module';" to "import 'module';"
  1375. return fixer.removeRange([
  1376. getPreviousTokenStart(parent, 1),
  1377. tokenAfter.range[1],
  1378. ]);
  1379. }
  1380. if (getTokenBeforeValue(parent) === "{") {
  1381. return fixer.removeRange([
  1382. parent.range[0],
  1383. getNextTokenEnd(parent),
  1384. ]);
  1385. }
  1386. return fixer.removeRange([
  1387. getPreviousTokenStart(parent),
  1388. parent.range[1],
  1389. ]);
  1390. }
  1391. if (parentType === "ImportNamespaceSpecifier") {
  1392. if (
  1393. hasImportOfCertainType(
  1394. parent.parent,
  1395. "ImportDefaultSpecifier",
  1396. )
  1397. ) {
  1398. return fixer.removeRange([
  1399. getPreviousTokenStart(parent),
  1400. parent.range[1],
  1401. ]);
  1402. }
  1403. // fixes "import * as foo from 'module';" to "import 'module';"
  1404. return fixer.removeRange([
  1405. parent.range[0],
  1406. parent.parent.source.range[0],
  1407. ]);
  1408. }
  1409. // skip error in catch(error) variable
  1410. if (parentType === "CatchClause") {
  1411. return null;
  1412. }
  1413. // remove unused declared classes
  1414. if (parentType === "ClassDeclaration") {
  1415. return fixer.removeRange(parent.range);
  1416. }
  1417. // remove unused variable that is in a sequence [a,b] fixes to [a]
  1418. if (tokenBefore?.value === ",") {
  1419. return fixer.removeRange([tokenBefore.range[0], id.range[1]]);
  1420. }
  1421. // remove unused variable that is in a sequence inside function arguments and object pattern
  1422. if (tokenAfter.value === ",") {
  1423. // fix function foo(a, b) {}
  1424. if (tokenBefore.value === "(") {
  1425. return fixer.removeRange([
  1426. id.range[0],
  1427. tokenAfter.range[1],
  1428. ]);
  1429. }
  1430. // fix const {a, b} = foo;
  1431. if (tokenBefore.value === "{") {
  1432. return fixer.removeRange([
  1433. id.range[0],
  1434. tokenAfter.range[1],
  1435. ]);
  1436. }
  1437. }
  1438. if (
  1439. parentType === "ArrowFunctionExpression" &&
  1440. parent.params.length === 1 &&
  1441. tokenAfter?.value !== ")"
  1442. ) {
  1443. return fixer.replaceText(id, "()");
  1444. }
  1445. return fixer.removeRange(id.range);
  1446. }
  1447. //--------------------------------------------------------------------------
  1448. // Public
  1449. //--------------------------------------------------------------------------
  1450. return {
  1451. "Program:exit"(programNode) {
  1452. const unusedVars = collectUnusedVariables(
  1453. sourceCode.getScope(programNode),
  1454. [],
  1455. );
  1456. for (let i = 0, l = unusedVars.length; i < l; ++i) {
  1457. const unusedVar = unusedVars[i];
  1458. // Report the first declaration.
  1459. if (unusedVar.defs.length > 0) {
  1460. // report last write reference, https://github.com/eslint/eslint/issues/14324
  1461. const writeReferences = unusedVar.references.filter(
  1462. ref =>
  1463. ref.isWrite() &&
  1464. ref.from.variableScope ===
  1465. unusedVar.scope.variableScope,
  1466. );
  1467. let referenceToReport;
  1468. if (writeReferences.length > 0) {
  1469. referenceToReport = writeReferences.at(-1);
  1470. }
  1471. context.report({
  1472. node: referenceToReport
  1473. ? referenceToReport.identifier
  1474. : unusedVar.identifiers[0],
  1475. messageId: "unusedVar",
  1476. data: unusedVar.references.some(ref =>
  1477. ref.isWrite(),
  1478. )
  1479. ? getAssignedMessageData(unusedVar)
  1480. : getDefinedMessageData(unusedVar),
  1481. suggest: [
  1482. {
  1483. messageId: "removeVar",
  1484. data: {
  1485. varName: unusedVar.name,
  1486. },
  1487. fix(fixer) {
  1488. return handleFixes(fixer, unusedVar);
  1489. },
  1490. },
  1491. ],
  1492. });
  1493. // If there are no regular declaration, report the first `/*globals*/` comment directive.
  1494. } else if (unusedVar.eslintExplicitGlobalComments) {
  1495. const directiveComment =
  1496. unusedVar.eslintExplicitGlobalComments[0];
  1497. context.report({
  1498. node: programNode,
  1499. loc: astUtils.getNameLocationInGlobalDirectiveComment(
  1500. sourceCode,
  1501. directiveComment,
  1502. unusedVar.name,
  1503. ),
  1504. messageId: "unusedVar",
  1505. data: getDefinedMessageData(unusedVar),
  1506. });
  1507. }
  1508. }
  1509. },
  1510. };
  1511. },
  1512. };