newline-after-var.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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('../shared/types').Rule} */
  15. module.exports = {
  16. meta: {
  17. type: "layout",
  18. docs: {
  19. description: "Require or disallow an empty line after variable declarations",
  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. * @param {ASTNode} node The node to get.
  62. * @returns {Token} The token to compare line to the next statement.
  63. */
  64. function getLastToken(node) {
  65. const lastToken = sourceCode.getLastToken(node);
  66. if (lastToken.type === "Punctuator" && lastToken.value === ";") {
  67. const prevToken = sourceCode.getTokenBefore(lastToken);
  68. if (prevToken.loc.end.line !== lastToken.loc.start.line) {
  69. return prevToken;
  70. }
  71. }
  72. return lastToken;
  73. }
  74. /**
  75. * Determine if provided keyword is a variable declaration
  76. * @private
  77. * @param {string} keyword keyword to test
  78. * @returns {boolean} True if `keyword` is a type of var
  79. */
  80. function isVar(keyword) {
  81. return keyword === "var" || keyword === "let" || keyword === "const";
  82. }
  83. /**
  84. * Determine if provided keyword is a variant of for specifiers
  85. * @private
  86. * @param {string} keyword keyword to test
  87. * @returns {boolean} True if `keyword` is a variant of for specifier
  88. */
  89. function isForTypeSpecifier(keyword) {
  90. return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
  91. }
  92. /**
  93. * Determine if provided keyword is an export specifiers
  94. * @private
  95. * @param {string} nodeType nodeType to test
  96. * @returns {boolean} True if `nodeType` is an export specifier
  97. */
  98. function isExportSpecifier(nodeType) {
  99. return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" ||
  100. nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
  101. }
  102. /**
  103. * Determine if provided node is the last of their parent block.
  104. * @private
  105. * @param {ASTNode} node node to test
  106. * @returns {boolean} True if `node` is last of their parent block.
  107. */
  108. function isLastNode(node) {
  109. const token = sourceCode.getTokenAfter(node);
  110. return !token || (token.type === "Punctuator" && token.value === "}");
  111. }
  112. /**
  113. * Gets the last line of a group of consecutive comments
  114. * @param {number} commentStartLine The starting line of the group
  115. * @returns {number} The number of the last comment line of the group
  116. */
  117. function getLastCommentLineOfBlock(commentStartLine) {
  118. const currentCommentEnd = commentEndLine[commentStartLine];
  119. return commentEndLine[currentCommentEnd + 1] ? getLastCommentLineOfBlock(currentCommentEnd + 1) : currentCommentEnd;
  120. }
  121. /**
  122. * Determine if a token starts more than one line after a comment ends
  123. * @param {token} token The token being checked
  124. * @param {integer} commentStartLine The line number on which the comment starts
  125. * @returns {boolean} True if `token` does not start immediately after a comment
  126. */
  127. function hasBlankLineAfterComment(token, commentStartLine) {
  128. return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1;
  129. }
  130. /**
  131. * Checks that a blank line exists after a variable declaration when mode is
  132. * set to "always", or checks that there is no blank line when mode is set
  133. * to "never"
  134. * @private
  135. * @param {ASTNode} node `VariableDeclaration` node to test
  136. * @returns {void}
  137. */
  138. function checkForBlankLine(node) {
  139. /*
  140. * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will
  141. * sometimes be second-last if there is a semicolon on a different line.
  142. */
  143. const lastToken = getLastToken(node),
  144. /*
  145. * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken
  146. * is the last token of the node.
  147. */
  148. nextToken = lastToken === sourceCode.getLastToken(node) ? sourceCode.getTokenAfter(node) : sourceCode.getLastToken(node),
  149. nextLineNum = lastToken.loc.end.line + 1;
  150. // Ignore if there is no following statement
  151. if (!nextToken) {
  152. return;
  153. }
  154. // Ignore if parent of node is a for variant
  155. if (isForTypeSpecifier(node.parent.type)) {
  156. return;
  157. }
  158. // Ignore if parent of node is an export specifier
  159. if (isExportSpecifier(node.parent.type)) {
  160. return;
  161. }
  162. /*
  163. * Some coding styles use multiple `var` statements, so do nothing if
  164. * the next token is a `var` statement.
  165. */
  166. if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
  167. return;
  168. }
  169. // Ignore if it is last statement in a block
  170. if (isLastNode(node)) {
  171. return;
  172. }
  173. // Next statement is not a `var`...
  174. const noNextLineToken = nextToken.loc.start.line > nextLineNum;
  175. const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
  176. if (mode === "never" && noNextLineToken && !hasNextLineComment) {
  177. context.report({
  178. node,
  179. messageId: "unexpected",
  180. data: { identifier: node.name },
  181. fix(fixer) {
  182. const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER);
  183. return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`);
  184. }
  185. });
  186. }
  187. // Token on the next line, or comment without blank line
  188. if (
  189. mode === "always" && (
  190. !noNextLineToken ||
  191. hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
  192. )
  193. ) {
  194. context.report({
  195. node,
  196. messageId: "expected",
  197. data: { identifier: node.name },
  198. fix(fixer) {
  199. if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) {
  200. return fixer.insertTextBefore(nextToken, "\n\n");
  201. }
  202. return fixer.insertTextBeforeRange([nextToken.range[0] - nextToken.loc.start.column, nextToken.range[1]], "\n");
  203. }
  204. });
  205. }
  206. }
  207. //--------------------------------------------------------------------------
  208. // Public
  209. //--------------------------------------------------------------------------
  210. return {
  211. VariableDeclaration: checkForBlankLine
  212. };
  213. }
  214. };