max-lines.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * @fileoverview enforce a maximum file length
  3. * @author Alberto Rodríguez
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const lodash = require("lodash");
  10. const astUtils = require("../ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. module.exports = {
  15. meta: {
  16. docs: {
  17. description: "enforce a maximum number of lines per file",
  18. category: "Stylistic Issues",
  19. recommended: false,
  20. url: "https://eslint.org/docs/rules/max-lines"
  21. },
  22. schema: [
  23. {
  24. oneOf: [
  25. {
  26. type: "integer",
  27. minimum: 0
  28. },
  29. {
  30. type: "object",
  31. properties: {
  32. max: {
  33. type: "integer",
  34. minimum: 0
  35. },
  36. skipComments: {
  37. type: "boolean"
  38. },
  39. skipBlankLines: {
  40. type: "boolean"
  41. }
  42. },
  43. additionalProperties: false
  44. }
  45. ]
  46. }
  47. ]
  48. },
  49. create(context) {
  50. const option = context.options[0];
  51. let max = 300;
  52. if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
  53. max = option.max;
  54. }
  55. if (typeof option === "number") {
  56. max = option;
  57. }
  58. const skipComments = option && option.skipComments;
  59. const skipBlankLines = option && option.skipBlankLines;
  60. const sourceCode = context.getSourceCode();
  61. /**
  62. * Returns whether or not a token is a comment node type
  63. * @param {Token} token The token to check
  64. * @returns {boolean} True if the token is a comment node
  65. */
  66. function isCommentNodeType(token) {
  67. return token && (token.type === "Block" || token.type === "Line");
  68. }
  69. /**
  70. * Returns the line numbers of a comment that don't have any code on the same line
  71. * @param {Node} comment The comment node to check
  72. * @returns {int[]} The line numbers
  73. */
  74. function getLinesWithoutCode(comment) {
  75. let start = comment.loc.start.line;
  76. let end = comment.loc.end.line;
  77. let token;
  78. token = comment;
  79. do {
  80. token = sourceCode.getTokenBefore(token, { includeComments: true });
  81. } while (isCommentNodeType(token));
  82. if (token && astUtils.isTokenOnSameLine(token, comment)) {
  83. start += 1;
  84. }
  85. token = comment;
  86. do {
  87. token = sourceCode.getTokenAfter(token, { includeComments: true });
  88. } while (isCommentNodeType(token));
  89. if (token && astUtils.isTokenOnSameLine(comment, token)) {
  90. end -= 1;
  91. }
  92. if (start <= end) {
  93. return lodash.range(start, end + 1);
  94. }
  95. return [];
  96. }
  97. return {
  98. "Program:exit"() {
  99. let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text }));
  100. if (skipBlankLines) {
  101. lines = lines.filter(l => l.text.trim() !== "");
  102. }
  103. if (skipComments) {
  104. const comments = sourceCode.getAllComments();
  105. const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment)));
  106. lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber));
  107. }
  108. if (lines.length > max) {
  109. context.report({
  110. loc: { line: 1, column: 0 },
  111. message: "File must be at most {{max}} lines long. It's {{actual}} lines long.",
  112. data: {
  113. max,
  114. actual: lines.length
  115. }
  116. });
  117. }
  118. }
  119. };
  120. }
  121. };