init-declarations.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /**
  2. * @fileoverview A rule to control the style of variable initializations.
  3. * @author Colin Ihrig
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Helpers
  8. //------------------------------------------------------------------------------
  9. const CONSTANT_BINDINGS = new Set(["const", "using", "await using"]);
  10. /**
  11. * Checks whether or not a given node is a for loop.
  12. * @param {ASTNode} block A node to check.
  13. * @returns {boolean} `true` when the node is a for loop.
  14. */
  15. function isForLoop(block) {
  16. return (
  17. block.type === "ForInStatement" ||
  18. block.type === "ForOfStatement" ||
  19. block.type === "ForStatement"
  20. );
  21. }
  22. /**
  23. * Checks whether or not a given declarator node has its initializer.
  24. * @param {ASTNode} node A declarator node to check.
  25. * @returns {boolean} `true` when the node has its initializer.
  26. */
  27. function isInitialized(node) {
  28. const declaration = node.parent;
  29. const block = declaration.parent;
  30. if (isForLoop(block)) {
  31. if (block.type === "ForStatement") {
  32. return block.init === declaration;
  33. }
  34. return block.left === declaration;
  35. }
  36. return Boolean(node.init);
  37. }
  38. //------------------------------------------------------------------------------
  39. // Rule Definition
  40. //------------------------------------------------------------------------------
  41. /** @type {import('../types').Rule.RuleModule} */
  42. module.exports = {
  43. meta: {
  44. type: "suggestion",
  45. dialects: ["typescript", "javascript"],
  46. language: "javascript",
  47. docs: {
  48. description:
  49. "Require or disallow initialization in variable declarations",
  50. recommended: false,
  51. frozen: true,
  52. url: "https://eslint.org/docs/latest/rules/init-declarations",
  53. },
  54. schema: {
  55. anyOf: [
  56. {
  57. type: "array",
  58. items: [
  59. {
  60. enum: ["always"],
  61. },
  62. ],
  63. minItems: 0,
  64. maxItems: 1,
  65. },
  66. {
  67. type: "array",
  68. items: [
  69. {
  70. enum: ["never"],
  71. },
  72. {
  73. type: "object",
  74. properties: {
  75. ignoreForLoopInit: {
  76. type: "boolean",
  77. },
  78. },
  79. additionalProperties: false,
  80. },
  81. ],
  82. minItems: 0,
  83. maxItems: 2,
  84. },
  85. ],
  86. },
  87. messages: {
  88. initialized:
  89. "Variable '{{idName}}' should be initialized on declaration.",
  90. notInitialized:
  91. "Variable '{{idName}}' should not be initialized on declaration.",
  92. },
  93. },
  94. create(context) {
  95. const MODE_ALWAYS = "always",
  96. MODE_NEVER = "never";
  97. const mode = context.options[0] || MODE_ALWAYS;
  98. const params = context.options[1] || {};
  99. // Track whether we're inside a declared namespace
  100. let insideDeclaredNamespace = false;
  101. //--------------------------------------------------------------------------
  102. // Public API
  103. //--------------------------------------------------------------------------
  104. return {
  105. TSModuleDeclaration(node) {
  106. if (node.declare) {
  107. insideDeclaredNamespace = true;
  108. }
  109. },
  110. "TSModuleDeclaration:exit"(node) {
  111. if (node.declare) {
  112. insideDeclaredNamespace = false;
  113. }
  114. },
  115. "VariableDeclaration:exit"(node) {
  116. const kind = node.kind,
  117. declarations = node.declarations;
  118. if (node.declare || insideDeclaredNamespace) {
  119. return;
  120. }
  121. for (let i = 0; i < declarations.length; ++i) {
  122. const declaration = declarations[i],
  123. id = declaration.id,
  124. initialized = isInitialized(declaration),
  125. isIgnoredForLoop =
  126. params.ignoreForLoopInit && isForLoop(node.parent);
  127. let messageId = "";
  128. if (mode === MODE_ALWAYS && !initialized) {
  129. messageId = "initialized";
  130. } else if (
  131. mode === MODE_NEVER &&
  132. !CONSTANT_BINDINGS.has(kind) &&
  133. initialized &&
  134. !isIgnoredForLoop
  135. ) {
  136. messageId = "notInitialized";
  137. }
  138. if (id.type === "Identifier" && messageId) {
  139. context.report({
  140. node: declaration,
  141. messageId,
  142. data: {
  143. idName: id.name,
  144. },
  145. });
  146. }
  147. }
  148. },
  149. };
  150. },
  151. };