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.

padding-line-between-statements.js 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. /**
  2. * @fileoverview Rule to require or disallow newlines between statements
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("../util/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`;
  14. const PADDING_LINE_SEQUENCE = new RegExp(
  15. String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`
  16. );
  17. const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/;
  18. const CJS_IMPORT = /^require\(/;
  19. /**
  20. * Creates tester which check if a node starts with specific keyword.
  21. *
  22. * @param {string} keyword The keyword to test.
  23. * @returns {Object} the created tester.
  24. * @private
  25. */
  26. function newKeywordTester(keyword) {
  27. return {
  28. test: (node, sourceCode) =>
  29. sourceCode.getFirstToken(node).value === keyword
  30. };
  31. }
  32. /**
  33. * Creates tester which check if a node is specific type.
  34. *
  35. * @param {string} type The node type to test.
  36. * @returns {Object} the created tester.
  37. * @private
  38. */
  39. function newNodeTypeTester(type) {
  40. return {
  41. test: node =>
  42. node.type === type
  43. };
  44. }
  45. /**
  46. * Checks the given node is an expression statement of IIFE.
  47. *
  48. * @param {ASTNode} node The node to check.
  49. * @returns {boolean} `true` if the node is an expression statement of IIFE.
  50. * @private
  51. */
  52. function isIIFEStatement(node) {
  53. if (node.type === "ExpressionStatement") {
  54. let call = node.expression;
  55. if (call.type === "UnaryExpression") {
  56. call = call.argument;
  57. }
  58. return call.type === "CallExpression" && astUtils.isFunction(call.callee);
  59. }
  60. return false;
  61. }
  62. /**
  63. * Checks whether the given node is a block-like statement.
  64. * This checks the last token of the node is the closing brace of a block.
  65. *
  66. * @param {SourceCode} sourceCode The source code to get tokens.
  67. * @param {ASTNode} node The node to check.
  68. * @returns {boolean} `true` if the node is a block-like statement.
  69. * @private
  70. */
  71. function isBlockLikeStatement(sourceCode, node) {
  72. // do-while with a block is a block-like statement.
  73. if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") {
  74. return true;
  75. }
  76. /*
  77. * IIFE is a block-like statement specially from
  78. * JSCS#disallowPaddingNewLinesAfterBlocks.
  79. */
  80. if (isIIFEStatement(node)) {
  81. return true;
  82. }
  83. // Checks the last token is a closing brace of blocks.
  84. const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
  85. const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken)
  86. ? sourceCode.getNodeByRangeIndex(lastToken.range[0])
  87. : null;
  88. return Boolean(belongingNode) && (
  89. belongingNode.type === "BlockStatement" ||
  90. belongingNode.type === "SwitchStatement"
  91. );
  92. }
  93. /**
  94. * Check whether the given node is a directive or not.
  95. * @param {ASTNode} node The node to check.
  96. * @param {SourceCode} sourceCode The source code object to get tokens.
  97. * @returns {boolean} `true` if the node is a directive.
  98. */
  99. function isDirective(node, sourceCode) {
  100. return (
  101. node.type === "ExpressionStatement" &&
  102. (
  103. node.parent.type === "Program" ||
  104. (
  105. node.parent.type === "BlockStatement" &&
  106. astUtils.isFunction(node.parent.parent)
  107. )
  108. ) &&
  109. node.expression.type === "Literal" &&
  110. typeof node.expression.value === "string" &&
  111. !astUtils.isParenthesised(sourceCode, node.expression)
  112. );
  113. }
  114. /**
  115. * Check whether the given node is a part of directive prologue or not.
  116. * @param {ASTNode} node The node to check.
  117. * @param {SourceCode} sourceCode The source code object to get tokens.
  118. * @returns {boolean} `true` if the node is a part of directive prologue.
  119. */
  120. function isDirectivePrologue(node, sourceCode) {
  121. if (isDirective(node, sourceCode)) {
  122. for (const sibling of node.parent.body) {
  123. if (sibling === node) {
  124. break;
  125. }
  126. if (!isDirective(sibling, sourceCode)) {
  127. return false;
  128. }
  129. }
  130. return true;
  131. }
  132. return false;
  133. }
  134. /**
  135. * Gets the actual last token.
  136. *
  137. * If a semicolon is semicolon-less style's semicolon, this ignores it.
  138. * For example:
  139. *
  140. * foo()
  141. * ;[1, 2, 3].forEach(bar)
  142. *
  143. * @param {SourceCode} sourceCode The source code to get tokens.
  144. * @param {ASTNode} node The node to get.
  145. * @returns {Token} The actual last token.
  146. * @private
  147. */
  148. function getActualLastToken(sourceCode, node) {
  149. const semiToken = sourceCode.getLastToken(node);
  150. const prevToken = sourceCode.getTokenBefore(semiToken);
  151. const nextToken = sourceCode.getTokenAfter(semiToken);
  152. const isSemicolonLessStyle = Boolean(
  153. prevToken &&
  154. nextToken &&
  155. prevToken.range[0] >= node.range[0] &&
  156. astUtils.isSemicolonToken(semiToken) &&
  157. semiToken.loc.start.line !== prevToken.loc.end.line &&
  158. semiToken.loc.end.line === nextToken.loc.start.line
  159. );
  160. return isSemicolonLessStyle ? prevToken : semiToken;
  161. }
  162. /**
  163. * This returns the concatenation of the first 2 captured strings.
  164. * @param {string} _ Unused. Whole matched string.
  165. * @param {string} trailingSpaces The trailing spaces of the first line.
  166. * @param {string} indentSpaces The indentation spaces of the last line.
  167. * @returns {string} The concatenation of trailingSpaces and indentSpaces.
  168. * @private
  169. */
  170. function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) {
  171. return trailingSpaces + indentSpaces;
  172. }
  173. /**
  174. * Check and report statements for `any` configuration.
  175. * It does nothing.
  176. *
  177. * @returns {void}
  178. * @private
  179. */
  180. function verifyForAny() {
  181. }
  182. /**
  183. * Check and report statements for `never` configuration.
  184. * This autofix removes blank lines between the given 2 statements.
  185. * However, if comments exist between 2 blank lines, it does not remove those
  186. * blank lines automatically.
  187. *
  188. * @param {RuleContext} context The rule context to report.
  189. * @param {ASTNode} _ Unused. The previous node to check.
  190. * @param {ASTNode} nextNode The next node to check.
  191. * @param {Array<Token[]>} paddingLines The array of token pairs that blank
  192. * lines exist between the pair.
  193. * @returns {void}
  194. * @private
  195. */
  196. function verifyForNever(context, _, nextNode, paddingLines) {
  197. if (paddingLines.length === 0) {
  198. return;
  199. }
  200. context.report({
  201. node: nextNode,
  202. message: "Unexpected blank line before this statement.",
  203. fix(fixer) {
  204. if (paddingLines.length >= 2) {
  205. return null;
  206. }
  207. const prevToken = paddingLines[0][0];
  208. const nextToken = paddingLines[0][1];
  209. const start = prevToken.range[1];
  210. const end = nextToken.range[0];
  211. const text = context.getSourceCode().text
  212. .slice(start, end)
  213. .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines);
  214. return fixer.replaceTextRange([start, end], text);
  215. }
  216. });
  217. }
  218. /**
  219. * Check and report statements for `always` configuration.
  220. * This autofix inserts a blank line between the given 2 statements.
  221. * If the `prevNode` has trailing comments, it inserts a blank line after the
  222. * trailing comments.
  223. *
  224. * @param {RuleContext} context The rule context to report.
  225. * @param {ASTNode} prevNode The previous node to check.
  226. * @param {ASTNode} nextNode The next node to check.
  227. * @param {Array<Token[]>} paddingLines The array of token pairs that blank
  228. * lines exist between the pair.
  229. * @returns {void}
  230. * @private
  231. */
  232. function verifyForAlways(context, prevNode, nextNode, paddingLines) {
  233. if (paddingLines.length > 0) {
  234. return;
  235. }
  236. context.report({
  237. node: nextNode,
  238. message: "Expected blank line before this statement.",
  239. fix(fixer) {
  240. const sourceCode = context.getSourceCode();
  241. let prevToken = getActualLastToken(sourceCode, prevNode);
  242. const nextToken = sourceCode.getFirstTokenBetween(
  243. prevToken,
  244. nextNode,
  245. {
  246. includeComments: true,
  247. /**
  248. * Skip the trailing comments of the previous node.
  249. * This inserts a blank line after the last trailing comment.
  250. *
  251. * For example:
  252. *
  253. * foo(); // trailing comment.
  254. * // comment.
  255. * bar();
  256. *
  257. * Get fixed to:
  258. *
  259. * foo(); // trailing comment.
  260. *
  261. * // comment.
  262. * bar();
  263. *
  264. * @param {Token} token The token to check.
  265. * @returns {boolean} `true` if the token is not a trailing comment.
  266. * @private
  267. */
  268. filter(token) {
  269. if (astUtils.isTokenOnSameLine(prevToken, token)) {
  270. prevToken = token;
  271. return false;
  272. }
  273. return true;
  274. }
  275. }
  276. ) || nextNode;
  277. const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken)
  278. ? "\n\n"
  279. : "\n";
  280. return fixer.insertTextAfter(prevToken, insertText);
  281. }
  282. });
  283. }
  284. /**
  285. * Types of blank lines.
  286. * `any`, `never`, and `always` are defined.
  287. * Those have `verify` method to check and report statements.
  288. * @private
  289. */
  290. const PaddingTypes = {
  291. any: { verify: verifyForAny },
  292. never: { verify: verifyForNever },
  293. always: { verify: verifyForAlways }
  294. };
  295. /**
  296. * Types of statements.
  297. * Those have `test` method to check it matches to the given statement.
  298. * @private
  299. */
  300. const StatementTypes = {
  301. "*": { test: () => true },
  302. "block-like": {
  303. test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node)
  304. },
  305. "cjs-export": {
  306. test: (node, sourceCode) =>
  307. node.type === "ExpressionStatement" &&
  308. node.expression.type === "AssignmentExpression" &&
  309. CJS_EXPORT.test(sourceCode.getText(node.expression.left))
  310. },
  311. "cjs-import": {
  312. test: (node, sourceCode) =>
  313. node.type === "VariableDeclaration" &&
  314. node.declarations.length > 0 &&
  315. Boolean(node.declarations[0].init) &&
  316. CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init))
  317. },
  318. directive: {
  319. test: isDirectivePrologue
  320. },
  321. expression: {
  322. test: (node, sourceCode) =>
  323. node.type === "ExpressionStatement" &&
  324. !isDirectivePrologue(node, sourceCode)
  325. },
  326. iife: {
  327. test: isIIFEStatement
  328. },
  329. "multiline-block-like": {
  330. test: (node, sourceCode) =>
  331. node.loc.start.line !== node.loc.end.line &&
  332. isBlockLikeStatement(sourceCode, node)
  333. },
  334. "multiline-expression": {
  335. test: (node, sourceCode) =>
  336. node.loc.start.line !== node.loc.end.line &&
  337. node.type === "ExpressionStatement" &&
  338. !isDirectivePrologue(node, sourceCode)
  339. },
  340. block: newNodeTypeTester("BlockStatement"),
  341. empty: newNodeTypeTester("EmptyStatement"),
  342. function: newNodeTypeTester("FunctionDeclaration"),
  343. break: newKeywordTester("break"),
  344. case: newKeywordTester("case"),
  345. class: newKeywordTester("class"),
  346. const: newKeywordTester("const"),
  347. continue: newKeywordTester("continue"),
  348. debugger: newKeywordTester("debugger"),
  349. default: newKeywordTester("default"),
  350. do: newKeywordTester("do"),
  351. export: newKeywordTester("export"),
  352. for: newKeywordTester("for"),
  353. if: newKeywordTester("if"),
  354. import: newKeywordTester("import"),
  355. let: newKeywordTester("let"),
  356. return: newKeywordTester("return"),
  357. switch: newKeywordTester("switch"),
  358. throw: newKeywordTester("throw"),
  359. try: newKeywordTester("try"),
  360. var: newKeywordTester("var"),
  361. while: newKeywordTester("while"),
  362. with: newKeywordTester("with")
  363. };
  364. //------------------------------------------------------------------------------
  365. // Rule Definition
  366. //------------------------------------------------------------------------------
  367. module.exports = {
  368. meta: {
  369. type: "layout",
  370. docs: {
  371. description: "require or disallow padding lines between statements",
  372. category: "Stylistic Issues",
  373. recommended: false,
  374. url: "https://eslint.org/docs/rules/padding-line-between-statements"
  375. },
  376. fixable: "whitespace",
  377. schema: {
  378. definitions: {
  379. paddingType: {
  380. enum: Object.keys(PaddingTypes)
  381. },
  382. statementType: {
  383. anyOf: [
  384. { enum: Object.keys(StatementTypes) },
  385. {
  386. type: "array",
  387. items: { enum: Object.keys(StatementTypes) },
  388. minItems: 1,
  389. uniqueItems: true,
  390. additionalItems: false
  391. }
  392. ]
  393. }
  394. },
  395. type: "array",
  396. items: {
  397. type: "object",
  398. properties: {
  399. blankLine: { $ref: "#/definitions/paddingType" },
  400. prev: { $ref: "#/definitions/statementType" },
  401. next: { $ref: "#/definitions/statementType" }
  402. },
  403. additionalProperties: false,
  404. required: ["blankLine", "prev", "next"]
  405. },
  406. additionalItems: false
  407. }
  408. },
  409. create(context) {
  410. const sourceCode = context.getSourceCode();
  411. const configureList = context.options || [];
  412. let scopeInfo = null;
  413. /**
  414. * Processes to enter to new scope.
  415. * This manages the current previous statement.
  416. * @returns {void}
  417. * @private
  418. */
  419. function enterScope() {
  420. scopeInfo = {
  421. upper: scopeInfo,
  422. prevNode: null
  423. };
  424. }
  425. /**
  426. * Processes to exit from the current scope.
  427. * @returns {void}
  428. * @private
  429. */
  430. function exitScope() {
  431. scopeInfo = scopeInfo.upper;
  432. }
  433. /**
  434. * Checks whether the given node matches the given type.
  435. *
  436. * @param {ASTNode} node The statement node to check.
  437. * @param {string|string[]} type The statement type to check.
  438. * @returns {boolean} `true` if the statement node matched the type.
  439. * @private
  440. */
  441. function match(node, type) {
  442. let innerStatementNode = node;
  443. while (innerStatementNode.type === "LabeledStatement") {
  444. innerStatementNode = innerStatementNode.body;
  445. }
  446. if (Array.isArray(type)) {
  447. return type.some(match.bind(null, innerStatementNode));
  448. }
  449. return StatementTypes[type].test(innerStatementNode, sourceCode);
  450. }
  451. /**
  452. * Finds the last matched configure from configureList.
  453. *
  454. * @param {ASTNode} prevNode The previous statement to match.
  455. * @param {ASTNode} nextNode The current statement to match.
  456. * @returns {Object} The tester of the last matched configure.
  457. * @private
  458. */
  459. function getPaddingType(prevNode, nextNode) {
  460. for (let i = configureList.length - 1; i >= 0; --i) {
  461. const configure = configureList[i];
  462. const matched =
  463. match(prevNode, configure.prev) &&
  464. match(nextNode, configure.next);
  465. if (matched) {
  466. return PaddingTypes[configure.blankLine];
  467. }
  468. }
  469. return PaddingTypes.any;
  470. }
  471. /**
  472. * Gets padding line sequences between the given 2 statements.
  473. * Comments are separators of the padding line sequences.
  474. *
  475. * @param {ASTNode} prevNode The previous statement to count.
  476. * @param {ASTNode} nextNode The current statement to count.
  477. * @returns {Array<Token[]>} The array of token pairs.
  478. * @private
  479. */
  480. function getPaddingLineSequences(prevNode, nextNode) {
  481. const pairs = [];
  482. let prevToken = getActualLastToken(sourceCode, prevNode);
  483. if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) {
  484. do {
  485. const token = sourceCode.getTokenAfter(
  486. prevToken,
  487. { includeComments: true }
  488. );
  489. if (token.loc.start.line - prevToken.loc.end.line >= 2) {
  490. pairs.push([prevToken, token]);
  491. }
  492. prevToken = token;
  493. } while (prevToken.range[0] < nextNode.range[0]);
  494. }
  495. return pairs;
  496. }
  497. /**
  498. * Verify padding lines between the given node and the previous node.
  499. *
  500. * @param {ASTNode} node The node to verify.
  501. * @returns {void}
  502. * @private
  503. */
  504. function verify(node) {
  505. const parentType = node.parent.type;
  506. const validParent =
  507. astUtils.STATEMENT_LIST_PARENTS.has(parentType) ||
  508. parentType === "SwitchStatement";
  509. if (!validParent) {
  510. return;
  511. }
  512. // Save this node as the current previous statement.
  513. const prevNode = scopeInfo.prevNode;
  514. // Verify.
  515. if (prevNode) {
  516. const type = getPaddingType(prevNode, node);
  517. const paddingLines = getPaddingLineSequences(prevNode, node);
  518. type.verify(context, prevNode, node, paddingLines);
  519. }
  520. scopeInfo.prevNode = node;
  521. }
  522. /**
  523. * Verify padding lines between the given node and the previous node.
  524. * Then process to enter to new scope.
  525. *
  526. * @param {ASTNode} node The node to verify.
  527. * @returns {void}
  528. * @private
  529. */
  530. function verifyThenEnterScope(node) {
  531. verify(node);
  532. enterScope();
  533. }
  534. return {
  535. Program: enterScope,
  536. BlockStatement: enterScope,
  537. SwitchStatement: enterScope,
  538. "Program:exit": exitScope,
  539. "BlockStatement:exit": exitScope,
  540. "SwitchStatement:exit": exitScope,
  541. ":statement": verify,
  542. SwitchCase: verifyThenEnterScope,
  543. "SwitchCase:exit": exitScope
  544. };
  545. }
  546. };