UNPKG

8.63 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to require or disallow line breaks inside braces.
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("../ast-utils");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17
18// Schema objects.
19const OPTION_VALUE = {
20 oneOf: [
21 {
22 enum: ["always", "never"]
23 },
24 {
25 type: "object",
26 properties: {
27 multiline: {
28 type: "boolean"
29 },
30 minProperties: {
31 type: "integer",
32 minimum: 0
33 },
34 consistent: {
35 type: "boolean"
36 }
37 },
38 additionalProperties: false,
39 minProperties: 1
40 }
41 ]
42};
43
44/**
45 * Normalizes a given option value.
46 *
47 * @param {string|Object|undefined} value - An option value to parse.
48 * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
49 */
50function normalizeOptionValue(value) {
51 let multiline = false;
52 let minProperties = Number.POSITIVE_INFINITY;
53 let consistent = false;
54
55 if (value) {
56 if (value === "always") {
57 minProperties = 0;
58 } else if (value === "never") {
59 minProperties = Number.POSITIVE_INFINITY;
60 } else {
61 multiline = Boolean(value.multiline);
62 minProperties = value.minProperties || Number.POSITIVE_INFINITY;
63 consistent = Boolean(value.consistent);
64 }
65 } else {
66 multiline = true;
67 }
68
69 return { multiline, minProperties, consistent };
70}
71
72/**
73 * Normalizes a given option value.
74 *
75 * @param {string|Object|undefined} options - An option value to parse.
76 * @returns {{ObjectExpression: {multiline: boolean, minProperties: number}, ObjectPattern: {multiline: boolean, minProperties: number}}} Normalized option object.
77 */
78function normalizeOptions(options) {
79 if (options && (options.ObjectExpression || options.ObjectPattern)) {
80 return {
81 ObjectExpression: normalizeOptionValue(options.ObjectExpression),
82 ObjectPattern: normalizeOptionValue(options.ObjectPattern)
83 };
84 }
85
86 const value = normalizeOptionValue(options);
87
88 return { ObjectExpression: value, ObjectPattern: value };
89}
90
91//------------------------------------------------------------------------------
92// Rule Definition
93//------------------------------------------------------------------------------
94
95module.exports = {
96 meta: {
97 docs: {
98 description: "enforce consistent line breaks inside braces",
99 category: "Stylistic Issues",
100 recommended: false
101 },
102 fixable: "whitespace",
103 schema: [
104 {
105 oneOf: [
106 OPTION_VALUE,
107 {
108 type: "object",
109 properties: {
110 ObjectExpression: OPTION_VALUE,
111 ObjectPattern: OPTION_VALUE
112 },
113 additionalProperties: false,
114 minProperties: 1
115 }
116 ]
117 }
118 ]
119 },
120
121 create(context) {
122 const sourceCode = context.getSourceCode();
123 const normalizedOptions = normalizeOptions(context.options[0]);
124
125 /**
126 * Reports a given node if it violated this rule.
127 *
128 * @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
129 * @param {{multiline: boolean, minProperties: number}} options - An option object.
130 * @returns {void}
131 */
132 function check(node) {
133 const options = normalizedOptions[node.type];
134 const openBrace = sourceCode.getFirstToken(node);
135 const closeBrace = sourceCode.getLastToken(node);
136 let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
137 let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
138 const needsLinebreaks = (
139 node.properties.length >= options.minProperties ||
140 (
141 options.multiline &&
142 node.properties.length > 0 &&
143 first.loc.start.line !== last.loc.end.line
144 )
145 );
146 const hasCommentsFirstToken = astUtils.isCommentToken(first);
147 const hasCommentsLastToken = astUtils.isCommentToken(last);
148
149 /*
150 * Use tokens or comments to check multiline or not.
151 * But use only tokens to check whether line breaks are needed.
152 * This allows:
153 * var obj = { // eslint-disable-line foo
154 * a: 1
155 * }
156 */
157 first = sourceCode.getTokenAfter(openBrace);
158 last = sourceCode.getTokenBefore(closeBrace);
159
160 if (needsLinebreaks) {
161 if (astUtils.isTokenOnSameLine(openBrace, first)) {
162 context.report({
163 message: "Expected a line break after this opening brace.",
164 node,
165 loc: openBrace.loc.start,
166 fix(fixer) {
167 if (hasCommentsFirstToken) {
168 return null;
169 }
170
171 return fixer.insertTextAfter(openBrace, "\n");
172 }
173 });
174 }
175 if (astUtils.isTokenOnSameLine(last, closeBrace)) {
176 context.report({
177 message: "Expected a line break before this closing brace.",
178 node,
179 loc: closeBrace.loc.start,
180 fix(fixer) {
181 if (hasCommentsLastToken) {
182 return null;
183 }
184
185 return fixer.insertTextBefore(closeBrace, "\n");
186 }
187 });
188 }
189 } else {
190 const consistent = options.consistent;
191 const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first);
192 const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace);
193
194 if (
195 (!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
196 (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast)
197 ) {
198 context.report({
199 message: "Unexpected line break after this opening brace.",
200 node,
201 loc: openBrace.loc.start,
202 fix(fixer) {
203 if (hasCommentsFirstToken) {
204 return null;
205 }
206
207 return fixer.removeRange([
208 openBrace.range[1],
209 first.range[0]
210 ]);
211 }
212 });
213 }
214 if (
215 (!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
216 (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast)
217 ) {
218 context.report({
219 message: "Unexpected line break before this closing brace.",
220 node,
221 loc: closeBrace.loc.start,
222 fix(fixer) {
223 if (hasCommentsLastToken) {
224 return null;
225 }
226
227 return fixer.removeRange([
228 last.range[1],
229 closeBrace.range[0]
230 ]);
231 }
232 });
233 }
234 }
235 }
236
237 return {
238 ObjectExpression: check,
239 ObjectPattern: check
240 };
241 }
242};