1 | /**
|
2 | * Requires identifiers to be camelCased or UPPERCASE_WITH_UNDERSCORES
|
3 | *
|
4 | * Types: `Boolean` or `String` or `Object`
|
5 | *
|
6 | * Values:
|
7 | *
|
8 | * - `true`
|
9 | * - `"ignoreProperties"` allows an exception for object property names. Deprecated, Please use the `Object` value
|
10 | * - `Object`:
|
11 | * - `ignoreProperties`: boolean that allows an exception for object property names
|
12 | * - `strict`: boolean that forces the first character to not be capitalized
|
13 | * - `allowedPrefixes`: array of String, RegExp, or ESTree RegExpLiteral values permitted as prefixes
|
14 | * - `allowedSuffixes`: array of String, RegExp, or ESTree RegExpLiteral values permitted as suffixes
|
15 | * - `allExcept`: array of String, RegExp, or ESTree RegExpLiteral values permitted as exceptions
|
16 | *
|
17 | * JSHint: [`camelcase`](http://jshint.com/docs/options/#camelcase)
|
18 | *
|
19 | * #### Example
|
20 | *
|
21 | * ```js
|
22 | * "requireCamelCaseOrUpperCaseIdentifiers": true
|
23 | *
|
24 | * "requireCamelCaseOrUpperCaseIdentifiers": {"ignoreProperties": true, "strict": true}
|
25 | *
|
26 | * "requireCamelCaseOrUpperCaseIdentifiers": {"allowedPrefixes": ["opt_", /pfx\d+_/]}
|
27 | *
|
28 | * "requireCamelCaseOrUpperCaseIdentifiers": {"allowedSuffixes": ["_dCel", {regex: {pattern: "_[kMG]?Hz"}}]}
|
29 | *
|
30 | * "requireCamelCaseOrUpperCaseIdentifiers": {"allExcept": ["var_args", {regex: {pattern: "^ignore", flags: "i"}}]}
|
31 | * ```
|
32 | *
|
33 | * ##### Valid for mode `true`
|
34 | *
|
35 | * ```js
|
36 | * var camelCase = 0;
|
37 | * var CamelCase = 1;
|
38 | * var _camelCase = 2;
|
39 | * var camelCase_ = 3;
|
40 | * var UPPER_CASE = 4;
|
41 | * ```
|
42 | *
|
43 | * ##### Invalid for mode `true`
|
44 | *
|
45 | * ```js
|
46 | * var lower_case = 1;
|
47 | * var Mixed_case = 2;
|
48 | * var mixed_Case = 3;
|
49 | * ```
|
50 | *
|
51 | * ##### Valid for mode `ignoreProperties`
|
52 | *
|
53 | * ```js
|
54 | * var camelCase = 0;
|
55 | * var CamelCase = 1;
|
56 | * var _camelCase = 2;
|
57 | * var camelCase_ = 3;
|
58 | * var UPPER_CASE = 4;
|
59 | * var obj.snake_case = 5;
|
60 | * var camelCase = { snake_case: 6 };
|
61 | * ```
|
62 | *
|
63 | * ##### Invalid for mode `ignoreProperties`
|
64 | *
|
65 | * ```js
|
66 | * var lower_case = 1;
|
67 | * var Mixed_case = 2;
|
68 | * var mixed_Case = 3;
|
69 | * var snake_case = { snake_case: 6 };
|
70 | * ```
|
71 | *
|
72 | * ##### Valid for mode `strict`
|
73 | *
|
74 | * ```js
|
75 | * var camelCase = 0;
|
76 | * var _camelCase = 2;
|
77 | * var camelCase_ = 3;
|
78 | * var UPPER_CASE = 4;
|
79 | * var obj.snake_case = 5;
|
80 | * var camelCase = { snake_case: 6 };
|
81 | * ```
|
82 | *
|
83 | * ##### Invalid for mode `strict`
|
84 | *
|
85 | * ```js
|
86 | * var Mixed_case = 2;
|
87 | * var Snake_case = { snake_case: 6 };
|
88 | * var snake_case = { SnakeCase: 6 };
|
89 | * ```
|
90 | *
|
91 | * ##### Valid for `{ allowedPrefix: ["opt_", /pfx\d+_/] }`
|
92 | * ```js
|
93 | * var camelCase = 0;
|
94 | * var CamelCase = 1;
|
95 | * var _camelCase = 2;
|
96 | * var camelCase_ = 3;
|
97 | * var UPPER_CASE = 4;
|
98 | * var opt_camelCase = 5;
|
99 | * var pfx32_camelCase = 6;
|
100 | * ```
|
101 | *
|
102 | * ##### Invalid for `{ allowedPrefix: ["opt_", /pfx\d+/] }`
|
103 | * ```js
|
104 | * var lower_case = 1;
|
105 | * var Mixed_case = 2;
|
106 | * var mixed_Case = 3;
|
107 | * var req_camelCase = 4;
|
108 | * var pfx_CamelCase = 5;
|
109 | * ```
|
110 | *
|
111 | * ##### Valid for `{ allowedSuffixes: ["_dCel", {regex:{pattern:"_[kMG]?Hz"}}] }`
|
112 | * ```js
|
113 | * var camelCase = 0;
|
114 | * var CamelCase = 1;
|
115 | * var _camelCase = 2;
|
116 | * var camelCase_ = 3;
|
117 | * var UPPER_CASE = 4;
|
118 | * var camelCase_dCel = 5;
|
119 | * var _camelCase_MHz = 6;
|
120 | * ```
|
121 | *
|
122 | * ##### Invalid for `{ allowedSuffixes: ["_dCel", {regex:{pattern:"_[kMG]?Hz"}}] }`
|
123 | * ```js
|
124 | * var lower_case = 1;
|
125 | * var Mixed_case = 2;
|
126 | * var mixed_Case = 3;
|
127 | * var camelCase_cCel = 4;
|
128 | * var CamelCase_THz = 5;
|
129 | * ```
|
130 | *
|
131 | * ##### Valid for `{ allExcept: ["var_args", {regex:{pattern:"^ignore",flags:"i"}}] }`
|
132 | * ```js
|
133 | * var camelCase = 0;
|
134 | * var CamelCase = 1;
|
135 | * var _camelCase = 2;
|
136 | * var camelCase_ = 3;
|
137 | * var UPPER_CASE = 4;
|
138 | * var var_args = 5;
|
139 | * var ignoreThis_Please = 6;
|
140 | * var iGnOrEeThis_Too = 7;
|
141 | * ```
|
142 | *
|
143 | * ##### Invalid for `{ allExcept: ["var_args", {regex:{pattern:"^ignore",flags:"i"}}] }`
|
144 | * ```js
|
145 | * var lower_case = 1;
|
146 | * var Mixed_case = 2;
|
147 | * var mixed_Case = 3;
|
148 | * var var_arg = 4;
|
149 | * var signore_per_favore = 5;
|
150 | * ```
|
151 | */
|
152 |
|
153 | var assert = require('assert');
|
154 |
|
155 | // Convert an array of String or RegExp or ESTree RegExpLiteral values
|
156 | // into an array of String or RegExp values. Returns falsy if the
|
157 | // input does not match expectations.
|
158 | function processArrayOfStringOrRegExp(iv) {
|
159 | if (!Array.isArray(iv)) {
|
160 | return;
|
161 | }
|
162 | var rv = [];
|
163 | var i = 0;
|
164 | while (rv && (i < iv.length)) {
|
165 | var elt = iv[i];
|
166 | if (typeof elt === 'string') {
|
167 | // string values OK
|
168 | rv.push(elt);
|
169 | } else if (elt instanceof RegExp) {
|
170 | // existing RegExp OK
|
171 | rv.push(elt);
|
172 | } else if (elt && (typeof elt === 'object')) {
|
173 | try {
|
174 | // ESTree RegExpLiteral ok if it produces RegExp
|
175 | rv.push(new RegExp(elt.regex.pattern, elt.regex.flags || ''));
|
176 | } catch (e) {
|
177 | // Not a valid RegExpLiteral
|
178 | rv = null;
|
179 | }
|
180 | } else {
|
181 | // Unknown value
|
182 | rv = null;
|
183 | }
|
184 | ++i;
|
185 | }
|
186 | return rv;
|
187 | }
|
188 |
|
189 | // Return undefined or the start of the unprefixed value.
|
190 | function startAfterStringPrefix(value, prefix) {
|
191 | var start = prefix.length;
|
192 | if (start >= value.length) {
|
193 | return;
|
194 | }
|
195 | if (value.substr(0, prefix.length) !== prefix) {
|
196 | return;
|
197 | }
|
198 | return start;
|
199 | }
|
200 |
|
201 | // Return undefined or the start of the unprefixed value.
|
202 | function startAfterRegExpPrefix(value, prefix) {
|
203 | var match = prefix.exec(value);
|
204 | if (!match) {
|
205 | return;
|
206 | }
|
207 | if (match.index !== 0) {
|
208 | return;
|
209 | }
|
210 | return match[0].length;
|
211 | }
|
212 |
|
213 | // Return undefined or the end of the unsuffixed value.
|
214 | function endBeforeStringSuffix(value, suffix) {
|
215 | var ends = value.length - suffix.length;
|
216 | if (ends <= 0) {
|
217 | return;
|
218 | }
|
219 | if (value.substr(ends) !== suffix) {
|
220 | return;
|
221 | }
|
222 | return ends;
|
223 | }
|
224 |
|
225 | // Return undefined or the end of the unsuffixed value.
|
226 | function endBeforeRegExpSuffix(value, suffix) {
|
227 | var match = suffix.exec(value);
|
228 | if (!match) {
|
229 | return;
|
230 | }
|
231 | var ends = match.index;
|
232 | if ((ends + match[0].length) !== value.length) {
|
233 | return;
|
234 | }
|
235 | return ends;
|
236 | }
|
237 |
|
238 | // Return truthy iff the value matches the exception.
|
239 | function matchException(value, exception) {
|
240 | if (typeof exception === 'string') {
|
241 | return (exception === value);
|
242 | }
|
243 | return exception.test(value);
|
244 | }
|
245 |
|
246 | module.exports = function() {};
|
247 |
|
248 | module.exports.prototype = {
|
249 |
|
250 | configure: function(options) {
|
251 | if (typeof options !== 'object') {
|
252 | assert(
|
253 | options === true || options === 'ignoreProperties',
|
254 | this.getOptionName() + ' option requires a true value or `ignoreProperties`'
|
255 | );
|
256 | var _options = {
|
257 | ignoreProperties: options === 'ignoreProperties' ? true : false,
|
258 | strict: false
|
259 | };
|
260 | return this.configure(_options);
|
261 | }
|
262 |
|
263 | assert(
|
264 | !options.hasOwnProperty('ignoreProperties') || typeof options.ignoreProperties === 'boolean',
|
265 | this.getOptionName() + ' option should have boolean value for ignoreProperties'
|
266 | );
|
267 | this._ignoreProperties = options.ignoreProperties;
|
268 |
|
269 | assert(
|
270 | !options.hasOwnProperty('strict') || typeof options.strict === 'boolean',
|
271 | this.getOptionName() + ' option should have boolean value for strict'
|
272 | );
|
273 | this._strict = options.strict;
|
274 |
|
275 | var asre = processArrayOfStringOrRegExp(options.allowedPrefixes);
|
276 | assert(
|
277 | !options.hasOwnProperty('allowedPrefixes') || asre,
|
278 | this.getOptionName() + ' option should have array of string or RegExp for allowedPrefixes'
|
279 | );
|
280 | if (asre) {
|
281 | this._allowedPrefixes = asre;
|
282 | }
|
283 |
|
284 | asre = processArrayOfStringOrRegExp(options.allowedSuffixes);
|
285 | assert(
|
286 | !options.hasOwnProperty('allowedSuffixes') || asre,
|
287 | this.getOptionName() + ' option should have array of string or RegExp for allowedSuffixes'
|
288 | );
|
289 | if (asre) {
|
290 | this._allowedSuffixes = asre;
|
291 | }
|
292 |
|
293 | asre = processArrayOfStringOrRegExp(options.allExcept);
|
294 | assert(
|
295 | !options.hasOwnProperty('allExcept') || asre,
|
296 | this.getOptionName() + ' option should have array of string or RegExp for allExcept'
|
297 | );
|
298 | if (asre) {
|
299 | this._allExcept = asre;
|
300 | }
|
301 |
|
302 | },
|
303 |
|
304 | getOptionName: function() {
|
305 | return 'requireCamelCaseOrUpperCaseIdentifiers';
|
306 | },
|
307 |
|
308 | check: function(file, errors) {
|
309 | file.iterateTokensByType('Identifier', function(token) {
|
310 | var value = token.value;
|
311 |
|
312 | // Leading and trailing underscores signify visibility/scope and do not affect
|
313 | // validation of the rule. Remove them to simplify the checks.
|
314 | var isPrivate = (value[0] === '_');
|
315 | value = value.replace(/^_+|_+$/g, '');
|
316 |
|
317 | // Detect exceptions before stripping prefixes/suffixes.
|
318 | if (this._allExcept) {
|
319 | for (i = 0, len = this._allExcept.length; i < len; ++i) {
|
320 | if (matchException(value, this._allExcept[i])) {
|
321 | return;
|
322 | }
|
323 | }
|
324 | }
|
325 |
|
326 | // Strip at most one prefix permitted text from the identifier. This transformation
|
327 | // cannot change an acceptable identifier into an unacceptable identifier so we can
|
328 | // continue with the normal verification of whatever it produces.
|
329 | var i;
|
330 | var len;
|
331 | if (this._allowedPrefixes) {
|
332 | for (i = 0, len = this._allowedPrefixes.length; i < len; ++i) {
|
333 | var prefix = this._allowedPrefixes[i];
|
334 | var start;
|
335 | if (typeof prefix === 'string') {
|
336 | start = startAfterStringPrefix(value, prefix);
|
337 | } else {
|
338 | start = startAfterRegExpPrefix(value, prefix);
|
339 | }
|
340 | if (start !== undefined) {
|
341 | value = value.substr(start);
|
342 | break;
|
343 | }
|
344 | }
|
345 | }
|
346 |
|
347 | // As with prefix but for one suffix permitted text.
|
348 | if (this._allowedSuffixes) {
|
349 | for (i = 0, len = this._allowedSuffixes.length; i < len; ++i) {
|
350 | var suffix = this._allowedSuffixes[i];
|
351 | var ends;
|
352 | if (typeof suffix === 'string') {
|
353 | ends = endBeforeStringSuffix(value, suffix);
|
354 | } else {
|
355 | ends = endBeforeRegExpSuffix(value, suffix);
|
356 | }
|
357 | if (ends !== undefined) {
|
358 | value = value.substr(0, ends);
|
359 | break;
|
360 | }
|
361 | }
|
362 | }
|
363 |
|
364 | if (value.indexOf('_') === -1 || value.toUpperCase() === value) {
|
365 | if (!this._strict) {return;}
|
366 | if (value.length === 0 || value[0].toUpperCase() !== value[0] || isPrivate) {
|
367 | return;
|
368 | }
|
369 | }
|
370 | if (this._ignoreProperties) {
|
371 | var nextToken = file.getNextToken(token);
|
372 | var prevToken = token.getPreviousCodeToken();
|
373 |
|
374 | if (nextToken && nextToken.value === ':') {
|
375 | return;
|
376 | }
|
377 |
|
378 | /* This enables an identifier to be snake cased via the object
|
379 | * destructuring pattern. We must check to see if the identifier
|
380 | * is being used to set values into an object to determine if
|
381 | * this is a legal assignment.
|
382 | * Example: ({camelCase: snake_case}) => camelCase.length
|
383 | */
|
384 | if (prevToken && prevToken.value === ':') {
|
385 | var node = token.parentElement;
|
386 | var parentElement = node.parentElement;
|
387 | if (parentElement && parentElement.type === 'ObjectProperty') {
|
388 | var grandpa = parentElement.parentElement;
|
389 | if (grandpa && grandpa.type === 'ObjectPattern') {
|
390 | return;
|
391 | }
|
392 | }
|
393 | }
|
394 |
|
395 | if (prevToken && (prevToken.value === '.' ||
|
396 | prevToken.value === 'get' || prevToken.value === 'set')) {
|
397 | return;
|
398 | }
|
399 | }
|
400 | errors.add(
|
401 | 'All identifiers must be camelCase or UPPER_CASE',
|
402 | token
|
403 | );
|
404 | }.bind(this));
|
405 | }
|
406 |
|
407 | };
|