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-use-before-define.js 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /**
  2. * @fileoverview Rule to flag use of variables before they are defined
  3. * @author Ilya Volodin
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Helpers
  8. //------------------------------------------------------------------------------
  9. const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/;
  10. const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/;
  11. /**
  12. * Parses a given value as options.
  13. *
  14. * @param {any} options - A value to parse.
  15. * @returns {Object} The parsed options.
  16. */
  17. function parseOptions(options) {
  18. let functions = true;
  19. let classes = true;
  20. let variables = true;
  21. if (typeof options === "string") {
  22. functions = (options !== "nofunc");
  23. } else if (typeof options === "object" && options !== null) {
  24. functions = options.functions !== false;
  25. classes = options.classes !== false;
  26. variables = options.variables !== false;
  27. }
  28. return { functions, classes, variables };
  29. }
  30. /**
  31. * Checks whether or not a given variable is a function declaration.
  32. *
  33. * @param {eslint-scope.Variable} variable - A variable to check.
  34. * @returns {boolean} `true` if the variable is a function declaration.
  35. */
  36. function isFunction(variable) {
  37. return variable.defs[0].type === "FunctionName";
  38. }
  39. /**
  40. * Checks whether or not a given variable is a class declaration in an upper function scope.
  41. *
  42. * @param {eslint-scope.Variable} variable - A variable to check.
  43. * @param {eslint-scope.Reference} reference - A reference to check.
  44. * @returns {boolean} `true` if the variable is a class declaration.
  45. */
  46. function isOuterClass(variable, reference) {
  47. return (
  48. variable.defs[0].type === "ClassName" &&
  49. variable.scope.variableScope !== reference.from.variableScope
  50. );
  51. }
  52. /**
  53. * Checks whether or not a given variable is a variable declaration in an upper function scope.
  54. * @param {eslint-scope.Variable} variable - A variable to check.
  55. * @param {eslint-scope.Reference} reference - A reference to check.
  56. * @returns {boolean} `true` if the variable is a variable declaration.
  57. */
  58. function isOuterVariable(variable, reference) {
  59. return (
  60. variable.defs[0].type === "Variable" &&
  61. variable.scope.variableScope !== reference.from.variableScope
  62. );
  63. }
  64. /**
  65. * Checks whether or not a given location is inside of the range of a given node.
  66. *
  67. * @param {ASTNode} node - An node to check.
  68. * @param {number} location - A location to check.
  69. * @returns {boolean} `true` if the location is inside of the range of the node.
  70. */
  71. function isInRange(node, location) {
  72. return node && node.range[0] <= location && location <= node.range[1];
  73. }
  74. /**
  75. * Checks whether or not a given reference is inside of the initializers of a given variable.
  76. *
  77. * This returns `true` in the following cases:
  78. *
  79. * var a = a
  80. * var [a = a] = list
  81. * var {a = a} = obj
  82. * for (var a in a) {}
  83. * for (var a of a) {}
  84. *
  85. * @param {Variable} variable - A variable to check.
  86. * @param {Reference} reference - A reference to check.
  87. * @returns {boolean} `true` if the reference is inside of the initializers.
  88. */
  89. function isInInitializer(variable, reference) {
  90. if (variable.scope !== reference.from) {
  91. return false;
  92. }
  93. let node = variable.identifiers[0].parent;
  94. const location = reference.identifier.range[1];
  95. while (node) {
  96. if (node.type === "VariableDeclarator") {
  97. if (isInRange(node.init, location)) {
  98. return true;
  99. }
  100. if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
  101. isInRange(node.parent.parent.right, location)
  102. ) {
  103. return true;
  104. }
  105. break;
  106. } else if (node.type === "AssignmentPattern") {
  107. if (isInRange(node.right, location)) {
  108. return true;
  109. }
  110. } else if (SENTINEL_TYPE.test(node.type)) {
  111. break;
  112. }
  113. node = node.parent;
  114. }
  115. return false;
  116. }
  117. //------------------------------------------------------------------------------
  118. // Rule Definition
  119. //------------------------------------------------------------------------------
  120. module.exports = {
  121. meta: {
  122. type: "problem",
  123. docs: {
  124. description: "disallow the use of variables before they are defined",
  125. category: "Variables",
  126. recommended: false,
  127. url: "https://eslint.org/docs/rules/no-use-before-define"
  128. },
  129. schema: [
  130. {
  131. oneOf: [
  132. {
  133. enum: ["nofunc"]
  134. },
  135. {
  136. type: "object",
  137. properties: {
  138. functions: { type: "boolean" },
  139. classes: { type: "boolean" },
  140. variables: { type: "boolean" }
  141. },
  142. additionalProperties: false
  143. }
  144. ]
  145. }
  146. ]
  147. },
  148. create(context) {
  149. const options = parseOptions(context.options[0]);
  150. /**
  151. * Determines whether a given use-before-define case should be reported according to the options.
  152. * @param {eslint-scope.Variable} variable The variable that gets used before being defined
  153. * @param {eslint-scope.Reference} reference The reference to the variable
  154. * @returns {boolean} `true` if the usage should be reported
  155. */
  156. function isForbidden(variable, reference) {
  157. if (isFunction(variable)) {
  158. return options.functions;
  159. }
  160. if (isOuterClass(variable, reference)) {
  161. return options.classes;
  162. }
  163. if (isOuterVariable(variable, reference)) {
  164. return options.variables;
  165. }
  166. return true;
  167. }
  168. /**
  169. * Finds and validates all variables in a given scope.
  170. * @param {Scope} scope The scope object.
  171. * @returns {void}
  172. * @private
  173. */
  174. function findVariablesInScope(scope) {
  175. scope.references.forEach(reference => {
  176. const variable = reference.resolved;
  177. /*
  178. * Skips when the reference is:
  179. * - initialization's.
  180. * - referring to an undefined variable.
  181. * - referring to a global environment variable (there're no identifiers).
  182. * - located preceded by the variable (except in initializers).
  183. * - allowed by options.
  184. */
  185. if (reference.init ||
  186. !variable ||
  187. variable.identifiers.length === 0 ||
  188. (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||
  189. !isForbidden(variable, reference)
  190. ) {
  191. return;
  192. }
  193. // Reports.
  194. context.report({
  195. node: reference.identifier,
  196. message: "'{{name}}' was used before it was defined.",
  197. data: reference.identifier
  198. });
  199. });
  200. scope.childScopes.forEach(findVariablesInScope);
  201. }
  202. return {
  203. Program() {
  204. findVariablesInScope(context.getScope());
  205. }
  206. };
  207. }
  208. };