1010 * - `Object`:
1111 * - `ignoreProperties`: boolean that allows an exception for object property names
1212 * - `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
1316 *
1417 * JSHint: [`camelcase`](http://jshint.com/docs/options/#camelcase)
1518 *
1922 * "requireCamelCaseOrUpperCaseIdentifiers": true
2023 *
2124 * "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"}}] }
2231 * ```
2332 *
2433 * ##### Valid for mode `true`
7988 * var snake_case = { SnakeCase: 6 };
8089 * ```
8190 *
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+ * ```
82151 */
83152
84153var assert = require ( 'assert' ) ;
85154
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+
86246module . exports = function ( ) { } ;
87247
88248module . exports . prototype = {
@@ -101,11 +261,44 @@ module.exports.prototype = {
101261 }
102262
103263 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'
106266 ) ;
107267 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+ ) ;
108273 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+
109302 } ,
110303
111304 getOptionName : function ( ) {
@@ -115,9 +308,62 @@ module.exports.prototype = {
115308 check : function ( file , errors ) {
116309 file . iterateTokensByType ( 'Identifier' , function ( token ) {
117310 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 ) {
119365 if ( ! this . _strict ) { return ; }
120- if ( value [ 0 ] . toUpperCase ( ) !== value [ 0 ] || value [ 0 ] === '_' ) {
366+ if ( value . length === 0 || value [ 0 ] . toUpperCase ( ) !== value [ 0 ] || isPrivate ) {
121367 return ;
122368 }
123369 }
0 commit comments