lines-around-directive.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /**
  2. * @fileoverview Require or disallow newlines around directives.
  3. * @author Kai Cataldo
  4. * @deprecated in ESLint v4.0.0
  5. */
  6. "use strict";
  7. const astUtils = require("./utils/ast-utils");
  8. //------------------------------------------------------------------------------
  9. // Rule Definition
  10. //------------------------------------------------------------------------------
  11. /** @type {import('../shared/types').Rule} */
  12. module.exports = {
  13. meta: {
  14. type: "layout",
  15. docs: {
  16. description: "Require or disallow newlines around directives",
  17. recommended: false,
  18. url: "https://eslint.org/docs/rules/lines-around-directive"
  19. },
  20. schema: [{
  21. oneOf: [
  22. {
  23. enum: ["always", "never"]
  24. },
  25. {
  26. type: "object",
  27. properties: {
  28. before: {
  29. enum: ["always", "never"]
  30. },
  31. after: {
  32. enum: ["always", "never"]
  33. }
  34. },
  35. additionalProperties: false,
  36. minProperties: 2
  37. }
  38. ]
  39. }],
  40. fixable: "whitespace",
  41. messages: {
  42. expected: "Expected newline {{location}} \"{{value}}\" directive.",
  43. unexpected: "Unexpected newline {{location}} \"{{value}}\" directive."
  44. },
  45. deprecated: true,
  46. replacedBy: ["padding-line-between-statements"]
  47. },
  48. create(context) {
  49. const sourceCode = context.getSourceCode();
  50. const config = context.options[0] || "always";
  51. const expectLineBefore = typeof config === "string" ? config : config.before;
  52. const expectLineAfter = typeof config === "string" ? config : config.after;
  53. //--------------------------------------------------------------------------
  54. // Helpers
  55. //--------------------------------------------------------------------------
  56. /**
  57. * Check if node is preceded by a blank newline.
  58. * @param {ASTNode} node Node to check.
  59. * @returns {boolean} Whether or not the passed in node is preceded by a blank newline.
  60. */
  61. function hasNewlineBefore(node) {
  62. const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
  63. const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0;
  64. return node.loc.start.line - tokenLineBefore >= 2;
  65. }
  66. /**
  67. * Gets the last token of a node that is on the same line as the rest of the node.
  68. * This will usually be the last token of the node, but it will be the second-to-last token if the node has a trailing
  69. * semicolon on a different line.
  70. * @param {ASTNode} node A directive node
  71. * @returns {Token} The last token of the node on the line
  72. */
  73. function getLastTokenOnLine(node) {
  74. const lastToken = sourceCode.getLastToken(node);
  75. const secondToLastToken = sourceCode.getTokenBefore(lastToken);
  76. return astUtils.isSemicolonToken(lastToken) && lastToken.loc.start.line > secondToLastToken.loc.end.line
  77. ? secondToLastToken
  78. : lastToken;
  79. }
  80. /**
  81. * Check if node is followed by a blank newline.
  82. * @param {ASTNode} node Node to check.
  83. * @returns {boolean} Whether or not the passed in node is followed by a blank newline.
  84. */
  85. function hasNewlineAfter(node) {
  86. const lastToken = getLastTokenOnLine(node);
  87. const tokenAfter = sourceCode.getTokenAfter(lastToken, { includeComments: true });
  88. return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
  89. }
  90. /**
  91. * Report errors for newlines around directives.
  92. * @param {ASTNode} node Node to check.
  93. * @param {string} location Whether the error was found before or after the directive.
  94. * @param {boolean} expected Whether or not a newline was expected or unexpected.
  95. * @returns {void}
  96. */
  97. function reportError(node, location, expected) {
  98. context.report({
  99. node,
  100. messageId: expected ? "expected" : "unexpected",
  101. data: {
  102. value: node.expression.value,
  103. location
  104. },
  105. fix(fixer) {
  106. const lastToken = getLastTokenOnLine(node);
  107. if (expected) {
  108. return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(lastToken, "\n");
  109. }
  110. return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [lastToken.range[1], lastToken.range[1] + 1]);
  111. }
  112. });
  113. }
  114. /**
  115. * Check lines around directives in node
  116. * @param {ASTNode} node node to check
  117. * @returns {void}
  118. */
  119. function checkDirectives(node) {
  120. const directives = astUtils.getDirectivePrologue(node);
  121. if (!directives.length) {
  122. return;
  123. }
  124. const firstDirective = directives[0];
  125. const leadingComments = sourceCode.getCommentsBefore(firstDirective);
  126. /*
  127. * Only check before the first directive if it is preceded by a comment or if it is at the top of
  128. * the file and expectLineBefore is set to "never". This is to not force a newline at the top of
  129. * the file if there are no comments as well as for compatibility with padded-blocks.
  130. */
  131. if (leadingComments.length) {
  132. if (expectLineBefore === "always" && !hasNewlineBefore(firstDirective)) {
  133. reportError(firstDirective, "before", true);
  134. }
  135. if (expectLineBefore === "never" && hasNewlineBefore(firstDirective)) {
  136. reportError(firstDirective, "before", false);
  137. }
  138. } else if (
  139. node.type === "Program" &&
  140. expectLineBefore === "never" &&
  141. !leadingComments.length &&
  142. hasNewlineBefore(firstDirective)
  143. ) {
  144. reportError(firstDirective, "before", false);
  145. }
  146. const lastDirective = directives[directives.length - 1];
  147. const statements = node.type === "Program" ? node.body : node.body.body;
  148. /*
  149. * Do not check after the last directive if the body only
  150. * contains a directive prologue and isn't followed by a comment to ensure
  151. * this rule behaves well with padded-blocks.
  152. */
  153. if (lastDirective === statements[statements.length - 1] && !lastDirective.trailingComments) {
  154. return;
  155. }
  156. if (expectLineAfter === "always" && !hasNewlineAfter(lastDirective)) {
  157. reportError(lastDirective, "after", true);
  158. }
  159. if (expectLineAfter === "never" && hasNewlineAfter(lastDirective)) {
  160. reportError(lastDirective, "after", false);
  161. }
  162. }
  163. //--------------------------------------------------------------------------
  164. // Public
  165. //--------------------------------------------------------------------------
  166. return {
  167. Program: checkDirectives,
  168. FunctionDeclaration: checkDirectives,
  169. FunctionExpression: checkDirectives,
  170. ArrowFunctionExpression: checkDirectives
  171. };
  172. }
  173. };