Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

one-var.js 22KB

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