newline-after-var.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /**
  2. * @fileoverview Rule to check empty newline after "var" statement
  3. * @author Gopal Venkatesan
  4. * @deprecated
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("../util/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. module.exports = {
  15. meta: {
  16. type: "layout",
  17. docs: {
  18. description: "require or disallow an empty line after variable declarations",
  19. category: "Stylistic Issues",
  20. recommended: false,
  21. url: "https://eslint.org/docs/rules/newline-after-var"
  22. },
  23. schema: [
  24. {
  25. enum: ["never", "always"]
  26. }
  27. ],
  28. fixable: "whitespace",
  29. messages: {
  30. expected: "Expected blank line after variable declarations.",
  31. unexpected: "Unexpected blank line after variable declarations."
  32. },
  33. deprecated: true,
  34. replacedBy: ["padding-line-between-statements"]
  35. },
  36. create(context) {
  37. const sourceCode = context.getSourceCode();
  38. // Default `mode` to "always".
  39. const mode = context.options[0] === "never" ? "never" : "always";
  40. // Cache starting and ending line numbers of comments for faster lookup
  41. const commentEndLine = sourceCode.getAllComments().reduce((result, token) => {
  42. result[token.loc.start.line] = token.loc.end.line;
  43. return result;
  44. }, {});
  45. //--------------------------------------------------------------------------
  46. // Helpers
  47. //--------------------------------------------------------------------------
  48. /**
  49. * Gets a token from the given node to compare line to the next statement.
  50. *
  51. * In general, the token is the last token of the node. However, the token is the second last token if the following conditions satisfy.
  52. *
  53. * - The last token is semicolon.
  54. * - The semicolon is on a different line from the previous token of the semicolon.
  55. *
  56. * This behavior would address semicolon-less style code. e.g.:
  57. *
  58. * var foo = 1
  59. *
  60. * ;(a || b).doSomething()
  61. *
  62. * @param {ASTNode} node - The node to get.
  63. * @returns {Token} The token to compare line to the next statement.
  64. */
  65. function getLastToken(node) {
  66. const lastToken = sourceCode.getLastToken(node);
  67. if (lastToken.type === "Punctuator" && lastToken.value === ";") {
  68. const prevToken = sourceCode.getTokenBefore(lastToken);
  69. if (prevToken.loc.end.line !== lastToken.loc.start.line) {
  70. return prevToken;
  71. }
  72. }
  73. return lastToken;
  74. }
  75. /**
  76. * Determine if provided keyword is a variable declaration
  77. * @private
  78. * @param {string} keyword - keyword to test
  79. * @returns {boolean} True if `keyword` is a type of var
  80. */
  81. function isVar(keyword) {
  82. return keyword === "var" || keyword === "let" || keyword === "const";
  83. }
  84. /**
  85. * Determine if provided keyword is a variant of for specifiers
  86. * @private
  87. * @param {string} keyword - keyword to test
  88. * @returns {boolean} True if `keyword` is a variant of for specifier
  89. */
  90. function isForTypeSpecifier(keyword) {
  91. return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
  92. }
  93. /**
  94. * Determine if provided keyword is an export specifiers
  95. * @private
  96. * @param {string} nodeType - nodeType to test
  97. * @returns {boolean} True if `nodeType` is an export specifier
  98. */
  99. function isExportSpecifier(nodeType) {
  100. return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" ||
  101. nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
  102. }
  103. /**
  104. * Determine if provided node is the last of their parent block.
  105. * @private
  106. * @param {ASTNode} node - node to test
  107. * @returns {boolean} True if `node` is last of their parent block.
  108. */
  109. function isLastNode(node) {
  110. const token = sourceCode.getTokenAfter(node);
  111. return !token || (token.type === "Punctuator" && token.value === "}");
  112. }
  113. /**
  114. * Gets the last line of a group of consecutive comments
  115. * @param {number} commentStartLine The starting line of the group
  116. * @returns {number} The number of the last comment line of the group
  117. */
  118. function getLastCommentLineOfBlock(commentStartLine) {
  119. const currentCommentEnd = commentEndLine[commentStartLine];
  120. return commentEndLine[currentCommentEnd + 1] ? getLastCommentLineOfBlock(currentCommentEnd + 1) : currentCommentEnd;
  121. }
  122. /**
  123. * Determine if a token starts more than one line after a comment ends
  124. * @param {token} token The token being checked
  125. * @param {integer} commentStartLine The line number on which the comment starts
  126. * @returns {boolean} True if `token` does not start immediately after a comment
  127. */
  128. function hasBlankLineAfterComment(token, commentStartLine) {
  129. return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1;
  130. }
  131. /**
  132. * Checks that a blank line exists after a variable declaration when mode is
  133. * set to "always", or checks that there is no blank line when mode is set
  134. * to "never"
  135. * @private
  136. * @param {ASTNode} node - `VariableDeclaration` node to test
  137. * @returns {void}
  138. */
  139. function checkForBlankLine(node) {
  140. /*
  141. * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will
  142. * sometimes be second-last if there is a semicolon on a different line.
  143. */
  144. const lastToken = getLastToken(node),
  145. /*
  146. * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken
  147. * is the last token of the node.
  148. */
  149. nextToken = lastToken === sourceCode.getLastToken(node) ? sourceCode.getTokenAfter(node) : sourceCode.getLastToken(node),
  150. nextLineNum = lastToken.loc.end.line + 1;
  151. // Ignore if there is no following statement
  152. if (!nextToken) {
  153. return;
  154. }
  155. // Ignore if parent of node is a for variant
  156. if (isForTypeSpecifier(node.parent.type)) {
  157. return;
  158. }
  159. // Ignore if parent of node is an export specifier
  160. if (isExportSpecifier(node.parent.type)) {
  161. return;
  162. }
  163. /*
  164. * Some coding styles use multiple `var` statements, so do nothing if
  165. * the next token is a `var` statement.
  166. */
  167. if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
  168. return;
  169. }
  170. // Ignore if it is last statement in a block
  171. if (isLastNode(node)) {
  172. return;
  173. }
  174. // Next statement is not a `var`...
  175. const noNextLineToken = nextToken.loc.start.line > nextLineNum;
  176. const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
  177. if (mode === "never" && noNextLineToken && !hasNextLineComment) {
  178. context.report({
  179. node,
  180. messageId: "unexpected",
  181. data: { identifier: node.name },
  182. fix(fixer) {
  183. const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER);
  184. return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`);
  185. }
  186. });
  187. }
  188. // Token on the next line, or comment without blank line
  189. if (
  190. mode === "always" && (
  191. !noNextLineToken ||
  192. hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
  193. )
  194. ) {
  195. context.report({
  196. node,
  197. messageId: "expected",
  198. data: { identifier: node.name },
  199. fix(fixer) {
  200. if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) {
  201. return fixer.insertTextBefore(nextToken, "\n\n");
  202. }
  203. return fixer.insertTextBeforeRange([nextToken.range[0] - nextToken.loc.start.column, nextToken.range[1]], "\n");
  204. }
  205. });
  206. }
  207. }
  208. //--------------------------------------------------------------------------
  209. // Public
  210. //--------------------------------------------------------------------------
  211. return {
  212. VariableDeclaration: checkForBlankLine
  213. };
  214. }
  215. };