Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

533 wiersze
21 KiB

  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. fixable: "code",
  18. schema: [
  19. {
  20. oneOf: [
  21. {
  22. enum: ["always", "never", "consecutive"]
  23. },
  24. {
  25. type: "object",
  26. properties: {
  27. separateRequires: {
  28. type: "boolean"
  29. },
  30. var: {
  31. enum: ["always", "never", "consecutive"]
  32. },
  33. let: {
  34. enum: ["always", "never", "consecutive"]
  35. },
  36. const: {
  37. enum: ["always", "never", "consecutive"]
  38. }
  39. },
  40. additionalProperties: false
  41. },
  42. {
  43. type: "object",
  44. properties: {
  45. initialized: {
  46. enum: ["always", "never", "consecutive"]
  47. },
  48. uninitialized: {
  49. enum: ["always", "never", "consecutive"]
  50. }
  51. },
  52. additionalProperties: false
  53. }
  54. ]
  55. }
  56. ]
  57. },
  58. create(context) {
  59. const MODE_ALWAYS = "always";
  60. const MODE_NEVER = "never";
  61. const MODE_CONSECUTIVE = "consecutive";
  62. const mode = context.options[0] || MODE_ALWAYS;
  63. const options = {};
  64. if (typeof mode === "string") { // simple options configuration with just a string
  65. options.var = { uninitialized: mode, initialized: mode };
  66. options.let = { uninitialized: mode, initialized: mode };
  67. options.const = { uninitialized: mode, initialized: mode };
  68. } else if (typeof mode === "object") { // options configuration is an object
  69. if (Object.prototype.hasOwnProperty.call(mode, "separateRequires")) {
  70. options.separateRequires = !!mode.separateRequires;
  71. }
  72. if (Object.prototype.hasOwnProperty.call(mode, "var")) {
  73. options.var = { uninitialized: mode.var, initialized: mode.var };
  74. }
  75. if (Object.prototype.hasOwnProperty.call(mode, "let")) {
  76. options.let = { uninitialized: mode.let, initialized: mode.let };
  77. }
  78. if (Object.prototype.hasOwnProperty.call(mode, "const")) {
  79. options.const = { uninitialized: mode.const, initialized: mode.const };
  80. }
  81. if (Object.prototype.hasOwnProperty.call(mode, "uninitialized")) {
  82. if (!options.var) {
  83. options.var = {};
  84. }
  85. if (!options.let) {
  86. options.let = {};
  87. }
  88. if (!options.const) {
  89. options.const = {};
  90. }
  91. options.var.uninitialized = mode.uninitialized;
  92. options.let.uninitialized = mode.uninitialized;
  93. options.const.uninitialized = mode.uninitialized;
  94. }
  95. if (Object.prototype.hasOwnProperty.call(mode, "initialized")) {
  96. if (!options.var) {
  97. options.var = {};
  98. }
  99. if (!options.let) {
  100. options.let = {};
  101. }
  102. if (!options.const) {
  103. options.const = {};
  104. }
  105. options.var.initialized = mode.initialized;
  106. options.let.initialized = mode.initialized;
  107. options.const.initialized = mode.initialized;
  108. }
  109. }
  110. const sourceCode = context.getSourceCode();
  111. //--------------------------------------------------------------------------
  112. // Helpers
  113. //--------------------------------------------------------------------------
  114. const functionStack = [];
  115. const blockStack = [];
  116. /**
  117. * Increments the blockStack counter.
  118. * @returns {void}
  119. * @private
  120. */
  121. function startBlock() {
  122. blockStack.push({
  123. let: { initialized: false, uninitialized: false },
  124. const: { initialized: false, uninitialized: false }
  125. });
  126. }
  127. /**
  128. * Increments the functionStack counter.
  129. * @returns {void}
  130. * @private
  131. */
  132. function startFunction() {
  133. functionStack.push({ initialized: false, uninitialized: false });
  134. startBlock();
  135. }
  136. /**
  137. * Decrements the blockStack counter.
  138. * @returns {void}
  139. * @private
  140. */
  141. function endBlock() {
  142. blockStack.pop();
  143. }
  144. /**
  145. * Decrements the functionStack counter.
  146. * @returns {void}
  147. * @private
  148. */
  149. function endFunction() {
  150. functionStack.pop();
  151. endBlock();
  152. }
  153. /**
  154. * Check if a variable declaration is a require.
  155. * @param {ASTNode} decl variable declaration Node
  156. * @returns {bool} if decl is a require, return true; else return false.
  157. * @private
  158. */
  159. function isRequire(decl) {
  160. return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require";
  161. }
  162. /**
  163. * Records whether initialized/uninitialized/required variables are defined in current scope.
  164. * @param {string} statementType node.kind, one of: "var", "let", or "const"
  165. * @param {ASTNode[]} declarations List of declarations
  166. * @param {Object} currentScope The scope being investigated
  167. * @returns {void}
  168. * @private
  169. */
  170. function recordTypes(statementType, declarations, currentScope) {
  171. for (let i = 0; i < declarations.length; i++) {
  172. if (declarations[i].init === null) {
  173. if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) {
  174. currentScope.uninitialized = true;
  175. }
  176. } else {
  177. if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) {
  178. if (options.separateRequires && isRequire(declarations[i])) {
  179. currentScope.required = true;
  180. } else {
  181. currentScope.initialized = true;
  182. }
  183. }
  184. }
  185. }
  186. }
  187. /**
  188. * Determines the current scope (function or block)
  189. * @param {string} statementType node.kind, one of: "var", "let", or "const"
  190. * @returns {Object} The scope associated with statementType
  191. */
  192. function getCurrentScope(statementType) {
  193. let currentScope;
  194. if (statementType === "var") {
  195. currentScope = functionStack[functionStack.length - 1];
  196. } else if (statementType === "let") {
  197. currentScope = blockStack[blockStack.length - 1].let;
  198. } else if (statementType === "const") {
  199. currentScope = blockStack[blockStack.length - 1].const;
  200. }
  201. return currentScope;
  202. }
  203. /**
  204. * Counts the number of initialized and uninitialized declarations in a list of declarations
  205. * @param {ASTNode[]} declarations List of declarations
  206. * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations
  207. * @private
  208. */
  209. function countDeclarations(declarations) {
  210. const counts = { uninitialized: 0, initialized: 0 };
  211. for (let i = 0; i < declarations.length; i++) {
  212. if (declarations[i].init === null) {
  213. counts.uninitialized++;
  214. } else {
  215. counts.initialized++;
  216. }
  217. }
  218. return counts;
  219. }
  220. /**
  221. * Determines if there is more than one var statement in the current scope.
  222. * @param {string} statementType node.kind, one of: "var", "let", or "const"
  223. * @param {ASTNode[]} declarations List of declarations
  224. * @returns {boolean} Returns true if it is the first var declaration, false if not.
  225. * @private
  226. */
  227. function hasOnlyOneStatement(statementType, declarations) {
  228. const declarationCounts = countDeclarations(declarations);
  229. const currentOptions = options[statementType] || {};
  230. const currentScope = getCurrentScope(statementType);
  231. const hasRequires = declarations.some(isRequire);
  232. if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) {
  233. if (currentScope.uninitialized || currentScope.initialized) {
  234. return false;
  235. }
  236. }
  237. if (declarationCounts.uninitialized > 0) {
  238. if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) {
  239. return false;
  240. }
  241. }
  242. if (declarationCounts.initialized > 0) {
  243. if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) {
  244. return false;
  245. }
  246. }
  247. if (currentScope.required && hasRequires) {
  248. return false;
  249. }
  250. recordTypes(statementType, declarations, currentScope);
  251. return true;
  252. }
  253. /**
  254. * Fixer to join VariableDeclaration's into a single declaration
  255. * @param {VariableDeclarator[]} declarations The `VariableDeclaration` to join
  256. * @returns {Function} The fixer function
  257. */
  258. function joinDeclarations(declarations) {
  259. const declaration = declarations[0];
  260. const body = Array.isArray(declaration.parent.parent.body) ? declaration.parent.parent.body : [];
  261. const currentIndex = body.findIndex(node => node.range[0] === declaration.parent.range[0]);
  262. const previousNode = body[currentIndex - 1];
  263. return fixer => {
  264. const type = sourceCode.getTokenBefore(declaration);
  265. const prevSemi = sourceCode.getTokenBefore(type);
  266. const res = [];
  267. if (previousNode && previousNode.kind === sourceCode.getText(type)) {
  268. if (prevSemi.value === ";") {
  269. res.push(fixer.replaceText(prevSemi, ","));
  270. } else {
  271. res.push(fixer.insertTextAfter(prevSemi, ","));
  272. }
  273. res.push(fixer.replaceText(type, ""));
  274. }
  275. return res;
  276. };
  277. }
  278. /**
  279. * Fixer to split a VariableDeclaration into individual declarations
  280. * @param {VariableDeclaration} declaration The `VariableDeclaration` to split
  281. * @param {?Function} filter Function to filter the declarations
  282. * @returns {Function} The fixer function
  283. */
  284. function splitDeclarations(declaration) {
  285. return fixer => declaration.declarations.map(declarator => {
  286. const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator);
  287. const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true });
  288. if (tokenAfterDeclarator.value !== ",") {
  289. return null;
  290. }
  291. /*
  292. * `var x,y`
  293. * tokenAfterDeclarator ^^ afterComma
  294. */
  295. if (afterComma.range[0] === tokenAfterDeclarator.range[1]) {
  296. return fixer.replaceText(tokenAfterDeclarator, `; ${declaration.kind} `);
  297. }
  298. /*
  299. * `var x,
  300. * tokenAfterDeclarator ^
  301. * y`
  302. * ^ afterComma
  303. */
  304. if (afterComma.loc.start.line > tokenAfterDeclarator.loc.end.line || afterComma.type === "Line" || afterComma.type === "Block") {
  305. let lastComment = afterComma;
  306. while (lastComment.type === "Line" || lastComment.type === "Block") {
  307. lastComment = sourceCode.getTokenAfter(lastComment, { includeComments: true });
  308. }
  309. return fixer.replaceTextRange(
  310. [tokenAfterDeclarator.range[0], lastComment.range[0]],
  311. `;\n${sourceCode.text.slice(tokenAfterDeclarator.range[1], lastComment.range[0])}\n${declaration.kind} `
  312. );
  313. }
  314. return fixer.replaceText(tokenAfterDeclarator, `; ${declaration.kind}`);
  315. }).filter(x => x);
  316. }
  317. /**
  318. * Checks a given VariableDeclaration node for errors.
  319. * @param {ASTNode} node The VariableDeclaration node to check
  320. * @returns {void}
  321. * @private
  322. */
  323. function checkVariableDeclaration(node) {
  324. const parent = node.parent;
  325. const type = node.kind;
  326. if (!options[type]) {
  327. return;
  328. }
  329. const declarations = node.declarations;
  330. const declarationCounts = countDeclarations(declarations);
  331. const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire);
  332. if (options[type].initialized === MODE_ALWAYS) {
  333. if (options.separateRequires && mixedRequires) {
  334. context.report({
  335. node,
  336. message: "Split requires to be separated into a single block."
  337. });
  338. }
  339. }
  340. // consecutive
  341. const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0;
  342. if (nodeIndex > 0) {
  343. const previousNode = parent.body[nodeIndex - 1];
  344. const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration";
  345. if (isPreviousNodeDeclaration && previousNode.kind === type) {
  346. const previousDeclCounts = countDeclarations(previousNode.declarations);
  347. if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) {
  348. context.report({
  349. node,
  350. message: "Combine this with the previous '{{type}}' statement.",
  351. data: {
  352. type
  353. },
  354. fix: joinDeclarations(declarations)
  355. });
  356. } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) {
  357. context.report({
  358. node,
  359. message: "Combine this with the previous '{{type}}' statement with initialized variables.",
  360. data: {
  361. type
  362. },
  363. fix: joinDeclarations(declarations)
  364. });
  365. } else if (options[type].uninitialized === MODE_CONSECUTIVE &&
  366. declarationCounts.uninitialized > 0 &&
  367. previousDeclCounts.uninitialized > 0) {
  368. context.report({
  369. node,
  370. message: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
  371. data: {
  372. type
  373. },
  374. fix: joinDeclarations(declarations)
  375. });
  376. }
  377. }
  378. }
  379. // always
  380. if (!hasOnlyOneStatement(type, declarations)) {
  381. if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) {
  382. context.report({
  383. node,
  384. message: "Combine this with the previous '{{type}}' statement.",
  385. data: {
  386. type
  387. },
  388. fix: joinDeclarations(declarations)
  389. });
  390. } else {
  391. if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) {
  392. context.report({
  393. node,
  394. message: "Combine this with the previous '{{type}}' statement with initialized variables.",
  395. data: {
  396. type
  397. },
  398. fix: joinDeclarations(declarations)
  399. });
  400. }
  401. if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) {
  402. if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
  403. return;
  404. }
  405. context.report({
  406. node,
  407. message: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
  408. data: {
  409. type
  410. },
  411. fix: joinDeclarations(declarations)
  412. });
  413. }
  414. }
  415. }
  416. // never
  417. if (parent.type !== "ForStatement" || parent.init !== node) {
  418. const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized;
  419. if (totalDeclarations > 1) {
  420. if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) {
  421. // both initialized and uninitialized
  422. context.report({
  423. node,
  424. message: "Split '{{type}}' declarations into multiple statements.",
  425. data: {
  426. type
  427. },
  428. fix: splitDeclarations(node)
  429. });
  430. } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) {
  431. // initialized
  432. context.report({
  433. node,
  434. message: "Split initialized '{{type}}' declarations into multiple statements.",
  435. data: {
  436. type
  437. },
  438. fix: splitDeclarations(node)
  439. });
  440. } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) {
  441. // uninitialized
  442. context.report({
  443. node,
  444. message: "Split uninitialized '{{type}}' declarations into multiple statements.",
  445. data: {
  446. type
  447. },
  448. fix: splitDeclarations(node)
  449. });
  450. }
  451. }
  452. }
  453. }
  454. //--------------------------------------------------------------------------
  455. // Public API
  456. //--------------------------------------------------------------------------
  457. return {
  458. Program: startFunction,
  459. FunctionDeclaration: startFunction,
  460. FunctionExpression: startFunction,
  461. ArrowFunctionExpression: startFunction,
  462. BlockStatement: startBlock,
  463. ForStatement: startBlock,
  464. ForInStatement: startBlock,
  465. ForOfStatement: startBlock,
  466. SwitchStatement: startBlock,
  467. VariableDeclaration: checkVariableDeclaration,
  468. "ForStatement:exit": endBlock,
  469. "ForOfStatement:exit": endBlock,
  470. "ForInStatement:exit": endBlock,
  471. "SwitchStatement:exit": endBlock,
  472. "BlockStatement:exit": endBlock,
  473. "Program:exit": endFunction,
  474. "FunctionDeclaration:exit": endFunction,
  475. "FunctionExpression:exit": endFunction,
  476. "ArrowFunctionExpression:exit": endFunction
  477. };
  478. }
  479. };