1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const astUtils = require("../ast-utils");
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const 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 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | function 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 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | function 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 |
|
93 |
|
94 |
|
95 | module.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 |
|
127 |
|
128 |
|
129 |
|
130 |
|
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 |
|
151 |
|
152 |
|
153 |
|
154 |
|
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 | };
|