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 //------------------------------------------------------------------------------
152 description: "enforce consistent line breaks after opening and before closing braces",
153 category: "Stylistic Issues",
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