]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/object-curly-spacing.js
import 8.4.0 source
[pve-eslint.git] / eslint / lib / rules / object-curly-spacing.js
1 /**
2 * @fileoverview Disallows or enforces spaces inside of object literals.
3 * @author Jamund Ferguson
4 */
5 "use strict";
6
7 const astUtils = require("./utils/ast-utils");
8
9 //------------------------------------------------------------------------------
10 // Rule Definition
11 //------------------------------------------------------------------------------
12
13 /** @type {import('../shared/types').Rule} */
14 module.exports = {
15 meta: {
16 type: "layout",
17
18 docs: {
19 description: "enforce consistent spacing inside braces",
20 recommended: false,
21 url: "https://eslint.org/docs/rules/object-curly-spacing"
22 },
23
24 fixable: "whitespace",
25
26 schema: [
27 {
28 enum: ["always", "never"]
29 },
30 {
31 type: "object",
32 properties: {
33 arraysInObjects: {
34 type: "boolean"
35 },
36 objectsInObjects: {
37 type: "boolean"
38 }
39 },
40 additionalProperties: false
41 }
42 ],
43
44 messages: {
45 requireSpaceBefore: "A space is required before '{{token}}'.",
46 requireSpaceAfter: "A space is required after '{{token}}'.",
47 unexpectedSpaceBefore: "There should be no space before '{{token}}'.",
48 unexpectedSpaceAfter: "There should be no space after '{{token}}'."
49 }
50 },
51
52 create(context) {
53 const spaced = context.options[0] === "always",
54 sourceCode = context.getSourceCode();
55
56 /**
57 * Determines whether an option is set, relative to the spacing option.
58 * If spaced is "always", then check whether option is set to false.
59 * If spaced is "never", then check whether option is set to true.
60 * @param {Object} option The option to exclude.
61 * @returns {boolean} Whether or not the property is excluded.
62 */
63 function isOptionSet(option) {
64 return context.options[1] ? context.options[1][option] === !spaced : false;
65 }
66
67 const options = {
68 spaced,
69 arraysInObjectsException: isOptionSet("arraysInObjects"),
70 objectsInObjectsException: isOptionSet("objectsInObjects")
71 };
72
73 //--------------------------------------------------------------------------
74 // Helpers
75 //--------------------------------------------------------------------------
76
77 /**
78 * Reports that there shouldn't be a space after the first token
79 * @param {ASTNode} node The node to report in the event of an error.
80 * @param {Token} token The token to use for the report.
81 * @returns {void}
82 */
83 function reportNoBeginningSpace(node, token) {
84 const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true });
85
86 context.report({
87 node,
88 loc: { start: token.loc.end, end: nextToken.loc.start },
89 messageId: "unexpectedSpaceAfter",
90 data: {
91 token: token.value
92 },
93 fix(fixer) {
94 return fixer.removeRange([token.range[1], nextToken.range[0]]);
95 }
96 });
97 }
98
99 /**
100 * Reports that there shouldn't be a space before the last token
101 * @param {ASTNode} node The node to report in the event of an error.
102 * @param {Token} token The token to use for the report.
103 * @returns {void}
104 */
105 function reportNoEndingSpace(node, token) {
106 const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true });
107
108 context.report({
109 node,
110 loc: { start: previousToken.loc.end, end: token.loc.start },
111 messageId: "unexpectedSpaceBefore",
112 data: {
113 token: token.value
114 },
115 fix(fixer) {
116 return fixer.removeRange([previousToken.range[1], token.range[0]]);
117 }
118 });
119 }
120
121 /**
122 * Reports that there should be a space after the first token
123 * @param {ASTNode} node The node to report in the event of an error.
124 * @param {Token} token The token to use for the report.
125 * @returns {void}
126 */
127 function reportRequiredBeginningSpace(node, token) {
128 context.report({
129 node,
130 loc: token.loc,
131 messageId: "requireSpaceAfter",
132 data: {
133 token: token.value
134 },
135 fix(fixer) {
136 return fixer.insertTextAfter(token, " ");
137 }
138 });
139 }
140
141 /**
142 * Reports that there should be a space before the last token
143 * @param {ASTNode} node The node to report in the event of an error.
144 * @param {Token} token The token to use for the report.
145 * @returns {void}
146 */
147 function reportRequiredEndingSpace(node, token) {
148 context.report({
149 node,
150 loc: token.loc,
151 messageId: "requireSpaceBefore",
152 data: {
153 token: token.value
154 },
155 fix(fixer) {
156 return fixer.insertTextBefore(token, " ");
157 }
158 });
159 }
160
161 /**
162 * Determines if spacing in curly braces is valid.
163 * @param {ASTNode} node The AST node to check.
164 * @param {Token} first The first token to check (should be the opening brace)
165 * @param {Token} second The second token to check (should be first after the opening brace)
166 * @param {Token} penultimate The penultimate token to check (should be last before closing brace)
167 * @param {Token} last The last token to check (should be closing brace)
168 * @returns {void}
169 */
170 function validateBraceSpacing(node, first, second, penultimate, last) {
171 if (astUtils.isTokenOnSameLine(first, second)) {
172 const firstSpaced = sourceCode.isSpaceBetweenTokens(first, second);
173
174 if (options.spaced && !firstSpaced) {
175 reportRequiredBeginningSpace(node, first);
176 }
177 if (!options.spaced && firstSpaced && second.type !== "Line") {
178 reportNoBeginningSpace(node, first);
179 }
180 }
181
182 if (astUtils.isTokenOnSameLine(penultimate, last)) {
183 const shouldCheckPenultimate = (
184 options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) ||
185 options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate)
186 );
187 const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.range[0]).type;
188
189 const closingCurlyBraceMustBeSpaced = (
190 options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
191 options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern")
192 ) ? !options.spaced : options.spaced;
193
194 const lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last);
195
196 if (closingCurlyBraceMustBeSpaced && !lastSpaced) {
197 reportRequiredEndingSpace(node, last);
198 }
199 if (!closingCurlyBraceMustBeSpaced && lastSpaced) {
200 reportNoEndingSpace(node, last);
201 }
202 }
203 }
204
205 /**
206 * Gets '}' token of an object node.
207 *
208 * Because the last token of object patterns might be a type annotation,
209 * this traverses tokens preceded by the last property, then returns the
210 * first '}' token.
211 * @param {ASTNode} node The node to get. This node is an
212 * ObjectExpression or an ObjectPattern. And this node has one or
213 * more properties.
214 * @returns {Token} '}' token.
215 */
216 function getClosingBraceOfObject(node) {
217 const lastProperty = node.properties[node.properties.length - 1];
218
219 return sourceCode.getTokenAfter(lastProperty, astUtils.isClosingBraceToken);
220 }
221
222 /**
223 * Reports a given object node if spacing in curly braces is invalid.
224 * @param {ASTNode} node An ObjectExpression or ObjectPattern node to check.
225 * @returns {void}
226 */
227 function checkForObject(node) {
228 if (node.properties.length === 0) {
229 return;
230 }
231
232 const first = sourceCode.getFirstToken(node),
233 last = getClosingBraceOfObject(node),
234 second = sourceCode.getTokenAfter(first, { includeComments: true }),
235 penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
236
237 validateBraceSpacing(node, first, second, penultimate, last);
238 }
239
240 /**
241 * Reports a given import node if spacing in curly braces is invalid.
242 * @param {ASTNode} node An ImportDeclaration node to check.
243 * @returns {void}
244 */
245 function checkForImport(node) {
246 if (node.specifiers.length === 0) {
247 return;
248 }
249
250 let firstSpecifier = node.specifiers[0];
251 const lastSpecifier = node.specifiers[node.specifiers.length - 1];
252
253 if (lastSpecifier.type !== "ImportSpecifier") {
254 return;
255 }
256 if (firstSpecifier.type !== "ImportSpecifier") {
257 firstSpecifier = node.specifiers[1];
258 }
259
260 const first = sourceCode.getTokenBefore(firstSpecifier),
261 last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken),
262 second = sourceCode.getTokenAfter(first, { includeComments: true }),
263 penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
264
265 validateBraceSpacing(node, first, second, penultimate, last);
266 }
267
268 /**
269 * Reports a given export node if spacing in curly braces is invalid.
270 * @param {ASTNode} node An ExportNamedDeclaration node to check.
271 * @returns {void}
272 */
273 function checkForExport(node) {
274 if (node.specifiers.length === 0) {
275 return;
276 }
277
278 const firstSpecifier = node.specifiers[0],
279 lastSpecifier = node.specifiers[node.specifiers.length - 1],
280 first = sourceCode.getTokenBefore(firstSpecifier),
281 last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken),
282 second = sourceCode.getTokenAfter(first, { includeComments: true }),
283 penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
284
285 validateBraceSpacing(node, first, second, penultimate, last);
286 }
287
288 //--------------------------------------------------------------------------
289 // Public
290 //--------------------------------------------------------------------------
291
292 return {
293
294 // var {x} = y;
295 ObjectPattern: checkForObject,
296
297 // var y = {x: 'y'}
298 ObjectExpression: checkForObject,
299
300 // import {y} from 'x';
301 ImportDeclaration: checkForImport,
302
303 // export {name} from 'yo';
304 ExportNamedDeclaration: checkForExport
305 };
306
307 }
308 };