项目原始demo,不改动
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

no-unused-vars.js 24 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. /**
  2. * @fileoverview Rule to flag declared but unused variables
  3. * @author Ilya Volodin
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const lodash = require("lodash");
  10. const astUtils = require("../ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. module.exports = {
  15. meta: {
  16. docs: {
  17. description: "disallow unused variables",
  18. category: "Variables",
  19. recommended: true,
  20. url: "https://eslint.org/docs/rules/no-unused-vars"
  21. },
  22. schema: [
  23. {
  24. oneOf: [
  25. {
  26. enum: ["all", "local"]
  27. },
  28. {
  29. type: "object",
  30. properties: {
  31. vars: {
  32. enum: ["all", "local"]
  33. },
  34. varsIgnorePattern: {
  35. type: "string"
  36. },
  37. args: {
  38. enum: ["all", "after-used", "none"]
  39. },
  40. ignoreRestSiblings: {
  41. type: "boolean"
  42. },
  43. argsIgnorePattern: {
  44. type: "string"
  45. },
  46. caughtErrors: {
  47. enum: ["all", "none"]
  48. },
  49. caughtErrorsIgnorePattern: {
  50. type: "string"
  51. }
  52. }
  53. }
  54. ]
  55. }
  56. ]
  57. },
  58. create(context) {
  59. const sourceCode = context.getSourceCode();
  60. const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/;
  61. const config = {
  62. vars: "all",
  63. args: "after-used",
  64. ignoreRestSiblings: false,
  65. caughtErrors: "none"
  66. };
  67. const firstOption = context.options[0];
  68. if (firstOption) {
  69. if (typeof firstOption === "string") {
  70. config.vars = firstOption;
  71. } else {
  72. config.vars = firstOption.vars || config.vars;
  73. config.args = firstOption.args || config.args;
  74. config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
  75. config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
  76. if (firstOption.varsIgnorePattern) {
  77. config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern);
  78. }
  79. if (firstOption.argsIgnorePattern) {
  80. config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern);
  81. }
  82. if (firstOption.caughtErrorsIgnorePattern) {
  83. config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern);
  84. }
  85. }
  86. }
  87. /**
  88. * Generate the warning message about the variable being
  89. * defined and unused, including the ignore pattern if configured.
  90. * @param {Variable} unusedVar - eslint-scope variable object.
  91. * @returns {string} The warning message to be used with this unused variable.
  92. */
  93. function getDefinedMessage(unusedVar) {
  94. const defType = unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type;
  95. let type;
  96. let pattern;
  97. if (defType === "CatchClause" && config.caughtErrorsIgnorePattern) {
  98. type = "args";
  99. pattern = config.caughtErrorsIgnorePattern.toString();
  100. } else if (defType === "Parameter" && config.argsIgnorePattern) {
  101. type = "args";
  102. pattern = config.argsIgnorePattern.toString();
  103. } else if (defType !== "Parameter" && config.varsIgnorePattern) {
  104. type = "vars";
  105. pattern = config.varsIgnorePattern.toString();
  106. }
  107. const additional = type ? ` Allowed unused ${type} must match ${pattern}.` : "";
  108. return `'{{name}}' is defined but never used.${additional}`;
  109. }
  110. /**
  111. * Generate the warning message about the variable being
  112. * assigned and unused, including the ignore pattern if configured.
  113. * @returns {string} The warning message to be used with this unused variable.
  114. */
  115. function getAssignedMessage() {
  116. const additional = config.varsIgnorePattern ? ` Allowed unused vars must match ${config.varsIgnorePattern.toString()}.` : "";
  117. return `'{{name}}' is assigned a value but never used.${additional}`;
  118. }
  119. //--------------------------------------------------------------------------
  120. // Helpers
  121. //--------------------------------------------------------------------------
  122. const STATEMENT_TYPE = /(?:Statement|Declaration)$/;
  123. /**
  124. * Determines if a given variable is being exported from a module.
  125. * @param {Variable} variable - eslint-scope variable object.
  126. * @returns {boolean} True if the variable is exported, false if not.
  127. * @private
  128. */
  129. function isExported(variable) {
  130. const definition = variable.defs[0];
  131. if (definition) {
  132. let node = definition.node;
  133. if (node.type === "VariableDeclarator") {
  134. node = node.parent;
  135. } else if (definition.type === "Parameter") {
  136. return false;
  137. }
  138. return node.parent.type.indexOf("Export") === 0;
  139. }
  140. return false;
  141. }
  142. /**
  143. * Determines if a variable has a sibling rest property
  144. * @param {Variable} variable - eslint-scope variable object.
  145. * @returns {boolean} True if the variable is exported, false if not.
  146. * @private
  147. */
  148. function hasRestSpreadSibling(variable) {
  149. if (config.ignoreRestSiblings) {
  150. return variable.defs.some(def => {
  151. const propertyNode = def.name.parent;
  152. const patternNode = propertyNode.parent;
  153. return (
  154. propertyNode.type === "Property" &&
  155. patternNode.type === "ObjectPattern" &&
  156. REST_PROPERTY_TYPE.test(patternNode.properties[patternNode.properties.length - 1].type)
  157. );
  158. });
  159. }
  160. return false;
  161. }
  162. /**
  163. * Determines if a reference is a read operation.
  164. * @param {Reference} ref - An eslint-scope Reference
  165. * @returns {boolean} whether the given reference represents a read operation
  166. * @private
  167. */
  168. function isReadRef(ref) {
  169. return ref.isRead();
  170. }
  171. /**
  172. * Determine if an identifier is referencing an enclosing function name.
  173. * @param {Reference} ref - The reference to check.
  174. * @param {ASTNode[]} nodes - The candidate function nodes.
  175. * @returns {boolean} True if it's a self-reference, false if not.
  176. * @private
  177. */
  178. function isSelfReference(ref, nodes) {
  179. let scope = ref.from;
  180. while (scope) {
  181. if (nodes.indexOf(scope.block) >= 0) {
  182. return true;
  183. }
  184. scope = scope.upper;
  185. }
  186. return false;
  187. }
  188. /**
  189. * Checks the position of given nodes.
  190. *
  191. * @param {ASTNode} inner - A node which is expected as inside.
  192. * @param {ASTNode} outer - A node which is expected as outside.
  193. * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
  194. * @private
  195. */
  196. function isInside(inner, outer) {
  197. return (
  198. inner.range[0] >= outer.range[0] &&
  199. inner.range[1] <= outer.range[1]
  200. );
  201. }
  202. /**
  203. * If a given reference is left-hand side of an assignment, this gets
  204. * the right-hand side node of the assignment.
  205. *
  206. * In the following cases, this returns null.
  207. *
  208. * - The reference is not the LHS of an assignment expression.
  209. * - The reference is inside of a loop.
  210. * - The reference is inside of a function scope which is different from
  211. * the declaration.
  212. *
  213. * @param {eslint-scope.Reference} ref - A reference to check.
  214. * @param {ASTNode} prevRhsNode - The previous RHS node.
  215. * @returns {ASTNode|null} The RHS node or null.
  216. * @private
  217. */
  218. function getRhsNode(ref, prevRhsNode) {
  219. const id = ref.identifier;
  220. const parent = id.parent;
  221. const granpa = parent.parent;
  222. const refScope = ref.from.variableScope;
  223. const varScope = ref.resolved.scope.variableScope;
  224. const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
  225. /*
  226. * Inherits the previous node if this reference is in the node.
  227. * This is for `a = a + a`-like code.
  228. */
  229. if (prevRhsNode && isInside(id, prevRhsNode)) {
  230. return prevRhsNode;
  231. }
  232. if (parent.type === "AssignmentExpression" &&
  233. granpa.type === "ExpressionStatement" &&
  234. id === parent.left &&
  235. !canBeUsedLater
  236. ) {
  237. return parent.right;
  238. }
  239. return null;
  240. }
  241. /**
  242. * Checks whether a given function node is stored to somewhere or not.
  243. * If the function node is stored, the function can be used later.
  244. *
  245. * @param {ASTNode} funcNode - A function node to check.
  246. * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
  247. * @returns {boolean} `true` if under the following conditions:
  248. * - the funcNode is assigned to a variable.
  249. * - the funcNode is bound as an argument of a function call.
  250. * - the function is bound to a property and the object satisfies above conditions.
  251. * @private
  252. */
  253. function isStorableFunction(funcNode, rhsNode) {
  254. let node = funcNode;
  255. let parent = funcNode.parent;
  256. while (parent && isInside(parent, rhsNode)) {
  257. switch (parent.type) {
  258. case "SequenceExpression":
  259. if (parent.expressions[parent.expressions.length - 1] !== node) {
  260. return false;
  261. }
  262. break;
  263. case "CallExpression":
  264. case "NewExpression":
  265. return parent.callee !== node;
  266. case "AssignmentExpression":
  267. case "TaggedTemplateExpression":
  268. case "YieldExpression":
  269. return true;
  270. default:
  271. if (STATEMENT_TYPE.test(parent.type)) {
  272. /*
  273. * If it encountered statements, this is a complex pattern.
  274. * Since analyzeing complex patterns is hard, this returns `true` to avoid false positive.
  275. */
  276. return true;
  277. }
  278. }
  279. node = parent;
  280. parent = parent.parent;
  281. }
  282. return false;
  283. }
  284. /**
  285. * Checks whether a given Identifier node exists inside of a function node which can be used later.
  286. *
  287. * "can be used later" means:
  288. * - the function is assigned to a variable.
  289. * - the function is bound to a property and the object can be used later.
  290. * - the function is bound as an argument of a function call.
  291. *
  292. * If a reference exists in a function which can be used later, the reference is read when the function is called.
  293. *
  294. * @param {ASTNode} id - An Identifier node to check.
  295. * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
  296. * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
  297. * @private
  298. */
  299. function isInsideOfStorableFunction(id, rhsNode) {
  300. const funcNode = astUtils.getUpperFunction(id);
  301. return (
  302. funcNode &&
  303. isInside(funcNode, rhsNode) &&
  304. isStorableFunction(funcNode, rhsNode)
  305. );
  306. }
  307. /**
  308. * Checks whether a given reference is a read to update itself or not.
  309. *
  310. * @param {eslint-scope.Reference} ref - A reference to check.
  311. * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
  312. * @returns {boolean} The reference is a read to update itself.
  313. * @private
  314. */
  315. function isReadForItself(ref, rhsNode) {
  316. const id = ref.identifier;
  317. const parent = id.parent;
  318. const granpa = parent.parent;
  319. return ref.isRead() && (
  320. // self update. e.g. `a += 1`, `a++`
  321. (
  322. parent.type === "AssignmentExpression" &&
  323. granpa.type === "ExpressionStatement" &&
  324. parent.left === id
  325. ) ||
  326. (
  327. parent.type === "UpdateExpression" &&
  328. granpa.type === "ExpressionStatement"
  329. ) ||
  330. // in RHS of an assignment for itself. e.g. `a = a + 1`
  331. (
  332. rhsNode &&
  333. isInside(id, rhsNode) &&
  334. !isInsideOfStorableFunction(id, rhsNode)
  335. )
  336. );
  337. }
  338. /**
  339. * Determine if an identifier is used either in for-in loops.
  340. *
  341. * @param {Reference} ref - The reference to check.
  342. * @returns {boolean} whether reference is used in the for-in loops
  343. * @private
  344. */
  345. function isForInRef(ref) {
  346. let target = ref.identifier.parent;
  347. // "for (var ...) { return; }"
  348. if (target.type === "VariableDeclarator") {
  349. target = target.parent.parent;
  350. }
  351. if (target.type !== "ForInStatement") {
  352. return false;
  353. }
  354. // "for (...) { return; }"
  355. if (target.body.type === "BlockStatement") {
  356. target = target.body.body[0];
  357. // "for (...) return;"
  358. } else {
  359. target = target.body;
  360. }
  361. // For empty loop body
  362. if (!target) {
  363. return false;
  364. }
  365. return target.type === "ReturnStatement";
  366. }
  367. /**
  368. * Determines if the variable is used.
  369. * @param {Variable} variable - The variable to check.
  370. * @returns {boolean} True if the variable is used
  371. * @private
  372. */
  373. function isUsedVariable(variable) {
  374. const functionNodes = variable.defs.filter(def => def.type === "FunctionName").map(def => def.node),
  375. isFunctionDefinition = functionNodes.length > 0;
  376. let rhsNode = null;
  377. return variable.references.some(ref => {
  378. if (isForInRef(ref)) {
  379. return true;
  380. }
  381. const forItself = isReadForItself(ref, rhsNode);
  382. rhsNode = getRhsNode(ref, rhsNode);
  383. return (
  384. isReadRef(ref) &&
  385. !forItself &&
  386. !(isFunctionDefinition && isSelfReference(ref, functionNodes))
  387. );
  388. });
  389. }
  390. /**
  391. * Checks whether the given variable is the last parameter in the non-ignored parameters.
  392. *
  393. * @param {eslint-scope.Variable} variable - The variable to check.
  394. * @returns {boolean} `true` if the variable is the last.
  395. */
  396. function isLastInNonIgnoredParameters(variable) {
  397. const def = variable.defs[0];
  398. // This is the last.
  399. if (def.index === def.node.params.length - 1) {
  400. return true;
  401. }
  402. // if all parameters preceded by this variable are ignored and unused, this is the last.
  403. if (config.argsIgnorePattern) {
  404. const params = context.getDeclaredVariables(def.node);
  405. const posteriorParams = params.slice(params.indexOf(variable) + 1);
  406. if (posteriorParams.every(v => v.references.length === 0 && config.argsIgnorePattern.test(v.name))) {
  407. return true;
  408. }
  409. }
  410. return false;
  411. }
  412. /**
  413. * Gets an array of variables without read references.
  414. * @param {Scope} scope - an eslint-scope Scope object.
  415. * @param {Variable[]} unusedVars - an array that saving result.
  416. * @returns {Variable[]} unused variables of the scope and descendant scopes.
  417. * @private
  418. */
  419. function collectUnusedVariables(scope, unusedVars) {
  420. const variables = scope.variables;
  421. const childScopes = scope.childScopes;
  422. let i, l;
  423. if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) {
  424. for (i = 0, l = variables.length; i < l; ++i) {
  425. const variable = variables[i];
  426. // skip a variable of class itself name in the class scope
  427. if (scope.type === "class" && scope.block.id === variable.identifiers[0]) {
  428. continue;
  429. }
  430. // skip function expression names and variables marked with markVariableAsUsed()
  431. if (scope.functionExpressionScope || variable.eslintUsed) {
  432. continue;
  433. }
  434. // skip implicit "arguments" variable
  435. if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) {
  436. continue;
  437. }
  438. // explicit global variables don't have definitions.
  439. const def = variable.defs[0];
  440. if (def) {
  441. const type = def.type;
  442. // skip catch variables
  443. if (type === "CatchClause") {
  444. if (config.caughtErrors === "none") {
  445. continue;
  446. }
  447. // skip ignored parameters
  448. if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) {
  449. continue;
  450. }
  451. }
  452. if (type === "Parameter") {
  453. // skip any setter argument
  454. if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") {
  455. continue;
  456. }
  457. // if "args" option is "none", skip any parameter
  458. if (config.args === "none") {
  459. continue;
  460. }
  461. // skip ignored parameters
  462. if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) {
  463. continue;
  464. }
  465. // if "args" option is "after-used", skip all but the last parameter
  466. if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isLastInNonIgnoredParameters(variable)) {
  467. continue;
  468. }
  469. } else {
  470. // skip ignored variables
  471. if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) {
  472. continue;
  473. }
  474. }
  475. }
  476. if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
  477. unusedVars.push(variable);
  478. }
  479. }
  480. }
  481. for (i = 0, l = childScopes.length; i < l; ++i) {
  482. collectUnusedVariables(childScopes[i], unusedVars);
  483. }
  484. return unusedVars;
  485. }
  486. /**
  487. * Gets the index of a given variable name in a given comment.
  488. * @param {eslint-scope.Variable} variable - A variable to get.
  489. * @param {ASTNode} comment - A comment node which includes the variable name.
  490. * @returns {number} The index of the variable name's location.
  491. * @private
  492. */
  493. function getColumnInComment(variable, comment) {
  494. const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(variable.name)}(?:$|[\\s,:])`, "g");
  495. // To ignore the first text "global".
  496. namePattern.lastIndex = comment.value.indexOf("global") + 6;
  497. // Search a given variable name.
  498. const match = namePattern.exec(comment.value);
  499. return match ? match.index + 1 : 0;
  500. }
  501. /**
  502. * Creates the correct location of a given variables.
  503. * The location is at its name string in a `/*global` comment.
  504. *
  505. * @param {eslint-scope.Variable} variable - A variable to get its location.
  506. * @returns {{line: number, column: number}} The location object for the variable.
  507. * @private
  508. */
  509. function getLocation(variable) {
  510. const comment = variable.eslintExplicitGlobalComment;
  511. return sourceCode.getLocFromIndex(comment.range[0] + 2 + getColumnInComment(variable, comment));
  512. }
  513. //--------------------------------------------------------------------------
  514. // Public
  515. //--------------------------------------------------------------------------
  516. return {
  517. "Program:exit"(programNode) {
  518. const unusedVars = collectUnusedVariables(context.getScope(), []);
  519. for (let i = 0, l = unusedVars.length; i < l; ++i) {
  520. const unusedVar = unusedVars[i];
  521. if (unusedVar.eslintExplicitGlobal) {
  522. context.report({
  523. node: programNode,
  524. loc: getLocation(unusedVar),
  525. message: getDefinedMessage(unusedVar),
  526. data: unusedVar
  527. });
  528. } else if (unusedVar.defs.length > 0) {
  529. context.report({
  530. node: unusedVar.identifiers[0],
  531. message: unusedVar.references.some(ref => ref.isWrite())
  532. ? getAssignedMessage()
  533. : getDefinedMessage(unusedVar),
  534. data: unusedVar
  535. });
  536. }
  537. }
  538. }
  539. };
  540. }
  541. };