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