Ohm-Management - Projektarbeit B-ME
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.

no-unused-vars.js 24KB

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