10
10
* - `Object`:
11
11
* - `ignoreProperties`: boolean that allows an exception for object property names
12
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
13
16
*
14
17
* JSHint: [`camelcase`](http://jshint.com/docs/options/#camelcase)
15
18
*
19
22
* "requireCamelCaseOrUpperCaseIdentifiers": true
20
23
*
21
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"}}] }
22
31
* ```
23
32
*
24
33
* ##### Valid for mode `true`
79
88
* var snake_case = { SnakeCase: 6 };
80
89
* ```
81
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
+ * ```
82
151
*/
83
152
84
153
var assert = require ( 'assert' ) ;
85
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
+
86
246
module . exports = function ( ) { } ;
87
247
88
248
module . exports . prototype = {
@@ -101,11 +261,44 @@ module.exports.prototype = {
101
261
}
102
262
103
263
assert (
104
- typeof options . ignoreProperties === 'boolean' || typeof options . strict === 'boolean' ,
105
- this . getOptionName ( ) + ' option should have boolean values for ignoreProperties and/or strict '
264
+ ! options . hasOwnProperty ( ' ignoreProperties' ) || typeof options . ignoreProperties === 'boolean' ,
265
+ this . getOptionName ( ) + ' option should have boolean value for ignoreProperties'
106
266
) ;
107
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
+ ) ;
108
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
+
109
302
} ,
110
303
111
304
getOptionName : function ( ) {
@@ -115,9 +308,62 @@ module.exports.prototype = {
115
308
check : function ( file , errors ) {
116
309
file . iterateTokensByType ( 'Identifier' , function ( token ) {
117
310
var value = token . value ;
118
- if ( value . replace ( / ^ _ + | _ + $ / g, '' ) . indexOf ( '_' ) === - 1 || value . toUpperCase ( ) === 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 ) {
119
365
if ( ! this . _strict ) { return ; }
120
- if ( value [ 0 ] . toUpperCase ( ) !== value [ 0 ] || value [ 0 ] === '_' ) {
366
+ if ( value . length === 0 || value [ 0 ] . toUpperCase ( ) !== value [ 0 ] || isPrivate ) {
121
367
return ;
122
368
}
123
369
}
0 commit comments