UNPKG

8.13 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to enforce line breaks after each array element
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 line breaks after each array element",
18 category: "Stylistic Issues",
19 recommended: false
20 },
21 fixable: "whitespace",
22 schema: [
23 {
24 oneOf: [
25 {
26 enum: ["always", "never"]
27 },
28 {
29 type: "object",
30 properties: {
31 multiline: {
32 type: "boolean"
33 },
34 minItems: {
35 type: ["integer", "null"],
36 minimum: 0
37 }
38 },
39 additionalProperties: false
40 }
41 ]
42 }
43 ]
44 },
45
46 create(context) {
47 const sourceCode = context.getSourceCode();
48
49 //----------------------------------------------------------------------
50 // Helpers
51 //----------------------------------------------------------------------
52
53 /**
54 * Normalizes a given option value.
55 *
56 * @param {string|Object|undefined} option - An option value to parse.
57 * @returns {{multiline: boolean, minItems: number}} Normalized option object.
58 */
59 function normalizeOptionValue(option) {
60 let multiline = false;
61 let minItems;
62
63 option = option || "always";
64
65 if (option === "always" || option.minItems === 0) {
66 minItems = 0;
67 } else if (option === "never") {
68 minItems = Number.POSITIVE_INFINITY;
69 } else {
70 multiline = Boolean(option.multiline);
71 minItems = option.minItems || Number.POSITIVE_INFINITY;
72 }
73
74 return { multiline, minItems };
75 }
76
77 /**
78 * Normalizes a given option value.
79 *
80 * @param {string|Object|undefined} options - An option value to parse.
81 * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
82 */
83 function normalizeOptions(options) {
84 const value = normalizeOptionValue(options);
85
86 return { ArrayExpression: value, ArrayPattern: value };
87 }
88
89 /**
90 * Reports that there shouldn't be a line break after the first token
91 * @param {Token} token - The token to use for the report.
92 * @returns {void}
93 */
94 function reportNoLineBreak(token) {
95 const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
96
97 context.report({
98 loc: {
99 start: tokenBefore.loc.end,
100 end: token.loc.start
101 },
102 message: "There should be no linebreak here.",
103 fix(fixer) {
104 if (astUtils.isCommentToken(tokenBefore)) {
105 return null;
106 }
107
108 if (!astUtils.isTokenOnSameLine(tokenBefore, token)) {
109 return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ");
110 }
111
112 /*
113 * This will check if the comma is on the same line as the next element
114 * Following array:
115 * [
116 * 1
117 * , 2
118 * , 3
119 * ]
120 *
121 * will be fixed to:
122 * [
123 * 1, 2, 3
124 * ]
125 */
126 const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true });
127
128 if (astUtils.isCommentToken(twoTokensBefore)) {
129 return null;
130 }
131
132 return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], "");
133
134 }
135 });
136 }
137
138 /**
139 * Reports that there should be a line break after the first token
140 * @param {Token} token - The token to use for the report.
141 * @returns {void}
142 */
143 function reportRequiredLineBreak(token) {
144 const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
145
146 context.report({
147 loc: {
148 start: tokenBefore.loc.end,
149 end: token.loc.start
150 },
151 message: "There should be a linebreak after this element.",
152 fix(fixer) {
153 return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n");
154 }
155 });
156 }
157
158 /**
159 * Reports a given node if it violated this rule.
160 *
161 * @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
162 * @param {{multiline: boolean, minItems: number}} options - An option object.
163 * @returns {void}
164 */
165 function check(node) {
166 const elements = node.elements;
167 const normalizedOptions = normalizeOptions(context.options[0]);
168 const options = normalizedOptions[node.type];
169
170 let elementBreak = false;
171
172 /*
173 * MULTILINE: true
174 * loop through every element and check
175 * if at least one element has linebreaks inside
176 * this ensures that following is not valid (due to elements are on the same line):
177 *
178 * [
179 * 1,
180 * 2,
181 * 3
182 * ]
183 */
184 if (options.multiline) {
185 elementBreak = elements
186 .filter(element => element !== null)
187 .some(element => element.loc.start.line !== element.loc.end.line);
188 }
189
190 const needsLinebreaks = (
191 elements.length >= options.minItems ||
192 (
193 options.multiline &&
194 elementBreak
195 )
196 );
197
198 elements.forEach((element, i) => {
199 const previousElement = elements[i - 1];
200
201 if (i === 0 || element === null || previousElement === null) {
202 return;
203 }
204
205 const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
206 const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
207 const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
208
209 if (needsLinebreaks) {
210 if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
211 reportRequiredLineBreak(firstTokenOfCurrentElement);
212 }
213 } else {
214 if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
215 reportNoLineBreak(firstTokenOfCurrentElement);
216 }
217 }
218 });
219 }
220
221 //----------------------------------------------------------------------
222 // Public
223 //----------------------------------------------------------------------
224
225 return {
226 ArrayPattern: check,
227 ArrayExpression: check
228 };
229 }
230};