padded-blocks.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /**
  2. * @fileoverview A rule to ensure blank lines within blocks.
  3. * @author Mathias Schreck <https://github.com/lo1tuma>
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. /** @type {import('../types').Rule.RuleModule} */
  15. module.exports = {
  16. meta: {
  17. deprecated: {
  18. message: "Formatting rules are being moved out of ESLint core.",
  19. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  20. deprecatedSince: "8.53.0",
  21. availableUntil: "11.0.0",
  22. replacedBy: [
  23. {
  24. message:
  25. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  26. url: "https://eslint.style/guide/migration",
  27. plugin: {
  28. name: "@stylistic/eslint-plugin",
  29. url: "https://eslint.style",
  30. },
  31. rule: {
  32. name: "padded-blocks",
  33. url: "https://eslint.style/rules/padded-blocks",
  34. },
  35. },
  36. ],
  37. },
  38. type: "layout",
  39. docs: {
  40. description: "Require or disallow padding within blocks",
  41. recommended: false,
  42. url: "https://eslint.org/docs/latest/rules/padded-blocks",
  43. },
  44. fixable: "whitespace",
  45. schema: [
  46. {
  47. oneOf: [
  48. {
  49. enum: ["always", "never"],
  50. },
  51. {
  52. type: "object",
  53. properties: {
  54. blocks: {
  55. enum: ["always", "never"],
  56. },
  57. switches: {
  58. enum: ["always", "never"],
  59. },
  60. classes: {
  61. enum: ["always", "never"],
  62. },
  63. },
  64. additionalProperties: false,
  65. minProperties: 1,
  66. },
  67. ],
  68. },
  69. {
  70. type: "object",
  71. properties: {
  72. allowSingleLineBlocks: {
  73. type: "boolean",
  74. },
  75. },
  76. additionalProperties: false,
  77. },
  78. ],
  79. messages: {
  80. alwaysPadBlock: "Block must be padded by blank lines.",
  81. neverPadBlock: "Block must not be padded by blank lines.",
  82. },
  83. },
  84. create(context) {
  85. const options = {};
  86. const typeOptions = context.options[0] || "always";
  87. const exceptOptions = context.options[1] || {};
  88. if (typeof typeOptions === "string") {
  89. const shouldHavePadding = typeOptions === "always";
  90. options.blocks = shouldHavePadding;
  91. options.switches = shouldHavePadding;
  92. options.classes = shouldHavePadding;
  93. } else {
  94. if (Object.hasOwn(typeOptions, "blocks")) {
  95. options.blocks = typeOptions.blocks === "always";
  96. }
  97. if (Object.hasOwn(typeOptions, "switches")) {
  98. options.switches = typeOptions.switches === "always";
  99. }
  100. if (Object.hasOwn(typeOptions, "classes")) {
  101. options.classes = typeOptions.classes === "always";
  102. }
  103. }
  104. if (Object.hasOwn(exceptOptions, "allowSingleLineBlocks")) {
  105. options.allowSingleLineBlocks =
  106. exceptOptions.allowSingleLineBlocks === true;
  107. }
  108. const sourceCode = context.sourceCode;
  109. /**
  110. * Gets the open brace token from a given node.
  111. * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace.
  112. * @returns {Token} The token of the open brace.
  113. */
  114. function getOpenBrace(node) {
  115. if (node.type === "SwitchStatement") {
  116. return sourceCode.getTokenBefore(node.cases[0]);
  117. }
  118. if (node.type === "StaticBlock") {
  119. return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
  120. }
  121. // `BlockStatement` or `ClassBody`
  122. return sourceCode.getFirstToken(node);
  123. }
  124. /**
  125. * Checks if the given parameter is a comment node
  126. * @param {ASTNode|Token} node An AST node or token
  127. * @returns {boolean} True if node is a comment
  128. */
  129. function isComment(node) {
  130. return node.type === "Line" || node.type === "Block";
  131. }
  132. /**
  133. * Checks if there is padding between two tokens
  134. * @param {Token} first The first token
  135. * @param {Token} second The second token
  136. * @returns {boolean} True if there is at least a line between the tokens
  137. */
  138. function isPaddingBetweenTokens(first, second) {
  139. return second.loc.start.line - first.loc.end.line >= 2;
  140. }
  141. /**
  142. * Checks if the given token has a blank line after it.
  143. * @param {Token} token The token to check.
  144. * @returns {boolean} Whether or not the token is followed by a blank line.
  145. */
  146. function getFirstBlockToken(token) {
  147. let prev,
  148. first = token;
  149. do {
  150. prev = first;
  151. first = sourceCode.getTokenAfter(first, {
  152. includeComments: true,
  153. });
  154. } while (
  155. isComment(first) &&
  156. first.loc.start.line === prev.loc.end.line
  157. );
  158. return first;
  159. }
  160. /**
  161. * Checks if the given token is preceded by a blank line.
  162. * @param {Token} token The token to check
  163. * @returns {boolean} Whether or not the token is preceded by a blank line
  164. */
  165. function getLastBlockToken(token) {
  166. let last = token,
  167. next;
  168. do {
  169. next = last;
  170. last = sourceCode.getTokenBefore(last, {
  171. includeComments: true,
  172. });
  173. } while (
  174. isComment(last) &&
  175. last.loc.end.line === next.loc.start.line
  176. );
  177. return last;
  178. }
  179. /**
  180. * Checks if a node should be padded, according to the rule config.
  181. * @param {ASTNode} node The AST node to check.
  182. * @throws {Error} (Unreachable)
  183. * @returns {boolean} True if the node should be padded, false otherwise.
  184. */
  185. function requirePaddingFor(node) {
  186. switch (node.type) {
  187. case "BlockStatement":
  188. case "StaticBlock":
  189. return options.blocks;
  190. case "SwitchStatement":
  191. return options.switches;
  192. case "ClassBody":
  193. return options.classes;
  194. /* c8 ignore next */
  195. default:
  196. throw new Error("unreachable");
  197. }
  198. }
  199. /**
  200. * Checks the given BlockStatement node to be padded if the block is not empty.
  201. * @param {ASTNode} node The AST node of a BlockStatement.
  202. * @returns {void} undefined.
  203. */
  204. function checkPadding(node) {
  205. const openBrace = getOpenBrace(node),
  206. firstBlockToken = getFirstBlockToken(openBrace),
  207. tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, {
  208. includeComments: true,
  209. }),
  210. closeBrace = sourceCode.getLastToken(node),
  211. lastBlockToken = getLastBlockToken(closeBrace),
  212. tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, {
  213. includeComments: true,
  214. }),
  215. blockHasTopPadding = isPaddingBetweenTokens(
  216. tokenBeforeFirst,
  217. firstBlockToken,
  218. ),
  219. blockHasBottomPadding = isPaddingBetweenTokens(
  220. lastBlockToken,
  221. tokenAfterLast,
  222. );
  223. if (
  224. options.allowSingleLineBlocks &&
  225. astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)
  226. ) {
  227. return;
  228. }
  229. if (requirePaddingFor(node)) {
  230. if (!blockHasTopPadding) {
  231. context.report({
  232. node,
  233. loc: {
  234. start: tokenBeforeFirst.loc.start,
  235. end: firstBlockToken.loc.start,
  236. },
  237. fix(fixer) {
  238. return fixer.insertTextAfter(
  239. tokenBeforeFirst,
  240. "\n",
  241. );
  242. },
  243. messageId: "alwaysPadBlock",
  244. });
  245. }
  246. if (!blockHasBottomPadding) {
  247. context.report({
  248. node,
  249. loc: {
  250. end: tokenAfterLast.loc.start,
  251. start: lastBlockToken.loc.end,
  252. },
  253. fix(fixer) {
  254. return fixer.insertTextBefore(tokenAfterLast, "\n");
  255. },
  256. messageId: "alwaysPadBlock",
  257. });
  258. }
  259. } else {
  260. if (blockHasTopPadding) {
  261. context.report({
  262. node,
  263. loc: {
  264. start: tokenBeforeFirst.loc.start,
  265. end: firstBlockToken.loc.start,
  266. },
  267. fix(fixer) {
  268. return fixer.replaceTextRange(
  269. [
  270. tokenBeforeFirst.range[1],
  271. firstBlockToken.range[0] -
  272. firstBlockToken.loc.start.column,
  273. ],
  274. "\n",
  275. );
  276. },
  277. messageId: "neverPadBlock",
  278. });
  279. }
  280. if (blockHasBottomPadding) {
  281. context.report({
  282. node,
  283. loc: {
  284. end: tokenAfterLast.loc.start,
  285. start: lastBlockToken.loc.end,
  286. },
  287. messageId: "neverPadBlock",
  288. fix(fixer) {
  289. return fixer.replaceTextRange(
  290. [
  291. lastBlockToken.range[1],
  292. tokenAfterLast.range[0] -
  293. tokenAfterLast.loc.start.column,
  294. ],
  295. "\n",
  296. );
  297. },
  298. });
  299. }
  300. }
  301. }
  302. const rule = {};
  303. if (Object.hasOwn(options, "switches")) {
  304. rule.SwitchStatement = function (node) {
  305. if (node.cases.length === 0) {
  306. return;
  307. }
  308. checkPadding(node);
  309. };
  310. }
  311. if (Object.hasOwn(options, "blocks")) {
  312. rule.BlockStatement = function (node) {
  313. if (node.body.length === 0) {
  314. return;
  315. }
  316. checkPadding(node);
  317. };
  318. rule.StaticBlock = rule.BlockStatement;
  319. }
  320. if (Object.hasOwn(options, "classes")) {
  321. rule.ClassBody = function (node) {
  322. if (node.body.length === 0) {
  323. return;
  324. }
  325. checkPadding(node);
  326. };
  327. }
  328. return rule;
  329. },
  330. };