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