.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / object-curly-newline.js
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
12 const astUtils = require("./utils/ast-utils");
13 const lodash = require("lodash");
14
15 //------------------------------------------------------------------------------
16 // Helpers
17 //------------------------------------------------------------------------------
18
19 // Schema objects.
20 const OPTION_VALUE = {
21     oneOf: [
22         {
23             enum: ["always", "never"]
24         },
25         {
26             type: "object",
27             properties: {
28                 multiline: {
29                     type: "boolean"
30                 },
31                 minProperties: {
32                     type: "integer",
33                     minimum: 0
34                 },
35                 consistent: {
36                     type: "boolean"
37                 }
38             },
39             additionalProperties: false,
40             minProperties: 1
41         }
42     ]
43 };
44
45 /**
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.
49  */
50 function normalizeOptionValue(value) {
51     let multiline = false;
52     let minProperties = Number.POSITIVE_INFINITY;
53     let consistent = false;
54
55     if (value) {
56         if (value === "always") {
57             minProperties = 0;
58         } else if (value === "never") {
59             minProperties = Number.POSITIVE_INFINITY;
60         } else {
61             multiline = Boolean(value.multiline);
62             minProperties = value.minProperties || Number.POSITIVE_INFINITY;
63             consistent = Boolean(value.consistent);
64         }
65     } else {
66         consistent = true;
67     }
68
69     return { multiline, minProperties, consistent };
70 }
71
72 /**
73  * Normalizes a given option value.
74  * @param {string|Object|undefined} options An option value to parse.
75  * @returns {{
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.
81  */
82 function normalizeOptions(options) {
83     const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]);
84
85     if (lodash.isPlainObject(options) && Object.values(options).some(isNodeSpecificOption)) {
86         return {
87             ObjectExpression: normalizeOptionValue(options.ObjectExpression),
88             ObjectPattern: normalizeOptionValue(options.ObjectPattern),
89             ImportDeclaration: normalizeOptionValue(options.ImportDeclaration),
90             ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration)
91         };
92     }
93
94     const value = normalizeOptionValue(options);
95
96     return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value };
97 }
98
99 /**
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
107  */
108 function areLineBreaksRequired(node, options, first, last) {
109     let objectProperties;
110
111     if (node.type === "ObjectExpression" || node.type === "ObjectPattern") {
112         objectProperties = node.properties;
113     } else {
114
115         // is ImportDeclaration or ExportNamedDeclaration
116         objectProperties = node.specifiers
117             .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier");
118     }
119
120     return objectProperties.length >= options.minProperties ||
121         (
122             options.multiline &&
123             objectProperties.length > 0 &&
124             first.loc.start.line !== last.loc.end.line
125         );
126 }
127
128 //------------------------------------------------------------------------------
129 // Rule Definition
130 //------------------------------------------------------------------------------
131
132 module.exports = {
133     meta: {
134         type: "layout",
135
136         docs: {
137             description: "enforce consistent line breaks after opening and before closing braces",
138             category: "Stylistic Issues",
139             recommended: false,
140             url: "https://eslint.org/docs/rules/object-curly-newline"
141         },
142
143         fixable: "whitespace",
144
145         schema: [
146             {
147                 oneOf: [
148                     OPTION_VALUE,
149                     {
150                         type: "object",
151                         properties: {
152                             ObjectExpression: OPTION_VALUE,
153                             ObjectPattern: OPTION_VALUE,
154                             ImportDeclaration: OPTION_VALUE,
155                             ExportDeclaration: OPTION_VALUE
156                         },
157                         additionalProperties: false,
158                         minProperties: 1
159                     }
160                 ]
161             }
162         ],
163
164         messages: {
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."
169         }
170     },
171
172     create(context) {
173         const sourceCode = context.getSourceCode();
174         const normalizedOptions = normalizeOptions(context.options[0]);
175
176         /**
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.
179          * @returns {void}
180          */
181         function check(node) {
182             const options = normalizedOptions[node.type];
183
184             if (
185                 (node.type === "ImportDeclaration" &&
186                     !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) ||
187                 (node.type === "ExportNamedDeclaration" &&
188                     !node.specifiers.some(specifier => specifier.type === "ExportSpecifier"))
189             ) {
190                 return;
191             }
192
193             const openBrace = sourceCode.getFirstToken(node, token => token.value === "{");
194
195             let closeBrace;
196
197             if (node.typeAnnotation) {
198                 closeBrace = sourceCode.getTokenBefore(node.typeAnnotation);
199             } else {
200                 closeBrace = sourceCode.getLastToken(node, token => token.value === "}");
201             }
202
203             let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
204             let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
205
206             const needsLineBreaks = areLineBreaksRequired(node, options, first, last);
207
208             const hasCommentsFirstToken = astUtils.isCommentToken(first);
209             const hasCommentsLastToken = astUtils.isCommentToken(last);
210
211             /*
212              * Use tokens or comments to check multiline or not.
213              * But use only tokens to check whether line breaks are needed.
214              * This allows:
215              *     var obj = { // eslint-disable-line foo
216              *         a: 1
217              *     }
218              */
219             first = sourceCode.getTokenAfter(openBrace);
220             last = sourceCode.getTokenBefore(closeBrace);
221
222             if (needsLineBreaks) {
223                 if (astUtils.isTokenOnSameLine(openBrace, first)) {
224                     context.report({
225                         messageId: "expectedLinebreakAfterOpeningBrace",
226                         node,
227                         loc: openBrace.loc,
228                         fix(fixer) {
229                             if (hasCommentsFirstToken) {
230                                 return null;
231                             }
232
233                             return fixer.insertTextAfter(openBrace, "\n");
234                         }
235                     });
236                 }
237                 if (astUtils.isTokenOnSameLine(last, closeBrace)) {
238                     context.report({
239                         messageId: "expectedLinebreakBeforeClosingBrace",
240                         node,
241                         loc: closeBrace.loc,
242                         fix(fixer) {
243                             if (hasCommentsLastToken) {
244                                 return null;
245                             }
246
247                             return fixer.insertTextBefore(closeBrace, "\n");
248                         }
249                     });
250                 }
251             } else {
252                 const consistent = options.consistent;
253                 const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first);
254                 const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace);
255
256                 if (
257                     (!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
258                     (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast)
259                 ) {
260                     context.report({
261                         messageId: "unexpectedLinebreakAfterOpeningBrace",
262                         node,
263                         loc: openBrace.loc,
264                         fix(fixer) {
265                             if (hasCommentsFirstToken) {
266                                 return null;
267                             }
268
269                             return fixer.removeRange([
270                                 openBrace.range[1],
271                                 first.range[0]
272                             ]);
273                         }
274                     });
275                 }
276                 if (
277                     (!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
278                     (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast)
279                 ) {
280                     context.report({
281                         messageId: "unexpectedLinebreakBeforeClosingBrace",
282                         node,
283                         loc: closeBrace.loc,
284                         fix(fixer) {
285                             if (hasCommentsLastToken) {
286                                 return null;
287                             }
288
289                             return fixer.removeRange([
290                                 last.range[1],
291                                 closeBrace.range[0]
292                             ]);
293                         }
294                     });
295                 }
296             }
297         }
298
299         return {
300             ObjectExpression: check,
301             ObjectPattern: check,
302             ImportDeclaration: check,
303             ExportNamedDeclaration: check
304         };
305     }
306 };