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