UNPKG

7.12 kBJavaScriptView Raw
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("../ast-utils");
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 },
35 additionalProperties: false,
36 minProperties: 1
37 }
38 ]
39};
40
41/**
42 * Normalizes a given option value.
43 *
44 * @param {string|Object|undefined} value - An option value to parse.
45 * @returns {{multiline: boolean, minProperties: number}} Normalized option object.
46 */
47function normalizeOptionValue(value) {
48 let multiline = false;
49 let minProperties = Number.POSITIVE_INFINITY;
50
51 if (value) {
52 if (value === "always") {
53 minProperties = 0;
54 } else if (value === "never") {
55 minProperties = Number.POSITIVE_INFINITY;
56 } else {
57 multiline = Boolean(value.multiline);
58 minProperties = value.minProperties || Number.POSITIVE_INFINITY;
59 }
60 } else {
61 multiline = true;
62 }
63
64 return {multiline, minProperties};
65}
66
67/**
68 * Normalizes a given option value.
69 *
70 * @param {string|Object|undefined} options - An option value to parse.
71 * @returns {{ObjectExpression: {multiline: boolean, minProperties: number}, ObjectPattern: {multiline: boolean, minProperties: number}}} Normalized option object.
72 */
73function normalizeOptions(options) {
74 if (options && (options.ObjectExpression || options.ObjectPattern)) {
75 return {
76 ObjectExpression: normalizeOptionValue(options.ObjectExpression),
77 ObjectPattern: normalizeOptionValue(options.ObjectPattern)
78 };
79 }
80
81 const value = normalizeOptionValue(options);
82
83 return {ObjectExpression: value, ObjectPattern: value};
84}
85
86//------------------------------------------------------------------------------
87// Rule Definition
88//------------------------------------------------------------------------------
89
90module.exports = {
91 meta: {
92 docs: {
93 description: "enforce consistent line breaks inside braces",
94 category: "Stylistic Issues",
95 recommended: false
96 },
97 fixable: "whitespace",
98 schema: [
99 {
100 oneOf: [
101 OPTION_VALUE,
102 {
103 type: "object",
104 properties: {
105 ObjectExpression: OPTION_VALUE,
106 ObjectPattern: OPTION_VALUE
107 },
108 additionalProperties: false,
109 minProperties: 1
110 }
111 ]
112 }
113 ]
114 },
115
116 create(context) {
117 const sourceCode = context.getSourceCode();
118 const normalizedOptions = normalizeOptions(context.options[0]);
119
120 /**
121 * Reports a given node if it violated this rule.
122 *
123 * @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
124 * @param {{multiline: boolean, minProperties: number}} options - An option object.
125 * @returns {void}
126 */
127 function check(node) {
128 const options = normalizedOptions[node.type];
129 const openBrace = sourceCode.getFirstToken(node);
130 const closeBrace = sourceCode.getLastToken(node);
131 let first = sourceCode.getTokenOrCommentAfter(openBrace);
132 let last = sourceCode.getTokenOrCommentBefore(closeBrace);
133 const needsLinebreaks = (
134 node.properties.length >= options.minProperties ||
135 (
136 options.multiline &&
137 node.properties.length > 0 &&
138 first.loc.start.line !== last.loc.end.line
139 )
140 );
141
142 /*
143 * Use tokens or comments to check multiline or not.
144 * But use only tokens to check whether line breaks are needed.
145 * This allows:
146 * var obj = { // eslint-disable-line foo
147 * a: 1
148 * }
149 */
150 first = sourceCode.getTokenAfter(openBrace);
151 last = sourceCode.getTokenBefore(closeBrace);
152
153 if (needsLinebreaks) {
154 if (astUtils.isTokenOnSameLine(openBrace, first)) {
155 context.report({
156 message: "Expected a line break after this opening brace.",
157 node,
158 loc: openBrace.loc.start,
159 fix(fixer) {
160 return fixer.insertTextAfter(openBrace, "\n");
161 }
162 });
163 }
164 if (astUtils.isTokenOnSameLine(last, closeBrace)) {
165 context.report({
166 message: "Expected a line break before this closing brace.",
167 node,
168 loc: closeBrace.loc.start,
169 fix(fixer) {
170 return fixer.insertTextBefore(closeBrace, "\n");
171 }
172 });
173 }
174 } else {
175 if (!astUtils.isTokenOnSameLine(openBrace, first)) {
176 context.report({
177 message: "Unexpected line break after this opening brace.",
178 node,
179 loc: openBrace.loc.start,
180 fix(fixer) {
181 return fixer.removeRange([
182 openBrace.range[1],
183 first.range[0]
184 ]);
185 }
186 });
187 }
188 if (!astUtils.isTokenOnSameLine(last, closeBrace)) {
189 context.report({
190 message: "Unexpected line break before this closing brace.",
191 node,
192 loc: closeBrace.loc.start,
193 fix(fixer) {
194 return fixer.removeRange([
195 last.range[1],
196 closeBrace.range[0]
197 ]);
198 }
199 });
200 }
201 }
202 }
203
204 return {
205 ObjectExpression: check,
206 ObjectPattern: check
207 };
208 }
209};