vars-on-top.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /**
  2. * @fileoverview Rule to enforce var declarations are only at the top of a function.
  3. * @author Danny Fritz
  4. * @author Gyandeep Singh
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. /** @type {import('../types').Rule.RuleModule} */
  11. module.exports = {
  12. meta: {
  13. type: "suggestion",
  14. docs: {
  15. description:
  16. "Require `var` declarations be placed at the top of their containing scope",
  17. recommended: false,
  18. frozen: true,
  19. url: "https://eslint.org/docs/latest/rules/vars-on-top",
  20. },
  21. schema: [],
  22. messages: {
  23. top: "All 'var' declarations must be at the top of the function scope.",
  24. },
  25. },
  26. create(context) {
  27. //--------------------------------------------------------------------------
  28. // Helpers
  29. //--------------------------------------------------------------------------
  30. /**
  31. * Has AST suggesting a directive.
  32. * @param {ASTNode} node any node
  33. * @returns {boolean} whether the given node structurally represents a directive
  34. */
  35. function looksLikeDirective(node) {
  36. return (
  37. node.type === "ExpressionStatement" &&
  38. node.expression.type === "Literal" &&
  39. typeof node.expression.value === "string"
  40. );
  41. }
  42. /**
  43. * Check to see if its a ES6 import declaration
  44. * @param {ASTNode} node any node
  45. * @returns {boolean} whether the given node represents a import declaration
  46. */
  47. function looksLikeImport(node) {
  48. return (
  49. node.type === "ImportDeclaration" ||
  50. node.type === "ImportSpecifier" ||
  51. node.type === "ImportDefaultSpecifier" ||
  52. node.type === "ImportNamespaceSpecifier"
  53. );
  54. }
  55. /**
  56. * Checks whether a given node is a variable declaration or not.
  57. * @param {ASTNode} node any node
  58. * @returns {boolean} `true` if the node is a variable declaration.
  59. */
  60. function isVariableDeclaration(node) {
  61. return (
  62. node.type === "VariableDeclaration" ||
  63. (node.type === "ExportNamedDeclaration" &&
  64. node.declaration &&
  65. node.declaration.type === "VariableDeclaration")
  66. );
  67. }
  68. /**
  69. * Checks whether this variable is on top of the block body
  70. * @param {ASTNode} node The node to check
  71. * @param {ASTNode[]} statements collection of ASTNodes for the parent node block
  72. * @returns {boolean} True if var is on top otherwise false
  73. */
  74. function isVarOnTop(node, statements) {
  75. const l = statements.length;
  76. let i = 0;
  77. // Skip over directives and imports. Static blocks don't have either.
  78. if (node.parent.type !== "StaticBlock") {
  79. for (; i < l; ++i) {
  80. if (
  81. !looksLikeDirective(statements[i]) &&
  82. !looksLikeImport(statements[i])
  83. ) {
  84. break;
  85. }
  86. }
  87. }
  88. for (; i < l; ++i) {
  89. if (!isVariableDeclaration(statements[i])) {
  90. return false;
  91. }
  92. if (statements[i] === node) {
  93. return true;
  94. }
  95. }
  96. return false;
  97. }
  98. /**
  99. * Checks whether variable is on top at the global level
  100. * @param {ASTNode} node The node to check
  101. * @param {ASTNode} parent Parent of the node
  102. * @returns {void}
  103. */
  104. function globalVarCheck(node, parent) {
  105. if (!isVarOnTop(node, parent.body)) {
  106. context.report({ node, messageId: "top" });
  107. }
  108. }
  109. /**
  110. * Checks whether variable is on top at functional block scope level
  111. * @param {ASTNode} node The node to check
  112. * @returns {void}
  113. */
  114. function blockScopeVarCheck(node) {
  115. const { parent } = node;
  116. if (
  117. parent.type === "BlockStatement" &&
  118. /Function/u.test(parent.parent.type) &&
  119. isVarOnTop(node, parent.body)
  120. ) {
  121. return;
  122. }
  123. if (
  124. parent.type === "StaticBlock" &&
  125. isVarOnTop(node, parent.body)
  126. ) {
  127. return;
  128. }
  129. context.report({ node, messageId: "top" });
  130. }
  131. //--------------------------------------------------------------------------
  132. // Public API
  133. //--------------------------------------------------------------------------
  134. return {
  135. "VariableDeclaration[kind='var']"(node) {
  136. if (node.parent.type === "ExportNamedDeclaration") {
  137. globalVarCheck(node.parent, node.parent.parent);
  138. } else if (node.parent.type === "Program") {
  139. globalVarCheck(node, node.parent);
  140. } else {
  141. blockScopeVarCheck(node);
  142. }
  143. },
  144. };
  145. },
  146. };