2 * @fileoverview Rule to require or disallow line breaks inside braces.
3 * @author Toru Nagashima
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils
= require("./utils/ast-utils");
13 const lodash
= require("lodash");
15 //------------------------------------------------------------------------------
17 //------------------------------------------------------------------------------
20 const OPTION_VALUE
= {
23 enum: ["always", "never"]
39 additionalProperties
: false,
46 * Normalizes a given option value.
47 * @param {string|Object|undefined} value An option value to parse.
48 * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
50 function normalizeOptionValue(value
) {
51 let multiline
= false;
52 let minProperties
= Number
.POSITIVE_INFINITY
;
53 let consistent
= false;
56 if (value
=== "always") {
58 } else if (value
=== "never") {
59 minProperties
= Number
.POSITIVE_INFINITY
;
61 multiline
= Boolean(value
.multiline
);
62 minProperties
= value
.minProperties
|| Number
.POSITIVE_INFINITY
;
63 consistent
= Boolean(value
.consistent
);
69 return { multiline
, minProperties
, consistent
};
73 * Normalizes a given option value.
74 * @param {string|Object|undefined} options An option value to parse.
76 * ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean},
77 * ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean},
78 * ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean},
79 * ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean}
80 * }} Normalized option object.
82 function normalizeOptions(options
) {
83 const isNodeSpecificOption
= lodash
.overSome([lodash
.isPlainObject
, lodash
.isString
]);
85 if (lodash
.isPlainObject(options
) && lodash
.some(options
, isNodeSpecificOption
)) {
87 ObjectExpression
: normalizeOptionValue(options
.ObjectExpression
),
88 ObjectPattern
: normalizeOptionValue(options
.ObjectPattern
),
89 ImportDeclaration
: normalizeOptionValue(options
.ImportDeclaration
),
90 ExportNamedDeclaration
: normalizeOptionValue(options
.ExportDeclaration
)
94 const value
= normalizeOptionValue(options
);
96 return { ObjectExpression
: value
, ObjectPattern
: value
, ImportDeclaration
: value
, ExportNamedDeclaration
: value
};
100 * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration
101 * node needs to be checked for missing line breaks
102 * @param {ASTNode} node Node under inspection
103 * @param {Object} options option specific to node type
104 * @param {Token} first First object property
105 * @param {Token} last Last object property
106 * @returns {boolean} `true` if node needs to be checked for missing line breaks
108 function areLineBreaksRequired(node
, options
, first
, last
) {
109 let objectProperties
;
111 if (node
.type
=== "ObjectExpression" || node
.type
=== "ObjectPattern") {
112 objectProperties
= node
.properties
;
115 // is ImportDeclaration or ExportNamedDeclaration
116 objectProperties
= node
.specifiers
117 .filter(s
=> s
.type
=== "ImportSpecifier" || s
.type
=== "ExportSpecifier");
120 return objectProperties
.length
>= options
.minProperties
||
123 objectProperties
.length
> 0 &&
124 first
.loc
.start
.line
!== last
.loc
.end
.line
128 //------------------------------------------------------------------------------
130 //------------------------------------------------------------------------------
137 description
: "enforce consistent line breaks inside braces",
138 category
: "Stylistic Issues",
140 url
: "https://eslint.org/docs/rules/object-curly-newline"
143 fixable
: "whitespace",
152 ObjectExpression
: OPTION_VALUE
,
153 ObjectPattern
: OPTION_VALUE
,
154 ImportDeclaration
: OPTION_VALUE
,
155 ExportDeclaration
: OPTION_VALUE
157 additionalProperties
: false,
165 unexpectedLinebreakBeforeClosingBrace
: "Unexpected line break before this closing brace.",
166 unexpectedLinebreakAfterOpeningBrace
: "Unexpected line break after this opening brace.",
167 expectedLinebreakBeforeClosingBrace
: "Expected a line break before this closing brace.",
168 expectedLinebreakAfterOpeningBrace
: "Expected a line break after this opening brace."
173 const sourceCode
= context
.getSourceCode();
174 const normalizedOptions
= normalizeOptions(context
.options
[0]);
177 * Reports a given node if it violated this rule.
178 * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node.
181 function check(node
) {
182 const options
= normalizedOptions
[node
.type
];
185 (node
.type
=== "ImportDeclaration" &&
186 !node
.specifiers
.some(specifier
=> specifier
.type
=== "ImportSpecifier")) ||
187 (node
.type
=== "ExportNamedDeclaration" &&
188 !node
.specifiers
.some(specifier
=> specifier
.type
=== "ExportSpecifier"))
193 const openBrace
= sourceCode
.getFirstToken(node
, token
=> token
.value
=== "{");
197 if (node
.typeAnnotation
) {
198 closeBrace
= sourceCode
.getTokenBefore(node
.typeAnnotation
);
200 closeBrace
= sourceCode
.getLastToken(node
, token
=> token
.value
=== "}");
203 let first
= sourceCode
.getTokenAfter(openBrace
, { includeComments
: true });
204 let last
= sourceCode
.getTokenBefore(closeBrace
, { includeComments
: true });
206 const needsLineBreaks
= areLineBreaksRequired(node
, options
, first
, last
);
208 const hasCommentsFirstToken
= astUtils
.isCommentToken(first
);
209 const hasCommentsLastToken
= astUtils
.isCommentToken(last
);
212 * Use tokens or comments to check multiline or not.
213 * But use only tokens to check whether line breaks are needed.
215 * var obj = { // eslint-disable-line foo
219 first
= sourceCode
.getTokenAfter(openBrace
);
220 last
= sourceCode
.getTokenBefore(closeBrace
);
222 if (needsLineBreaks
) {
223 if (astUtils
.isTokenOnSameLine(openBrace
, first
)) {
225 messageId
: "expectedLinebreakAfterOpeningBrace",
229 if (hasCommentsFirstToken
) {
233 return fixer
.insertTextAfter(openBrace
, "\n");
237 if (astUtils
.isTokenOnSameLine(last
, closeBrace
)) {
239 messageId
: "expectedLinebreakBeforeClosingBrace",
243 if (hasCommentsLastToken
) {
247 return fixer
.insertTextBefore(closeBrace
, "\n");
252 const consistent
= options
.consistent
;
253 const hasLineBreakBetweenOpenBraceAndFirst
= !astUtils
.isTokenOnSameLine(openBrace
, first
);
254 const hasLineBreakBetweenCloseBraceAndLast
= !astUtils
.isTokenOnSameLine(last
, closeBrace
);
257 (!consistent
&& hasLineBreakBetweenOpenBraceAndFirst
) ||
258 (consistent
&& hasLineBreakBetweenOpenBraceAndFirst
&& !hasLineBreakBetweenCloseBraceAndLast
)
261 messageId
: "unexpectedLinebreakAfterOpeningBrace",
265 if (hasCommentsFirstToken
) {
269 return fixer
.removeRange([
277 (!consistent
&& hasLineBreakBetweenCloseBraceAndLast
) ||
278 (consistent
&& !hasLineBreakBetweenOpenBraceAndFirst
&& hasLineBreakBetweenCloseBraceAndLast
)
281 messageId
: "unexpectedLinebreakBeforeClosingBrace",
285 if (hasCommentsLastToken
) {
289 return fixer
.removeRange([
300 ObjectExpression
: check
,
301 ObjectPattern
: check
,
302 ImportDeclaration
: check
,
303 ExportNamedDeclaration
: check