Skip to content

Commit 7fffb27

Browse files
authored
refactor(useFilenamingConvention): use the Cases bit flags for filename cases (#4316)
1 parent ea7d35c commit 7fffb27

File tree

2 files changed

+111
-70
lines changed

2 files changed

+111
-70
lines changed

crates/biome_deserialize_macros/src/deserializable_derive.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ fn generate_deserializable_enum(
273273
name: &str,
274274
diagnostics: &mut Vec<biome_deserialize::DeserializationDiagnostic>,
275275
) -> Option<Self> {
276-
let result = match biome_deserialize::Text::deserialize(value, name, diagnostics)?.text() {
276+
let mut result = match biome_deserialize::Text::deserialize(value, name, diagnostics)?.text() {
277277
#(#deserialize_variants),*,
278278
unknown_variant => {
279279
const ALLOWED_VARIANTS: &[&str] = &[#(#allowed_variants),*];
@@ -317,7 +317,7 @@ fn generate_deserializable_newtype(
317317
name: &str,
318318
diagnostics: &mut Vec<biome_deserialize::DeserializationDiagnostic>,
319319
) -> Option<Self> {
320-
let result = biome_deserialize::Deserializable::deserialize(value, name, diagnostics).map(Self)?;
320+
let mut result = biome_deserialize::Deserializable::deserialize(value, name, diagnostics).map(Self)?;
321321
#validator
322322
Some(result)
323323
}
@@ -529,7 +529,7 @@ fn generate_deserializable_from(
529529
let from = data.from;
530530
let validator = if data.with_validator {
531531
quote! {
532-
if !biome_deserialize::DeserializableValidator::validate(&result, name, range, diagnostics) {
532+
if !biome_deserialize::DeserializableValidator::validate(&mut result, name, value.range(), diagnostics) {
533533
return None;
534534
}
535535
}
@@ -544,7 +544,7 @@ fn generate_deserializable_from(
544544
diagnostics: &mut Vec<biome_deserialize::DeserializationDiagnostic>,
545545
) -> Option<Self> {
546546
let result: #from = biome_deserialize::Deserializable::deserialize(value, name, diagnostics)?;
547-
let result: Self = result.into();
547+
let mut result: Self = result.into();
548548
#validator
549549
Some(result)
550550
}
@@ -562,7 +562,7 @@ fn generate_deserializable_try_from(
562562
let try_from = data.try_from;
563563
let validator = if data.with_validator {
564564
quote! {
565-
if !biome_deserialize::DeserializableValidator::validate(&result, name, range, diagnostics) {
565+
if !biome_deserialize::DeserializableValidator::validate(&mut result, name, value.range(), diagnostics) {
566566
return None;
567567
}
568568
}
@@ -576,7 +576,7 @@ fn generate_deserializable_try_from(
576576
name: &str,
577577
diagnostics: &mut Vec<biome_deserialize::DeserializationDiagnostic>,
578578
) -> Option<Self> {
579-
let result: #try_from = biome_deserialize::Deserializable::deserialize(value, name, diagnostics)?;
579+
let mut result: #try_from = biome_deserialize::Deserializable::deserialize(value, name, diagnostics)?;
580580
match result.try_into() {
581581
Ok(result) => {
582582
#validator

crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs

+105-64
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,15 @@ use biome_analyze::{
33
context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource, RuleSourceKind,
44
};
55
use biome_console::markup;
6+
use biome_deserialize::DeserializableValidator;
67
use biome_deserialize_macros::Deserializable;
78
use biome_js_syntax::{
89
binding_ext::AnyJsIdentifierBinding, AnyJsIdentifierUsage, JsExportNamedSpecifier,
910
};
1011
use biome_rowan::{AstNode, TextRange};
1112
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;
1913
use smallvec::SmallVec;
14+
use std::{hash::Hash, str::FromStr};
2015

2116
declare_lint_rule! {
2217
/// Enforce naming conventions for JavaScript and TypeScript filenames.
@@ -192,7 +187,7 @@ impl Rule for UseFilenamingConvention {
192187
};
193188
(name, split)
194189
};
195-
let allowed_cases = options.filename_cases.cases();
190+
let allowed_cases = options.filename_cases.cases;
196191
let allowed_extension_cases = allowed_cases | Case::Lower;
197192
// Check extension case
198193
if extensions.any(|extension| {
@@ -211,7 +206,7 @@ impl Rule for UseFilenamingConvention {
211206
return None;
212207
}
213208
}
214-
if options.filename_cases.0.contains(&FilenameCase::Export) {
209+
if options.filename_cases.allow_export {
215210
// If no exported binding has the file name, then reports the filename
216211
ctx.model()
217212
.all_exported_bindings()
@@ -249,9 +244,9 @@ impl Rule for UseFilenamingConvention {
249244
}))
250245
},
251246
FileNamingConventionState::Filename => {
252-
let allowed_cases = options.filename_cases.cases();
247+
let allowed_cases = options.filename_cases.cases;
253248
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 {
255250
allowed_case_names
256251
.chain(["equal to the name of an export".to_string()])
257252
.collect::<SmallVec<[_; 4]>>()
@@ -277,7 +272,7 @@ impl Rule for UseFilenamingConvention {
277272
} else {
278273
markup! {""}.to_owned()
279274
};
280-
if options.strict_case && options.filename_cases.0.contains(&FilenameCase::Camel) {
275+
if options.strict_case && options.filename_cases.cases.contains(Case::Camel) {
281276
let case_type = Case::identify(trimmed_name, false);
282277
let case_strict = Case::identify(trimmed_name, true);
283278
if case_type == Case::Camel && case_strict == Case::Unknown {
@@ -304,7 +299,7 @@ impl Rule for UseFilenamingConvention {
304299
}
305300
})
306301
// Deduplicate suggestions
307-
.collect::<FxHashSet<_>>()
302+
.collect::<rustc_hash::FxHashSet<_>>()
308303
.into_iter()
309304
.collect::<SmallVec<[_; 3]>>()
310305
.join("\n");
@@ -323,7 +318,7 @@ impl Rule for UseFilenamingConvention {
323318
}))
324319
},
325320
FileNamingConventionState::Extension => {
326-
let allowed_cases = options.filename_cases.cases() | Case::Lower;
321+
let allowed_cases = options.filename_cases.cases | Case::Lower;
327322
let allowed_case_names = allowed_cases.into_iter().map(|case| case.to_string());
328323
let allowed_case_names = allowed_case_names.collect::<SmallVec<[_; 4]>>().join(" or ");
329324
Some(RuleDiagnostic::new(
@@ -349,40 +344,32 @@ pub enum FileNamingConventionState {
349344
}
350345

351346
/// 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))]
354349
#[serde(rename_all = "camelCase", deny_unknown_fields)]
355350
pub struct FilenamingConventionOptions {
356351
/// If `false`, then consecutive uppercase are allowed in _camel_ and _pascal_ cases.
357352
/// This does not affect other [Case].
358-
#[serde(default = "enabled", skip_serializing_if = "is_enabled")]
353+
#[serde(default = "enabled", skip_serializing_if = "bool::clone")]
359354
pub strict_case: bool,
360355

361356
/// If `false`, then non-ASCII characters are allowed.
362357
#[serde(default, skip_serializing_if = "is_default")]
363358
pub require_ascii: bool,
364359

365360
/// Allowed cases for file names.
366-
#[serde(default, skip_serializing_if = "is_default_filename_cases")]
361+
#[serde(default, skip_serializing_if = "is_default")]
367362
pub filename_cases: FilenameCases,
368363
}
369364

370365
const fn enabled() -> bool {
371366
true
372367
}
373368

374-
const fn is_enabled(value: &bool) -> bool {
375-
*value
376-
}
377-
378369
fn is_default<T: Default + Eq>(value: &T) -> bool {
379370
value == &T::default()
380371
}
381372

382-
fn is_default_filename_cases(value: &FilenameCases) -> bool {
383-
value.0.len() == 4 && !value.0.contains(&FilenameCase::Pascal)
384-
}
385-
386373
impl Default for FilenamingConventionOptions {
387374
fn default() -> Self {
388375
Self {
@@ -393,60 +380,98 @@ impl Default for FilenamingConventionOptions {
393380
}
394381
}
395382

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)
406399
}
407400
}
408-
409401
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)
412439
}
413440
}
414-
415441
impl Default for FilenameCases {
416442
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+
}
423447
}
424448
}
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,
429452
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() {
435457
diagnostics.push(
436-
DeserializationDiagnostic::new(markup! {
458+
biome_deserialize::DeserializationDiagnostic::new(markup! {
437459
""<Emphasis>{name}</Emphasis>" cannot be an empty array."
438460
})
439-
.with_range(value.range()),
461+
.with_range(range),
440462
);
441-
return None;
463+
false
464+
} else {
465+
true
442466
}
443-
Some(Self(cases))
444467
}
445468
}
446469

447470
/// 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))]
450475
pub enum FilenameCase {
451476
/// camelCase
452477
#[serde(rename = "camelCase")]
@@ -468,7 +493,6 @@ pub enum FilenameCase {
468493
#[serde(rename = "snake_case")]
469494
Snake,
470495
}
471-
472496
impl FilenameCase {
473497
pub const ALLOWED_VARIANTS: &'static [&'static str] = &[
474498
"camelCase",
@@ -478,7 +502,6 @@ impl FilenameCase {
478502
"snake_case",
479503
];
480504
}
481-
482505
impl FromStr for FilenameCase {
483506
type Err = &'static str;
484507

@@ -493,7 +516,6 @@ impl FromStr for FilenameCase {
493516
}
494517
}
495518
}
496-
497519
impl TryFrom<FilenameCase> for Case {
498520
type Error = &'static str;
499521

@@ -507,3 +529,22 @@ impl TryFrom<FilenameCase> for Case {
507529
}
508530
}
509531
}
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

Comments
 (0)