]> git.proxmox.com Git - pve-eslint.git/blame - eslint/lib/rules/object-curly-newline.js
import 8.41.0 source
[pve-eslint.git] / eslint / lib / rules / object-curly-newline.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Rule to require or disallow line breaks inside braces.
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
eb39fafa
DC
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17
18// Schema objects.
19const OPTION_VALUE = {
20 oneOf: [
21 {
22 enum: ["always", "never"]
23 },
24 {
25 type: "object",
26 properties: {
27 multiline: {
28 type: "boolean"
29 },
30 minProperties: {
31 type: "integer",
32 minimum: 0
33 },
34 consistent: {
35 type: "boolean"
36 }
37 },
38 additionalProperties: false,
39 minProperties: 1
40 }
41 ]
42};
43
44/**
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.
48 */
49function normalizeOptionValue(value) {
50 let multiline = false;
51 let minProperties = Number.POSITIVE_INFINITY;
52 let consistent = false;
53
54 if (value) {
55 if (value === "always") {
56 minProperties = 0;
57 } else if (value === "never") {
58 minProperties = Number.POSITIVE_INFINITY;
59 } else {
60 multiline = Boolean(value.multiline);
61 minProperties = value.minProperties || Number.POSITIVE_INFINITY;
62 consistent = Boolean(value.consistent);
63 }
64 } else {
65 consistent = true;
66 }
67
68 return { multiline, minProperties, consistent };
69}
70
5422a9cc
TL
71/**
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`
75 */
76function isObject(value) {
77 return typeof value === "object" && value !== null;
78}
79
80/**
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`
84 */
85function isNodeSpecificOption(option) {
86 return isObject(option) || typeof option === "string";
87}
88
eb39fafa
DC
89/**
90 * Normalizes a given option value.
91 * @param {string|Object|undefined} options An option value to parse.
92 * @returns {{
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.
98 */
99function normalizeOptions(options) {
5422a9cc 100 if (isObject(options) && Object.values(options).some(isNodeSpecificOption)) {
eb39fafa
DC
101 return {
102 ObjectExpression: normalizeOptionValue(options.ObjectExpression),
103 ObjectPattern: normalizeOptionValue(options.ObjectPattern),
104 ImportDeclaration: normalizeOptionValue(options.ImportDeclaration),
105 ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration)
106 };
107 }
108
109 const value = normalizeOptionValue(options);
110
111 return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value };
112}
113
114/**
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
122 */
123function areLineBreaksRequired(node, options, first, last) {
124 let objectProperties;
125
126 if (node.type === "ObjectExpression" || node.type === "ObjectPattern") {
127 objectProperties = node.properties;
128 } else {
129
130 // is ImportDeclaration or ExportNamedDeclaration
131 objectProperties = node.specifiers
132 .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier");
133 }
134
135 return objectProperties.length >= options.minProperties ||
136 (
137 options.multiline &&
138 objectProperties.length > 0 &&
139 first.loc.start.line !== last.loc.end.line
140 );
141}
142
143//------------------------------------------------------------------------------
144// Rule Definition
145//------------------------------------------------------------------------------
146
34eeec05 147/** @type {import('../shared/types').Rule} */
eb39fafa
DC
148module.exports = {
149 meta: {
150 type: "layout",
151
152 docs: {
8f9d1d4d 153 description: "Enforce consistent line breaks after opening and before closing braces",
eb39fafa 154 recommended: false,
f2a92ac6 155 url: "https://eslint.org/docs/latest/rules/object-curly-newline"
eb39fafa
DC
156 },
157
158 fixable: "whitespace",
159
160 schema: [
161 {
162 oneOf: [
163 OPTION_VALUE,
164 {
165 type: "object",
166 properties: {
167 ObjectExpression: OPTION_VALUE,
168 ObjectPattern: OPTION_VALUE,
169 ImportDeclaration: OPTION_VALUE,
170 ExportDeclaration: OPTION_VALUE
171 },
172 additionalProperties: false,
173 minProperties: 1
174 }
175 ]
176 }
177 ],
178
179 messages: {
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."
184 }
185 },
186
187 create(context) {
f2a92ac6 188 const sourceCode = context.sourceCode;
eb39fafa
DC
189 const normalizedOptions = normalizeOptions(context.options[0]);
190
191 /**
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.
194 * @returns {void}
195 */
196 function check(node) {
197 const options = normalizedOptions[node.type];
198
199 if (
200 (node.type === "ImportDeclaration" &&
201 !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) ||
202 (node.type === "ExportNamedDeclaration" &&
203 !node.specifiers.some(specifier => specifier.type === "ExportSpecifier"))
204 ) {
205 return;
206 }
207
208 const openBrace = sourceCode.getFirstToken(node, token => token.value === "{");
209
210 let closeBrace;
211
212 if (node.typeAnnotation) {
213 closeBrace = sourceCode.getTokenBefore(node.typeAnnotation);
214 } else {
215 closeBrace = sourceCode.getLastToken(node, token => token.value === "}");
216 }
217
218 let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
219 let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
220
221 const needsLineBreaks = areLineBreaksRequired(node, options, first, last);
222
223 const hasCommentsFirstToken = astUtils.isCommentToken(first);
224 const hasCommentsLastToken = astUtils.isCommentToken(last);
225
226 /*
227 * Use tokens or comments to check multiline or not.
228 * But use only tokens to check whether line breaks are needed.
229 * This allows:
230 * var obj = { // eslint-disable-line foo
231 * a: 1
232 * }
233 */
234 first = sourceCode.getTokenAfter(openBrace);
235 last = sourceCode.getTokenBefore(closeBrace);
236
237 if (needsLineBreaks) {
238 if (astUtils.isTokenOnSameLine(openBrace, first)) {
239 context.report({
240 messageId: "expectedLinebreakAfterOpeningBrace",
241 node,
6f036462 242 loc: openBrace.loc,
eb39fafa
DC
243 fix(fixer) {
244 if (hasCommentsFirstToken) {
245 return null;
246 }
247
248 return fixer.insertTextAfter(openBrace, "\n");
249 }
250 });
251 }
252 if (astUtils.isTokenOnSameLine(last, closeBrace)) {
253 context.report({
254 messageId: "expectedLinebreakBeforeClosingBrace",
255 node,
6f036462 256 loc: closeBrace.loc,
eb39fafa
DC
257 fix(fixer) {
258 if (hasCommentsLastToken) {
259 return null;
260 }
261
262 return fixer.insertTextBefore(closeBrace, "\n");
263 }
264 });
265 }
266 } else {
267 const consistent = options.consistent;
268 const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first);
269 const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace);
270
271 if (
272 (!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
273 (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast)
274 ) {
275 context.report({
276 messageId: "unexpectedLinebreakAfterOpeningBrace",
277 node,
6f036462 278 loc: openBrace.loc,
eb39fafa
DC
279 fix(fixer) {
280 if (hasCommentsFirstToken) {
281 return null;
282 }
283
284 return fixer.removeRange([
285 openBrace.range[1],
286 first.range[0]
287 ]);
288 }
289 });
290 }
291 if (
292 (!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
293 (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast)
294 ) {
295 context.report({
296 messageId: "unexpectedLinebreakBeforeClosingBrace",
297 node,
6f036462 298 loc: closeBrace.loc,
eb39fafa
DC
299 fix(fixer) {
300 if (hasCommentsLastToken) {
301 return null;
302 }
303
304 return fixer.removeRange([
305 last.range[1],
306 closeBrace.range[0]
307 ]);
308 }
309 });
310 }
311 }
312 }
313
314 return {
315 ObjectExpression: check,
316 ObjectPattern: check,
317 ImportDeclaration: check,
318 ExportNamedDeclaration: check
319 };
320 }
321};