1 | /**
|
2 | * @fileoverview Rule to enforce linebreaks after open and before close array brackets
|
3 | * @author Jan Peer Stöcklmair <https://github.com/JPeer264>
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | const astUtils = require("../ast-utils");
|
9 |
|
10 | //------------------------------------------------------------------------------
|
11 | // Rule Definition
|
12 | //------------------------------------------------------------------------------
|
13 |
|
14 | module.exports = {
|
15 | meta: {
|
16 | docs: {
|
17 | description: "enforce linebreaks after opening and before closing array brackets",
|
18 | category: "Stylistic Issues",
|
19 | recommended: false,
|
20 | url: "https://eslint.org/docs/rules/array-bracket-newline"
|
21 | },
|
22 | fixable: "whitespace",
|
23 | schema: [
|
24 | {
|
25 | oneOf: [
|
26 | {
|
27 | enum: ["always", "never", "consistent"]
|
28 | },
|
29 | {
|
30 | type: "object",
|
31 | properties: {
|
32 | multiline: {
|
33 | type: "boolean"
|
34 | },
|
35 | minItems: {
|
36 | type: ["integer", "null"],
|
37 | minimum: 0
|
38 | }
|
39 | },
|
40 | additionalProperties: false
|
41 | }
|
42 | ]
|
43 | }
|
44 | ],
|
45 | messages: {
|
46 | unexpectedOpeningLinebreak: "There should be no linebreak after '['.",
|
47 | unexpectedClosingLinebreak: "There should be no linebreak before ']'.",
|
48 | missingOpeningLinebreak: "A linebreak is required after '['.",
|
49 | missingClosingLinebreak: "A linebreak is required before ']'."
|
50 | }
|
51 | },
|
52 |
|
53 | create(context) {
|
54 | const sourceCode = context.getSourceCode();
|
55 |
|
56 |
|
57 | //----------------------------------------------------------------------
|
58 | // Helpers
|
59 | //----------------------------------------------------------------------
|
60 |
|
61 | /**
|
62 | * Normalizes a given option value.
|
63 | *
|
64 | * @param {string|Object|undefined} option - An option value to parse.
|
65 | * @returns {{multiline: boolean, minItems: number}} Normalized option object.
|
66 | */
|
67 | function normalizeOptionValue(option) {
|
68 | let consistent = false;
|
69 | let multiline = false;
|
70 | let minItems = 0;
|
71 |
|
72 | if (option) {
|
73 | if (option === "consistent") {
|
74 | consistent = true;
|
75 | minItems = Number.POSITIVE_INFINITY;
|
76 | } else if (option === "always" || option.minItems === 0) {
|
77 | minItems = 0;
|
78 | } else if (option === "never") {
|
79 | minItems = Number.POSITIVE_INFINITY;
|
80 | } else {
|
81 | multiline = Boolean(option.multiline);
|
82 | minItems = option.minItems || Number.POSITIVE_INFINITY;
|
83 | }
|
84 | } else {
|
85 | consistent = false;
|
86 | multiline = true;
|
87 | minItems = Number.POSITIVE_INFINITY;
|
88 | }
|
89 |
|
90 | return { consistent, multiline, minItems };
|
91 | }
|
92 |
|
93 | /**
|
94 | * Normalizes a given option value.
|
95 | *
|
96 | * @param {string|Object|undefined} options - An option value to parse.
|
97 | * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
|
98 | */
|
99 | function normalizeOptions(options) {
|
100 | const value = normalizeOptionValue(options);
|
101 |
|
102 | return { ArrayExpression: value, ArrayPattern: value };
|
103 | }
|
104 |
|
105 | /**
|
106 | * Reports that there shouldn't be a linebreak after the first token
|
107 | * @param {ASTNode} node - The node to report in the event of an error.
|
108 | * @param {Token} token - The token to use for the report.
|
109 | * @returns {void}
|
110 | */
|
111 | function reportNoBeginningLinebreak(node, token) {
|
112 | context.report({
|
113 | node,
|
114 | loc: token.loc,
|
115 | messageId: "unexpectedOpeningLinebreak",
|
116 | fix(fixer) {
|
117 | const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
|
118 |
|
119 | if (astUtils.isCommentToken(nextToken)) {
|
120 | return null;
|
121 | }
|
122 |
|
123 | return fixer.removeRange([token.range[1], nextToken.range[0]]);
|
124 | }
|
125 | });
|
126 | }
|
127 |
|
128 | /**
|
129 | * Reports that there shouldn't be a linebreak before the last token
|
130 | * @param {ASTNode} node - The node to report in the event of an error.
|
131 | * @param {Token} token - The token to use for the report.
|
132 | * @returns {void}
|
133 | */
|
134 | function reportNoEndingLinebreak(node, token) {
|
135 | context.report({
|
136 | node,
|
137 | loc: token.loc,
|
138 | messageId: "unexpectedClosingLinebreak",
|
139 | fix(fixer) {
|
140 | const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
|
141 |
|
142 | if (astUtils.isCommentToken(previousToken)) {
|
143 | return null;
|
144 | }
|
145 |
|
146 | return fixer.removeRange([previousToken.range[1], token.range[0]]);
|
147 | }
|
148 | });
|
149 | }
|
150 |
|
151 | /**
|
152 | * Reports that there should be a linebreak after the first token
|
153 | * @param {ASTNode} node - The node to report in the event of an error.
|
154 | * @param {Token} token - The token to use for the report.
|
155 | * @returns {void}
|
156 | */
|
157 | function reportRequiredBeginningLinebreak(node, token) {
|
158 | context.report({
|
159 | node,
|
160 | loc: token.loc,
|
161 | messageId: "missingOpeningLinebreak",
|
162 | fix(fixer) {
|
163 | return fixer.insertTextAfter(token, "\n");
|
164 | }
|
165 | });
|
166 | }
|
167 |
|
168 | /**
|
169 | * Reports that there should be a linebreak before the last token
|
170 | * @param {ASTNode} node - The node to report in the event of an error.
|
171 | * @param {Token} token - The token to use for the report.
|
172 | * @returns {void}
|
173 | */
|
174 | function reportRequiredEndingLinebreak(node, token) {
|
175 | context.report({
|
176 | node,
|
177 | loc: token.loc,
|
178 | messageId: "missingClosingLinebreak",
|
179 | fix(fixer) {
|
180 | return fixer.insertTextBefore(token, "\n");
|
181 | }
|
182 | });
|
183 | }
|
184 |
|
185 | /**
|
186 | * Reports a given node if it violated this rule.
|
187 | *
|
188 | * @param {ASTNode} node - A node to check. This is an ArrayExpression node or an ArrayPattern node.
|
189 | * @returns {void}
|
190 | */
|
191 | function check(node) {
|
192 | const elements = node.elements;
|
193 | const normalizedOptions = normalizeOptions(context.options[0]);
|
194 | const options = normalizedOptions[node.type];
|
195 | const openBracket = sourceCode.getFirstToken(node);
|
196 | const closeBracket = sourceCode.getLastToken(node);
|
197 | const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true });
|
198 | const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true });
|
199 | const first = sourceCode.getTokenAfter(openBracket);
|
200 | const last = sourceCode.getTokenBefore(closeBracket);
|
201 |
|
202 | const needsLinebreaks = (
|
203 | elements.length >= options.minItems ||
|
204 | (
|
205 | options.multiline &&
|
206 | elements.length > 0 &&
|
207 | firstIncComment.loc.start.line !== lastIncComment.loc.end.line
|
208 | ) ||
|
209 | (
|
210 | elements.length === 0 &&
|
211 | firstIncComment.type === "Block" &&
|
212 | firstIncComment.loc.start.line !== lastIncComment.loc.end.line &&
|
213 | firstIncComment === lastIncComment
|
214 | ) ||
|
215 | (
|
216 | options.consistent &&
|
217 | firstIncComment.loc.start.line !== openBracket.loc.end.line
|
218 | )
|
219 | );
|
220 |
|
221 | /*
|
222 | * Use tokens or comments to check multiline or not.
|
223 | * But use only tokens to check whether linebreaks are needed.
|
224 | * This allows:
|
225 | * var arr = [ // eslint-disable-line foo
|
226 | * 'a'
|
227 | * ]
|
228 | */
|
229 |
|
230 | if (needsLinebreaks) {
|
231 | if (astUtils.isTokenOnSameLine(openBracket, first)) {
|
232 | reportRequiredBeginningLinebreak(node, openBracket);
|
233 | }
|
234 | if (astUtils.isTokenOnSameLine(last, closeBracket)) {
|
235 | reportRequiredEndingLinebreak(node, closeBracket);
|
236 | }
|
237 | } else {
|
238 | if (!astUtils.isTokenOnSameLine(openBracket, first)) {
|
239 | reportNoBeginningLinebreak(node, openBracket);
|
240 | }
|
241 | if (!astUtils.isTokenOnSameLine(last, closeBracket)) {
|
242 | reportNoEndingLinebreak(node, closeBracket);
|
243 | }
|
244 | }
|
245 | }
|
246 |
|
247 | //----------------------------------------------------------------------
|
248 | // Public
|
249 | //----------------------------------------------------------------------
|
250 |
|
251 | return {
|
252 | ArrayPattern: check,
|
253 | ArrayExpression: check
|
254 | };
|
255 | }
|
256 | };
|