one-var.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /**
  2. * @fileoverview A rule to control the use of single variable declarations.
  3. * @author Ian Christian Myers
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. module.exports = {
  10. meta: {
  11. docs: {
  12. description: "enforce variables to be declared either together or separately in functions",
  13. category: "Stylistic Issues",
  14. recommended: false,
  15. url: "https://eslint.org/docs/rules/one-var"
  16. },
  17. schema: [
  18. {
  19. oneOf: [
  20. {
  21. enum: ["always", "never", "consecutive"]
  22. },
  23. {
  24. type: "object",
  25. properties: {
  26. separateRequires: {
  27. type: "boolean"
  28. },
  29. var: {
  30. enum: ["always", "never", "consecutive"]
  31. },
  32. let: {
  33. enum: ["always", "never", "consecutive"]
  34. },
  35. const: {
  36. enum: ["always", "never", "consecutive"]
  37. }
  38. },
  39. additionalProperties: false
  40. },
  41. {
  42. type: "object",
  43. properties: {
  44. initialized: {
  45. enum: ["always", "never", "consecutive"]
  46. },
  47. uninitialized: {
  48. enum: ["always", "never", "consecutive"]
  49. }
  50. },
  51. additionalProperties: false
  52. }
  53. ]
  54. }
  55. ]
  56. },
  57. create(context) {
  58. const MODE_ALWAYS = "always";
  59. const MODE_NEVER = "never";
  60. const MODE_CONSECUTIVE = "consecutive";
  61. const mode = context.options[0] || MODE_ALWAYS;
  62. const options = {};
  63. if (typeof mode === "string") { // simple options configuration with just a string
  64. options.var = { uninitialized: mode, initialized: mode };
  65. options.let = { uninitialized: mode, initialized: mode };
  66. options.const = { uninitialized: mode, initialized: mode };
  67. } else if (typeof mode === "object") { // options configuration is an object
  68. if (mode.hasOwnProperty("separateRequires")) {
  69. options.separateRequires = !!mode.separateRequires;
  70. }
  71. if (mode.hasOwnProperty("var")) {
  72. options.var = { uninitialized: mode.var, initialized: mode.var };
  73. }
  74. if (mode.hasOwnProperty("let")) {
  75. options.let = { uninitialized: mode.let, initialized: mode.let };
  76. }
  77. if (mode.hasOwnProperty("const")) {
  78. options.const = { uninitialized: mode.const, initialized: mode.const };
  79. }
  80. if (mode.hasOwnProperty("uninitialized")) {
  81. if (!options.var) {
  82. options.var = {};
  83. }
  84. if (!options.let) {
  85. options.let = {};
  86. }
  87. if (!options.const) {
  88. options.const = {};
  89. }
  90. options.var.uninitialized = mode.uninitialized;
  91. options.let.uninitialized = mode.uninitialized;
  92. options.const.uninitialized = mode.uninitialized;
  93. }
  94. if (mode.hasOwnProperty("initialized")) {
  95. if (!options.var) {
  96. options.var = {};
  97. }
  98. if (!options.let) {
  99. options.let = {};
  100. }
  101. if (!options.const) {
  102. options.const = {};
  103. }
  104. options.var.initialized = mode.initialized;
  105. options.let.initialized = mode.initialized;
  106. options.const.initialized = mode.initialized;
  107. }
  108. }
  109. //--------------------------------------------------------------------------
  110. // Helpers
  111. //--------------------------------------------------------------------------
  112. const functionStack = [];
  113. const blockStack = [];
  114. /**
  115. * Increments the blockStack counter.
  116. * @returns {void}
  117. * @private
  118. */
  119. function startBlock() {
  120. blockStack.push({
  121. let: { initialized: false, uninitialized: false },
  122. const: { initialized: false, uninitialized: false }
  123. });
  124. }
  125. /**
  126. * Increments the functionStack counter.
  127. * @returns {void}
  128. * @private
  129. */
  130. function startFunction() {
  131. functionStack.push({ initialized: false, uninitialized: false });
  132. startBlock();
  133. }
  134. /**
  135. * Decrements the blockStack counter.
  136. * @returns {void}
  137. * @private
  138. */
  139. function endBlock() {
  140. blockStack.pop();
  141. }
  142. /**
  143. * Decrements the functionStack counter.
  144. * @returns {void}
  145. * @private
  146. */
  147. function endFunction() {
  148. functionStack.pop();
  149. endBlock();
  150. }
  151. /**
  152. * Check if a variable declaration is a require.
  153. * @param {ASTNode} decl variable declaration Node
  154. * @returns {bool} if decl is a require, return true; else return false.
  155. * @private
  156. */
  157. function isRequire(decl) {
  158. return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require";
  159. }
  160. /**
  161. * Records whether initialized/uninitialized/required variables are defined in current scope.
  162. * @param {string} statementType node.kind, one of: "var", "let", or "const"
  163. * @param {ASTNode[]} declarations List of declarations
  164. * @param {Object} currentScope The scope being investigated
  165. * @returns {void}
  166. * @private
  167. */
  168. function recordTypes(statementType, declarations, currentScope) {
  169. for (let i = 0; i < declarations.length; i++) {
  170. if (declarations[i].init === null) {
  171. if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) {
  172. currentScope.uninitialized = true;
  173. }
  174. } else {
  175. if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) {
  176. if (options.separateRequires && isRequire(declarations[i])) {
  177. currentScope.required = true;
  178. } else {
  179. currentScope.initialized = true;
  180. }
  181. }
  182. }
  183. }
  184. }
  185. /**
  186. * Determines the current scope (function or block)
  187. * @param {string} statementType node.kind, one of: "var", "let", or "const"
  188. * @returns {Object} The scope associated with statementType
  189. */
  190. function getCurrentScope(statementType) {
  191. let currentScope;
  192. if (statementType === "var") {
  193. currentScope = functionStack[functionStack.length - 1];
  194. } else if (statementType === "let") {
  195. currentScope = blockStack[blockStack.length - 1].let;
  196. } else if (statementType === "const") {
  197. currentScope = blockStack[blockStack.length - 1].const;
  198. }
  199. return currentScope;
  200. }
  201. /**
  202. * Counts the number of initialized and uninitialized declarations in a list of declarations
  203. * @param {ASTNode[]} declarations List of declarations
  204. * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations
  205. * @private
  206. */
  207. function countDeclarations(declarations) {
  208. const counts = { uninitialized: 0, initialized: 0 };
  209. for (let i = 0; i < declarations.length; i++) {
  210. if (declarations[i].init === null) {
  211. counts.uninitialized++;
  212. } else {
  213. counts.initialized++;
  214. }
  215. }
  216. return counts;
  217. }
  218. /**
  219. * Determines if there is more than one var statement in the current scope.
  220. * @param {string} statementType node.kind, one of: "var", "let", or "const"
  221. * @param {ASTNode[]} declarations List of declarations
  222. * @returns {boolean} Returns true if it is the first var declaration, false if not.
  223. * @private
  224. */
  225. function hasOnlyOneStatement(statementType, declarations) {
  226. const declarationCounts = countDeclarations(declarations);
  227. const currentOptions = options[statementType] || {};
  228. const currentScope = getCurrentScope(statementType);
  229. const hasRequires = declarations.some(isRequire);
  230. if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) {
  231. if (currentScope.uninitialized || currentScope.initialized) {
  232. return false;
  233. }
  234. }
  235. if (declarationCounts.uninitialized > 0) {
  236. if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) {
  237. return false;
  238. }
  239. }
  240. if (declarationCounts.initialized > 0) {
  241. if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) {
  242. return false;
  243. }
  244. }
  245. if (currentScope.required && hasRequires) {
  246. return false;
  247. }
  248. recordTypes(statementType, declarations, currentScope);
  249. return true;
  250. }
  251. /**
  252. * Checks a given VariableDeclaration node for errors.
  253. * @param {ASTNode} node The VariableDeclaration node to check
  254. * @returns {void}
  255. * @private
  256. */
  257. function checkVariableDeclaration(node) {
  258. const parent = node.parent;
  259. const type = node.kind;
  260. if (!options[type]) {
  261. return;
  262. }
  263. const declarations = node.declarations;
  264. const declarationCounts = countDeclarations(declarations);
  265. const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire);
  266. if (options[type].initialized === MODE_ALWAYS) {
  267. if (options.separateRequires && mixedRequires) {
  268. context.report({
  269. node,
  270. message: "Split requires to be separated into a single block."
  271. });
  272. }
  273. }
  274. // consecutive
  275. const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0;
  276. if (nodeIndex > 0) {
  277. const previousNode = parent.body[nodeIndex - 1];
  278. const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration";
  279. if (isPreviousNodeDeclaration && previousNode.kind === type) {
  280. const previousDeclCounts = countDeclarations(previousNode.declarations);
  281. if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) {
  282. context.report({
  283. node,
  284. message: "Combine this with the previous '{{type}}' statement.",
  285. data: {
  286. type
  287. }
  288. });
  289. } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) {
  290. context.report({
  291. node,
  292. message: "Combine this with the previous '{{type}}' statement with initialized variables.",
  293. data: {
  294. type
  295. }
  296. });
  297. } else if (options[type].uninitialized === MODE_CONSECUTIVE &&
  298. declarationCounts.uninitialized > 0 &&
  299. previousDeclCounts.uninitialized > 0) {
  300. context.report({
  301. node,
  302. message: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
  303. data: {
  304. type
  305. }
  306. });
  307. }
  308. }
  309. }
  310. // always
  311. if (!hasOnlyOneStatement(type, declarations)) {
  312. if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) {
  313. context.report({
  314. node,
  315. message: "Combine this with the previous '{{type}}' statement.",
  316. data: {
  317. type
  318. }
  319. });
  320. } else {
  321. if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) {
  322. context.report({
  323. node,
  324. message: "Combine this with the previous '{{type}}' statement with initialized variables.",
  325. data: {
  326. type
  327. }
  328. });
  329. }
  330. if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) {
  331. if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
  332. return;
  333. }
  334. context.report({
  335. node,
  336. message: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
  337. data: {
  338. type
  339. }
  340. });
  341. }
  342. }
  343. }
  344. // never
  345. if (parent.type !== "ForStatement" || parent.init !== node) {
  346. const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized;
  347. if (totalDeclarations > 1) {
  348. if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) {
  349. // both initialized and uninitialized
  350. context.report({
  351. node,
  352. message: "Split '{{type}}' declarations into multiple statements.",
  353. data: {
  354. type
  355. }
  356. });
  357. } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) {
  358. // initialized
  359. context.report({
  360. node,
  361. message: "Split initialized '{{type}}' declarations into multiple statements.",
  362. data: {
  363. type
  364. }
  365. });
  366. } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) {
  367. // uninitialized
  368. context.report({
  369. node,
  370. message: "Split uninitialized '{{type}}' declarations into multiple statements.",
  371. data: {
  372. type
  373. }
  374. });
  375. }
  376. }
  377. }
  378. }
  379. //--------------------------------------------------------------------------
  380. // Public API
  381. //--------------------------------------------------------------------------
  382. return {
  383. Program: startFunction,
  384. FunctionDeclaration: startFunction,
  385. FunctionExpression: startFunction,
  386. ArrowFunctionExpression: startFunction,
  387. BlockStatement: startBlock,
  388. ForStatement: startBlock,
  389. ForInStatement: startBlock,
  390. ForOfStatement: startBlock,
  391. SwitchStatement: startBlock,
  392. VariableDeclaration: checkVariableDeclaration,
  393. "ForStatement:exit": endBlock,
  394. "ForOfStatement:exit": endBlock,
  395. "ForInStatement:exit": endBlock,
  396. "SwitchStatement:exit": endBlock,
  397. "BlockStatement:exit": endBlock,
  398. "Program:exit": endFunction,
  399. "FunctionDeclaration:exit": endFunction,
  400. "FunctionExpression:exit": endFunction,
  401. "ArrowFunctionExpression:exit": endFunction
  402. };
  403. }
  404. };