@@ -3,20 +3,15 @@ use biome_analyze::{
3
3
context:: RuleContext , declare_lint_rule, Rule , RuleDiagnostic , RuleSource , RuleSourceKind ,
4
4
} ;
5
5
use biome_console:: markup;
6
+ use biome_deserialize:: DeserializableValidator ;
6
7
use biome_deserialize_macros:: Deserializable ;
7
8
use biome_js_syntax:: {
8
9
binding_ext:: AnyJsIdentifierBinding , AnyJsIdentifierUsage , JsExportNamedSpecifier ,
9
10
} ;
10
11
use biome_rowan:: { AstNode , TextRange } ;
11
12
use biome_string_case:: { Case , Cases } ;
12
- use rustc_hash:: FxHashSet ;
13
- use serde:: { Deserialize , Serialize } ;
14
- use std:: { hash:: Hash , str:: FromStr } ;
15
-
16
- use biome_deserialize:: { DeserializableValue , DeserializationDiagnostic } ;
17
- #[ cfg( feature = "schemars" ) ]
18
- use schemars:: JsonSchema ;
19
13
use smallvec:: SmallVec ;
14
+ use std:: { hash:: Hash , str:: FromStr } ;
20
15
21
16
declare_lint_rule ! {
22
17
/// Enforce naming conventions for JavaScript and TypeScript filenames.
@@ -192,7 +187,7 @@ impl Rule for UseFilenamingConvention {
192
187
} ;
193
188
( name, split)
194
189
} ;
195
- let allowed_cases = options. filename_cases . cases ( ) ;
190
+ let allowed_cases = options. filename_cases . cases ;
196
191
let allowed_extension_cases = allowed_cases | Case :: Lower ;
197
192
// Check extension case
198
193
if extensions. any ( |extension| {
@@ -211,7 +206,7 @@ impl Rule for UseFilenamingConvention {
211
206
return None ;
212
207
}
213
208
}
214
- if options. filename_cases . 0 . contains ( & FilenameCase :: Export ) {
209
+ if options. filename_cases . allow_export {
215
210
// If no exported binding has the file name, then reports the filename
216
211
ctx. model ( )
217
212
. all_exported_bindings ( )
@@ -249,9 +244,9 @@ impl Rule for UseFilenamingConvention {
249
244
} ) )
250
245
} ,
251
246
FileNamingConventionState :: Filename => {
252
- let allowed_cases = options. filename_cases . cases ( ) ;
247
+ let allowed_cases = options. filename_cases . cases ;
253
248
let allowed_case_names = allowed_cases. into_iter ( ) . map ( |case| case. to_string ( ) ) ;
254
- let allowed_case_names = if options. filename_cases . 0 . contains ( & FilenameCase :: Export ) {
249
+ let allowed_case_names = if options. filename_cases . allow_export {
255
250
allowed_case_names
256
251
. chain ( [ "equal to the name of an export" . to_string ( ) ] )
257
252
. collect :: < SmallVec < [ _ ; 4 ] > > ( )
@@ -277,7 +272,7 @@ impl Rule for UseFilenamingConvention {
277
272
} else {
278
273
markup ! { "" } . to_owned ( )
279
274
} ;
280
- if options. strict_case && options. filename_cases . 0 . contains ( & FilenameCase :: Camel ) {
275
+ if options. strict_case && options. filename_cases . cases . contains ( Case :: Camel ) {
281
276
let case_type = Case :: identify ( trimmed_name, false ) ;
282
277
let case_strict = Case :: identify ( trimmed_name, true ) ;
283
278
if case_type == Case :: Camel && case_strict == Case :: Unknown {
@@ -304,7 +299,7 @@ impl Rule for UseFilenamingConvention {
304
299
}
305
300
} )
306
301
// Deduplicate suggestions
307
- . collect :: < FxHashSet < _ > > ( )
302
+ . collect :: < rustc_hash :: FxHashSet < _ > > ( )
308
303
. into_iter ( )
309
304
. collect :: < SmallVec < [ _ ; 3 ] > > ( )
310
305
. join ( "\n " ) ;
@@ -323,7 +318,7 @@ impl Rule for UseFilenamingConvention {
323
318
} ) )
324
319
} ,
325
320
FileNamingConventionState :: Extension => {
326
- let allowed_cases = options. filename_cases . cases ( ) | Case :: Lower ;
321
+ let allowed_cases = options. filename_cases . cases | Case :: Lower ;
327
322
let allowed_case_names = allowed_cases. into_iter ( ) . map ( |case| case. to_string ( ) ) ;
328
323
let allowed_case_names = allowed_case_names. collect :: < SmallVec < [ _ ; 4 ] > > ( ) . join ( " or " ) ;
329
324
Some ( RuleDiagnostic :: new (
@@ -349,40 +344,32 @@ pub enum FileNamingConventionState {
349
344
}
350
345
351
346
/// Rule's options.
352
- #[ derive( Clone , Debug , Deserialize , Deserializable , Eq , PartialEq , Serialize ) ]
353
- #[ cfg_attr( feature = "schemars" , derive( JsonSchema ) ) ]
347
+ #[ derive( Clone , Debug , serde :: Deserialize , Deserializable , Eq , PartialEq , serde :: Serialize ) ]
348
+ #[ cfg_attr( feature = "schemars" , derive( schemars :: JsonSchema ) ) ]
354
349
#[ serde( rename_all = "camelCase" , deny_unknown_fields) ]
355
350
pub struct FilenamingConventionOptions {
356
351
/// If `false`, then consecutive uppercase are allowed in _camel_ and _pascal_ cases.
357
352
/// This does not affect other [Case].
358
- #[ serde( default = "enabled" , skip_serializing_if = "is_enabled " ) ]
353
+ #[ serde( default = "enabled" , skip_serializing_if = "bool::clone " ) ]
359
354
pub strict_case : bool ,
360
355
361
356
/// If `false`, then non-ASCII characters are allowed.
362
357
#[ serde( default , skip_serializing_if = "is_default" ) ]
363
358
pub require_ascii : bool ,
364
359
365
360
/// Allowed cases for file names.
366
- #[ serde( default , skip_serializing_if = "is_default_filename_cases " ) ]
361
+ #[ serde( default , skip_serializing_if = "is_default " ) ]
367
362
pub filename_cases : FilenameCases ,
368
363
}
369
364
370
365
const fn enabled ( ) -> bool {
371
366
true
372
367
}
373
368
374
- const fn is_enabled ( value : & bool ) -> bool {
375
- * value
376
- }
377
-
378
369
fn is_default < T : Default + Eq > ( value : & T ) -> bool {
379
370
value == & T :: default ( )
380
371
}
381
372
382
- fn is_default_filename_cases ( value : & FilenameCases ) -> bool {
383
- value. 0 . len ( ) == 4 && !value. 0 . contains ( & FilenameCase :: Pascal )
384
- }
385
-
386
373
impl Default for FilenamingConventionOptions {
387
374
fn default ( ) -> Self {
388
375
Self {
@@ -393,60 +380,98 @@ impl Default for FilenamingConventionOptions {
393
380
}
394
381
}
395
382
396
- #[ derive( Clone , Debug , Deserialize , Eq , PartialEq , Serialize ) ]
397
- #[ cfg_attr( feature = "schemars" , derive( JsonSchema ) ) ]
398
- pub struct FilenameCases ( FxHashSet < FilenameCase > ) ;
399
-
400
- impl FilenameCases {
401
- fn cases ( & self ) -> Cases {
402
- self . 0
403
- . iter ( )
404
- . filter_map ( |case| Case :: try_from ( * case) . ok ( ) )
405
- . fold ( Cases :: empty ( ) , |acc, case| acc | case)
383
+ #[ derive(
384
+ Clone , Copy , Debug , Deserializable , Eq , Hash , PartialEq , serde:: Deserialize , serde:: Serialize ,
385
+ ) ]
386
+ #[ serde(
387
+ from = "SmallVec<[FilenameCase; 5]>" ,
388
+ into = "SmallVec<[FilenameCase; 5]>"
389
+ ) ]
390
+ #[ deserializable( with_validator) ]
391
+ pub struct FilenameCases {
392
+ cases : Cases ,
393
+ /// `true` is the filename can be equal to the name of an export.
394
+ allow_export : bool ,
395
+ }
396
+ impl From < SmallVec < [ FilenameCase ; 5 ] > > for FilenameCases {
397
+ fn from ( values : SmallVec < [ FilenameCase ; 5 ] > ) -> Self {
398
+ Self :: from_iter ( values)
406
399
}
407
400
}
408
-
409
401
impl FromIterator < FilenameCase > for FilenameCases {
410
- fn from_iter < T : IntoIterator < Item = FilenameCase > > ( iter : T ) -> Self {
411
- Self ( FxHashSet :: from_iter ( iter) )
402
+ fn from_iter < T : IntoIterator < Item = FilenameCase > > ( values : T ) -> Self {
403
+ let mut result = Self {
404
+ cases : Cases :: empty ( ) ,
405
+ allow_export : false ,
406
+ } ;
407
+ for filename_case in values {
408
+ if let Ok ( case) = Case :: try_from ( filename_case) {
409
+ result. cases |= case;
410
+ } else {
411
+ result. allow_export = true ;
412
+ }
413
+ }
414
+ result
415
+ }
416
+ }
417
+ impl From < FilenameCases > for SmallVec < [ FilenameCase ; 5 ] > {
418
+ fn from ( value : FilenameCases ) -> Self {
419
+ let maybe_export = if value. allow_export {
420
+ & [ FilenameCase :: Export ] [ ..]
421
+ } else {
422
+ & [ ]
423
+ } ;
424
+ value
425
+ . cases
426
+ . into_iter ( )
427
+ . filter_map ( |case| FilenameCase :: try_from ( case) . ok ( ) )
428
+ . chain ( maybe_export. iter ( ) . copied ( ) )
429
+ . collect ( )
430
+ }
431
+ }
432
+ #[ cfg( feature = "schemars" ) ]
433
+ impl schemars:: JsonSchema for FilenameCases {
434
+ fn schema_name ( ) -> String {
435
+ "FilenameCases" . to_string ( )
436
+ }
437
+ fn json_schema ( gen : & mut schemars:: gen:: SchemaGenerator ) -> schemars:: schema:: Schema {
438
+ <std:: collections:: HashSet < FilenameCase > >:: json_schema ( gen)
412
439
}
413
440
}
414
-
415
441
impl Default for FilenameCases {
416
442
fn default ( ) -> Self {
417
- Self ( FxHashSet :: from_iter ( [
418
- FilenameCase :: Camel ,
419
- FilenameCase :: Export ,
420
- FilenameCase :: Kebab ,
421
- FilenameCase :: Snake ,
422
- ] ) )
443
+ Self {
444
+ cases : Case :: Camel | Case :: Kebab | Case :: Snake ,
445
+ allow_export : true ,
446
+ }
423
447
}
424
448
}
425
-
426
- impl biome_deserialize:: Deserializable for FilenameCases {
427
- fn deserialize (
428
- value : & impl DeserializableValue ,
449
+ impl DeserializableValidator for FilenameCases {
450
+ fn validate (
451
+ & mut self ,
429
452
name : & str ,
430
- diagnostics : & mut Vec < DeserializationDiagnostic > ,
431
- ) -> Option < Self > {
432
- let cases: FxHashSet < _ > =
433
- biome_deserialize:: Deserializable :: deserialize ( value, name, diagnostics) ?;
434
- if cases. is_empty ( ) {
453
+ range : TextRange ,
454
+ diagnostics : & mut Vec < biome_deserialize:: DeserializationDiagnostic > ,
455
+ ) -> bool {
456
+ if !self . allow_export && self . cases . is_empty ( ) {
435
457
diagnostics. push (
436
- DeserializationDiagnostic :: new ( markup ! {
458
+ biome_deserialize :: DeserializationDiagnostic :: new ( markup ! {
437
459
"" <Emphasis >{ name} </Emphasis >" cannot be an empty array."
438
460
} )
439
- . with_range ( value . range ( ) ) ,
461
+ . with_range ( range) ,
440
462
) ;
441
- return None ;
463
+ false
464
+ } else {
465
+ true
442
466
}
443
- Some ( Self ( cases) )
444
467
}
445
468
}
446
469
447
470
/// Supported cases for file names.
448
- #[ derive( Clone , Copy , Debug , Deserialize , Deserializable , Eq , Hash , PartialEq , Serialize ) ]
449
- #[ cfg_attr( feature = "schemars" , derive( JsonSchema ) ) ]
471
+ #[ derive(
472
+ Clone , Copy , Debug , serde:: Deserialize , Deserializable , Eq , Hash , PartialEq , serde:: Serialize ,
473
+ ) ]
474
+ #[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
450
475
pub enum FilenameCase {
451
476
/// camelCase
452
477
#[ serde( rename = "camelCase" ) ]
@@ -468,7 +493,6 @@ pub enum FilenameCase {
468
493
#[ serde( rename = "snake_case" ) ]
469
494
Snake ,
470
495
}
471
-
472
496
impl FilenameCase {
473
497
pub const ALLOWED_VARIANTS : & ' static [ & ' static str ] = & [
474
498
"camelCase" ,
@@ -478,7 +502,6 @@ impl FilenameCase {
478
502
"snake_case" ,
479
503
] ;
480
504
}
481
-
482
505
impl FromStr for FilenameCase {
483
506
type Err = & ' static str ;
484
507
@@ -493,7 +516,6 @@ impl FromStr for FilenameCase {
493
516
}
494
517
}
495
518
}
496
-
497
519
impl TryFrom < FilenameCase > for Case {
498
520
type Error = & ' static str ;
499
521
@@ -507,3 +529,22 @@ impl TryFrom<FilenameCase> for Case {
507
529
}
508
530
}
509
531
}
532
+ impl TryFrom < Case > for FilenameCase {
533
+ type Error = & ' static str ;
534
+
535
+ fn try_from ( value : Case ) -> Result < Self , Self :: Error > {
536
+ match value {
537
+ Case :: Camel => Ok ( FilenameCase :: Camel ) ,
538
+ Case :: Kebab => Ok ( FilenameCase :: Kebab ) ,
539
+ Case :: Pascal => Ok ( FilenameCase :: Pascal ) ,
540
+ Case :: Snake => Ok ( FilenameCase :: Snake ) ,
541
+ Case :: Constant
542
+ | Case :: Lower
543
+ | Case :: Number
544
+ | Case :: NumberableCapital
545
+ | Case :: Uni
546
+ | Case :: Upper
547
+ | Case :: Unknown => Err ( "Unsupported case" ) ,
548
+ }
549
+ }
550
+ }
0 commit comments