2 * @fileoverview Rule to require or disallow line breaks inside braces.
3 * @author Toru Nagashima
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils
= require("./utils/ast-utils");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
19 const OPTION_VALUE
= {
22 enum: ["always", "never"]
38 additionalProperties
: false,
45 * Normalizes a given option value.
46 * @param {string|Object|undefined} value An option value to parse.
47 * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
49 function normalizeOptionValue(value
) {
50 let multiline
= false;
51 let minProperties
= Number
.POSITIVE_INFINITY
;
52 let consistent
= false;
55 if (value
=== "always") {
57 } else if (value
=== "never") {
58 minProperties
= Number
.POSITIVE_INFINITY
;
60 multiline
= Boolean(value
.multiline
);
61 minProperties
= value
.minProperties
|| Number
.POSITIVE_INFINITY
;
62 consistent
= Boolean(value
.consistent
);
68 return { multiline
, minProperties
, consistent
};
72 * Checks if a value is an object.
73 * @param {any} value The value to check
74 * @returns {boolean} `true` if the value is an object, otherwise `false`
76 function isObject(value
) {
77 return typeof value
=== "object" && value
!== null;
81 * Checks if an option is a node-specific option
82 * @param {any} option The option to check
83 * @returns {boolean} `true` if the option is node-specific, otherwise `false`
85 function isNodeSpecificOption(option
) {
86 return isObject(option
) || typeof option
=== "string";
90 * Normalizes a given option value.
91 * @param {string|Object|undefined} options An option value to parse.
93 * ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean},
94 * ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean},
95 * ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean},
96 * ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean}
97 * }} Normalized option object.
99 function normalizeOptions(options
) {
100 if (isObject(options
) && Object
.values(options
).some(isNodeSpecificOption
)) {
102 ObjectExpression
: normalizeOptionValue(options
.ObjectExpression
),
103 ObjectPattern
: normalizeOptionValue(options
.ObjectPattern
),
104 ImportDeclaration
: normalizeOptionValue(options
.ImportDeclaration
),
105 ExportNamedDeclaration
: normalizeOptionValue(options
.ExportDeclaration
)
109 const value
= normalizeOptionValue(options
);
111 return { ObjectExpression
: value
, ObjectPattern
: value
, ImportDeclaration
: value
, ExportNamedDeclaration
: value
};
115 * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration
116 * node needs to be checked for missing line breaks
117 * @param {ASTNode} node Node under inspection
118 * @param {Object} options option specific to node type
119 * @param {Token} first First object property
120 * @param {Token} last Last object property
121 * @returns {boolean} `true` if node needs to be checked for missing line breaks
123 function areLineBreaksRequired(node
, options
, first
, last
) {
124 let objectProperties
;
126 if (node
.type
=== "ObjectExpression" || node
.type
=== "ObjectPattern") {
127 objectProperties
= node
.properties
;
130 // is ImportDeclaration or ExportNamedDeclaration
131 objectProperties
= node
.specifiers
132 .filter(s
=> s
.type
=== "ImportSpecifier" || s
.type
=== "ExportSpecifier");
135 return objectProperties
.length
>= options
.minProperties
||
138 objectProperties
.length
> 0 &&
139 first
.loc
.start
.line
!== last
.loc
.end
.line
143 //------------------------------------------------------------------------------
145 //------------------------------------------------------------------------------
147 /** @type {import('../shared/types').Rule} */
153 description
: "Enforce consistent line breaks after opening and before closing braces",
155 url
: "https://eslint.org/docs/rules/object-curly-newline"
158 fixable
: "whitespace",
167 ObjectExpression
: OPTION_VALUE
,
168 ObjectPattern
: OPTION_VALUE
,
169 ImportDeclaration
: OPTION_VALUE
,
170 ExportDeclaration
: OPTION_VALUE
172 additionalProperties
: false,
180 unexpectedLinebreakBeforeClosingBrace
: "Unexpected line break before this closing brace.",
181 unexpectedLinebreakAfterOpeningBrace
: "Unexpected line break after this opening brace.",
182 expectedLinebreakBeforeClosingBrace
: "Expected a line break before this closing brace.",
183 expectedLinebreakAfterOpeningBrace
: "Expected a line break after this opening brace."
188 const sourceCode
= context
.getSourceCode();
189 const normalizedOptions
= normalizeOptions(context
.options
[0]);
192 * Reports a given node if it violated this rule.
193 * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node.
196 function check(node
) {
197 const options
= normalizedOptions
[node
.type
];
200 (node
.type
=== "ImportDeclaration" &&
201 !node
.specifiers
.some(specifier
=> specifier
.type
=== "ImportSpecifier")) ||
202 (node
.type
=== "ExportNamedDeclaration" &&
203 !node
.specifiers
.some(specifier
=> specifier
.type
=== "ExportSpecifier"))
208 const openBrace
= sourceCode
.getFirstToken(node
, token
=> token
.value
=== "{");
212 if (node
.typeAnnotation
) {
213 closeBrace
= sourceCode
.getTokenBefore(node
.typeAnnotation
);
215 closeBrace
= sourceCode
.getLastToken(node
, token
=> token
.value
=== "}");
218 let first
= sourceCode
.getTokenAfter(openBrace
, { includeComments
: true });
219 let last
= sourceCode
.getTokenBefore(closeBrace
, { includeComments
: true });
221 const needsLineBreaks
= areLineBreaksRequired(node
, options
, first
, last
);
223 const hasCommentsFirstToken
= astUtils
.isCommentToken(first
);
224 const hasCommentsLastToken
= astUtils
.isCommentToken(last
);
227 * Use tokens or comments to check multiline or not.
228 * But use only tokens to check whether line breaks are needed.
230 * var obj = { // eslint-disable-line foo
234 first
= sourceCode
.getTokenAfter(openBrace
);
235 last
= sourceCode
.getTokenBefore(closeBrace
);
237 if (needsLineBreaks
) {
238 if (astUtils
.isTokenOnSameLine(openBrace
, first
)) {
240 messageId
: "expectedLinebreakAfterOpeningBrace",
244 if (hasCommentsFirstToken
) {
248 return fixer
.insertTextAfter(openBrace
, "\n");
252 if (astUtils
.isTokenOnSameLine(last
, closeBrace
)) {
254 messageId
: "expectedLinebreakBeforeClosingBrace",
258 if (hasCommentsLastToken
) {
262 return fixer
.insertTextBefore(closeBrace
, "\n");
267 const consistent
= options
.consistent
;
268 const hasLineBreakBetweenOpenBraceAndFirst
= !astUtils
.isTokenOnSameLine(openBrace
, first
);
269 const hasLineBreakBetweenCloseBraceAndLast
= !astUtils
.isTokenOnSameLine(last
, closeBrace
);
272 (!consistent
&& hasLineBreakBetweenOpenBraceAndFirst
) ||
273 (consistent
&& hasLineBreakBetweenOpenBraceAndFirst
&& !hasLineBreakBetweenCloseBraceAndLast
)
276 messageId
: "unexpectedLinebreakAfterOpeningBrace",
280 if (hasCommentsFirstToken
) {
284 return fixer
.removeRange([
292 (!consistent
&& hasLineBreakBetweenCloseBraceAndLast
) ||
293 (consistent
&& !hasLineBreakBetweenOpenBraceAndFirst
&& hasLineBreakBetweenCloseBraceAndLast
)
296 messageId
: "unexpectedLinebreakBeforeClosingBrace",
300 if (hasCommentsLastToken
) {
304 return fixer
.removeRange([
315 ObjectExpression
: check
,
316 ObjectPattern
: check
,
317 ImportDeclaration
: check
,
318 ExportNamedDeclaration
: check