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) && Object.values(options).some(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 after opening and before closing 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