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",
20 url
: "https://eslint.org/docs/rules/object-curly-spacing"
23 fixable
: "whitespace",
27 enum: ["always", "never"]
39 additionalProperties
: false
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}}'."
52 const spaced
= context
.options
[0] === "always",
53 sourceCode
= context
.getSourceCode();
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.
62 function isOptionSet(option
) {
63 return context
.options
[1] ? context
.options
[1][option
] === !spaced
: false;
68 arraysInObjectsException
: isOptionSet("arraysInObjects"),
69 objectsInObjectsException
: isOptionSet("objectsInObjects")
72 //--------------------------------------------------------------------------
74 //--------------------------------------------------------------------------
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.
82 function reportNoBeginningSpace(node
, token
) {
83 const nextToken
= context
.getSourceCode().getTokenAfter(token
, { includeComments
: true });
87 loc
: { start
: token
.loc
.end
, end
: nextToken
.loc
.start
},
88 messageId
: "unexpectedSpaceAfter",
93 return fixer
.removeRange([token
.range
[1], nextToken
.range
[0]]);
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.
104 function reportNoEndingSpace(node
, token
) {
105 const previousToken
= context
.getSourceCode().getTokenBefore(token
, { includeComments
: true });
109 loc
: { start
: previousToken
.loc
.end
, end
: token
.loc
.start
},
110 messageId
: "unexpectedSpaceBefore",
115 return fixer
.removeRange([previousToken
.range
[1], token
.range
[0]]);
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.
126 function reportRequiredBeginningSpace(node
, token
) {
130 messageId
: "requireSpaceAfter",
135 return fixer
.insertTextAfter(token
, " ");
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.
146 function reportRequiredEndingSpace(node
, token
) {
150 messageId
: "requireSpaceBefore",
155 return fixer
.insertTextBefore(token
, " ");
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)
169 function validateBraceSpacing(node
, first
, second
, penultimate
, last
) {
170 if (astUtils
.isTokenOnSameLine(first
, second
)) {
171 const firstSpaced
= sourceCode
.isSpaceBetweenTokens(first
, second
);
173 if (options
.spaced
&& !firstSpaced
) {
174 reportRequiredBeginningSpace(node
, first
);
176 if (!options
.spaced
&& firstSpaced
&& second
.type
!== "Line") {
177 reportNoBeginningSpace(node
, first
);
181 if (astUtils
.isTokenOnSameLine(penultimate
, last
)) {
182 const shouldCheckPenultimate
= (
183 options
.arraysInObjectsException
&& astUtils
.isClosingBracketToken(penultimate
) ||
184 options
.objectsInObjectsException
&& astUtils
.isClosingBraceToken(penultimate
)
186 const penultimateType
= shouldCheckPenultimate
&& sourceCode
.getNodeByRangeIndex(penultimate
.range
[0]).type
;
188 const closingCurlyBraceMustBeSpaced
= (
189 options
.arraysInObjectsException
&& penultimateType
=== "ArrayExpression" ||
190 options
.objectsInObjectsException
&& (penultimateType
=== "ObjectExpression" || penultimateType
=== "ObjectPattern")
191 ) ? !options
.spaced
: options
.spaced
;
193 const lastSpaced
= sourceCode
.isSpaceBetweenTokens(penultimate
, last
);
195 if (closingCurlyBraceMustBeSpaced
&& !lastSpaced
) {
196 reportRequiredEndingSpace(node
, last
);
198 if (!closingCurlyBraceMustBeSpaced
&& lastSpaced
) {
199 reportNoEndingSpace(node
, last
);
205 * Gets '}' token of an object node.
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
210 * @param {ASTNode} node The node to get. This node is an
211 * ObjectExpression or an ObjectPattern. And this node has one or
213 * @returns {Token} '}' token.
215 function getClosingBraceOfObject(node
) {
216 const lastProperty
= node
.properties
[node
.properties
.length
- 1];
218 return sourceCode
.getTokenAfter(lastProperty
, astUtils
.isClosingBraceToken
);
222 * Reports a given object node if spacing in curly braces is invalid.
223 * @param {ASTNode} node An ObjectExpression or ObjectPattern node to check.
226 function checkForObject(node
) {
227 if (node
.properties
.length
=== 0) {
231 const first
= sourceCode
.getFirstToken(node
),
232 last
= getClosingBraceOfObject(node
),
233 second
= sourceCode
.getTokenAfter(first
, { includeComments
: true }),
234 penultimate
= sourceCode
.getTokenBefore(last
, { includeComments
: true });
236 validateBraceSpacing(node
, first
, second
, penultimate
, last
);
240 * Reports a given import node if spacing in curly braces is invalid.
241 * @param {ASTNode} node An ImportDeclaration node to check.
244 function checkForImport(node
) {
245 if (node
.specifiers
.length
=== 0) {
249 let firstSpecifier
= node
.specifiers
[0];
250 const lastSpecifier
= node
.specifiers
[node
.specifiers
.length
- 1];
252 if (lastSpecifier
.type
!== "ImportSpecifier") {
255 if (firstSpecifier
.type
!== "ImportSpecifier") {
256 firstSpecifier
= node
.specifiers
[1];
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 });
264 validateBraceSpacing(node
, first
, second
, penultimate
, last
);
268 * Reports a given export node if spacing in curly braces is invalid.
269 * @param {ASTNode} node An ExportNamedDeclaration node to check.
272 function checkForExport(node
) {
273 if (node
.specifiers
.length
=== 0) {
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 });
284 validateBraceSpacing(node
, first
, second
, penultimate
, last
);
287 //--------------------------------------------------------------------------
289 //--------------------------------------------------------------------------
294 ObjectPattern
: checkForObject
,
297 ObjectExpression
: checkForObject
,
299 // import {y} from 'x';
300 ImportDeclaration
: checkForImport
,
302 // export {name} from 'yo';
303 ExportNamedDeclaration
: checkForExport