UNPKG

10.2 kBJavaScriptView Raw
1/**
2 * @fileoverview Disallows or enforces spaces inside of object literals.
3 * @author Jamund Ferguson
4 * @copyright 2014 Brandyn Bennett. All rights reserved.
5 * @copyright 2014 Michael Ficarra. No rights reserved.
6 * @copyright 2014 Vignesh Anand. All rights reserved.
7 * @copyright 2015 Jamund Ferguson. All rights reserved.
8 * @copyright 2015 Mathieu M-Gosselin. All rights reserved.
9 * @copyright 2015 Toru Nagashima. All rights reserved.
10 * See LICENSE file in root directory for full license.
11 */
12"use strict";
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = function(context) {
19 var spaced = context.options[0] === "always",
20 sourceCode = context.getSourceCode();
21
22 /**
23 * Determines whether an option is set, relative to the spacing option.
24 * If spaced is "always", then check whether option is set to false.
25 * If spaced is "never", then check whether option is set to true.
26 * @param {Object} option - The option to exclude.
27 * @returns {boolean} Whether or not the property is excluded.
28 */
29 function isOptionSet(option) {
30 return context.options[1] != null ? context.options[1][option] === !spaced : false;
31 }
32
33 var options = {
34 spaced: spaced,
35 arraysInObjectsException: isOptionSet("arraysInObjects"),
36 objectsInObjectsException: isOptionSet("objectsInObjects")
37 };
38
39 //--------------------------------------------------------------------------
40 // Helpers
41 //--------------------------------------------------------------------------
42
43 /**
44 * Determines whether two adjacent tokens are have whitespace between them.
45 * @param {Object} left - The left token object.
46 * @param {Object} right - The right token object.
47 * @returns {boolean} Whether or not there is space between the tokens.
48 */
49 function isSpaced(left, right) {
50 return sourceCode.isSpaceBetweenTokens(left, right);
51 }
52
53 /**
54 * Determines whether two adjacent tokens are on the same line.
55 * @param {Object} left - The left token object.
56 * @param {Object} right - The right token object.
57 * @returns {boolean} Whether or not the tokens are on the same line.
58 */
59 function isSameLine(left, right) {
60 return left.loc.start.line === right.loc.start.line;
61 }
62
63 /**
64 * Reports that there shouldn't be a space after the first token
65 * @param {ASTNode} node - The node to report in the event of an error.
66 * @param {Token} token - The token to use for the report.
67 * @returns {void}
68 */
69 function reportNoBeginningSpace(node, token) {
70 context.report({
71 node: node,
72 loc: token.loc.end,
73 message: "There should be no space after '" + token.value + "'",
74 fix: function(fixer) {
75 var nextToken = sourceCode.getTokenAfter(token);
76 return fixer.removeRange([token.range[1], nextToken.range[0]]);
77 }
78 });
79 }
80
81 /**
82 * Reports that there shouldn't be a space before the last token
83 * @param {ASTNode} node - The node to report in the event of an error.
84 * @param {Token} token - The token to use for the report.
85 * @returns {void}
86 */
87 function reportNoEndingSpace(node, token) {
88 context.report({
89 node: node,
90 loc: token.loc.start,
91 message: "There should be no space before '" + token.value + "'",
92 fix: function(fixer) {
93 var previousToken = sourceCode.getTokenBefore(token);
94 return fixer.removeRange([previousToken.range[1], token.range[0]]);
95 }
96 });
97 }
98
99 /**
100 * Reports that there should be a space after the first token
101 * @param {ASTNode} node - The node to report in the event of an error.
102 * @param {Token} token - The token to use for the report.
103 * @returns {void}
104 */
105 function reportRequiredBeginningSpace(node, token) {
106 context.report({
107 node: node,
108 loc: token.loc.end,
109 message: "A space is required after '" + token.value + "'",
110 fix: function(fixer) {
111 return fixer.insertTextAfter(token, " ");
112 }
113 });
114 }
115
116 /**
117 * Reports that there should be a space before the last token
118 * @param {ASTNode} node - The node to report in the event of an error.
119 * @param {Token} token - The token to use for the report.
120 * @returns {void}
121 */
122 function reportRequiredEndingSpace(node, token) {
123 context.report({
124 node: node,
125 loc: token.loc.start,
126 message: "A space is required before '" + token.value + "'",
127 fix: function(fixer) {
128 return fixer.insertTextBefore(token, " ");
129 }
130 });
131 }
132
133 /**
134 * Determines if spacing in curly braces is valid.
135 * @param {ASTNode} node The AST node to check.
136 * @param {Token} first The first token to check (should be the opening brace)
137 * @param {Token} second The second token to check (should be first after the opening brace)
138 * @param {Token} penultimate The penultimate token to check (should be last before closing brace)
139 * @param {Token} last The last token to check (should be closing brace)
140 * @returns {void}
141 */
142 function validateBraceSpacing(node, first, second, penultimate, last) {
143 var closingCurlyBraceMustBeSpaced =
144 options.arraysInObjectsException && penultimate.value === "]" ||
145 options.objectsInObjectsException && penultimate.value === "}"
146 ? !options.spaced : options.spaced;
147
148 if (isSameLine(first, second)) {
149 if (options.spaced && !isSpaced(first, second)) {
150 reportRequiredBeginningSpace(node, first);
151 }
152 if (!options.spaced && isSpaced(first, second)) {
153 reportNoBeginningSpace(node, first);
154 }
155 }
156
157 if (isSameLine(penultimate, last)) {
158 if (closingCurlyBraceMustBeSpaced && !isSpaced(penultimate, last)) {
159 reportRequiredEndingSpace(node, last);
160 }
161 if (!closingCurlyBraceMustBeSpaced && isSpaced(penultimate, last)) {
162 reportNoEndingSpace(node, last);
163 }
164 }
165 }
166
167 /**
168 * Reports a given object node if spacing in curly braces is invalid.
169 * @param {ASTNode} node - An ObjectExpression or ObjectPattern node to check.
170 * @returns {void}
171 */
172 function checkForObject(node) {
173 if (node.properties.length === 0) {
174 return;
175 }
176
177 var firstSpecifier = node.properties[0],
178 lastSpecifier = node.properties[node.properties.length - 1];
179
180 var first = sourceCode.getTokenBefore(firstSpecifier),
181 last = sourceCode.getTokenAfter(lastSpecifier);
182
183 // support trailing commas
184 if (last.value === ",") {
185 last = sourceCode.getTokenAfter(last);
186 }
187
188 var second = sourceCode.getTokenAfter(first),
189 penultimate = sourceCode.getTokenBefore(last);
190
191 validateBraceSpacing(node, first, second, penultimate, last);
192 }
193
194 /**
195 * Reports a given import node if spacing in curly braces is invalid.
196 * @param {ASTNode} node - An ImportDeclaration node to check.
197 * @returns {void}
198 */
199 function checkForImport(node) {
200 if (node.specifiers.length === 0) {
201 return;
202 }
203
204 var firstSpecifier = node.specifiers[0],
205 lastSpecifier = node.specifiers[node.specifiers.length - 1];
206
207 if (lastSpecifier.type !== "ImportSpecifier") {
208 return;
209 }
210 if (firstSpecifier.type !== "ImportSpecifier") {
211 firstSpecifier = node.specifiers[1];
212 }
213
214 var first = sourceCode.getTokenBefore(firstSpecifier),
215 last = sourceCode.getTokenAfter(lastSpecifier);
216
217 // to support a trailing comma.
218 if (last.value === ",") {
219 last = sourceCode.getTokenAfter(last);
220 }
221
222 var second = sourceCode.getTokenAfter(first),
223 penultimate = sourceCode.getTokenBefore(last);
224
225 validateBraceSpacing(node, first, second, penultimate, last);
226 }
227
228 /**
229 * Reports a given export node if spacing in curly braces is invalid.
230 * @param {ASTNode} node - An ExportNamedDeclaration node to check.
231 * @returns {void}
232 */
233 function checkForExport(node) {
234 if (node.specifiers.length === 0) {
235 return;
236 }
237
238 var firstSpecifier = node.specifiers[0],
239 lastSpecifier = node.specifiers[node.specifiers.length - 1],
240 first = sourceCode.getTokenBefore(firstSpecifier),
241 last = sourceCode.getTokenAfter(lastSpecifier);
242
243 // export * as x from '...';
244 // export x from '...';
245 if (first.value === "export") {
246 return;
247 }
248
249 // to support a trailing comma.
250 if (last.value === ",") {
251 last = sourceCode.getTokenAfter(last);
252 }
253
254 var second = sourceCode.getTokenAfter(first),
255 penultimate = sourceCode.getTokenBefore(last);
256
257 validateBraceSpacing(node, first, second, penultimate, last);
258 }
259
260 //--------------------------------------------------------------------------
261 // Public
262 //--------------------------------------------------------------------------
263
264 return {
265 // var {x} = y;
266 ObjectPattern: checkForObject,
267
268 // var y = {x: 'y'}
269 ObjectExpression: checkForObject,
270
271 // import {y} from 'x';
272 ImportDeclaration: checkForImport,
273
274 // export {name} from 'yo';
275 ExportNamedDeclaration: checkForExport
276 };
277
278};
279
280module.exports.schema = [
281 {
282 "enum": ["always", "never"]
283 },
284 {
285 "type": "object",
286 "properties": {
287 "arraysInObjects": {
288 "type": "boolean"
289 },
290 "objectsInObjects": {
291 "type": "boolean"
292 }
293 },
294 "additionalProperties": false
295 }
296];