2 * @fileoverview Disallows or enforces spaces inside of object literals.
3 * @author Jamund Ferguson
7 const astUtils = require("./utils/ast-utils");
9 //------------------------------------------------------------------------------
11 //------------------------------------------------------------------------------
18 description: "enforce consistent spacing inside braces",
19 category: "Stylistic Issues",
21 url: "https://eslint.org/docs/rules/object-curly-spacing"
24 fixable: "whitespace",
28 enum: ["always", "never"]
40 additionalProperties: false
46 const spaced = context.options[0] === "always",
47 sourceCode = context.getSourceCode();
50 * Determines whether an option is set, relative to the spacing option.
51 * If spaced is "always", then check whether option is set to false.
52 * If spaced is "never", then check whether option is set to true.
53 * @param {Object} option The option to exclude.
54 * @returns {boolean} Whether or not the property is excluded.
56 function isOptionSet(option) {
57 return context.options[1] ? context.options[1][option] === !spaced : false;
62 arraysInObjectsException: isOptionSet("arraysInObjects"),
63 objectsInObjectsException: isOptionSet("objectsInObjects")
66 //--------------------------------------------------------------------------
68 //--------------------------------------------------------------------------
71 * Reports that there shouldn't be a space after the first token
72 * @param {ASTNode} node The node to report in the event of an error.
73 * @param {Token} token The token to use for the report.
76 function reportNoBeginningSpace(node, token) {
77 const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true });
81 loc: { start: token.loc.end, end: nextToken.loc.start },
82 message: "There should be no space after '{{token}}'.",
87 return fixer.removeRange([token.range[1], nextToken.range[0]]);
93 * Reports that there shouldn't be a space before the last token
94 * @param {ASTNode} node The node to report in the event of an error.
95 * @param {Token} token The token to use for the report.
98 function reportNoEndingSpace(node, token) {
99 const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true });
103 loc: { start: previousToken.loc.end, end: token.loc.start },
104 message: "There should be no space before '{{token}}'.",
109 return fixer.removeRange([previousToken.range[1], token.range[0]]);
115 * Reports that there should be a space after the first token
116 * @param {ASTNode} node The node to report in the event of an error.
117 * @param {Token} token The token to use for the report.
120 function reportRequiredBeginningSpace(node, token) {
124 message: "A space is required after '{{token}}'.",
129 return fixer.insertTextAfter(token, " ");
135 * Reports that there should be a space before the last token
136 * @param {ASTNode} node The node to report in the event of an error.
137 * @param {Token} token The token to use for the report.
140 function reportRequiredEndingSpace(node, token) {
144 message: "A space is required before '{{token}}'.",
149 return fixer.insertTextBefore(token, " ");
155 * Determines if spacing in curly braces is valid.
156 * @param {ASTNode} node The AST node to check.
157 * @param {Token} first The first token to check (should be the opening brace)
158 * @param {Token} second The second token to check (should be first after the opening brace)
159 * @param {Token} penultimate The penultimate token to check (should be last before closing brace)
160 * @param {Token} last The last token to check (should be closing brace)
163 function validateBraceSpacing(node, first, second, penultimate, last) {
164 if (astUtils.isTokenOnSameLine(first, second)) {
165 const firstSpaced = sourceCode.isSpaceBetweenTokens(first, second);
167 if (options.spaced && !firstSpaced) {
168 reportRequiredBeginningSpace(node, first);
170 if (!options.spaced && firstSpaced && second.type !== "Line") {
171 reportNoBeginningSpace(node, first);
175 if (astUtils.isTokenOnSameLine(penultimate, last)) {
176 const shouldCheckPenultimate = (
177 options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) ||
178 options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate)
180 const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.range[0]).type;
182 const closingCurlyBraceMustBeSpaced = (
183 options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
184 options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern")
185 ) ? !options.spaced : options.spaced;
187 const lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last);
189 if (closingCurlyBraceMustBeSpaced && !lastSpaced) {
190 reportRequiredEndingSpace(node, last);
192 if (!closingCurlyBraceMustBeSpaced && lastSpaced) {
193 reportNoEndingSpace(node, last);
199 * Gets '}' token of an object node.
201 * Because the last token of object patterns might be a type annotation,
202 * this traverses tokens preceded by the last property, then returns the
204 * @param {ASTNode} node The node to get. This node is an
205 * ObjectExpression or an ObjectPattern. And this node has one or
207 * @returns {Token} '}' token.
209 function getClosingBraceOfObject(node) {
210 const lastProperty = node.properties[node.properties.length - 1];
212 return sourceCode.getTokenAfter(lastProperty, astUtils.isClosingBraceToken);
216 * Reports a given object node if spacing in curly braces is invalid.
217 * @param {ASTNode} node An ObjectExpression or ObjectPattern node to check.
220 function checkForObject(node) {
221 if (node.properties.length === 0) {
225 const first = sourceCode.getFirstToken(node),
226 last = getClosingBraceOfObject(node),
227 second = sourceCode.getTokenAfter(first, { includeComments: true }),
228 penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
230 validateBraceSpacing(node, first, second, penultimate, last);
234 * Reports a given import node if spacing in curly braces is invalid.
235 * @param {ASTNode} node An ImportDeclaration node to check.
238 function checkForImport(node) {
239 if (node.specifiers.length === 0) {
243 let firstSpecifier = node.specifiers[0];
244 const lastSpecifier = node.specifiers[node.specifiers.length - 1];
246 if (lastSpecifier.type !== "ImportSpecifier") {
249 if (firstSpecifier.type !== "ImportSpecifier") {
250 firstSpecifier = node.specifiers[1];
253 const first = sourceCode.getTokenBefore(firstSpecifier),
254 last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken),
255 second = sourceCode.getTokenAfter(first, { includeComments: true }),
256 penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
258 validateBraceSpacing(node, first, second, penultimate, last);
262 * Reports a given export node if spacing in curly braces is invalid.
263 * @param {ASTNode} node An ExportNamedDeclaration node to check.
266 function checkForExport(node) {
267 if (node.specifiers.length === 0) {
271 const firstSpecifier = node.specifiers[0],
272 lastSpecifier = node.specifiers[node.specifiers.length - 1],
273 first = sourceCode.getTokenBefore(firstSpecifier),
274 last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken),
275 second = sourceCode.getTokenAfter(first, { includeComments: true }),
276 penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
278 validateBraceSpacing(node, first, second, penultimate, last);
281 //--------------------------------------------------------------------------
283 //--------------------------------------------------------------------------
288 ObjectPattern: checkForObject,
291 ObjectExpression: checkForObject,
293 // import {y} from 'x';
294 ImportDeclaration: checkForImport,
296 // export {name} from 'yo';
297 ExportNamedDeclaration: checkForExport