newline-after-var.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /**
  2. * @fileoverview Rule to check empty newline after "var" statement
  3. * @author Gopal Venkatesan
  4. * @deprecated in ESLint v4.0.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. type: "layout",
  18. docs: {
  19. description:
  20. "Require or disallow an empty line after variable declarations",
  21. recommended: false,
  22. url: "https://eslint.org/docs/latest/rules/newline-after-var",
  23. },
  24. schema: [
  25. {
  26. enum: ["never", "always"],
  27. },
  28. ],
  29. fixable: "whitespace",
  30. messages: {
  31. expected: "Expected blank line after variable declarations.",
  32. unexpected: "Unexpected blank line after variable declarations.",
  33. },
  34. deprecated: {
  35. message: "The rule was replaced with a more general rule.",
  36. url: "https://eslint.org/blog/2017/06/eslint-v4.0.0-released/",
  37. deprecatedSince: "4.0.0",
  38. availableUntil: "11.0.0",
  39. replacedBy: [
  40. {
  41. message: "The new rule moved to a plugin.",
  42. url: "https://eslint.org/docs/latest/rules/padding-line-between-statements#examples",
  43. plugin: {
  44. name: "@stylistic/eslint-plugin",
  45. url: "https://eslint.style",
  46. },
  47. rule: {
  48. name: "padding-line-between-statements",
  49. url: "https://eslint.style/rules/padding-line-between-statements",
  50. },
  51. },
  52. ],
  53. },
  54. },
  55. create(context) {
  56. const sourceCode = context.sourceCode;
  57. // Default `mode` to "always".
  58. const mode = context.options[0] === "never" ? "never" : "always";
  59. // Cache starting and ending line numbers of comments for faster lookup
  60. const commentEndLine = sourceCode
  61. .getAllComments()
  62. .reduce((result, token) => {
  63. result[token.loc.start.line] = token.loc.end.line;
  64. return result;
  65. }, {});
  66. //--------------------------------------------------------------------------
  67. // Helpers
  68. //--------------------------------------------------------------------------
  69. /**
  70. * Gets a token from the given node to compare line to the next statement.
  71. *
  72. * In general, the token is the last token of the node. However, the token is the second last token if the following conditions satisfy.
  73. *
  74. * - The last token is semicolon.
  75. * - The semicolon is on a different line from the previous token of the semicolon.
  76. *
  77. * This behavior would address semicolon-less style code. e.g.:
  78. *
  79. * var foo = 1
  80. *
  81. * ;(a || b).doSomething()
  82. * @param {ASTNode} node The node to get.
  83. * @returns {Token} The token to compare line to the next statement.
  84. */
  85. function getLastToken(node) {
  86. const lastToken = sourceCode.getLastToken(node);
  87. if (lastToken.type === "Punctuator" && lastToken.value === ";") {
  88. const prevToken = sourceCode.getTokenBefore(lastToken);
  89. if (prevToken.loc.end.line !== lastToken.loc.start.line) {
  90. return prevToken;
  91. }
  92. }
  93. return lastToken;
  94. }
  95. /**
  96. * Determine if provided keyword is a variable declaration
  97. * @private
  98. * @param {string} keyword keyword to test
  99. * @returns {boolean} True if `keyword` is a type of var
  100. */
  101. function isVar(keyword) {
  102. return (
  103. keyword === "var" || keyword === "let" || keyword === "const"
  104. );
  105. }
  106. /**
  107. * Determine if provided keyword is a variant of for specifiers
  108. * @private
  109. * @param {string} keyword keyword to test
  110. * @returns {boolean} True if `keyword` is a variant of for specifier
  111. */
  112. function isForTypeSpecifier(keyword) {
  113. return (
  114. keyword === "ForStatement" ||
  115. keyword === "ForInStatement" ||
  116. keyword === "ForOfStatement"
  117. );
  118. }
  119. /**
  120. * Determine if provided keyword is an export specifiers
  121. * @private
  122. * @param {string} nodeType nodeType to test
  123. * @returns {boolean} True if `nodeType` is an export specifier
  124. */
  125. function isExportSpecifier(nodeType) {
  126. return (
  127. nodeType === "ExportNamedDeclaration" ||
  128. nodeType === "ExportSpecifier" ||
  129. nodeType === "ExportDefaultDeclaration" ||
  130. nodeType === "ExportAllDeclaration"
  131. );
  132. }
  133. /**
  134. * Determine if provided node is the last of their parent block.
  135. * @private
  136. * @param {ASTNode} node node to test
  137. * @returns {boolean} True if `node` is last of their parent block.
  138. */
  139. function isLastNode(node) {
  140. const token = sourceCode.getTokenAfter(node);
  141. return (
  142. !token || (token.type === "Punctuator" && token.value === "}")
  143. );
  144. }
  145. /**
  146. * Gets the last line of a group of consecutive comments
  147. * @param {number} commentStartLine The starting line of the group
  148. * @returns {number} The number of the last comment line of the group
  149. */
  150. function getLastCommentLineOfBlock(commentStartLine) {
  151. const currentCommentEnd = commentEndLine[commentStartLine];
  152. return commentEndLine[currentCommentEnd + 1]
  153. ? getLastCommentLineOfBlock(currentCommentEnd + 1)
  154. : currentCommentEnd;
  155. }
  156. /**
  157. * Determine if a token starts more than one line after a comment ends
  158. * @param {token} token The token being checked
  159. * @param {integer} commentStartLine The line number on which the comment starts
  160. * @returns {boolean} True if `token` does not start immediately after a comment
  161. */
  162. function hasBlankLineAfterComment(token, commentStartLine) {
  163. return (
  164. token.loc.start.line >
  165. getLastCommentLineOfBlock(commentStartLine) + 1
  166. );
  167. }
  168. /**
  169. * Checks that a blank line exists after a variable declaration when mode is
  170. * set to "always", or checks that there is no blank line when mode is set
  171. * to "never"
  172. * @private
  173. * @param {ASTNode} node `VariableDeclaration` node to test
  174. * @returns {void}
  175. */
  176. function checkForBlankLine(node) {
  177. /*
  178. * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will
  179. * sometimes be second-last if there is a semicolon on a different line.
  180. */
  181. const lastToken = getLastToken(node),
  182. /*
  183. * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken
  184. * is the last token of the node.
  185. */
  186. nextToken =
  187. lastToken === sourceCode.getLastToken(node)
  188. ? sourceCode.getTokenAfter(node)
  189. : sourceCode.getLastToken(node),
  190. nextLineNum = lastToken.loc.end.line + 1;
  191. // Ignore if there is no following statement
  192. if (!nextToken) {
  193. return;
  194. }
  195. // Ignore if parent of node is a for variant
  196. if (isForTypeSpecifier(node.parent.type)) {
  197. return;
  198. }
  199. // Ignore if parent of node is an export specifier
  200. if (isExportSpecifier(node.parent.type)) {
  201. return;
  202. }
  203. /*
  204. * Some coding styles use multiple `var` statements, so do nothing if
  205. * the next token is a `var` statement.
  206. */
  207. if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
  208. return;
  209. }
  210. // Ignore if it is last statement in a block
  211. if (isLastNode(node)) {
  212. return;
  213. }
  214. // Next statement is not a `var`...
  215. const noNextLineToken = nextToken.loc.start.line > nextLineNum;
  216. const hasNextLineComment =
  217. typeof commentEndLine[nextLineNum] !== "undefined";
  218. if (mode === "never" && noNextLineToken && !hasNextLineComment) {
  219. context.report({
  220. node,
  221. messageId: "unexpected",
  222. fix(fixer) {
  223. const linesBetween = sourceCode
  224. .getText()
  225. .slice(lastToken.range[1], nextToken.range[0])
  226. .split(astUtils.LINEBREAK_MATCHER);
  227. return fixer.replaceTextRange(
  228. [lastToken.range[1], nextToken.range[0]],
  229. `${linesBetween.slice(0, -1).join("")}\n${linesBetween.at(-1)}`,
  230. );
  231. },
  232. });
  233. }
  234. // Token on the next line, or comment without blank line
  235. if (
  236. mode === "always" &&
  237. (!noNextLineToken ||
  238. (hasNextLineComment &&
  239. !hasBlankLineAfterComment(nextToken, nextLineNum)))
  240. ) {
  241. context.report({
  242. node,
  243. messageId: "expected",
  244. fix(fixer) {
  245. if (
  246. (noNextLineToken
  247. ? getLastCommentLineOfBlock(nextLineNum)
  248. : lastToken.loc.end.line) ===
  249. nextToken.loc.start.line
  250. ) {
  251. return fixer.insertTextBefore(nextToken, "\n\n");
  252. }
  253. return fixer.insertTextBeforeRange(
  254. [
  255. nextToken.range[0] - nextToken.loc.start.column,
  256. nextToken.range[1],
  257. ],
  258. "\n",
  259. );
  260. },
  261. });
  262. }
  263. }
  264. //--------------------------------------------------------------------------
  265. // Public
  266. //--------------------------------------------------------------------------
  267. return {
  268. VariableDeclaration: checkForBlankLine,
  269. };
  270. },
  271. };