vars-on-top.js 5.2 KB

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