]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/max-lines.js
import eslint 7.28.0
[pve-eslint.git] / eslint / lib / rules / max-lines.js
1 /**
2 * @fileoverview enforce a maximum file length
3 * @author Alberto Rodríguez
4 */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10
11 const astUtils = require("./utils/ast-utils");
12
13 //------------------------------------------------------------------------------
14 // Helpers
15 //------------------------------------------------------------------------------
16
17 /**
18 * Creates an array of numbers from `start` up to, but not including, `end`
19 * @param {number} start The start of the range
20 * @param {number} end The end of the range
21 * @returns {number[]} The range of numbers
22 */
23 function range(start, end) {
24 return [...Array(end - start).keys()].map(x => x + start);
25 }
26
27 //------------------------------------------------------------------------------
28 // Rule Definition
29 //------------------------------------------------------------------------------
30
31 module.exports = {
32 meta: {
33 type: "suggestion",
34
35 docs: {
36 description: "enforce a maximum number of lines per file",
37 category: "Stylistic Issues",
38 recommended: false,
39 url: "https://eslint.org/docs/rules/max-lines"
40 },
41
42 schema: [
43 {
44 oneOf: [
45 {
46 type: "integer",
47 minimum: 0
48 },
49 {
50 type: "object",
51 properties: {
52 max: {
53 type: "integer",
54 minimum: 0
55 },
56 skipComments: {
57 type: "boolean"
58 },
59 skipBlankLines: {
60 type: "boolean"
61 }
62 },
63 additionalProperties: false
64 }
65 ]
66 }
67 ],
68 messages: {
69 exceed:
70 "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
71 }
72 },
73
74 create(context) {
75 const option = context.options[0];
76 let max = 300;
77
78 if (
79 typeof option === "object" &&
80 Object.prototype.hasOwnProperty.call(option, "max")
81 ) {
82 max = option.max;
83 } else if (typeof option === "number") {
84 max = option;
85 }
86
87 const skipComments = option && option.skipComments;
88 const skipBlankLines = option && option.skipBlankLines;
89
90 const sourceCode = context.getSourceCode();
91
92 /**
93 * Returns whether or not a token is a comment node type
94 * @param {Token} token The token to check
95 * @returns {boolean} True if the token is a comment node
96 */
97 function isCommentNodeType(token) {
98 return token && (token.type === "Block" || token.type === "Line");
99 }
100
101 /**
102 * Returns the line numbers of a comment that don't have any code on the same line
103 * @param {Node} comment The comment node to check
104 * @returns {number[]} The line numbers
105 */
106 function getLinesWithoutCode(comment) {
107 let start = comment.loc.start.line;
108 let end = comment.loc.end.line;
109
110 let token;
111
112 token = comment;
113 do {
114 token = sourceCode.getTokenBefore(token, {
115 includeComments: true
116 });
117 } while (isCommentNodeType(token));
118
119 if (token && astUtils.isTokenOnSameLine(token, comment)) {
120 start += 1;
121 }
122
123 token = comment;
124 do {
125 token = sourceCode.getTokenAfter(token, {
126 includeComments: true
127 });
128 } while (isCommentNodeType(token));
129
130 if (token && astUtils.isTokenOnSameLine(comment, token)) {
131 end -= 1;
132 }
133
134 if (start <= end) {
135 return range(start, end + 1);
136 }
137 return [];
138 }
139
140 /**
141 * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
142 * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
143 * @param {any[]} array The array to process
144 * @param {Function} fn The function to use
145 * @returns {any[]} The result array
146 */
147 function flatMap(array, fn) {
148 const mapped = array.map(fn);
149 const flattened = [].concat(...mapped);
150
151 return flattened;
152 }
153
154 return {
155 "Program:exit"() {
156 let lines = sourceCode.lines.map((text, i) => ({
157 lineNumber: i + 1,
158 text
159 }));
160
161 /*
162 * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
163 * That isn't a real line, so we shouldn't count it.
164 */
165 if (lines.length > 1 && lines[lines.length - 1].text === "") {
166 lines.pop();
167 }
168
169 if (skipBlankLines) {
170 lines = lines.filter(l => l.text.trim() !== "");
171 }
172
173 if (skipComments) {
174 const comments = sourceCode.getAllComments();
175
176 const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment));
177
178 lines = lines.filter(
179 l => !commentLines.includes(l.lineNumber)
180 );
181 }
182
183 if (lines.length > max) {
184 const loc = {
185 start: {
186 line: lines[max].lineNumber,
187 column: 0
188 },
189 end: {
190 line: sourceCode.lines.length,
191 column: sourceCode.lines[sourceCode.lines.length - 1].length
192 }
193 };
194
195 context.report({
196 loc,
197 messageId: "exceed",
198 data: {
199 max,
200 actual: lines.length
201 }
202 });
203 }
204 }
205 };
206 }
207 };