From 81ea01e93ed5918c38ff78b1250d1941dc9ff56c Mon Sep 17 00:00:00 2001 From: Nat Date: Mon, 14 Oct 2024 21:53:46 +1100 Subject: [PATCH] fix(lint): allow readonly modifier on indexParameter in `useNamingConvention` (#4286) Co-authored-by: Victorien Elvinger --- CHANGELOG.md | 4 + .../src/lint/style/use_naming_convention.rs | 38 +++++- .../invalidIndexParameter.ts | 8 ++ .../invalidIndexParameter.ts.snap | 111 +++++++++++++++--- .../validIndexParameter.ts | 8 ++ .../validIndexParameter.ts.snap | 8 +- crates/biome_js_syntax/src/modifier_ext.rs | 11 +- 7 files changed, 164 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 393a3a5ce8e9..cb178ec18aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,10 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b Contributed by @fireairforce + - [useNamingConvention](https://biomejs.dev/linter/rules/use-naming-convention/) now allows configuring conventions for readonly index signatures. + + Contributed by @sepruko + - [noDuplicateCustomProperties](https://biomejs.dev/linter/rules/no-duplicate-custom-properties/) now correctly handles custom properties and ignores non-custom properties. Previously, the rule incorrectly reported duplicates for all properties, including non-custom ones. Contributed by @togami2864 diff --git a/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs b/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs index ca7ccf3cc432..821962d96cd3 100644 --- a/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs +++ b/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs @@ -21,8 +21,8 @@ use biome_js_syntax::{ AnyJsVariableDeclaration, AnyTsTypeMember, JsFileSource, JsIdentifierBinding, JsLiteralExportName, JsLiteralMemberName, JsMethodModifierList, JsPrivateClassMemberName, JsPropertyModifierList, JsSyntaxKind, JsSyntaxToken, JsVariableDeclarator, JsVariableKind, - Modifier, TsIdentifierBinding, TsLiteralEnumMemberName, TsMethodSignatureModifierList, - TsPropertySignatureModifierList, TsTypeParameterName, + Modifier, TsIdentifierBinding, TsIndexSignatureModifierList, TsLiteralEnumMemberName, + TsMethodSignatureModifierList, TsPropertySignatureModifierList, TsTypeParameterName, }; use biome_rowan::{ declare_node_union, AstNode, BatchMutationExt, SyntaxResult, TextRange, TextSize, @@ -1092,7 +1092,10 @@ impl Selector { } } if self.modifiers.contains(Modifier::Readonly) - && !matches!(self.kind, Kind::ClassProperty | Kind::TypeProperty) + && !matches!( + self.kind, + Kind::ClassProperty | Kind::IndexParameter | Kind::TypeProperty + ) { return Err(InvalidSelector::UnsupportedModifiers( self.kind, @@ -1236,7 +1239,9 @@ impl Selector { | AnyJsClassMember::TsConstructorSignatureClassMember(_) | AnyJsClassMember::JsEmptyClassMember(_) | AnyJsClassMember::JsStaticInitializationBlockClassMember(_) => return None, - AnyJsClassMember::TsIndexSignatureClassMember(_) => Kind::IndexParameter.into(), + AnyJsClassMember::TsIndexSignatureClassMember(getter) => { + Selector::with_modifiers(Kind::IndexParameter, getter.modifiers()) + } AnyJsClassMember::JsGetterClassMember(getter) => { Selector::with_modifiers(Kind::ClassGetter, getter.modifiers()) } @@ -1290,7 +1295,17 @@ impl Selector { | AnyJsBindingDeclaration::JsRestParameter(_) => Some(Kind::FunctionParameter.into()), AnyJsBindingDeclaration::JsCatchDeclaration(_) => Some(Kind::CatchParameter.into()), AnyJsBindingDeclaration::TsPropertyParameter(_) => Some(Kind::ClassProperty.into()), - AnyJsBindingDeclaration::TsIndexSignatureParameter(_) => Some(Kind::IndexParameter.into()), + AnyJsBindingDeclaration::TsIndexSignatureParameter(member_name) => { + if let Some(member) = member_name.parent::<>() { + Selector::from_class_member(&member) + } else if let Some(member) = member_name.parent::() { + Selector::from_type_member(&member) + } else if let Some(member) = member_name.parent::() { + Selector::from_object_member(&member) + } else { + Some(Kind::IndexParameter.into()) + } + }, AnyJsBindingDeclaration::JsNamespaceImportSpecifier(_) => Some(Selector::with_scope(Kind::ImportNamespace, Scope::Global)), AnyJsBindingDeclaration::JsFunctionDeclaration(_) | AnyJsBindingDeclaration::JsFunctionExpression(_) @@ -1384,7 +1399,13 @@ impl Selector { AnyTsTypeMember::JsBogusMember(_) | AnyTsTypeMember::TsCallSignatureTypeMember(_) | AnyTsTypeMember::TsConstructSignatureTypeMember(_) => None, - AnyTsTypeMember::TsIndexSignatureTypeMember(_) => Some(Kind::IndexParameter.into()), + AnyTsTypeMember::TsIndexSignatureTypeMember(property) => { + Some(if property.readonly_token().is_some() { + Selector::with_modifiers(Kind::IndexParameter, Modifier::Readonly) + } else { + Kind::IndexParameter.into() + }) + } AnyTsTypeMember::TsGetterSignatureTypeMember(_) => Some(Kind::TypeGetter.into()), AnyTsTypeMember::TsMethodSignatureTypeMember(_) => Some(Kind::TypeMethod.into()), AnyTsTypeMember::TsPropertySignatureTypeMember(property) => { @@ -1778,6 +1799,11 @@ impl From for Modifiers { Modifiers((&value).into()) } } +impl From for Modifiers { + fn from(value: TsIndexSignatureModifierList) -> Self { + Modifiers((&value).into()) + } +} impl From for Modifiers { fn from(value: TsMethodSignatureModifierList) -> Self { Modifiers((&value).into()) diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts index ac87505b22b0..8d4c9a47cade 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts @@ -4,4 +4,12 @@ export interface X { [CONSTANT_CASE: number]: unknown [snake_case: symbol]: unknown +} + +export interface Y { + readonly [PascalCase: string]: unknown + + readonly [CONSTANT_CASE: number]: unknown + + readonly [snake_case: symbol]: unknown } \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts.snap index a24c9277437a..a23d2fb5573b 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidIndexParameter.ts.snap @@ -11,6 +11,14 @@ export interface X { [snake_case: symbol]: unknown } + +export interface Y { + readonly [PascalCase: string]: unknown + + readonly [CONSTANT_CASE: number]: unknown + + readonly [snake_case: symbol]: unknown +} ``` # Diagnostics @@ -27,11 +35,11 @@ invalidIndexParameter.ts:2:6 lint/style/useNamingConvention FIXABLE ━━━ i Safe fix: Rename this symbol in camelCase. - 1 1 │ export interface X { - 2 │ - ····[PascalCase:·string]:·unknown - 2 │ + ····[pascalCase:·string]:·unknown - 3 3 │ - 4 4 │ [CONSTANT_CASE: number]: unknown + 1 1 │ export interface X { + 2 │ - ····[PascalCase:·string]:·unknown + 2 │ + ····[pascalCase:·string]:·unknown + 3 3 │ + 4 4 │ [CONSTANT_CASE: number]: unknown ``` @@ -50,12 +58,12 @@ invalidIndexParameter.ts:4:6 lint/style/useNamingConvention FIXABLE ━━━ i Safe fix: Rename this symbol in camelCase. - 2 2 │ [PascalCase: string]: unknown - 3 3 │ - 4 │ - ····[CONSTANT_CASE:·number]:·unknown - 4 │ + ····[constantCase:·number]:·unknown - 5 5 │ - 6 6 │ [snake_case: symbol]: unknown + 2 2 │ [PascalCase: string]: unknown + 3 3 │ + 4 │ - ····[CONSTANT_CASE:·number]:·unknown + 4 │ + ····[constantCase:·number]:·unknown + 5 5 │ + 6 6 │ [snake_case: symbol]: unknown ``` @@ -70,14 +78,85 @@ invalidIndexParameter.ts:6:6 lint/style/useNamingConvention FIXABLE ━━━ > 6 │ [snake_case: symbol]: unknown │ ^^^^^^^^^^ 7 │ } + 8 │ + + i Safe fix: Rename this symbol in camelCase. + + 4 4 │ [CONSTANT_CASE: number]: unknown + 5 5 │ + 6 │ - ····[snake_case:·symbol]:·unknown + 6 │ + ····[snakeCase:·symbol]:·unknown + 7 7 │ } + 8 8 │ + + +``` + +``` +invalidIndexParameter.ts:10:15 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This index parameter name should be in camelCase. + + 9 │ export interface Y { + > 10 │ readonly [PascalCase: string]: unknown + │ ^^^^^^^^^^ + 11 │ + 12 │ readonly [CONSTANT_CASE: number]: unknown + + i Safe fix: Rename this symbol in camelCase. + + 8 8 │ + 9 9 │ export interface Y { + 10 │ - ····readonly·[PascalCase:·string]:·unknown + 10 │ + ····readonly·[pascalCase:·string]:·unknown + 11 11 │ + 12 12 │ readonly [CONSTANT_CASE: number]: unknown + + +``` + +``` +invalidIndexParameter.ts:12:15 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This index parameter name should be in camelCase. + + 10 │ readonly [PascalCase: string]: unknown + 11 │ + > 12 │ readonly [CONSTANT_CASE: number]: unknown + │ ^^^^^^^^^^^^^ + 13 │ + 14 │ readonly [snake_case: symbol]: unknown + + i Safe fix: Rename this symbol in camelCase. + + 10 10 │ readonly [PascalCase: string]: unknown + 11 11 │ + 12 │ - ····readonly·[CONSTANT_CASE:·number]:·unknown + 12 │ + ····readonly·[constantCase:·number]:·unknown + 13 13 │ + 14 14 │ readonly [snake_case: symbol]: unknown + + +``` + +``` +invalidIndexParameter.ts:14:15 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This index parameter name should be in camelCase. + + 12 │ readonly [CONSTANT_CASE: number]: unknown + 13 │ + > 14 │ readonly [snake_case: symbol]: unknown + │ ^^^^^^^^^^ + 15 │ } i Safe fix: Rename this symbol in camelCase. - 4 4 │ [CONSTANT_CASE: number]: unknown - 5 5 │ - 6 │ - ····[snake_case:·symbol]:·unknown - 6 │ + ····[snakeCase:·symbol]:·unknown - 7 7 │ } + 12 12 │ readonly [CONSTANT_CASE: number]: unknown + 13 13 │ + 14 │ - ····readonly·[snake_case:·symbol]:·unknown + 14 │ + ····readonly·[snakeCase:·symbol]:·unknown + 15 15 │ } ``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts index 3cb9b1d49c63..57c8fce96666 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts @@ -4,4 +4,12 @@ export interface X { [index: number]: unknown [specialSymbol: symbol]: unknown +} + +export interface Y { + readonly [s: string]: unknown; + + readonly [index: number]: unknown; + + readonly [specialSymbol: symbol]: unknown; } \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts.snap index 857714ec7281..c501d2b68da6 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validIndexParameter.ts.snap @@ -11,6 +11,12 @@ export interface X { [specialSymbol: symbol]: unknown } -``` +export interface Y { + readonly [s: string]: unknown; + + readonly [index: number]: unknown; + readonly [specialSymbol: symbol]: unknown; +} +``` diff --git a/crates/biome_js_syntax/src/modifier_ext.rs b/crates/biome_js_syntax/src/modifier_ext.rs index f39c703cfd41..e10339dd33d0 100644 --- a/crates/biome_js_syntax/src/modifier_ext.rs +++ b/crates/biome_js_syntax/src/modifier_ext.rs @@ -4,7 +4,8 @@ use crate::{ AnyJsMethodModifier, AnyJsPropertyModifier, AnyTsIndexSignatureModifier, AnyTsMethodSignatureModifier, AnyTsPropertyParameterModifier, AnyTsPropertySignatureModifier, AnyTsTypeParameterModifier, JsMethodModifierList, JsPropertyModifierList, JsSyntaxKind, - TsAccessibilityModifier, TsMethodSignatureModifierList, TsPropertySignatureModifierList, + TsAccessibilityModifier, TsIndexSignatureModifierList, TsMethodSignatureModifierList, + TsPropertySignatureModifierList, }; /// Helpful data structure to make the order of modifiers predictable inside the formatter @@ -174,6 +175,14 @@ impl From<&JsPropertyModifierList> for enumflags2::BitFlags { .fold(Self::empty(), |acc, m| acc | m) } } +impl From<&TsIndexSignatureModifierList> for enumflags2::BitFlags { + fn from(value: &TsIndexSignatureModifierList) -> Self { + value + .into_iter() + .map(|m| Modifier::from(&m)) + .fold(Self::empty(), |acc, m| acc | m) + } +} impl From<&TsPropertySignatureModifierList> for enumflags2::BitFlags { fn from(value: &TsPropertySignatureModifierList) -> Self { value