1 |
|
2 |
|
3 | /**
|
4 | * @fileoverview Disallows or enforces spaces inside of array brackets.
|
5 | * @author Jamund Ferguson
|
6 | * @copyright 2015 Jamund Ferguson. All rights reserved.
|
7 | * @copyright 2014 Brandyn Bennett. All rights reserved.
|
8 | * @copyright 2014 Michael Ficarra. No rights reserved.
|
9 | * @copyright 2014 Vignesh Anand. All rights reserved.
|
10 | */
|
11 | // ------------------------------------------------------------------------------
|
12 | // Rule Definition
|
13 | // ------------------------------------------------------------------------------
|
14 |
|
15 | module.exports = {
|
16 | meta: {
|
17 | docs: {
|
18 | url: 'https://github.com/standard/eslint-plugin-standard#rules-explanations'
|
19 | }
|
20 | },
|
21 |
|
22 | create: function (context) {
|
23 | var spaced = context.options[0] === 'always'
|
24 | var either = context.options[0] === 'either'
|
25 |
|
26 | /**
|
27 | * Determines whether an option is set, relative to the spacing option.
|
28 | * If spaced is "always", then check whether option is set to false.
|
29 | * If spaced is "never", then check whether option is set to true.
|
30 | * @param {Object} option - The option to exclude.
|
31 | * @returns {boolean} Whether or not the property is excluded.
|
32 | */
|
33 | function isOptionSet (option) {
|
34 | return context.options[1] != null ? context.options[1][option] === !spaced : false
|
35 | }
|
36 |
|
37 | var options = {
|
38 | either: either,
|
39 | spaced: spaced,
|
40 | singleElementException: isOptionSet('singleValue'),
|
41 | objectsInArraysException: isOptionSet('objectsInArrays'),
|
42 | arraysInArraysException: isOptionSet('arraysInArrays')
|
43 | }
|
44 |
|
45 | // --------------------------------------------------------------------------
|
46 | // Helpers
|
47 | // --------------------------------------------------------------------------
|
48 |
|
49 | /**
|
50 | * Determines whether two adjacent tokens are have whitespace between them.
|
51 | * @param {Object} left - The left token object.
|
52 | * @param {Object} right - The right token object.
|
53 | * @returns {boolean} Whether or not there is space between the tokens.
|
54 | */
|
55 | function isSpaced (left, right) {
|
56 | return left.range[1] < right.range[0]
|
57 | }
|
58 |
|
59 | /**
|
60 | * Determines whether two adjacent tokens are on the same line.
|
61 | * @param {Object} left - The left token object.
|
62 | * @param {Object} right - The right token object.
|
63 | * @returns {boolean} Whether or not the tokens are on the same line.
|
64 | */
|
65 | function isSameLine (left, right) {
|
66 | return left.loc.start.line === right.loc.start.line
|
67 | }
|
68 |
|
69 | /**
|
70 | * Reports that there shouldn't be a space after the first token
|
71 | * @param {ASTNode} node - The node to report in the event of an error.
|
72 | * @param {Token} token - The token to use for the report.
|
73 | * @returns {void}
|
74 | */
|
75 | function reportNoBeginningSpace (node, token) {
|
76 | context.report(node, token.loc.start,
|
77 | "There should be no space after '" + token.value + "'")
|
78 | }
|
79 |
|
80 | /**
|
81 | * Reports that there shouldn't be a space before the last token
|
82 | * @param {ASTNode} node - The node to report in the event of an error.
|
83 | * @param {Token} token - The token to use for the report.
|
84 | * @returns {void}
|
85 | */
|
86 | function reportNoEndingSpace (node, token) {
|
87 | context.report(node, token.loc.start,
|
88 | "There should be no space before '" + token.value + "'")
|
89 | }
|
90 |
|
91 | /**
|
92 | * Reports that there should be a space after the first token
|
93 | * @param {ASTNode} node - The node to report in the event of an error.
|
94 | * @param {Token} token - The token to use for the report.
|
95 | * @returns {void}
|
96 | */
|
97 | function reportRequiredBeginningSpace (node, token) {
|
98 | context.report(node, token.loc.start,
|
99 | "A space is required after '" + token.value + "'")
|
100 | }
|
101 |
|
102 | /**
|
103 | * Reports that there should be a space before the last token
|
104 | * @param {ASTNode} node - The node to report in the event of an error.
|
105 | * @param {Token} token - The token to use for the report.
|
106 | * @returns {void}
|
107 | */
|
108 | function reportRequiredEndingSpace (node, token) {
|
109 | context.report(node, token.loc.start,
|
110 | "A space is required before '" + token.value + "'")
|
111 | }
|
112 |
|
113 | /**
|
114 | * Checks if a start and end brace in a node are spaced evenly
|
115 | * and not too long (>1 space)
|
116 | * @param node
|
117 | * @param start
|
118 | * @param end
|
119 | * @returns {boolean}
|
120 | */
|
121 | function isEvenlySpacedAndNotTooLong (node, start, end) {
|
122 | var expectedSpace = start[1].range[0] - start[0].range[1]
|
123 | var endSpace = end[1].range[0] - end[0].range[1]
|
124 | return endSpace === expectedSpace && endSpace <= 1
|
125 | }
|
126 |
|
127 | /**
|
128 | * Validates the spacing around array brackets
|
129 | * @param {ASTNode} node - The node we're checking for spacing
|
130 | * @returns {void}
|
131 | */
|
132 | function validateArraySpacing (node) {
|
133 | if (node.elements.length === 0) {
|
134 | return
|
135 | }
|
136 |
|
137 | var first = context.getFirstToken(node)
|
138 | var second = context.getFirstToken(node, 1)
|
139 | var penultimate = context.getLastToken(node, 1)
|
140 | var last = context.getLastToken(node)
|
141 |
|
142 | var openingBracketMustBeSpaced =
|
143 | (options.objectsInArraysException && second.value === '{') ||
|
144 | (options.arraysInArraysException && second.value === '[') ||
|
145 | (options.singleElementException && node.elements.length === 1)
|
146 | ? !options.spaced : options.spaced
|
147 |
|
148 | var closingBracketMustBeSpaced =
|
149 | (options.objectsInArraysException && penultimate.value === '}') ||
|
150 | (options.arraysInArraysException && penultimate.value === ']') ||
|
151 | (options.singleElementException && node.elements.length === 1)
|
152 | ? !options.spaced : options.spaced
|
153 |
|
154 | // we only care about evenly spaced things
|
155 | if (options.either) {
|
156 | // newlines at any point means return
|
157 | if (!isSameLine(first, last)) {
|
158 | return
|
159 | }
|
160 |
|
161 | // confirm that the object expression/literal is spaced evenly
|
162 | if (!isEvenlySpacedAndNotTooLong(node, [first, second], [penultimate, last])) {
|
163 | context.report(node, 'Expected consistent spacing')
|
164 | }
|
165 |
|
166 | return
|
167 | }
|
168 |
|
169 | if (isSameLine(first, second)) {
|
170 | if (openingBracketMustBeSpaced && !isSpaced(first, second)) {
|
171 | reportRequiredBeginningSpace(node, first)
|
172 | }
|
173 | if (!openingBracketMustBeSpaced && isSpaced(first, second)) {
|
174 | reportNoBeginningSpace(node, first)
|
175 | }
|
176 | }
|
177 |
|
178 | if (isSameLine(penultimate, last)) {
|
179 | if (closingBracketMustBeSpaced && !isSpaced(penultimate, last)) {
|
180 | reportRequiredEndingSpace(node, last)
|
181 | }
|
182 | if (!closingBracketMustBeSpaced && isSpaced(penultimate, last)) {
|
183 | reportNoEndingSpace(node, last)
|
184 | }
|
185 | }
|
186 | }
|
187 |
|
188 | // --------------------------------------------------------------------------
|
189 | // Public
|
190 | // --------------------------------------------------------------------------
|
191 |
|
192 | return {
|
193 | ArrayPattern: validateArraySpacing,
|
194 | ArrayExpression: validateArraySpacing
|
195 | }
|
196 | }
|
197 | }
|