UNPKG

9.62 kBJavaScriptView Raw
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"use strict";
7
8const astUtils = require("../ast-utils");
9
10//------------------------------------------------------------------------------
11// Rule Definition
12//------------------------------------------------------------------------------
13
14module.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};