From 477a5f2437545a3297b81c59d2b5dafc3eea1a69 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Tue, 7 May 2024 19:39:22 +0200 Subject: [PATCH] feat(useNamingConvention): add options for custom conventions --- CHANGELOG.md | 36 + Cargo.lock | 30 + Cargo.toml | 5 +- .../src/execute/migrate/eslint_typescript.rs | 7 +- crates/biome_deserialize/Cargo.toml | 4 +- crates/biome_deserialize/src/impls.rs | 30 + crates/biome_js_analyze/Cargo.toml | 5 +- .../lint/correctness/no_unused_variables.rs | 2 +- .../lint/style/use_filenaming_convention.rs | 16 +- .../src/lint/style/use_naming_convention.rs | 1703 ++++++++++++----- crates/biome_js_analyze/src/utils.rs | 1 + crates/biome_js_analyze/src/utils/regex.rs | 197 ++ crates/biome_js_analyze/src/utils/rename.rs | 10 +- crates/biome_js_analyze/src/utils/tests.rs | 6 +- .../invalidCatchParameter.js.snap | 10 - .../useNamingConvention/invalidClass.js.snap | 12 - .../invalidClassGetter.js.snap | 42 +- .../invalidClassMethod.js.snap | 48 +- .../invalidClassProperty.js.snap | 66 +- .../invalidClassSetter.js.snap | 42 +- .../invalidClassStaticGetter.js.snap | 38 +- .../invalidClassStaticMethod.js.snap | 60 +- .../invalidClassStaticSetter.js.snap | 62 +- .../invalidCustomStyle.options.json | 21 + .../useNamingConvention/invalidCustomStyle.ts | 8 + .../invalidCustomStyle.ts.snap | 50 + .../invalidCustomStyleExceptions.options.json | 19 + .../invalidCustomStyleExceptions.ts | 2 + .../invalidCustomStyleExceptions.ts.snap | 28 + ...dCustomStyleUnderscorePrivate.options.json | 23 + .../invalidCustomStyleUnderscorePrivate.ts | 3 + ...nvalidCustomStyleUnderscorePrivate.ts.snap | 24 + .../useNamingConvention/invalidEnum.ts.snap | 8 - .../invalidEnumMember.ts.snap | 10 - .../invalidExportAlias.js.snap | 16 +- .../invalidExportNamespace.js.snap | 8 - .../useNamingConvention/invalidFunction.js | 8 +- .../invalidFunction.js.snap | 104 +- .../invalidFunctionParameter.js.snap | 10 +- .../invalidImportAlias.js.snap | 30 +- .../invalidImportNamespace.js.snap | 8 - .../invalidIndexParameter.ts.snap | 8 - .../invalidInterface.ts.snap | 52 +- .../invalidLocalVariable.js.snap | 20 +- .../invalidNamespace.ts.snap | 42 +- .../invalidNonAscii.js.snap | 4 +- .../invalidObjectGetter.js.snap | 8 - .../invalidObjectMethod.js.snap | 13 +- .../invalidObjectProperty.js.snap | 12 - .../invalidObjectSetter.js.snap | 12 - .../invalidParameterProperty.ts.snap | 14 +- .../invalidSyllabary.js.snap | 4 +- .../invalidTopLevelVariable.ts.snap | 32 +- .../invalidTypeAlias.ts.snap | 36 +- .../invalidTypeGetter.ts.snap | 8 - .../invalidTypeMethod.ts.snap | 13 +- .../invalidTypeParameter.ts.snap | 16 - .../invalidTypeProperty.ts.snap | 14 - .../invalidTypeReadonlyProperty.ts.snap | 12 - .../invalidTypeSetter.ts.snap | 12 - .../malformedOptions.js.snap | 11 +- .../malformedOptions.options.json | 2 +- .../useNamingConvention/malformedSelector.js | 15 + .../malformedSelector.js.snap | 138 ++ .../malformedSelector.options.json | 53 + .../validCustomStyle.options.json | 19 + .../useNamingConvention/validCustomStyle.ts | 1 + .../validCustomStyle.ts.snap | 8 + .../validCustomStyleExceptions.options.json | 19 + .../validCustomStyleExceptions.ts | 1 + .../validCustomStyleExceptions.ts.snap | 8 + ...dCustomStyleUnderscorePrivate.options.json | 24 + .../validCustomStyleUnderscorePrivate.ts | 3 + .../validCustomStyleUnderscorePrivate.ts.snap | 10 + .../validObjectGetter.js.snap | 2 - .../validObjectProperty.js.snap | 2 - .../useNamingConvention/wellformedSelector.js | 41 + .../wellformedSelector.js.snap | 49 + .../wellformedSelector.options.json | 41 + .../src/utils/format_modifiers.rs | 4 +- crates/biome_js_formatter/src/utils/mod.rs | 6 +- crates/biome_js_syntax/Cargo.toml | 1 + crates/biome_js_syntax/src/modifier_ext.rs | 172 +- .../src/file_handlers/javascript.rs | 2 +- ...ing_convention_incorrect_options.json.snap | 4 +- crates/biome_string_case/src/lib.rs | 625 ++++-- .../@biomejs/backend-jsonrpc/src/workspace.ts | 91 +- .../@biomejs/biome/configuration_schema.json | 183 +- 88 files changed, 3222 insertions(+), 1457 deletions(-) create mode 100644 crates/biome_js_analyze/src/utils/regex.rs create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.options.json create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.options.json create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.options.json create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.options.json create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.options.json create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.options.json create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.options.json create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.options.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e1fd12e3a20f..5182130c2108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,42 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b ### Linter +#### New features + +- [useNamingConvention](https://biomejs.dev/linter/rules/use-naming-convention/) now supports an option to enforce custom conventions ([#1900](https://github.com/biomejs/biome/issues/1900)). + + For example, you can enforce the use of a prefix for private class members: + + ```json + { + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "custom": [ + { + "selector": { + "kind": "classMember", + "modifiers": ["private"] + }, + "match": "_(.*)", + "formats": ["camelCase"] + } + ] + } + } + } + } + } + } + ``` + + Please, find more details in the [rule documentation](https://biomejs.dev/linter/rules/use-naming-convention/#conventions). + + Contributed by @Conaclos + #### Bug fixes - `useJsxKeyInIterable` now handles more cases involving fragments. See the snippets below. Contributed by @dyc3 diff --git a/Cargo.lock b/Cargo.lock index babd21700a80..5cc2d9fd9801 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,6 +329,7 @@ dependencies = [ "indexmap 2.2.6", "schemars", "serde", + "smallvec", ] [[package]] @@ -597,9 +598,12 @@ dependencies = [ "biome_suppression", "biome_test_utils", "biome_unicode_table", + "bitflags 2.5.0", + "enumflags2", "insta", "lazy_static", "natord", + "regex", "roaring", "rustc-hash", "schemars", @@ -692,6 +696,7 @@ dependencies = [ "biome_js_factory", "biome_js_parser", "biome_rowan", + "enumflags2", "schemars", "serde", ] @@ -1554,6 +1559,26 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "enumflags2" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.59", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -2882,9 +2907,11 @@ checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309" dependencies = [ "dyn-clone", "indexmap 1.9.3", + "indexmap 2.2.6", "schemars_derive", "serde", "serde_json", + "smallvec", ] [[package]] @@ -3073,6 +3100,9 @@ name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +dependencies = [ + "serde", +] [[package]] name = "socket2" diff --git a/Cargo.toml b/Cargo.toml index 7cbd01d9feea..596c9ffdbe56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,6 +157,7 @@ bpaf = { version = "0.9.9", features = ["derive"] } countme = "3.0.1" crossbeam = "0.8.4" dashmap = "5.4.0" +enumflags2 = "0.7.3" getrandom = "0.2.14" ignore = "0.4.21" indexmap = { version = "2.2.6", features = ["serde"] } @@ -170,12 +171,12 @@ quote = "1.0.36" rayon = "1.8.1" regex = "1.10.4" rustc-hash = "1.1.0" -schemars = "0.8.17" +schemars = { version = "0.8.17", features = ["indexmap2", "smallvec"] } serde = { version = "1.0.200", features = ["derive"] } serde_json = "1.0.116" similar = "2.5.0" slotmap = "1.0.7" -smallvec = { version = "1.10.0", features = ["union", "const_new"] } +smallvec = { version = "1.10.0", features = ["union", "const_new", "serde"] } syn = "1.0.109" termcolor = "1.4.1" tokio = "1.36.0" diff --git a/crates/biome_cli/src/execute/migrate/eslint_typescript.rs b/crates/biome_cli/src/execute/migrate/eslint_typescript.rs index adc9457a2cfc..cda559c01bd1 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_typescript.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_typescript.rs @@ -104,17 +104,18 @@ impl From for use_naming_convention::NamingConventionOp Some(NamingConventionCase::StrictCamel | NamingConventionCase::StrictPascal) ), require_ascii: false, + conventions: Vec::new(), enum_member_case: enum_member_format .and_then(|format| { match format { NamingConventionCase::Camel | NamingConventionCase::StrictCamel => { - Some(use_naming_convention::EnumMemberCase::Camel) + Some(use_naming_convention::Format::Camel) } NamingConventionCase::Pascal | NamingConventionCase::StrictPascal => { - Some(use_naming_convention::EnumMemberCase::Pascal) + Some(use_naming_convention::Format::Pascal) } NamingConventionCase::Upper => { - Some(use_naming_convention::EnumMemberCase::Constant) + Some(use_naming_convention::Format::Constant) } // Biome doesn't support `snake_case` for enum member NamingConventionCase::Snake => None, diff --git a/crates/biome_deserialize/Cargo.toml b/crates/biome_deserialize/Cargo.toml index 30c9c5d67011..869c4191da24 100644 --- a/crates/biome_deserialize/Cargo.toml +++ b/crates/biome_deserialize/Cargo.toml @@ -21,9 +21,11 @@ bitflags = { workspace = true } indexmap = { workspace = true, features = ["serde"] } schemars = { workspace = true, optional = true } serde = { workspace = true } +smallvec = { workspace = true, optional = true } [features] -schema = ["schemars", "schemars/indexmap"] +schema = ["schemars", "schemars/indexmap"] +smallvec = ["dep:smallvec"] [lints] workspace = true diff --git a/crates/biome_deserialize/src/impls.rs b/crates/biome_deserialize/src/impls.rs index 58fb0c3e53bf..4609d200ed56 100644 --- a/crates/biome_deserialize/src/impls.rs +++ b/crates/biome_deserialize/src/impls.rs @@ -14,6 +14,7 @@ use std::{ num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}, ops::Deref, path::PathBuf, + u8, }; /// Type that allows deserializing a string without heap-allocation. @@ -531,6 +532,35 @@ impl Deserializable for Vec { } } +#[cfg(feature = "smallvec")] +impl Deserializable for smallvec::SmallVec<[T; L]> { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + struct Visitor(PhantomData); + impl DeserializationVisitor for Visitor { + type Output = smallvec::SmallVec<[T; L]>; + const EXPECTED_TYPE: VisitableType = VisitableType::ARRAY; + fn visit_array( + self, + values: impl Iterator>, + _range: TextRange, + _name: &str, + diagnostics: &mut Vec, + ) -> Option { + Some( + values + .filter_map(|value| Deserializable::deserialize(&value?, "", diagnostics)) + .collect(), + ) + } + } + value.deserialize(Visitor(PhantomData), name, diagnostics) + } +} + impl Deserializable for HashSet { fn deserialize( value: &impl DeserializableValue, diff --git a/crates/biome_js_analyze/Cargo.toml b/crates/biome_js_analyze/Cargo.toml index 7f8e5ba901b4..2accc30cb4b2 100644 --- a/crates/biome_js_analyze/Cargo.toml +++ b/crates/biome_js_analyze/Cargo.toml @@ -15,7 +15,7 @@ biome_analyze = { workspace = true } biome_aria = { workspace = true } biome_console = { workspace = true } biome_control_flow = { workspace = true } -biome_deserialize = { workspace = true } +biome_deserialize = { workspace = true, features = ["smallvec"] } biome_deserialize_macros = { workspace = true } biome_diagnostics = { workspace = true } biome_js_factory = { workspace = true } @@ -26,8 +26,11 @@ biome_rowan = { workspace = true } biome_string_case = { workspace = true } biome_suppression = { workspace = true } biome_unicode_table = { workspace = true } +bitflags = { workspace = true } +enumflags2 = { workspace = true } lazy_static = { workspace = true } natord = "1.0.9" +regex = { workspace = true } roaring = "0.10.4" rustc-hash = { workspace = true } schemars = { workspace = true, optional = true } diff --git a/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs b/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs index 7d23a65cd8e3..c181047ae506 100644 --- a/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs +++ b/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs @@ -422,7 +422,7 @@ impl Rule for NoUnusedVariables { let new_name = format!("_{}", name_trimmed); let model = ctx.model(); - mutation.rename_node_declaration(model, binding.clone(), &new_name); + mutation.rename_node_declaration(model, binding, &new_name); Some(JsRuleAction { mutation, diff --git a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs index a723af8f5d8e..26b96ee755b7 100644 --- a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs +++ b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs @@ -5,7 +5,7 @@ use biome_analyze::{ use biome_console::markup; use biome_deserialize_macros::Deserializable; use biome_rowan::TextRange; -use biome_string_case::Case; +use biome_string_case::{Case, Cases}; use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; use std::{hash::Hash, str::FromStr}; @@ -128,10 +128,8 @@ impl Rule for UseFilenamingConvention { if !allowed_cases.is_empty() { let trimmed_name = name.trim_matches('_'); let case = Case::identify(trimmed_name, options.strict_case); - for allowed_case in allowed_cases { - if case.is_compatible_with(allowed_case) { - return None; - } + if allowed_cases.contains(case) { + return None; } } if options.filename_cases.0.contains(&FilenameCase::Export) { @@ -166,7 +164,7 @@ impl Rule for UseFilenamingConvention { }, FileNamingConventionState::Filename => { let allowed_cases = options.filename_cases.cases(); - let allowed_case_names = allowed_cases.iter().map(|style| style.to_string()); + let allowed_case_names = allowed_cases.into_iter().map(|case| case.to_string()); let allowed_case_names = if options.filename_cases.0.contains(&FilenameCase::Export) { allowed_case_names .chain(["equal to the name of an export".to_string()]) @@ -209,7 +207,7 @@ impl Rule for UseFilenamingConvention { } } let suggested_filenames = allowed_cases - .iter() + .into_iter() .map(|case| file_name.replacen(trimmed_name, &case.convert(trimmed_name), 1)) // Deduplicate suggestions .collect::>() @@ -299,11 +297,11 @@ impl Default for FilenamingConventionOptions { pub struct FilenameCases(FxHashSet); impl FilenameCases { - fn cases(&self) -> SmallVec<[Case; 3]> { + fn cases(&self) -> Cases { self.0 .iter() .filter_map(|case| Case::try_from(*case).ok()) - .collect() + .fold(Cases::empty(), |acc, case| acc | case) } } 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 7cbd5d487a5f..f1dd965f7ca8 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 @@ -1,9 +1,11 @@ -use std::str::FromStr; +use std::ops::{Deref, Range}; use crate::{ - services::control_flow::AnyJsControlFlowRoot, - services::semantic::Semantic, - utils::rename::{AnyJsRenamableDeclaration, RenameSymbolExtensions}, + services::{control_flow::AnyJsControlFlowRoot, semantic::Semantic}, + utils::{ + regex::RestrictedRegex, + rename::{AnyJsRenamableDeclaration, RenameSymbolExtensions}, + }, JsRuleAction, }; use biome_analyze::{ @@ -11,21 +13,24 @@ use biome_analyze::{ RuleSourceKind, }; use biome_console::markup; +use biome_deserialize::{DeserializableValidator, DeserializationDiagnostic}; use biome_deserialize_macros::Deserializable; use biome_diagnostics::Applicability; -use biome_js_semantic::CanBeImportedExported; +use biome_js_semantic::{CanBeImportedExported, SemanticModel}; use biome_js_syntax::{ - binding_ext::AnyJsBindingDeclaration, inner_string_text, AnyJsClassMember, AnyJsObjectMember, + binding_ext::AnyJsBindingDeclaration, AnyJsClassMember, AnyJsObjectMember, AnyJsVariableDeclaration, AnyTsTypeMember, JsIdentifierBinding, JsLiteralExportName, - JsLiteralMemberName, JsPrivateClassMemberName, JsSyntaxKind, JsSyntaxToken, - JsVariableDeclarator, JsVariableKind, TsEnumMember, TsIdentifierBinding, TsTypeParameterName, + JsLiteralMemberName, JsMethodModifierList, JsPrivateClassMemberName, JsPropertyModifierList, + JsSyntaxKind, JsSyntaxToken, JsVariableDeclarator, JsVariableKind, Modifier, TsEnumMember, + TsIdentifierBinding, TsMethodSignatureModifierList, TsPropertySignatureModifierList, + TsTypeParameterName, }; use biome_rowan::{ - declare_node_union, AstNode, AstNodeList, BatchMutationExt, SyntaxResult, TokenText, + declare_node_union, AstNode, BatchMutationExt, SyntaxResult, TextRange, TextSize, }; -use biome_string_case::Case; +use biome_string_case::{Case, Cases}; use biome_unicode_table::is_js_ident; -use serde::{Deserialize, Serialize}; +use enumflags2::BitFlags; use smallvec::SmallVec; #[cfg(feature = "schemars")] @@ -37,18 +42,20 @@ declare_rule! { /// Enforcing [naming conventions](https://en.wikipedia.org/wiki/Naming_convention_(programming)) helps to keep the codebase consistent, /// and reduces overhead when thinking about the name [case] of a variable. /// + /// The following section describes the default conventions enforced by the rule. + /// /// ## Naming conventions /// /// All names can be prefixed and suffixed by underscores `_` and dollar signs `$`. /// - /// ### Variable names + /// ### Variable-like names /// /// All variables, including function parameters, are in [`camelCase`] or [`PascalCase`]. /// Catch parameters are always in [`camelCase`]. /// - /// Additionally, top-level variables declared as `const` or `var` may be in [`CONSTANT_CASE`]. - /// Top-level variables are declared at module or script level. - /// Variables declared in a TypeScript `module` or `namespace` are also considered top-level. + /// Additionally, global variables declared as `const` or `var` may be in [`CONSTANT_CASE`]. + /// Global variables are declared at module or script level. + /// Variables declared in a TypeScript `namespace` are also considered top-level. /// /// ```js /// function f(param, _unusedParam) { @@ -85,7 +92,9 @@ declare_rule! { /// /// ### Function names /// - /// A `function` name is in [`camelCase`] or [`PascalCase`]. + /// - A `function` name is in [`camelCase`] or [`PascalCase`]. + /// - A global `function` can also be in `UPPERCASE`. + /// This allows supporting the frameworks that require some function to use valid [HTTP method names](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). /// /// ```jsx /// function trimString(s) { /*...*/ } @@ -93,6 +102,8 @@ declare_rule! { /// function Component() { /// return
; /// } + /// + /// export function GET() { /*...*/ } /// ``` /// /// ### TypeScript `enum` names @@ -134,7 +145,7 @@ declare_rule! { /// /// - A `type` alias or an interface name are in [`PascalCase`]. /// - /// - Property and method names in a type are in [`camelCase`]. + /// - Member names of a type are in [`camelCase`]. /// /// - `readonly` property and getter names can also be in [`CONSTANT_CASE`]. /// @@ -166,9 +177,9 @@ declare_rule! { /// type person = { fullName: string }; /// ``` /// - /// ### Literal object property and method names + /// ### Literal object member names /// - /// Literal object property and method names are in [`camelCase`]. + /// - Literal object members are in [`camelCase`]. /// /// ```js /// const alice = { @@ -180,13 +191,13 @@ declare_rule! { /// /// ```js,expect_diagnostic /// const alice = { - /// FULL_NAME: "Alice", + /// full_name: "Alice", /// } /// ``` /// - /// ### Imported and exported module aliases + /// ### Import and export aliases and namespaces /// - /// Imported and exported module aliases are in [`camelCase`] or [`PascalCase`]. + /// Import and export namespaces are in [`camelCase`] or [`PascalCase`]. /// /// ```js /// import * as myLib from "my-lib"; @@ -221,7 +232,7 @@ declare_rule! { /// /// ### TypeScript `namespace` names /// - /// A _TypeScript_ `namespace` name is in [`camelCase`] or in [`PascalCase`]. + /// A _TypeScript_ `namespace` names are in [`camelCase`] or in [`PascalCase`]. /// /// ```ts /// namespace mathExtra { @@ -235,14 +246,25 @@ declare_rule! { /// /// ## Options /// - /// The rule provides two options that are detailed in the following subsections. + /// The rule provides several options that are detailed in the following subsections. /// /// ```json /// { /// "//": "...", /// "options": { /// "strictCase": false, - /// "enumMemberCase": "CONSTANT_CASE" + /// "requireAscii": true, + /// "enumMemberCase": "CONSTANT_CASE", + /// "custon": [ + /// { + /// "selector": { + /// "kind": "memberLike", + /// "modifiers": ["private"] + /// }, + /// "match": "_(.+)", + /// "formats": ["camelCase"] + /// } + /// ] /// } /// } /// ``` @@ -278,10 +300,172 @@ declare_rule! { /// You can enforce another convention by setting `enumMemberCase` option. /// The supported cases are: [`PascalCase`], [`CONSTANT_CASE`], and [`camelCase`]. /// + /// This option will be deprecated in the future. + /// Use the `conventions` option instead. + /// + /// ### conventions + /// + /// The `conventions` option allows applying custom conventions. + /// The option takes an array of conventions. + /// Every convention is an object that includes a `selector` and some requirements (`match` and `formats`). + /// + /// For example, you can enforce the use of [`CONSTANT_CASE`] for global `const` declarations: + /// + /// ```json + /// { + /// "//": "...", + /// "options": { + /// "conventions": [ + /// { + /// "selector": { + /// "kind": "const", + /// "scope": "global" + /// }, + /// "formats": ["CONSTANT_CASE"] + /// } + /// ] + /// } + /// } + /// ``` + /// + /// A selector descibes which decalrations the convention applies to. + /// You can select a decalration based on several criteria: + /// + /// - `kind`: the kind of the declaration among: + /// - `any` (default kind if the kind is unset) + /// - `typeLike`: classes, enums, type aliases, and interfaces + /// - `class` + /// - `enum` + /// - `interface` + /// - `typeAlias` + /// - `function`: named function declarations and expressions + /// - `namespaceLike`: TypeScript namespaces, import and export namespaces (`import * as namspace from`) + /// - `namespace`: TypeScript namespaces + /// - `importNamespace` + /// - `exportNamespace` + /// - `importAlias`: default imports and aliases of named imports + /// - `exportAlias`: aliases of re-exported names + /// - `variable`: const, let, using, and var declarations + /// - `const` + /// - `let` + /// - `var` + /// - `using` + /// - `functionParameter` + /// - `catchParameter` + /// - `indexParameter`: parameters of index signatures + /// = `typeParameter`: generic type parameter + /// - `classMember`: class properties, parameter properties, methods, getters, and setters + /// - `classProperty`: class properties, including parameter properties + /// - `classMethod` + /// - `classGetter` + /// - `classSetter` + /// - `objectLiteralMember`: literal object properties, methods, getters, and setters + /// - `objectLiteralProperty` + /// - `objectLiteralMethod` + /// - `objectLiteralGetter` + /// - `objectLiteralSetter` + /// - `typeMember`: properties, methods, getters, and setters declared in type alaises and interfaces + /// - `typeProperty` + /// - `typeMethod` + /// - `typeGetter` + /// - `typeSetter` + /// - `modifiers`: an array of modifiers among: + /// - `abstract`: applies to class members and classes + /// - `private`: applies to class members + /// - `protected`: applies to class members + /// - `readonly`: applies to class members and type members + /// - `static`: applies to class members + /// - `scope`: where the declaration appears. Allowd values: + /// - `any`: anywhere (default value if the scope is unset) + /// - `global`: the global scope (also includes the namespace scopes) + /// + /// For each declaration, + /// the `conventions` array is traversed until a selector selects the declaration. + /// The requirements of the convention are so verified on the declaration. + /// + /// A convention must set at least one requirement among: + /// + /// - `match`: a regular expression that the name of the declaration must match. + /// If the regular expression captures a part of the name, then this part is checked against `formats`. + /// Only the first capture is tested. Other captures are ignored. + /// - `formats`: the string [case] that the name must follow. + /// The supported cases are: [`PascalCase`], [`CONSTANT_CASE`], [`camelCase`], and [`snake_case`]. + /// + /// If `match` is set and `formats` is unset, + /// then the part of the name captured by the regular expression is forwarded to the next convention of the array. + /// + /// If a declaration is not selected or if a capture is forwarded while there is no more custom conventions, + /// Then the declaration is verified against the default convention. + /// If a forwarded capture is a part of the original name, then underscore and dollar signs are not trimmed. + /// + /// In the following example: + /// + /// = We require `static readonly` class members to be in ["CONSTANT_CASE"]. + /// - We require `private` class members to start with an underscore `_` and to be in [`camelCase`]. + /// - We require global constants to be in ["CONSTANT_CASE"] and + /// we allow these constants to be enclosed by double underscores or to be named `_SPECIAL_`. + /// - We require interfaces to start with `I`, except for interfaces ending with `Error`, + /// and to be in [`PascalCase`]. + /// - All other names follow the default conventions + /// + ///```json5 + /// { + /// "//": "...", + /// "options": { + /// "custon": [ + /// { + /// "selector": { + /// "kind": "classMember", + /// "modifiers": ["static", "readonly"] + /// }, + /// "formats": ["CONSTANT_CASE"] + /// }, + /// { + /// "selector": { + /// "kind": "classMember", + /// "modifiers": ["private"] + /// }, + /// "match": "_(.+)", + /// "formats": ["camelCase"] + /// }, { + /// "selector": { + /// "kind": "const", + /// "scope": "global" + /// }, + /// "match": "__(.+)__|_SPECIAL_|(.+)", + /// "formats": ["CONSTANT_CASE"] + /// }, { + /// "selector": { + /// "kind": "interface" + /// }, + /// "match": "I(.*)|(.*)Error", + /// "formats": ["PascalCase"] + /// } + /// // default conventions + /// ] + /// } + /// } + /// ``` + /// + /// ### Regular expression syntax + /// + /// The `match` option takes a regular expression that supports the following syntaxes: + /// + /// - Greedy quantifiers `*`, `?`, `+`, `{n}`, `{n,m}`, `{n,}`, `{m}` + /// - Non-greedy quantifiers `*?`, `??`, `+?`, `{n}?`, `{n,m}?`, `{n,}?`, `{m}?` + /// - Any character matcher `.` + /// - Character classes `[a-z]`, `[xyz]`, `[^a-z]` + /// - Alternations `|` + /// - Capturing groups `()` + /// - Non-capturing groups `(?:)` + /// - A limited set of escaped characters including all special characters + /// and regular string escape characters `\f`, `\n`, `\r`, `\t`, `\v` + /// /// [case]: https://en.wikipedia.org/wiki/Naming_convention_(programming)#Examples_of_multiple-word_identifier_formats /// [`camelCase`]: https://en.wikipedia.org/wiki/Camel_case /// [`PascalCase`]: https://en.wikipedia.org/wiki/Camel_case /// [`CONSTANT_CASE`]: https://en.wikipedia.org/wiki/Snake_case + /// [`snake_case`]: https://en.wikipedia.org/wiki/Snake_case pub UseNamingConvention { version: "1.0.0", name: "useNamingConvention", @@ -301,157 +485,197 @@ impl Rule for UseNamingConvention { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); let options = ctx.options(); - let element = Named::from_name(node)?; - let allowed_cases = element.allowed_cases(options); - if allowed_cases.is_empty() { - // No naming convention to verify. - return None; - } - let name = node.name().ok()?; - let name = name.text(); - if !is_js_ident(name) { - // ignore non-identifier strings - return None; + let name_token = node.name_token().ok()?; + let mut name = name_token.text_trimmed(); + let mut name_range_start = 0; + if name_token.kind() == JsSyntaxKind::JS_STRING_LITERAL { + name_range_start += 1; + name = &name[1..name.len() - 1]; + if name.is_empty() || !is_js_ident(name) { + // Ignore non-identifier strings + return None; + } } - let trimmed_name = trim_underscore_dollar(name); - if options.require_ascii && !trimmed_name.is_ascii() { + if options.require_ascii && !name.is_ascii() { return Some(State { - element, + convention_selector: Selector::default(), + name_range: Range { + start: 0u16, + end: name.len() as u16, + }, suggestion: Suggestion::Ascii, }); } - let actual_case = Case::identify(trimmed_name, options.strict_case); - if actual_case == Case::Uni - || trimmed_name.is_empty() - || allowed_cases - .iter() - .any(|&expected_style| actual_case.is_compatible_with(expected_style)) + let node_selector = Selector::from_name(node)?; + let mut is_not_trimmed = true; + for convention in options + .conventions + .iter() + .filter(|convention| node_selector.contains(convention.selector)) { + if let Some(matching) = &convention.matching { + let Some(capture) = matching.captures(name) else { + return Some(State { + convention_selector: convention.selector, + name_range: Range { + start: name_range_start as u16, + end: (name_range_start + name.len()) as u16, + }, + suggestion: Suggestion::Match(matching.as_source().to_string()), + }); + }; + if let Some(first_capture) = capture.iter().skip(1).find_map(|x| x) { + name_range_start += first_capture.start(); + let captured = first_capture.as_str(); + is_not_trimmed = name.len() == captured.len(); + name = captured; + if name.is_empty() { + // Empty string are always valid. + return None; + } + } else { + // Match without any capture implies a valid case + return None; + } + } + if !convention.formats.is_empty() { + let actual_case = Case::identify(name, options.strict_case); + if (*convention.formats | Case::Uni).contains(actual_case) { + // Valid case + return None; + } + return Some(State { + convention_selector: convention.selector, + name_range: Range { + start: name_range_start as u16, + end: (name_range_start + name.len()) as u16, + }, + suggestion: Suggestion::Formats(convention.formats.clone()), + }); + } + } + let default_convention = node_selector.default_convention(options); + // We only tim the name if it was not trimmed yet + if is_not_trimmed { + let (prefix_len, trimmed_name) = trim_underscore_dollar(name); + name_range_start += prefix_len; + name = trimmed_name; + } + let actual_case = Case::identify(name, options.strict_case); + if (*default_convention.formats | Case::Uni).contains(actual_case) || name.is_empty() { // Valid case return None; } - let preferred_case = element.allowed_cases(ctx.options())[0]; - let new_trimmed_name = preferred_case.convert(trimmed_name); - if trimmed_name != new_trimmed_name { - Some(State { - element, - suggestion: Suggestion::Name(name.replacen(trimmed_name, &new_trimmed_name, 1)), - }) - } else { - Some(State { - element, - suggestion: Suggestion::None, - }) - } + Some(State { + convention_selector: default_convention.selector, + name_range: Range { + start: name_range_start as u16, + end: (name_range_start + name.len()) as u16, + }, + suggestion: Suggestion::Formats(default_convention.formats.clone()), + }) } fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let State { - element, + convention_selector, + name_range, suggestion, } = state; let options = ctx.options(); - let name = ctx.query().name().ok()?; - let name = name.text(); - let trimmed_name = trim_underscore_dollar(name); - let allowed_cases = element.allowed_cases(ctx.options()); - let allowed_case_names = allowed_cases - .iter() - .map(|style| style.to_string()) - .collect::>() - .join(" or "); - let trimmed_info = if name != trimmed_name { - markup! {" trimmed as `"{trimmed_name}"`"}.to_owned() + let node = ctx.query(); + let name_token = node.name_token().ok()?; + let name_token_range = name_token.text_trimmed_range(); + let name = name_token.text_trimmed(); + let trimmed_info = if name_range.len() < name.len() { + " part" } else { - markup! {""}.to_owned() + "" }; - if matches!(suggestion, Suggestion::Ascii) { - return Some(RuleDiagnostic::new( - rule_category!(), - ctx.query().syntax().text_trimmed_range(), - markup! { - "This "{element.to_string()}" name"{trimmed_info}" should be in ASCII because ""requireAscii""` is set to `true`." - }, - ).note(markup! { - "If you want to use non-ASCII names, then set the ""requireAscii"" option to `false`.\nSee the rule ""options"" for more details." - })); - } - if options.strict_case { - let case_type = Case::identify(name, false); - let case_strict = Case::identify(name, true); - if case_type == Case::Camel && case_strict == Case::Unknown { - return Some(RuleDiagnostic::new( + match suggestion { + Suggestion::Ascii => { + Some(RuleDiagnostic::new( rule_category!(), - ctx.query().syntax().text_trimmed_range(), + name_token_range, markup! { - "Two consecutive uppercase characters are not allowed in camelCase and PascalCase because ""strictCase"" is set to `true`." + "This "{format_args!("{convention_selector}")}" name should be in ASCII because ""requireAscii"" is set to `true`." }, ).note(markup! { - "If you want to use consecutive uppercase characters in camelCase and PascalCase, then set the ""strictCase"" option to `false`.\nSee the rule ""options"" for more details." - })); + "If you want to use non-ASCII names, then set the ""requireAscii"" option to `false`.\nSee the rule ""options"" for more details." + })) } - } - let diagnostic = RuleDiagnostic::new( - rule_category!(), - ctx.query().syntax().text_trimmed_range(), - markup! { - "This "{element.to_string()}" name"{trimmed_info}" should be in "{allowed_case_names}"." + Suggestion::Match(regex) => { + let name_token_range = name_token_range.add_start(TextSize::from(name_range.start as u32)).sub_end(name_token_range.len() - TextSize::from(name_range.len() as u32)); + Some(RuleDiagnostic::new( + rule_category!(), + name_token_range, + markup! { + "This "{format_args!("{convention_selector}")}" name"{trimmed_info}" should match the following regex ""/"{regex}"/""." + }, + )) + } + Suggestion::Formats(expected_cases) => { + let name_token_range = TextRange::at(name_token_range.start() + TextSize::from(name_range.start as u32), TextSize::from(name_range.len() as u32)); + if options.strict_case && (expected_cases.contains(Case::Camel) || expected_cases.contains(Case::Pascal)) { + let trimmed_name = &name[(name_range.start as _)..(name_range.end as _)]; + let actual_case = Case::identify(trimmed_name, false); + if matches!(actual_case, Case::Camel | Case::Pascal) + && Case::identify(trimmed_name, true) == Case::Unknown + { + return Some(RuleDiagnostic::new( + rule_category!(), + name_token_range, + markup! { + "Two consecutive uppercase characters are not allowed in "{format_args!("{actual_case}")}" because ""strictCase"" is set to `true`." + }, + ).note(markup! { + "If you want to use consecutive uppercase characters in "{format_args!("{actual_case}")}", then set the ""strictCase"" option to `false`.\nSee the rule ""options"" for more details." + })); + } + } + let expected_case_names = expected_cases + .into_iter() + .map(|case| case.to_string()) + .collect::>() + .join(" or "); + Some(RuleDiagnostic::new( + rule_category!(), + name_token_range, + markup! { + "This "{format_args!("{convention_selector}")}" name"{trimmed_info}" should be in "{expected_case_names}"." + }, + )) }, - ); - Some(if let Suggestion::Name(suggested_name) = suggestion { - diagnostic.note(markup! { - "The name could be renamed to `"{suggested_name}"`." - }) - } else { - diagnostic - }) + } } fn action(ctx: &RuleContext, state: &Self::State) -> Option { let State { - element, - suggestion, - } = state; - let Suggestion::Name(suggested_name) = suggestion else { + name_range, + suggestion: Suggestion::Formats(expected_cases), + .. + } = state + else { return None; }; let node = ctx.query(); let model = ctx.model(); - let mut mutation = ctx.root().begin(); - let renamable = match node { - AnyIdentifierBindingLike::JsIdentifierBinding(binding) => { - if binding.is_exported(model) { - return None; - } - // Property parameters are also class properties. - // Shorthand binding patterns such as `const { a_a } = x;` should not be renamed. - // Shorthand named import specifiers such as `import { a_a } from "mod";` should not be renamed. - if let Some( - AnyJsBindingDeclaration::TsPropertyParameter(_) - | AnyJsBindingDeclaration::JsObjectBindingPatternShorthandProperty(_) - | AnyJsBindingDeclaration::JsShorthandNamedImportSpecifier(_), - ) = binding.declaration() - { - return None; - } - - Some(AnyJsRenamableDeclaration::JsIdentifierBinding( - binding.clone(), - )) - } - AnyIdentifierBindingLike::TsIdentifierBinding(binding) => { - if binding.is_exported(model) { - return None; - } - Some(AnyJsRenamableDeclaration::TsIdentifierBinding( - binding.clone(), - )) - } - _ => None, - }; - if let Some(renamable) = renamable { - let preferred_case = element.allowed_cases(ctx.options())[0]; - let renamed = mutation.rename_any_renamable_node(model, renamable, &suggested_name[..]); + if let Some(renamable) = renamable(node, model) { + let node = ctx.query(); + let name_token = &node.name_token().ok()?; + // This assertion hold because only identifiers are renamable. + debug_assert!(name_token.kind() != JsSyntaxKind::JS_STRING_LITERAL); + let name = name_token.text_trimmed(); + let preferred_case = expected_cases.into_iter().next()?; + let new_name_part = + preferred_case.convert(&name[(name_range.start as _)..(name_range.end as _)]); + let mut new_name = + String::with_capacity(name.len() + new_name_part.len() - name_range.len()); + new_name.push_str(&name[..(name_range.start as _)]); + new_name.push_str(&new_name_part); + new_name.push_str(&name[(name_range.end as _)..]); + let mut mutation = ctx.root().begin(); + let renamed = mutation.rename_any_renamable_node(model, &renamable, &new_name[..]); if renamed { return Some(JsRuleAction { category: ActionCategory::QuickFix, @@ -491,30 +715,66 @@ impl AnyIdentifierBindingLike { } } } - - fn name(&self) -> SyntaxResult { - Ok(inner_string_text(&self.name_token()?)) - } } #[derive(Debug)] pub struct State { - element: Named, + // Selector of the convention which is not fullfilled. + convention_selector: Selector, + // Range of the name where the suggestion applies + name_range: Range, suggestion: Suggestion, } #[derive(Debug)] pub enum Suggestion { - /// No suggestion - None, - /// Suggest aa new name - Name(String), - /// Use only ASCII + /// Use only ASCII characters Ascii, + /// Use a name that matches this regex + Match(String), + /// Use a name that follows one of these formats + Formats(Formats), +} + +fn renamable( + node: &AnyIdentifierBindingLike, + model: &SemanticModel, +) -> Option { + match node { + AnyIdentifierBindingLike::JsIdentifierBinding(binding) => { + if binding.is_exported(model) { + return None; + } + // Property parameters are also class properties. + // Shorthand binding patterns such as `const { a_a } = x;` should not be renamed. + // Shorthand named import specifiers such as `import { a_a } from "mod";` should not be renamed. + if let Some( + AnyJsBindingDeclaration::TsPropertyParameter(_) + | AnyJsBindingDeclaration::JsObjectBindingPatternShorthandProperty(_) + | AnyJsBindingDeclaration::JsShorthandNamedImportSpecifier(_), + ) = binding.declaration() + { + return None; + } + + Some(AnyJsRenamableDeclaration::JsIdentifierBinding( + binding.clone(), + )) + } + AnyIdentifierBindingLike::TsIdentifierBinding(binding) => { + if binding.is_exported(model) { + return None; + } + Some(AnyJsRenamableDeclaration::TsIdentifierBinding( + binding.clone(), + )) + } + _ => None, + } } /// Rule's options. -#[derive(Debug, Clone, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[derive(Debug, Clone, Deserializable, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct NamingConventionOptions { @@ -527,282 +787,298 @@ pub struct NamingConventionOptions { #[serde(default, skip_serializing_if = "is_default")] pub require_ascii: bool, + /// Custom conventions. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub conventions: Vec, + /// Allowed cases for _TypeScript_ `enum` member names. #[serde(default, skip_serializing_if = "is_default")] - pub enum_member_case: EnumMemberCase, + pub enum_member_case: Format, +} +impl Default for NamingConventionOptions { + fn default() -> Self { + Self { + strict_case: true, + require_ascii: false, + conventions: Vec::new(), + enum_member_case: Format::default(), + } + } } const fn enabled() -> bool { true } - const fn is_enabled(value: &bool) -> bool { *value } - fn is_default(value: &T) -> bool { value == &T::default() } -impl Default for NamingConventionOptions { - fn default() -> Self { - Self { - strict_case: true, - require_ascii: false, - enum_member_case: EnumMemberCase::default(), +#[derive( + Clone, Debug, Default, Deserializable, Eq, PartialEq, serde::Deserialize, serde::Serialize, +)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[serde(deny_unknown_fields)] +#[deserializable(with_validator)] +pub struct Convention { + /// Declarations concerned by this convention + #[serde(default, skip_serializing_if = "is_default")] + selector: Selector, + + /// Regular expression to enforce + #[serde(default, rename = "match", skip_serializing_if = "Option::is_none")] + matching: Option, + + /// String cases to enforce + #[serde(default, skip_serializing_if = "is_default")] + formats: Formats, +} + +impl DeserializableValidator for Convention { + fn validate( + &mut self, + _name: &str, + range: biome_rowan::TextRange, + diagnostics: &mut Vec, + ) -> bool { + if self.formats.is_empty() && self.matching.is_none() { + diagnostics.push( + DeserializationDiagnostic::new( + "At least one field among `format` and `match` must be set.", + ) + .with_range(range), + ); + false + } else { + true } } } -/// Supported cases for TypeScript `enum` member names. -#[derive(Clone, Copy, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[derive( + Clone, Copy, Debug, Default, Deserializable, Eq, PartialEq, serde::Deserialize, serde::Serialize, +)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -pub enum EnumMemberCase { - /// PascalCase - #[serde(rename = "PascalCase")] - #[default] - Pascal, +#[deserializable(with_validator)] +#[serde(deny_unknown_fields)] +struct Selector { + /// Declaration kind + #[serde(default, skip_serializing_if = "is_default")] + kind: Kind, - /// CONSTANT_CASE - #[serde(rename = "CONSTANT_CASE")] - Constant, + /// Modifiers used on the declaration + #[serde(default, skip_serializing_if = "is_default")] + modifiers: Modifiers, - /// camelCase - #[serde(rename = "camelCase")] - Camel, + /// Scope of the declaration + #[serde(default, skip_serializing_if = "is_default")] + scope: Scope, } -impl FromStr for EnumMemberCase { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "PascalCase" => Ok(Self::Pascal), - "CONSTANT_CASE" => Ok(Self::Constant), - "camelCase" => Ok(Self::Camel), - _ => Err("Value not supported for enum member case"), +impl DeserializableValidator for Selector { + fn validate( + &mut self, + _name: &str, + range: biome_rowan::TextRange, + diagnostics: &mut Vec, + ) -> bool { + let accessibility = Modifier::Private | Modifier::Protected; + let class_member_modifiers = accessibility | Modifier::Static; + if self.modifiers.intersects(class_member_modifiers) { + if self.modifiers.0 & accessibility == accessibility { + diagnostics.push( + DeserializationDiagnostic::new( + "The `private` and `protected` modifiers cannot be used toghether.", + ) + .with_range(range), + ); + return false; + } + if !Kind::ClassMember.contains(self.kind) { + let modifier = self.modifiers.0 & class_member_modifiers; + diagnostics.push( + DeserializationDiagnostic::new(format_args!( + "The `{modifier}` modifier can only be used on class member kinds." + )) + .with_range(range), + ); + return false; + } } + if self.modifiers.contains(Modifier::Abstract) { + if self.kind != Kind::Class && !Kind::ClassMember.contains(self.kind) { + diagnostics.push( + DeserializationDiagnostic::new( + "The `abstract` modifier can only be used on classes and class member kinds." + ) + .with_range(range), + ); + return false; + } + if self.modifiers.contains(Modifier::Static) { + diagnostics.push( + DeserializationDiagnostic::new( + "The `abstract` and `static` modifiers cannot be used toghether.", + ) + .with_range(range), + ); + return false; + } + } + if self.modifiers.contains(Modifier::Readonly) + && !matches!(self.kind, Kind::ClassProperty | Kind::TypeProperty) + { + diagnostics.push( + DeserializationDiagnostic::new( + "The `readonly` modifier can only be used on class and type property kinds.", + ) + .with_range(range), + ); + return false; + } + if self.scope == Scope::Global + && !Kind::Variable.contains(self.kind) + && !Kind::Function.contains(self.kind) + && !Kind::TypeLike.contains(self.kind) + { + diagnostics.push( + DeserializationDiagnostic::new( + "The `global` scope can only be used on type and variable kinds.", + ) + .with_range(range), + ); + return false; + } + true } } -impl From for Case { - fn from(case: EnumMemberCase) -> Case { - match case { - EnumMemberCase::Pascal => Case::Pascal, - EnumMemberCase::Constant => Case::Constant, - EnumMemberCase::Camel => Case::Camel, +impl From for Selector { + fn from(kind: Kind) -> Self { + Self { + kind, + modifiers: Modifiers::default(), + scope: Scope::Any, } } } - -/// Named elements with an attached naming convention. -/// -/// [Named::from_name] enables to get the element from an [AnyName]. -/// [Named::allowed_cases] enables to get a list of allowed cases for a given element. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -enum Named { - CatchParameter, - Class, - ClassGetter, - ClassMethod, - ClassProperty, - ClassSetter, - ClassStaticGetter, - ClassStaticMethod, - ClassStaticProperty, - ClassStaticSetter, - DestructuredObjectMember, - Enum, - EnumMember, - Export, - ExportAlias, - ExportNamespace, - Function, - FunctionParameter, - Import, - ImportAlias, - ImportNamespace, - IndexParameter, - Interface, - LocalConst, - LocalLet, - LocalVar, - LocalUsing, - LocalVariable, - Namespace, - ObjectGetter, - ObjectMethod, - ObjectProperty, - ObjectSetter, - ParameterProperty, - TopLevelConst, - TopLevelLet, - TopLevelVar, - TypeAlias, - TypeGetter, - TypeMethod, - TypeProperty, - TypeReadonlyProperty, - TypeSetter, - TypeParameter, +impl std::fmt::Display for Selector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}{}{}", self.scope, self.modifiers, self.kind) + } } +impl Selector { + fn with_modifiers(kind: Kind, modifiers: impl Into) -> Self { + Self { + kind, + modifiers: modifiers.into(), + ..Default::default() + } + } + + fn with_scope(kind: Kind, scope: Scope) -> Self { + Self { + kind, + scope, + ..Default::default() + } + } -impl Named { - fn from_name(js_name: &AnyIdentifierBindingLike) -> Option { + fn from_name(js_name: &AnyIdentifierBindingLike) -> Option { match js_name { AnyIdentifierBindingLike::JsIdentifierBinding(binding) => { - Named::from_binding_declaration(&binding.declaration()?) + Selector::from_binding_declaration(&binding.declaration()?) } AnyIdentifierBindingLike::TsIdentifierBinding(binding) => { - Named::from_binding_declaration(&binding.declaration()?) + Selector::from_binding_declaration(&binding.declaration()?) } AnyIdentifierBindingLike::JsLiteralMemberName(member_name) => { if let Some(member) = member_name.parent::() { - Named::from_class_member(&member) + Selector::from_class_member(&member) } else if let Some(member) = member_name.parent::() { - Named::from_type_member(&member) + Selector::from_type_member(&member) } else if let Some(member) = member_name.parent::() { - Named::from_object_member(&member) + Selector::from_object_member(&member) } else if member_name.parent::().is_some() { - Some(Named::EnumMember) + Some(Kind::EnumMember.into()) } else { None } } AnyIdentifierBindingLike::JsPrivateClassMemberName(member_name) => { - Named::from_class_member(&member_name.parent::()?) + Selector::from_class_member(&member_name.parent::()?) } AnyIdentifierBindingLike::JsLiteralExportName(export_name) => { let parent = export_name.syntax().parent()?; match parent.kind() { - JsSyntaxKind::JS_NAMED_IMPORT_SPECIFIER => Some(Named::Import), - JsSyntaxKind::JS_EXPORT_NAMED_FROM_SPECIFIER => Some(Named::Export), - JsSyntaxKind::JS_EXPORT_NAMED_SPECIFIER => Some(Named::ExportAlias), + JsSyntaxKind::JS_NAMED_IMPORT_SPECIFIER + | JsSyntaxKind::JS_EXPORT_NAMED_FROM_SPECIFIER => None, + JsSyntaxKind::JS_EXPORT_NAMED_SPECIFIER => Some(Kind::ExportAlias.into()), JsSyntaxKind::JS_EXPORT_AS_CLAUSE => { if parent.parent()?.kind() == JsSyntaxKind::JS_EXPORT_FROM_CLAUSE { - Some(Named::ExportNamespace) + Some(Kind::ExportNamespace.into()) } else { - Some(Named::ExportAlias) + Some(Kind::ExportAlias.into()) } } _ => None, } } - AnyIdentifierBindingLike::TsTypeParameterName(_) => Some(Named::TypeParameter), + AnyIdentifierBindingLike::TsTypeParameterName(_) => Some(Kind::TypeParameter.into()), } } - fn from_class_member(member: &AnyJsClassMember) -> Option { - match member { + fn from_class_member(member: &AnyJsClassMember) -> Option { + let Selector { + kind, + modifiers, + scope, + } = match member { AnyJsClassMember::JsBogusMember(_) | AnyJsClassMember::JsConstructorClassMember(_) | AnyJsClassMember::TsConstructorSignatureClassMember(_) | AnyJsClassMember::JsEmptyClassMember(_) - | AnyJsClassMember::JsStaticInitializationBlockClassMember(_) => None, - AnyJsClassMember::TsIndexSignatureClassMember(_) => Some(Named::IndexParameter), + | AnyJsClassMember::JsStaticInitializationBlockClassMember(_) => return None, + AnyJsClassMember::TsIndexSignatureClassMember(_) => Kind::IndexParameter.into(), AnyJsClassMember::JsGetterClassMember(getter) => { - let is_static = getter - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticGetter - } else { - Named::ClassGetter - }) + Selector::with_modifiers(Kind::ClassGetter, getter.modifiers()) } AnyJsClassMember::TsGetterSignatureClassMember(getter) => { - let is_static = getter - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticGetter - } else { - Named::ClassGetter - }) + Selector::with_modifiers(Kind::ClassGetter, getter.modifiers()) } AnyJsClassMember::JsMethodClassMember(method) => { - let is_static = method - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticMethod - } else { - Named::ClassMethod - }) + Selector::with_modifiers(Kind::ClassMethod, method.modifiers()) } AnyJsClassMember::TsMethodSignatureClassMember(method) => { - let is_static = method - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticMethod - } else { - Named::ClassMethod - }) + Selector::with_modifiers(Kind::ClassMethod, method.modifiers()) } AnyJsClassMember::JsPropertyClassMember(property) => { - let is_static = property - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticProperty - } else { - Named::ClassProperty - }) + Selector::with_modifiers(Kind::ClassProperty, property.modifiers()) } AnyJsClassMember::TsPropertySignatureClassMember(property) => { - let is_static = property - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticProperty - } else { - Named::ClassProperty - }) + Selector::with_modifiers(Kind::ClassProperty, property.modifiers()) } AnyJsClassMember::TsInitializedPropertySignatureClassMember(property) => { - let is_static = property - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticProperty - } else { - Named::ClassProperty - }) + Selector::with_modifiers(Kind::ClassProperty, property.modifiers()) } AnyJsClassMember::JsSetterClassMember(setter) => { - let is_static = setter - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticSetter - } else { - Named::ClassSetter - }) + Selector::with_modifiers(Kind::ClassSetter, setter.modifiers()) } AnyJsClassMember::TsSetterSignatureClassMember(setter) => { - let is_static = setter - .modifiers() - .iter() - .any(|modifier| modifier.as_js_static_modifier().is_some()); - Some(if is_static { - Named::ClassStaticSetter - } else { - Named::ClassSetter - }) + Selector::with_modifiers(Kind::ClassSetter, setter.modifiers()) } - } + }; + // Ignore explicitly overrided members + (!modifiers.contains(Modifier::Override)).then_some(Selector { + kind, + modifiers, + scope, + }) } - fn from_binding_declaration(decl: &AnyJsBindingDeclaration) -> Option { + fn from_binding_declaration(decl: &AnyJsBindingDeclaration) -> Option { match decl { AnyJsBindingDeclaration::JsArrayBindingPatternElement(_) | AnyJsBindingDeclaration::JsArrayBindingPatternRestElement(_) @@ -810,41 +1086,39 @@ impl Named { | AnyJsBindingDeclaration::JsObjectBindingPatternRest(_) => { Self::from_parent_binding_pattern_declaration(decl.parent_binding_pattern_declaration()?) } - AnyJsBindingDeclaration::JsObjectBindingPatternShorthandProperty(_) => { - Some(Named::DestructuredObjectMember) - } AnyJsBindingDeclaration::JsVariableDeclarator(var) => { - Named::from_variable_declarator(var) + Selector::from_variable_declarator(var, Scope::from_declaration(decl)?) } AnyJsBindingDeclaration::JsArrowFunctionExpression(_) | AnyJsBindingDeclaration::JsBogusParameter(_) | AnyJsBindingDeclaration::JsFormalParameter(_) - | AnyJsBindingDeclaration::JsRestParameter(_) => Some(Named::FunctionParameter), - AnyJsBindingDeclaration::JsCatchDeclaration(_) => Some(Named::CatchParameter), - AnyJsBindingDeclaration::TsPropertyParameter(_) => Some(Named::ParameterProperty), - AnyJsBindingDeclaration::TsIndexSignatureParameter(_) => Some(Named::IndexParameter), - AnyJsBindingDeclaration::JsNamespaceImportSpecifier(_) => Some(Named::ImportNamespace), + | 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::JsNamespaceImportSpecifier(_) => Some(Selector::with_scope(Kind::ImportNamespace, Scope::Global)), AnyJsBindingDeclaration::JsFunctionDeclaration(_) | AnyJsBindingDeclaration::JsFunctionExpression(_) | AnyJsBindingDeclaration::JsFunctionExportDefaultDeclaration(_) | AnyJsBindingDeclaration::TsDeclareFunctionDeclaration(_) | AnyJsBindingDeclaration::TsDeclareFunctionExportDefaultDeclaration(_) => { - Some(Named::Function) + Some(Selector::with_scope(Kind::Function, Scope::from_declaration(decl)?)) } AnyJsBindingDeclaration::TsImportEqualsDeclaration(_) | AnyJsBindingDeclaration::JsDefaultImportSpecifier(_) - | AnyJsBindingDeclaration::JsNamedImportSpecifier(_) => Some(Named::ImportAlias), - AnyJsBindingDeclaration::TsModuleDeclaration(_) => Some(Named::Namespace), - AnyJsBindingDeclaration::TsTypeAliasDeclaration(_) => Some(Named::TypeAlias), + | AnyJsBindingDeclaration::JsNamedImportSpecifier(_) => Some(Selector::with_scope(Kind::ImportAlias, Scope::Global)), + AnyJsBindingDeclaration::TsModuleDeclaration(_) => Some(Selector::with_scope(Kind::Namespace, Scope::Global)), + AnyJsBindingDeclaration::TsTypeAliasDeclaration(_) => Some(Selector::with_scope(Kind::TypeAlias, Scope::from_declaration(decl)?)), AnyJsBindingDeclaration::JsClassDeclaration(_) | AnyJsBindingDeclaration::JsClassExpression(_) - | AnyJsBindingDeclaration::JsClassExportDefaultDeclaration(_) => Some(Named::Class), - AnyJsBindingDeclaration::TsInterfaceDeclaration(_) => Some(Named::Interface), - AnyJsBindingDeclaration::TsEnumDeclaration(_) => Some(Named::Enum), - AnyJsBindingDeclaration::JsShorthandNamedImportSpecifier(_) => { - Some(Named::Import) - } - AnyJsBindingDeclaration::JsBogusNamedImportSpecifier(_) + | AnyJsBindingDeclaration::JsClassExportDefaultDeclaration(_) => { + Some(Selector::with_scope(Kind::Class, Scope::from_declaration(decl)?)) + }, + AnyJsBindingDeclaration::TsInterfaceDeclaration(_) => Some(Selector::with_scope(Kind::Interface, Scope::from_declaration(decl)?)), + AnyJsBindingDeclaration::TsEnumDeclaration(_) => Some(Selector::with_scope(Kind::Enum, Scope::from_declaration(decl)?)), + AnyJsBindingDeclaration::JsObjectBindingPatternShorthandProperty(_) + | AnyJsBindingDeclaration::JsShorthandNamedImportSpecifier(_) + | AnyJsBindingDeclaration::JsBogusNamedImportSpecifier(_) // Type parameters should be handled at call site | AnyJsBindingDeclaration::TsInferType(_) | AnyJsBindingDeclaration::TsMappedType(_) @@ -852,178 +1126,615 @@ impl Named { } } - fn from_parent_binding_pattern_declaration(decl: AnyJsBindingDeclaration) -> Option { + fn from_parent_binding_pattern_declaration(decl: AnyJsBindingDeclaration) -> Option { + let scope = Scope::from_declaration(&decl)?; if let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) = decl { - Named::from_variable_declarator(&declarator) + Selector::from_variable_declarator(&declarator, scope) } else { - Some(Named::LocalVariable) + Some(Selector::with_scope(Kind::Variable, scope)) } } - fn from_variable_declarator(var: &JsVariableDeclarator) -> Option { - let is_top_level_level = var - .syntax() - .ancestors() - .find(|x| AnyJsControlFlowRoot::can_cast(x.kind())) - .is_some_and(|x| { - matches!( - x.kind(), - JsSyntaxKind::JS_MODULE - | JsSyntaxKind::JS_SCRIPT - | JsSyntaxKind::TS_MODULE_DECLARATION - | JsSyntaxKind::TS_EXTERNAL_MODULE_DECLARATION - ) - }); + fn from_variable_declarator(var: &JsVariableDeclarator, scope: Scope) -> Option { let var_declaration = var .syntax() .ancestors() .find_map(AnyJsVariableDeclaration::cast)?; let var_kind = var_declaration.variable_kind().ok()?; - Some(match (var_kind, is_top_level_level) { - (JsVariableKind::Const, false) => Named::LocalConst, - (JsVariableKind::Let, false) => Named::LocalLet, - (JsVariableKind::Var, false) => Named::LocalVar, - (JsVariableKind::Using, false) => Named::LocalUsing, - (JsVariableKind::Const, true) => Named::TopLevelConst, - (JsVariableKind::Let, true) => Named::TopLevelLet, - (JsVariableKind::Var, true) => Named::TopLevelVar, - (JsVariableKind::Using, true) => Named::LocalUsing, - }) + let kind = match var_kind { + JsVariableKind::Const => Kind::Const, + JsVariableKind::Let => Kind::Let, + JsVariableKind::Using => Kind::Using, + JsVariableKind::Var => Kind::Var, + }; + Some(Selector::with_scope(kind, scope)) } - fn from_object_member(member: &AnyJsObjectMember) -> Option { + fn from_object_member(member: &AnyJsObjectMember) -> Option { match member { AnyJsObjectMember::JsBogusMember(_) | AnyJsObjectMember::JsSpread(_) => None, - AnyJsObjectMember::JsGetterObjectMember(_) => Some(Named::ObjectGetter), - AnyJsObjectMember::JsMethodObjectMember(_) => Some(Named::ObjectMethod), + AnyJsObjectMember::JsGetterObjectMember(_) => Some(Kind::ObjectLiteralGetter.into()), + AnyJsObjectMember::JsMethodObjectMember(_) => Some(Kind::ObjectLiteralMethod.into()), AnyJsObjectMember::JsPropertyObjectMember(_) - | AnyJsObjectMember::JsShorthandPropertyObjectMember(_) => Some(Named::ObjectProperty), - AnyJsObjectMember::JsSetterObjectMember(_) => Some(Named::ObjectSetter), + | AnyJsObjectMember::JsShorthandPropertyObjectMember(_) => { + Some(Kind::ObjectLiteralProperty.into()) + } + AnyJsObjectMember::JsSetterObjectMember(_) => Some(Kind::ObjectLiteralSetter.into()), } } - fn from_type_member(member: &AnyTsTypeMember) -> Option { + fn from_type_member(member: &AnyTsTypeMember) -> Option { match member { AnyTsTypeMember::JsBogusMember(_) | AnyTsTypeMember::TsCallSignatureTypeMember(_) | AnyTsTypeMember::TsConstructSignatureTypeMember(_) => None, - AnyTsTypeMember::TsIndexSignatureTypeMember(_) => Some(Named::IndexParameter), - AnyTsTypeMember::TsGetterSignatureTypeMember(_) => Some(Named::TypeGetter), - AnyTsTypeMember::TsMethodSignatureTypeMember(_) => Some(Named::TypeMethod), + AnyTsTypeMember::TsIndexSignatureTypeMember(_) => Some(Kind::IndexParameter.into()), + AnyTsTypeMember::TsGetterSignatureTypeMember(_) => Some(Kind::TypeGetter.into()), + AnyTsTypeMember::TsMethodSignatureTypeMember(_) => Some(Kind::TypeMethod.into()), AnyTsTypeMember::TsPropertySignatureTypeMember(property) => { Some(if property.readonly_token().is_some() { - Named::TypeReadonlyProperty + Selector::with_modifiers(Kind::TypeProperty, Modifier::Readonly) } else { - Named::TypeProperty + Kind::TypeProperty.into() }) } - AnyTsTypeMember::TsSetterSignatureTypeMember(_) => Some(Named::TypeSetter), + AnyTsTypeMember::TsSetterSignatureTypeMember(_) => Some(Kind::TypeSetter.into()), } } - /// Returns the list of allowed [Case] for `self`. + /// Returns the list of default [Case] for `self`. /// The preferred case comes first in the list. - fn allowed_cases(self, options: &NamingConventionOptions) -> SmallVec<[Case; 3]> { - match self { - Named::CatchParameter - | Named::ClassGetter - | Named::ClassMethod - | Named::ClassProperty - | Named::ClassSetter - | Named::ClassStaticMethod - | Named::ClassStaticSetter - | Named::IndexParameter - | Named::ObjectGetter - | Named::ObjectMethod - | Named::ObjectProperty - | Named::ObjectSetter - | Named::ParameterProperty - | Named::TypeMethod - | Named::TypeProperty - | Named::TypeSetter => SmallVec::from_slice(&[Case::Camel]), - Named::Class - | Named::Enum - | Named::Interface - | Named::TypeAlias - | Named::TypeParameter => SmallVec::from_slice(&[Case::Pascal]), - Named::ClassStaticGetter - | Named::ClassStaticProperty - | Named::TypeReadonlyProperty - | Named::TypeGetter => SmallVec::from_slice(&[Case::Camel, Case::Constant]), - Named::EnumMember => SmallVec::from_slice(&[options.enum_member_case.into()]), - Named::ExportAlias | Named::ImportAlias | Named::TopLevelConst | Named::TopLevelVar => { - SmallVec::from_slice(&[Case::Camel, Case::Pascal, Case::Constant]) + fn default_convention(self, options: &NamingConventionOptions) -> Convention { + let kind = self.kind; + match kind { + Kind::TypeProperty if self.modifiers.contains(Modifier::Readonly) => Convention { + selector: Selector::with_modifiers(self.kind, Modifier::Readonly), + matching: None, + formats: Formats(Case::Camel | Case::Constant), + }, + Kind::TypeGetter => Convention { + selector: kind.into(), + matching: None, + formats: Formats(Case::Camel | Case::Constant), + }, + Kind::Function if Scope::Global.contains(self.scope) => Convention { + selector: Selector::with_scope(kind, Scope::Global), + matching: None, + formats: Formats(Case::Camel | Case::Pascal | Case::Upper), + }, + Kind::Variable | Kind::Const | Kind::Var if Scope::Global.contains(self.scope) => { + Convention { + selector: Selector::with_scope(kind, Scope::Global), + matching: None, + formats: Formats(Case::Camel | Case::Pascal | Case::Constant), + } } - Named::DestructuredObjectMember | Named::Export | Named::Import => SmallVec::new(), - Named::ExportNamespace - | Named::Function - | Named::FunctionParameter - | Named::ImportNamespace - | Named::LocalConst - | Named::LocalLet - | Named::LocalVar - | Named::LocalVariable - | Named::LocalUsing - | Named::Namespace - | Named::TopLevelLet => SmallVec::from_slice(&[Case::Camel, Case::Pascal]), + Kind::Any | Kind::ExportAlias | Kind::ImportAlias => Convention { + selector: kind.into(), + matching: None, + formats: Formats(Case::Camel | Case::Pascal | Case::Constant), + }, + Kind::ClassProperty | Kind::ClassGetter + if self.modifiers.contains(Modifier::Static) => + { + Convention { + selector: Selector::with_modifiers(kind, Modifier::Static), + matching: None, + formats: Formats(Case::Camel | Case::Constant), + } + } + Kind::CatchParameter + | Kind::ClassGetter + | Kind::ClassMember + | Kind::ClassMethod + | Kind::ClassProperty + | Kind::ClassSetter + | Kind::IndexParameter + | Kind::ObjectLiteralGetter + | Kind::ObjectLiteralProperty + | Kind::ObjectLiteralMember + | Kind::ObjectLiteralMethod + | Kind::ObjectLiteralSetter + | Kind::TypeMember + | Kind::TypeMethod + | Kind::TypeProperty + | Kind::TypeSetter + | Kind::Using => Convention { + selector: kind.into(), + matching: None, + formats: Formats(Case::Camel.into()), + }, + Kind::TypeLike + | Kind::Class + | Kind::Enum + | Kind::Interface + | Kind::TypeAlias + | Kind::TypeParameter => Convention { + selector: kind.into(), + matching: None, + formats: Formats(Case::Pascal.into()), + }, + Kind::EnumMember => Convention { + selector: kind.into(), + matching: None, + formats: Formats(Case::from(options.enum_member_case).into()), + }, + Kind::Variable | Kind::Const | Kind::Var | Kind::Let => Convention { + selector: kind.into(), + matching: None, + formats: Formats(Case::Camel | Case::Pascal), + }, + Kind::Function + | Kind::ExportNamespace + | Kind::ImportNamespace + | Kind::Namespace + | Kind::NamespaceLike + | Kind::FunctionParameter => Convention { + selector: kind.into(), + matching: None, + formats: Formats(Case::Camel | Case::Pascal), + }, } } + + fn contains(&self, other: Selector) -> bool { + other.kind.contains(self.kind) + && self.modifiers.contains(other.modifiers.0) + && other.scope.contains(self.scope) + } } -impl std::fmt::Display for Named { +#[derive( + Clone, + Copy, + Debug, + Default, + Deserializable, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, +)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[serde(rename_all = "camelCase")] +pub enum Kind { + /// All kinds + #[default] + Any, + /// All type definitions: classes, enums, interfaces, and type aliases + TypeLike, + Class, + Enum, + /// Named function declarations and expressions + Function, + Interface, + EnumMember, + /// TypeScript mamespaces, import and export namesapces + NamespaceLike, + /// TypeScript mamespaces + Namespace, + ImportNamespace, + ExportNamespace, + // All variable declaration: const, let, using, var + Variable, + Const, + Let, + Using, + Var, + /// All function parameters, but parameter properties + FunctionParameter, + CatchParameter, + IndexParameter, + /// All generic type parameters + TypeParameter, + // All re-export default exports and aliases of re-exported names + ExportAlias, + // All default imports and aliases of named imports + ImportAlias, + /// All class members: properties, methods, getters, and setters + ClassMember, + /// All class properties, including parameter properties + ClassProperty, + ClassGetter, + ClassSetter, + ClassMethod, + /// All object literal members: properties, methods, getters, and setters + ObjectLiteralMember, + ObjectLiteralProperty, + ObjectLiteralGetter, + ObjectLiteralSetter, + ObjectLiteralMethod, + TypeAlias, + /// All members defined in type alaises and interfaces + TypeMember, + /// All getters defined in type alaises and interfaces + TypeGetter, + /// All properties defined in type alaises and interfaces + TypeProperty, + /// All setters defined in type alaises and interfaces + TypeSetter, + /// All methods defined in type alaises and interfaces + TypeMethod, +} +impl Kind { + pub fn contains(self, other: Self) -> bool { + self == other + || matches!( + (self, other), + (Self::Any, _) + | ( + Self::Variable, + Self::Const | Self::Let | Self::Using | Self::Var, + ) + | ( + Self::ClassMember, + Self::ClassGetter + | Self::ClassMethod + | Self::ClassProperty + | Self::ClassSetter + ) + | ( + Self::ObjectLiteralMember, + Self::ObjectLiteralGetter + | Self::ObjectLiteralMethod + | Self::ObjectLiteralProperty + | Self::ObjectLiteralSetter + ) + | ( + Self::TypeMember, + Self::TypeGetter + | Self::TypeMethod + | Self::TypeParameter + | Self::TypeProperty + | Self::TypeSetter + ) + | ( + Self::NamespaceLike, + Self::ExportNamespace | Self::ImportNamespace | Self::Namespace + ) + | ( + Self::TypeLike, + Self::Class + | Self::Enum + | Self::EnumMember + | Self::Interface + | Self::TypeAlias + | Self::TypeParameter + ) + ) + } +} +impl std::fmt::Display for Kind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let repr = match self { - Named::CatchParameter => "catch parameter", - Named::Class => "class", - Named::ClassGetter => "class getter", - Named::ClassMethod => "class method", - Named::ClassProperty => "class property", - Named::ClassSetter => "class setter", - Named::ClassStaticGetter => "static getter", - Named::ClassStaticMethod => "static method", - Named::ClassStaticProperty => "static property", - Named::ClassStaticSetter => "static setter", - Named::DestructuredObjectMember => "destructured object member", - Named::Enum => "enum", - Named::EnumMember => "enum member", - Named::ExportAlias => "export alias", - Named::ExportNamespace => "export namespace", - Named::Export => "export source", - Named::Function => "function", - Named::FunctionParameter => "function parameter", - Named::ImportAlias => "import alias", - Named::ImportNamespace => "import namespace", - Named::Import => "import source", - Named::IndexParameter => "index parameter", - Named::Interface => "interface", - Named::LocalConst => "local const", - Named::LocalLet => "local let", - Named::LocalVar => "local var", - Named::LocalVariable => "local variable", - Named::LocalUsing => "local using", - Named::Namespace => "namespace", - Named::ObjectGetter => "object getter", - Named::ObjectMethod => "object method", - Named::ObjectProperty => "object property", - Named::ObjectSetter => "object setter", - Named::ParameterProperty => "parameter property", - Named::TopLevelConst => "top-level const", - Named::TopLevelLet => "top-level let", - Named::TopLevelVar => "top-level var", - Named::TypeAlias => "type alias", - Named::TypeGetter => "getter", - Named::TypeMethod => "method", - Named::TypeProperty => "property", - Named::TypeReadonlyProperty => "readonly property", - Named::TypeSetter => "setter", - Named::TypeParameter => "type parameter", + Self::Any => "declaration", + Self::CatchParameter => "catch parameter", + Self::Class => "class", + Self::ClassGetter => "class getter", + Self::ClassMember => "class member", + Self::ClassMethod => "class method", + Self::ClassProperty => "class property", + Self::ClassSetter => "class setter", + Self::Const => "const", + Self::Enum => "enum", + Self::EnumMember => "enum member", + Self::ExportAlias => "export alias", + Self::ExportNamespace => "export namespace", + Self::Function => "function", + Self::ImportAlias => "import alias", + Self::ImportNamespace => "import namespace", + Self::IndexParameter => "index parameter", + Self::Interface => "interface", + Self::Let => "let", + Self::Namespace => "namespace", + Self::NamespaceLike => "namespace", + Self::ObjectLiteralGetter => "object getter", + Self::ObjectLiteralMember => "object member", + Self::ObjectLiteralMethod => "object method", + Self::ObjectLiteralProperty => "object property", + Self::ObjectLiteralSetter => "object setter", + Self::FunctionParameter => "function parameter", + Self::TypeAlias => "type alias", + Self::TypeGetter => "getter", + Self::TypeLike => "type", + Self::TypeMember => "type member", + Self::TypeMethod => "method", + Self::TypeParameter => "type parameter", + Self::TypeProperty => "property", + Self::TypeSetter => "setter", + Self::Using => "using", + Self::Var => "var", + Self::Variable => "variable", }; write!(f, "{}", repr) } } -/// trim underscores and dollar signs from `name`. -fn trim_underscore_dollar(name: &str) -> &str { - name.trim_start_matches(|c| c == '_' || c == '$') - .trim_end_matches(|c| c == '_' || c == '$') +#[derive(Debug, Deserializable, Copy, Clone, serde::Deserialize, serde::Serialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[serde(rename_all = "camelCase")] +#[repr(u16)] +enum RestrictedModifier { + Abstract = Modifier::Abstract as u16, + Private = Modifier::Private as u16, + Protected = Modifier::Protected as u16, + Readonly = Modifier::Readonly as u16, + Static = Modifier::Static as u16, +} + +impl From for Modifier { + fn from(modifier: RestrictedModifier) -> Self { + match modifier { + RestrictedModifier::Abstract => Modifier::Abstract, + RestrictedModifier::Private => Modifier::Private, + RestrictedModifier::Protected => Modifier::Protected, + RestrictedModifier::Readonly => Modifier::Readonly, + RestrictedModifier::Static => Modifier::Static, + } + } +} +impl From for RestrictedModifier { + fn from(modifier: Modifier) -> Self { + match modifier { + Modifier::Abstract => RestrictedModifier::Abstract, + Modifier::Private => RestrictedModifier::Private, + Modifier::Protected => RestrictedModifier::Protected, + Modifier::Readonly => RestrictedModifier::Readonly, + Modifier::Static => RestrictedModifier::Static, + _ => unreachable!("Unsupported case"), + } + } +} + +#[derive(Debug, Deserializable, Default, serde::Deserialize, serde::Serialize)] +struct RestrictedModifierList(SmallVec<[RestrictedModifier; 4]>); + +#[derive( + Debug, + Copy, + Default, + Deserializable, + Clone, + Hash, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, +)] +#[serde(from = "RestrictedModifierList", into = "RestrictedModifierList")] +struct Modifiers(BitFlags); + +#[cfg(feature = "schemars")] +impl JsonSchema for Modifiers { + fn schema_name() -> String { + "Modifiers".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + >::json_schema(gen) + } +} +impl Deref for Modifiers { + type Target = BitFlags; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl From for Modifiers { + fn from(value: Modifier) -> Self { + Modifiers(value.into()) + } +} +impl From for RestrictedModifierList { + fn from(value: Modifiers) -> Self { + Self(value.into_iter().map(|modifier| modifier.into()).collect()) + } +} +impl From for Modifiers { + fn from(value: RestrictedModifierList) -> Self { + Self( + value + .0 + .into_iter() + .map(Modifier::from) + .fold(BitFlags::empty(), |acc, m| acc | m), + ) + } +} +impl From for Modifiers { + fn from(value: JsMethodModifierList) -> Self { + Modifiers((&value).into()) + } +} +impl From for Modifiers { + fn from(value: JsPropertyModifierList) -> Self { + Modifiers((&value).into()) + } +} +impl From for Modifiers { + fn from(value: TsMethodSignatureModifierList) -> Self { + Modifiers((&value).into()) + } +} +impl From for Modifiers { + fn from(value: TsPropertySignatureModifierList) -> Self { + Modifiers((&value).into()) + } +} +impl std::fmt::Display for Modifiers { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for value in self.0.iter() { + write!(f, "{} ", value)?; + } + Ok(()) + } +} + +#[derive( + Debug, + Copy, + Default, + Deserializable, + Clone, + Hash, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, +)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[serde(rename_all = "camelCase")] +pub enum Scope { + #[default] + Any, + Global, +} + +impl Scope { + /// Returns thes scope of `node` or `None` if the scope cannot be determined or + /// if the scope is an external module. + fn from_declaration(node: &AnyJsBindingDeclaration) -> Option { + let control_flow_root = node + .syntax() + .ancestors() + .skip(1) + .find(|x| AnyJsControlFlowRoot::can_cast(x.kind()))?; + match control_flow_root.kind() { + JsSyntaxKind::JS_MODULE + | JsSyntaxKind::JS_SCRIPT + | JsSyntaxKind::TS_MODULE_DECLARATION => Some(Scope::Global), + // Ignore declarations in an external module declaration + JsSyntaxKind::TS_EXTERNAL_MODULE_DECLARATION => None, + _ => Some(Scope::Any), + } + } + + fn contains(self, scope: Scope) -> bool { + matches!(self, Self::Any) || self == scope + } +} +impl std::fmt::Display for Scope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let repr = match self { + Self::Any => "", + Self::Global => "global ", + }; + write!(f, "{repr}") + } +} + +/// Supported cases. +#[derive( + Clone, + Copy, + Debug, + Default, + Deserializable, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, +)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub enum Format { + #[serde(rename = "camelCase")] + Camel, + + #[serde(rename = "CONSTANT_CASE")] + Constant, + + #[serde(rename = "PascalCase")] + #[default] + Pascal, + + #[serde(rename = "snake_case")] + Snake, +} +impl From for Case { + fn from(value: Format) -> Self { + match value { + Format::Camel => Case::Camel, + Format::Constant => Case::Constant, + Format::Pascal => Case::Pascal, + Format::Snake => Case::Snake, + } + } +} +impl TryFrom for Format { + type Error = &'static str; + + fn try_from(value: Case) -> Result { + match value { + Case::Camel => Ok(Format::Camel), + Case::Constant => Ok(Format::Constant), + Case::Pascal => Ok(Format::Pascal), + Case::Snake => Ok(Format::Snake), + Case::Kebab + | Case::Lower + | Case::NumberableCapital + | Case::Uni + | Case::Upper + | Case::Unknown => Err("Unsupported case"), + } + } +} + +#[derive( + Clone, Debug, Default, Deserializable, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize, +)] +struct FormatList(SmallVec<[Format; 4]>); +impl From for FormatList { + fn from(value: Formats) -> Self { + FormatList( + value + .0 + .into_iter() + .filter_map(|case| case.try_into().ok()) + .collect(), + ) + } +} + +#[derive( + Clone, Debug, Default, Deserializable, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize, +)] +#[serde(from = "FormatList", into = "FormatList")] +pub struct Formats(Cases); +impl From for Formats { + fn from(value: FormatList) -> Self { + Self(value.0.into_iter().map(|format| format.into()).collect()) + } +} +#[cfg(feature = "schemars")] +impl JsonSchema for Formats { + fn schema_name() -> String { + "Formats".to_string() + } + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + >::json_schema(gen) + } +} +impl Deref for Formats { + type Target = Cases; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// trim underscores and dollar signs from `name` and returns the lengtj of the trimmed prefix. +fn trim_underscore_dollar(name: &str) -> (usize, &str) { + let prefix_len = name + .bytes() + .take_while(|c| matches!(c, b'_' | b'$')) + .count(); + let name = &name[prefix_len..]; + let suffix_len = name + .bytes() + .rev() + .take_while(|c| matches!(c, b'_' | b'$')) + .count(); + let name = &name[..(name.len() - suffix_len)]; + (prefix_len, name) } diff --git a/crates/biome_js_analyze/src/utils.rs b/crates/biome_js_analyze/src/utils.rs index 419519b13e28..7d3b02a60286 100644 --- a/crates/biome_js_analyze/src/utils.rs +++ b/crates/biome_js_analyze/src/utils.rs @@ -3,6 +3,7 @@ use biome_rowan::{AstNode, Direction, WalkEvent}; use std::iter; pub mod batch; +pub mod regex; pub mod rename; #[cfg(test)] pub mod tests; diff --git a/crates/biome_js_analyze/src/utils/regex.rs b/crates/biome_js_analyze/src/utils/regex.rs new file mode 100644 index 000000000000..9b72849ea21f --- /dev/null +++ b/crates/biome_js_analyze/src/utils/regex.rs @@ -0,0 +1,197 @@ +use std::ops::Deref; + +use biome_deserialize_macros::Deserializable; + +/// A restricted regular expression only supports widespread syntaxes: +/// +/// - Greedy quantifiers `*`, `?`, `+`, `{n}`, `{n,m}`, `{n,}`, `{m}` +/// - Non-greedy quantifiers `*?`, `??`, `+?`, `{n}?`, `{n,m}?`, `{n,}?`, `{m}?` +/// - Any character matcher `.` +/// - Character classes `[a-z]`, `[xyz]`, `[^a-z]` +/// - Alternations `|` +/// - Capturing groups `()` +/// - Non-capturing groups `(?:)` +/// - A limited set of escaped characters including all regex special characters +/// and regular string escape characters `\f`, `\n`, `\r`, `\t`, `\v` +/// +/// A restricted regular expression is implictly delimited by the anchors `^` and `$`. +#[derive(Clone, Debug, Deserializable, serde::Deserialize, serde::Serialize)] +#[serde(try_from = "String", into = "String")] +pub struct RestrictedRegex(regex::Regex); +impl RestrictedRegex { + /// Similar to [regex::Regex::as_str], but returns the original regex representation, + /// without the implicit anchors and the implicit group. + pub fn as_source(&self) -> &str { + let repr = self.0.as_str(); + debug_assert!(repr.starts_with("^(?:")); + debug_assert!(repr.ends_with(")$")); + &repr[4..(repr.len() - 2)] + } +} +impl From for String { + fn from(value: RestrictedRegex) -> Self { + value.into() + } +} +impl Deref for RestrictedRegex { + type Target = regex::Regex; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl TryFrom for RestrictedRegex { + type Error = regex::Error; + + fn try_from(value: String) -> Result { + is_restricted_regex(&value)?; + regex::Regex::new(&format!("^(?:{value})$")).map(RestrictedRegex) + } +} +impl TryFrom<&str> for RestrictedRegex { + type Error = regex::Error; + + fn try_from(value: &str) -> Result { + is_restricted_regex(value)?; + regex::Regex::new(&format!("^(?:{value})$")).map(RestrictedRegex) + } +} +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for RestrictedRegex { + fn schema_name() -> String { + "Regex".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + String::json_schema(gen) + } +} +impl Eq for RestrictedRegex {} +impl PartialEq for RestrictedRegex { + fn eq(&self, other: &Self) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +/// Rteurns an error if `pattern` doesn't follow the restricted regular expression syntax. +fn is_restricted_regex(pattern: &str) -> Result<(), regex::Error> { + let mut it = pattern.bytes(); + let mut is_in_char_class = false; + while let Some(c) = it.next() { + match c { + b'\\' => { + // Accept a restrictive set of escape sequence + // We keep only escaped chars that behave identically + // in unicode-enabled and unicode-disabled RegExes. + if let Some(c) = it.next() { + if !matches!( + c, + b'^' | b'|' + | b'*' + | b'?' + | b'{' + | b'}' + | b'[' + | b']' + | b'-' + | b'$' + | b'f' + | b'n' + | b'r' + | b't' + | b'v' + ) { + // Escape sequences https://docs.rs/regex/latest/regex/#escape-sequences + // and Perl char classes https://docs.rs/regex/latest/regex/#perl-character-classes-unicode-friendly + return Err(regex::Error::Syntax(format!( + "Escape sequence \\{c} is not supported." + ))); + } + } else { + return Err(regex::Error::Syntax( + r"`\` should be followed by a character.".to_string(), + )); + } + } + b'^' | b'$' if !is_in_char_class => { + // Anchors are implicit and always present in a restricted regex + return Err(regex::Error::Syntax(format!( + "The anchor \\{c} is not supported. It is implciitly present." + ))); + } + b'[' if is_in_char_class => { + return Err(regex::Error::Syntax( + "Nested character class are not supported.".to_string(), + )); + } + b'[' => { + is_in_char_class = true; + } + b']' => { + is_in_char_class = false; + } + b'&' | b'~' | b'-' if is_in_char_class => { + if it.next() == Some(c) { + return Err(regex::Error::Syntax(format!( + "Character class operator {c}{c} is not supported." + ))); + } + } + b'(' if !is_in_char_class => { + if it.next() == Some(b'?') { + match it.next() { + Some(b'P' | b'=' | b'!' | b'<') => { + return if c == b'P' + || (c == b'<' && !matches!(it.next(), Some(b'=' | b'!'))) + { + Err(regex::Error::Syntax( + "Named groups `(?)` are not supported.".to_string(), + )) + } else { + Err(regex::Error::Syntax(format!( + "Assertions `(?{c})` are not supported." + ))) + }; + } + Some(b':') => {} + _ => { + return Err(regex::Error::Syntax( + "Group flags `(?flags:)` are not supported.".to_string(), + )); + } + } + } + } + _ => {} + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + assert!(is_restricted_regex("^a").is_err()); + assert!(is_restricted_regex("a$").is_err()); + assert!(is_restricted_regex(r"\").is_err()); + assert!(is_restricted_regex(r"\p{L}").is_err()); + assert!(is_restricted_regex(r"(?i:)").is_err()); + assert!(is_restricted_regex(r"(?=a)").is_err()); + assert!(is_restricted_regex(r"(?!a)").is_err()); + assert!(is_restricted_regex(r"(?:a)").is_err()); + assert!(is_restricted_regex(r"[[:digit:]]").is_err()); + assert!(is_restricted_regex(r"[a[bc]d]").is_err()); + assert!(is_restricted_regex(r"[ab--a]").is_err()); + assert!(is_restricted_regex(r"[ab&&a]").is_err()); + assert!(is_restricted_regex(r"[ab~~a]").is_err()); + + assert!(is_restricted_regex("").is_ok()); + assert!(is_restricted_regex("abc").is_ok()); + assert!(is_restricted_regex("(?:a)(.+)z").is_ok()); + assert!(is_restricted_regex("[A-Z][^a-z]").is_ok()); + assert!(is_restricted_regex(r"\n\t\v\f").is_ok()); + } +} diff --git a/crates/biome_js_analyze/src/utils/rename.rs b/crates/biome_js_analyze/src/utils/rename.rs index 2aac1f9ac96e..f77ce70a6e35 100644 --- a/crates/biome_js_analyze/src/utils/rename.rs +++ b/crates/biome_js_analyze/src/utils/rename.rs @@ -167,7 +167,7 @@ pub trait RenameSymbolExtensions { fn rename_node_declaration( &mut self, model: &SemanticModel, - node: impl RenamableNode, + node: &impl RenamableNode, new_name: &str, ) -> bool; @@ -185,7 +185,7 @@ pub trait RenameSymbolExtensions { fn rename_node_declaration_with_retry( &mut self, model: &SemanticModel, - node: impl RenamableNode + Clone, + node: &impl RenamableNode, candidates: I, ) -> bool where @@ -193,7 +193,7 @@ pub trait RenameSymbolExtensions { I: Iterator, { for candidate in candidates { - if self.rename_node_declaration(model, node.clone(), candidate.as_ref()) { + if self.rename_node_declaration(model, node, candidate.as_ref()) { return true; } } @@ -205,7 +205,7 @@ pub trait RenameSymbolExtensions { fn rename_any_renamable_node( &mut self, model: &SemanticModel, - node: AnyJsRenamableDeclaration, + node: &AnyJsRenamableDeclaration, new_name: &str, ) -> bool { self.rename_node_declaration(model, node, new_name) @@ -241,7 +241,7 @@ impl RenameSymbolExtensions for BatchMutation { fn rename_node_declaration( &mut self, model: &SemanticModel, - node: impl RenamableNode, + node: &impl RenamableNode, new_name: &str, ) -> bool { let prev_binding = match node.binding(model).and_then(AnyJsIdentifierBinding::cast) { diff --git a/crates/biome_js_analyze/src/utils/tests.rs b/crates/biome_js_analyze/src/utils/tests.rs index 9f1d7f2332ca..89905117c33c 100644 --- a/crates/biome_js_analyze/src/utils/tests.rs +++ b/crates/biome_js_analyze/src/utils/tests.rs @@ -35,7 +35,7 @@ pub fn assert_rename_binding_a_to_b_ok(before: &str, expected: &str) { .text_trimmed() .replace('a', "b"); - assert!(batch.rename_node_declaration(&model, binding, &new_name)); + assert!(batch.rename_node_declaration(&model, &binding, &new_name)); } let root = batch.commit(); @@ -64,7 +64,7 @@ pub fn assert_rename_ts_binding_a_to_b_ok(before: &str, expected: &str) { .text_trimmed() .replace('a', "b"); - assert!(batch.rename_node_declaration(&model, binding, &new_name)); + assert!(batch.rename_node_declaration(&model, &binding, &new_name)); } let root = batch.commit(); @@ -92,7 +92,7 @@ pub fn assert_rename_binding_a_to_b_nok(before: &str) { .unwrap(); let mut batch = r.tree().begin(); - assert!(!batch.rename_node_declaration(&model, binding_a, "b")); + assert!(!batch.rename_node_declaration(&model, &binding_a, "b")); } /// Search an identifier named "a" and remove the entire node of type Anc around it. diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCatchParameter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCatchParameter.js.snap index 331d888df092..e6371f56d754 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCatchParameter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCatchParameter.js.snap @@ -25,8 +25,6 @@ invalidCatchParameter.js:1:14 lint/style/useNamingConvention FIXABLE ━━━ 2 │ 3 │ try {} catch(SpecificError) {} - i The name could be renamed to `e`. - i Safe fix: Rename this symbol in camelCase. 1 │ - try·{}·catch(E)·{} @@ -49,8 +47,6 @@ invalidCatchParameter.js:3:14 lint/style/useNamingConvention FIXABLE ━━━ 4 │ 5 │ try {} catch(specific_error) {} - i The name could be renamed to `specificError`. - i Safe fix: Rename this symbol in camelCase. 1 1 │ try {} catch(E) {} @@ -75,8 +71,6 @@ invalidCatchParameter.js:5:14 lint/style/useNamingConvention FIXABLE ━━━ 6 │ 7 │ try {} catch(SPECIFIC_ERROR) {} - i The name could be renamed to `specificError`. - i Safe fix: Rename this symbol in camelCase. 3 3 │ try {} catch(SpecificError) {} @@ -100,8 +94,6 @@ invalidCatchParameter.js:7:14 lint/style/useNamingConvention FIXABLE ━━━ │ ^^^^^^^^^^^^^^ 8 │ - i The name could be renamed to `specificError`. - i Safe fix: Rename this symbol in camelCase. 5 5 │ try {} catch(specific_error) {} @@ -112,5 +104,3 @@ invalidCatchParameter.js:7:14 lint/style/useNamingConvention FIXABLE ━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClass.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClass.js.snap index 0c04ae7faff4..c4e212ebf80c 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClass.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClass.js.snap @@ -26,8 +26,6 @@ invalidClass.js:1:7 lint/style/useNamingConvention FIXABLE ━━━━━━ 2 │ 3 │ class camelCase {} - i The name could be renamed to `C`. - i Safe fix: Rename this symbol in PascalCase. 1 │ - class·c·{} @@ -50,8 +48,6 @@ invalidClass.js:3:7 lint/style/useNamingConvention FIXABLE ━━━━━━ 4 │ 5 │ export default class default_class {} - i The name could be renamed to `CamelCase`. - i Safe fix: Rename this symbol in PascalCase. 1 1 │ class c {} @@ -76,8 +72,6 @@ invalidClass.js:5:22 lint/style/useNamingConvention ━━━━━━━━━ 6 │ 7 │ const x = class CLASS_EXPRESSION {} - i The name could be renamed to `DefaultClass`. - ``` @@ -93,8 +87,6 @@ invalidClass.js:7:17 lint/style/useNamingConvention FIXABLE ━━━━━━ 8 │ 9 │ class Unknown_Style {} - i The name could be renamed to `ClassExpression`. - i Safe fix: Rename this symbol in PascalCase. 5 5 │ export default class default_class {} @@ -117,8 +109,6 @@ invalidClass.js:9:7 lint/style/useNamingConvention FIXABLE ━━━━━━ > 9 │ class Unknown_Style {} │ ^^^^^^^^^^^^^ - i The name could be renamed to `UnknownStyle`. - i Safe fix: Rename this symbol in PascalCase. 7 7 │ const x = class CLASS_EXPRESSION {} @@ -128,5 +118,3 @@ invalidClass.js:9:7 lint/style/useNamingConvention FIXABLE ━━━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassGetter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassGetter.js.snap index 6711e0afa42f..9d9bc33ee8b9 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassGetter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassGetter.js.snap @@ -39,8 +39,6 @@ invalidClassGetter.js:2:9 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ get PROPERTY() {} - i The name could be renamed to `x`. - ``` @@ -56,8 +54,6 @@ invalidClassGetter.js:4:9 lint/style/useNamingConvention ━━━━━━━ 5 │ 6 │ get SpecialProperty() {} - i The name could be renamed to `property`. - ``` @@ -73,8 +69,6 @@ invalidClassGetter.js:6:9 lint/style/useNamingConvention ━━━━━━━ 7 │ 8 │ get special_property() {} - i The name could be renamed to `specialProperty`. - ``` @@ -90,8 +84,6 @@ invalidClassGetter.js:8:9 lint/style/useNamingConvention ━━━━━━━ 9 │ 10 │ get Unknown_Style() {} - i The name could be renamed to `specialProperty`. - ``` @@ -107,93 +99,79 @@ invalidClassGetter.js:10:9 lint/style/useNamingConvention ━━━━━━━ 11 │ 12 │ get #X() {} - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidClassGetter.js:12:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassGetter.js:12:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class getter name should be in camelCase. 10 │ get Unknown_Style() {} 11 │ > 12 │ get #X() {} - │ ^^ + │ ^ 13 │ 14 │ get #PROPERTY() {} - i The name could be renamed to `x`. - ``` ``` -invalidClassGetter.js:14:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassGetter.js:14:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class getter name should be in camelCase. 12 │ get #X() {} 13 │ > 14 │ get #PROPERTY() {} - │ ^^^^^^^^^ + │ ^^^^^^^^ 15 │ 16 │ get #SpecialProperty() {} - i The name could be renamed to `property`. - ``` ``` -invalidClassGetter.js:16:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassGetter.js:16:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class getter name should be in camelCase. 14 │ get #PROPERTY() {} 15 │ > 16 │ get #SpecialProperty() {} - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^ 17 │ 18 │ get #special_property() {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassGetter.js:18:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassGetter.js:18:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class getter name should be in camelCase. 16 │ get #SpecialProperty() {} 17 │ > 18 │ get #special_property() {} - │ ^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^ 19 │ 20 │ get #Unknown_Style() {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassGetter.js:20:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassGetter.js:20:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class getter name should be in camelCase. 18 │ get #special_property() {} 19 │ > 20 │ get #Unknown_Style() {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 21 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassMethod.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassMethod.js.snap index 36050f8a3eb5..d0bb04473fc6 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassMethod.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassMethod.js.snap @@ -39,8 +39,6 @@ invalidClassMethod.js:2:5 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ METHOD() {} - i The name could be renamed to `m`. - ``` @@ -56,15 +54,13 @@ invalidClassMethod.js:4:5 lint/style/useNamingConvention ━━━━━━━ 5 │ 6 │ AMethod() {} - i The name could be renamed to `method`. - ``` ``` invalidClassMethod.js:6:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This class method name should be in camelCase. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 4 │ METHOD() {} 5 │ @@ -73,7 +69,8 @@ invalidClassMethod.js:6:5 lint/style/useNamingConvention ━━━━━━━ 7 │ 8 │ method_1() {} - i The name could be renamed to `aMethod`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` @@ -90,8 +87,6 @@ invalidClassMethod.js:8:5 lint/style/useNamingConvention ━━━━━━━ 9 │ 10 │ Unknown_Style() {} - i The name could be renamed to `method1`. - ``` @@ -107,93 +102,82 @@ invalidClassMethod.js:10:5 lint/style/useNamingConvention ━━━━━━━ 11 │ 12 │ #M() {} - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidClassMethod.js:12:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassMethod.js:12:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class method name should be in camelCase. 10 │ Unknown_Style() {} 11 │ > 12 │ #M() {} - │ ^^ + │ ^ 13 │ 14 │ #METHOD() {} - i The name could be renamed to `m`. - ``` ``` -invalidClassMethod.js:14:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassMethod.js:14:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class method name should be in camelCase. 12 │ #M() {} 13 │ > 14 │ #METHOD() {} - │ ^^^^^^^ + │ ^^^^^^ 15 │ 16 │ #AMethod() {} - i The name could be renamed to `method`. - ``` ``` -invalidClassMethod.js:16:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassMethod.js:16:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This class method name should be in camelCase. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 14 │ #METHOD() {} 15 │ > 16 │ #AMethod() {} - │ ^^^^^^^^ + │ ^^^^^^^ 17 │ 18 │ #method_1() {} - i The name could be renamed to `aMethod`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` ``` -invalidClassMethod.js:18:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassMethod.js:18:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class method name should be in camelCase. 16 │ #AMethod() {} 17 │ > 18 │ #method_1() {} - │ ^^^^^^^^^ + │ ^^^^^^^^ 19 │ 20 │ #Unknown_Style() {} - i The name could be renamed to `method1`. - ``` ``` -invalidClassMethod.js:20:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassMethod.js:20:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class method name should be in camelCase. 18 │ #method_1() {} 19 │ > 20 │ #Unknown_Style() {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 21 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassProperty.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassProperty.js.snap index 4b30350ad40f..a5c001eb8f6c 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassProperty.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassProperty.js.snap @@ -49,42 +49,36 @@ invalidClassProperty.js:2:5 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ "Y" = 0 - i The name could be renamed to `x`. - ``` ``` -invalidClassProperty.js:4:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:4:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This class property name should be in camelCase. + ! This class property name part should be in camelCase. 2 │ X 3 │ > 4 │ "Y" = 0 - │ ^^^ + │ ^ 5 │ 6 │ #X - i The name could be renamed to `y`. - ``` ``` -invalidClassProperty.js:6:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:6:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class property name should be in camelCase. 4 │ "Y" = 0 5 │ > 6 │ #X - │ ^^ + │ ^ 7 │ 8 │ Initialized = 0 - i The name could be renamed to `x`. - ``` @@ -100,25 +94,21 @@ invalidClassProperty.js:8:5 lint/style/useNamingConvention ━━━━━━━ 9 │ 10 │ #Initialized = 0 - i The name could be renamed to `initialized`. - ``` ``` -invalidClassProperty.js:10:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:10:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class property name should be in camelCase. 8 │ Initialized = 0 9 │ > 10 │ #Initialized = 0 - │ ^^^^^^^^^^^^ + │ ^^^^^^^^^^^ 11 │ 12 │ PROPERTY - i The name could be renamed to `initialized`. - ``` @@ -134,25 +124,21 @@ invalidClassProperty.js:12:5 lint/style/useNamingConvention ━━━━━━ 13 │ 14 │ #PROPERTY - i The name could be renamed to `property`. - ``` ``` -invalidClassProperty.js:14:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:14:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class property name should be in camelCase. 12 │ PROPERTY 13 │ > 14 │ #PROPERTY - │ ^^^^^^^^^ + │ ^^^^^^^^ 15 │ 16 │ SpecialProperty - i The name could be renamed to `property`. - ``` @@ -168,25 +154,21 @@ invalidClassProperty.js:16:5 lint/style/useNamingConvention ━━━━━━ 17 │ 18 │ #SpecialProperty - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassProperty.js:18:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:18:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class property name should be in camelCase. 16 │ SpecialProperty 17 │ > 18 │ #SpecialProperty - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^ 19 │ 20 │ special_property - i The name could be renamed to `specialProperty`. - ``` @@ -202,25 +184,21 @@ invalidClassProperty.js:20:5 lint/style/useNamingConvention ━━━━━━ 21 │ 22 │ #special_property - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassProperty.js:22:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:22:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class property name should be in camelCase. 20 │ special_property 21 │ > 22 │ #special_property - │ ^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^ 23 │ 24 │ Unknown_Style - i The name could be renamed to `specialProperty`. - ``` @@ -236,25 +214,21 @@ invalidClassProperty.js:24:5 lint/style/useNamingConvention ━━━━━━ 25 │ 26 │ #Unknown_Style - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidClassProperty.js:26:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:26:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class property name should be in camelCase. 24 │ Unknown_Style 25 │ > 26 │ #Unknown_Style - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 27 │ 28 │ Unknown_Init_Style = 0 - i The name could be renamed to `unknownStyle`. - ``` @@ -270,25 +244,19 @@ invalidClassProperty.js:28:5 lint/style/useNamingConvention ━━━━━━ 29 │ 30 │ #Unknown_Init_Style = 0 - i The name could be renamed to `unknownInitStyle`. - ``` ``` -invalidClassProperty.js:30:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassProperty.js:30:6 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class property name should be in camelCase. 28 │ Unknown_Init_Style = 0 29 │ > 30 │ #Unknown_Init_Style = 0 - │ ^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^ 31 │ } - i The name could be renamed to `unknownInitStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassSetter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassSetter.js.snap index 446aa395a5ee..53daffaa799d 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassSetter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassSetter.js.snap @@ -39,8 +39,6 @@ invalidClassSetter.js:2:9 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ set PROPERTY(x) {} - i The name could be renamed to `x`. - ``` @@ -56,8 +54,6 @@ invalidClassSetter.js:4:9 lint/style/useNamingConvention ━━━━━━━ 5 │ 6 │ set SpecialProperty(x) {} - i The name could be renamed to `property`. - ``` @@ -73,8 +69,6 @@ invalidClassSetter.js:6:9 lint/style/useNamingConvention ━━━━━━━ 7 │ 8 │ set special_property(x) {} - i The name could be renamed to `specialProperty`. - ``` @@ -90,8 +84,6 @@ invalidClassSetter.js:8:9 lint/style/useNamingConvention ━━━━━━━ 9 │ 10 │ set Unknown_Style(x) {} - i The name could be renamed to `specialProperty`. - ``` @@ -107,93 +99,79 @@ invalidClassSetter.js:10:9 lint/style/useNamingConvention ━━━━━━━ 11 │ 12 │ set #X(x) {} - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidClassSetter.js:12:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassSetter.js:12:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class setter name should be in camelCase. 10 │ set Unknown_Style(x) {} 11 │ > 12 │ set #X(x) {} - │ ^^ + │ ^ 13 │ 14 │ set #PROPERTY(x) {} - i The name could be renamed to `x`. - ``` ``` -invalidClassSetter.js:14:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassSetter.js:14:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class setter name should be in camelCase. 12 │ set #X(x) {} 13 │ > 14 │ set #PROPERTY(x) {} - │ ^^^^^^^^^ + │ ^^^^^^^^ 15 │ 16 │ set #SpecialProperty(x) {} - i The name could be renamed to `property`. - ``` ``` -invalidClassSetter.js:16:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassSetter.js:16:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class setter name should be in camelCase. 14 │ set #PROPERTY(x) {} 15 │ > 16 │ set #SpecialProperty(x) {} - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^ 17 │ 18 │ set #special_property(x) {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassSetter.js:18:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassSetter.js:18:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class setter name should be in camelCase. 16 │ set #SpecialProperty(x) {} 17 │ > 18 │ set #special_property(x) {} - │ ^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^ 19 │ 20 │ set #Unknown_Style(x) {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassSetter.js:20:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassSetter.js:20:10 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This class setter name should be in camelCase. 18 │ set #special_property(x) {} 19 │ > 20 │ set #Unknown_Style(x) {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 21 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticGetter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticGetter.js.snap index 682e1b230ae7..7c9c4932fbed 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticGetter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticGetter.js.snap @@ -25,7 +25,7 @@ export default class { ``` invalidClassStaticGetter.js:2:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static getter name should be in camelCase or CONSTANT_CASE. + ! This static class getter name should be in camelCase or CONSTANT_CASE. 1 │ export default class { > 2 │ static get SpecialProperty() {} @@ -33,15 +33,13 @@ invalidClassStaticGetter.js:2:16 lint/style/useNamingConvention ━━━━━ 3 │ 4 │ static get special_property() {} - i The name could be renamed to `specialProperty`. - ``` ``` invalidClassStaticGetter.js:4:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static getter name should be in camelCase or CONSTANT_CASE. + ! This static class getter name should be in camelCase or CONSTANT_CASE. 2 │ static get SpecialProperty() {} 3 │ @@ -50,15 +48,13 @@ invalidClassStaticGetter.js:4:16 lint/style/useNamingConvention ━━━━━ 5 │ 6 │ static get Unknown_Style() {} - i The name could be renamed to `specialProperty`. - ``` ``` invalidClassStaticGetter.js:6:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static getter name should be in camelCase or CONSTANT_CASE. + ! This static class getter name should be in camelCase or CONSTANT_CASE. 4 │ static get special_property() {} 5 │ @@ -67,59 +63,49 @@ invalidClassStaticGetter.js:6:16 lint/style/useNamingConvention ━━━━━ 7 │ 8 │ static get #X() {} - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidClassStaticGetter.js:10:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticGetter.js:10:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static getter name should be in camelCase or CONSTANT_CASE. + ! This static class getter name should be in camelCase or CONSTANT_CASE. 8 │ static get #X() {} 9 │ > 10 │ static get #SpecialProperty() {} - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^ 11 │ 12 │ static get #special_property() {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassStaticGetter.js:12:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticGetter.js:12:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static getter name should be in camelCase or CONSTANT_CASE. + ! This static class getter name should be in camelCase or CONSTANT_CASE. 10 │ static get #SpecialProperty() {} 11 │ > 12 │ static get #special_property() {} - │ ^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^ 13 │ 14 │ static get #Unknown_Style() {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassStaticGetter.js:14:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticGetter.js:14:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static getter name should be in camelCase or CONSTANT_CASE. + ! This static class getter name should be in camelCase or CONSTANT_CASE. 12 │ static get #special_property() {} 13 │ > 14 │ static get #Unknown_Style() {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 15 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticMethod.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticMethod.js.snap index 6faf2c462edc..7235210bbc44 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticMethod.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticMethod.js.snap @@ -29,7 +29,7 @@ export default class { ``` invalidClassStaticMethod.js:2:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! This class method name should be in camelCase. 1 │ export default class { > 2 │ static METHOD() {} @@ -37,15 +37,13 @@ invalidClassStaticMethod.js:2:12 lint/style/useNamingConvention ━━━━━ 3 │ 4 │ static AMethod() {} - i The name could be renamed to `method`. - ``` ``` invalidClassStaticMethod.js:4:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 2 │ static METHOD() {} 3 │ @@ -54,7 +52,8 @@ invalidClassStaticMethod.js:4:12 lint/style/useNamingConvention ━━━━━ 5 │ 6 │ static method_1() {} - i The name could be renamed to `aMethod`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` @@ -62,7 +61,7 @@ invalidClassStaticMethod.js:4:12 lint/style/useNamingConvention ━━━━━ ``` invalidClassStaticMethod.js:6:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! This class method name should be in camelCase. 4 │ static AMethod() {} 5 │ @@ -71,15 +70,13 @@ invalidClassStaticMethod.js:6:12 lint/style/useNamingConvention ━━━━━ 7 │ 8 │ static Unknown_Style() {} - i The name could be renamed to `method1`. - ``` ``` invalidClassStaticMethod.js:8:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! This class method name should be in camelCase. 6 │ static method_1() {} 7 │ @@ -88,93 +85,82 @@ invalidClassStaticMethod.js:8:12 lint/style/useNamingConvention ━━━━━ 9 │ 10 │ static #M() {} - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidClassStaticMethod.js:10:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticMethod.js:10:13 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! This class method name should be in camelCase. 8 │ static Unknown_Style() {} 9 │ > 10 │ static #M() {} - │ ^^ + │ ^ 11 │ 12 │ static #METHOD() {} - i The name could be renamed to `m`. - ``` ``` -invalidClassStaticMethod.js:12:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticMethod.js:12:13 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! This class method name should be in camelCase. 10 │ static #M() {} 11 │ > 12 │ static #METHOD() {} - │ ^^^^^^^ + │ ^^^^^^ 13 │ 14 │ static #AMethod() {} - i The name could be renamed to `method`. - ``` ``` -invalidClassStaticMethod.js:14:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticMethod.js:14:13 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 12 │ static #METHOD() {} 13 │ > 14 │ static #AMethod() {} - │ ^^^^^^^^ + │ ^^^^^^^ 15 │ 16 │ static #method_1() {} - i The name could be renamed to `aMethod`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` ``` -invalidClassStaticMethod.js:16:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticMethod.js:16:13 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! This class method name should be in camelCase. 14 │ static #AMethod() {} 15 │ > 16 │ static #method_1() {} - │ ^^^^^^^^^ + │ ^^^^^^^^ 17 │ 18 │ static #Unknown_Style() {} - i The name could be renamed to `method1`. - ``` ``` -invalidClassStaticMethod.js:18:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticMethod.js:18:13 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static method name should be in camelCase. + ! This class method name should be in camelCase. 16 │ static #method_1() {} 17 │ > 18 │ static #Unknown_Style() {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 19 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticSetter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticSetter.js.snap index d234edf6ddae..212af41b60bf 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticSetter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidClassStaticSetter.js.snap @@ -31,7 +31,7 @@ export default class { ``` invalidClassStaticSetter.js:2:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 1 │ export default class { > 2 │ static set X(x) {} @@ -39,15 +39,13 @@ invalidClassStaticSetter.js:2:16 lint/style/useNamingConvention ━━━━━ 3 │ 4 │ static set PROPERTY(x) {} - i The name could be renamed to `x`. - ``` ``` invalidClassStaticSetter.js:4:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 2 │ static set X(x) {} 3 │ @@ -56,15 +54,13 @@ invalidClassStaticSetter.js:4:16 lint/style/useNamingConvention ━━━━━ 5 │ 6 │ static set SpecialProperty(x) {} - i The name could be renamed to `property`. - ``` ``` invalidClassStaticSetter.js:6:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 4 │ static set PROPERTY(x) {} 5 │ @@ -73,15 +69,13 @@ invalidClassStaticSetter.js:6:16 lint/style/useNamingConvention ━━━━━ 7 │ 8 │ static set special_property(x) {} - i The name could be renamed to `specialProperty`. - ``` ``` invalidClassStaticSetter.js:8:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 6 │ static set SpecialProperty(x) {} 7 │ @@ -90,15 +84,13 @@ invalidClassStaticSetter.js:8:16 lint/style/useNamingConvention ━━━━━ 9 │ 10 │ static set Unknown_Style(x) {} - i The name could be renamed to `specialProperty`. - ``` ``` invalidClassStaticSetter.js:10:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 8 │ static set special_property(x) {} 9 │ @@ -107,93 +99,79 @@ invalidClassStaticSetter.js:10:16 lint/style/useNamingConvention ━━━━━ 11 │ 12 │ static set #X(x) {} - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidClassStaticSetter.js:12:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticSetter.js:12:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 10 │ static set Unknown_Style(x) {} 11 │ > 12 │ static set #X(x) {} - │ ^^ + │ ^ 13 │ 14 │ static set #PROPERTY(x) {} - i The name could be renamed to `x`. - ``` ``` -invalidClassStaticSetter.js:14:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticSetter.js:14:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 12 │ static set #X(x) {} 13 │ > 14 │ static set #PROPERTY(x) {} - │ ^^^^^^^^^ + │ ^^^^^^^^ 15 │ 16 │ static set #SpecialProperty(x) {} - i The name could be renamed to `property`. - ``` ``` -invalidClassStaticSetter.js:16:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticSetter.js:16:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 14 │ static set #PROPERTY(x) {} 15 │ > 16 │ static set #SpecialProperty(x) {} - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^ 17 │ 18 │ static set #special_property(x) {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassStaticSetter.js:18:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticSetter.js:18:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 16 │ static set #SpecialProperty(x) {} 17 │ > 18 │ static set #special_property(x) {} - │ ^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^ 19 │ 20 │ static set #Unknown_Style(x) {} - i The name could be renamed to `specialProperty`. - ``` ``` -invalidClassStaticSetter.js:20:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidClassStaticSetter.js:20:17 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This static setter name should be in camelCase. + ! This class setter name should be in camelCase. 18 │ static set #special_property(x) {} 19 │ > 20 │ static set #Unknown_Style(x) {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 21 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.options.json new file mode 100644 index 000000000000..0a0ae23ac014 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.options.json @@ -0,0 +1,21 @@ +{ + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "selector": { + "kind": "interface" + }, + "match": "I(.+)|(.*)Error" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts new file mode 100644 index 000000000000..ed4903d17d0f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts @@ -0,0 +1,8 @@ +// Valid +interface IArguments {} +interface Error {} +interface CustomError {} + +// Invalid +interface Invalid {} +interface Other {} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts.snap new file mode 100644 index 000000000000..2bd037f88b31 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyle.ts.snap @@ -0,0 +1,50 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalidCustomStyle.ts +--- +# Input +```ts +// Valid +interface IArguments {} +interface Error {} +interface CustomError {} + +// Invalid +interface Invalid {} +interface Other {} +``` + +# Diagnostics +``` +invalidCustomStyle.ts:7:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This interface name part should be in PascalCase. + + 6 │ // Invalid + > 7 │ interface Invalid {} + │ ^^^^^^ + 8 │ interface Other {} + + i Safe fix: Rename this symbol in PascalCase. + + 5 5 │ + 6 6 │ // Invalid + 7 │ - interface·Invalid·{} + 7 │ + interface·INvalid·{} + 8 8 │ interface Other {} + + +``` + +``` +invalidCustomStyle.ts:8:11 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This interface name should match the following regex /I(.+)|(.*)Error/. + + 6 │ // Invalid + 7 │ interface Invalid {} + > 8 │ interface Other {} + │ ^^^^^ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.options.json new file mode 100644 index 000000000000..b67d9d25a56c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.options.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "match": "aSepcial_CASE|(.*)" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts new file mode 100644 index 000000000000..ff60210dedaa --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts @@ -0,0 +1,2 @@ +const aSepcial_CASE = 0; // valid +const aSepcial_CASE_2 = 0; // invalid \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts.snap new file mode 100644 index 000000000000..9ce750432319 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleExceptions.ts.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalidCustomStyleExceptions.ts +--- +# Input +```ts +const aSepcial_CASE = 0; // valid +const aSepcial_CASE_2 = 0; // invalid +``` + +# Diagnostics +``` +invalidCustomStyleExceptions.ts:2:7 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━ + + ! This global const name should be in camelCase or PascalCase or CONSTANT_CASE. + + 1 │ const aSepcial_CASE = 0; // valid + > 2 │ const aSepcial_CASE_2 = 0; // invalid + │ ^^^^^^^^^^^^^^^ + + i Safe fix: Rename this symbol in camelCase. + + 1 1 │ const aSepcial_CASE = 0; // valid + 2 │ - const·aSepcial_CASE_2·=·0;·//·invalid + 2 │ + const·aSepcialCase2·=·0;·//·invalid + + +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.options.json new file mode 100644 index 000000000000..02fff9ac1ede --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.options.json @@ -0,0 +1,23 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "selector": { + "kind": "classMember", + "modifiers": ["private"] + }, + "match": "_(.*)" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts new file mode 100644 index 000000000000..413d31683f74 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts @@ -0,0 +1,3 @@ +class X { + private property: number +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts.snap new file mode 100644 index 000000000000..7d630317b7c6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidCustomStyleUnderscorePrivate.ts.snap @@ -0,0 +1,24 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalidCustomStyleUnderscorePrivate.ts +--- +# Input +```ts +class X { + private property: number +} +``` + +# Diagnostics +``` +invalidCustomStyleUnderscorePrivate.ts:2:13 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This private class member name should match the following regex /_(.*)/. + + 1 │ class X { + > 2 │ private property: number + │ ^^^^^^^^ + 3 │ } + + +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnum.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnum.ts.snap index a998c19820aa..5053cec2a9a4 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnum.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnum.ts.snap @@ -22,8 +22,6 @@ invalidEnum.ts:1:6 lint/style/useNamingConvention FIXABLE ━━━━━━ 2 │ 3 │ enum special_status {} - i The name could be renamed to `SpecialStatus`. - i Safe fix: Rename this symbol in PascalCase. 1 │ - enum·specialStatus·{} @@ -46,8 +44,6 @@ invalidEnum.ts:3:6 lint/style/useNamingConvention FIXABLE ━━━━━━ 4 │ 5 │ enum SPECIAL_STATUS {} - i The name could be renamed to `SpecialStatus`. - i Safe fix: Rename this symbol in PascalCase. 1 1 │ enum specialStatus {} @@ -70,8 +66,6 @@ invalidEnum.ts:5:6 lint/style/useNamingConvention FIXABLE ━━━━━━ > 5 │ enum SPECIAL_STATUS {} │ ^^^^^^^^^^^^^^ - i The name could be renamed to `SpecialStatus`. - i Safe fix: Rename this symbol in PascalCase. 3 3 │ enum special_status {} @@ -81,5 +75,3 @@ invalidEnum.ts:5:6 lint/style/useNamingConvention FIXABLE ━━━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnumMember.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnumMember.ts.snap index 7d00497b7406..47a192adbb7b 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnumMember.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidEnumMember.ts.snap @@ -27,8 +27,6 @@ invalidEnumMember.ts:2:5 lint/style/useNamingConvention ━━━━━━━━ 3 │ CLOSE, 4 │ } - i The name could be renamed to `Open`. - ``` @@ -44,8 +42,6 @@ invalidEnumMember.ts:3:5 lint/style/useNamingConvention ━━━━━━━━ 4 │ } 5 │ - i The name could be renamed to `Close`. - ``` @@ -60,8 +56,6 @@ invalidEnumMember.ts:7:5 lint/style/useNamingConvention ━━━━━━━━ 8 │ left, 9 │ } - i The name could be renamed to `Right`. - ``` @@ -76,9 +70,5 @@ invalidEnumMember.ts:8:5 lint/style/useNamingConvention ━━━━━━━━ │ ^^^^ 9 │ } - i The name could be renamed to `Left`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportAlias.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportAlias.js.snap index c1e938dec7d8..1c479f6094e5 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportAlias.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportAlias.js.snap @@ -17,14 +17,15 @@ export { Y as snake_case } from "" ``` invalidExportAlias.js:1:15 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This export alias name should be in camelCase or PascalCase or CONSTANT_CASE. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. > 1 │ export { X as XxXX } │ ^^^^ 2 │ 3 │ export { Y as snake_case } - i The name could be renamed to `xxXx`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` @@ -41,15 +42,13 @@ invalidExportAlias.js:3:15 lint/style/useNamingConvention ━━━━━━━ 4 │ 5 │ export { X as XxXX } from "" - i The name could be renamed to `snakeCase`. - ``` ``` invalidExportAlias.js:5:15 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This export alias name should be in camelCase or PascalCase or CONSTANT_CASE. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 3 │ export { Y as snake_case } 4 │ @@ -58,7 +57,8 @@ invalidExportAlias.js:5:15 lint/style/useNamingConvention ━━━━━━━ 6 │ 7 │ export { Y as snake_case } from "" - i The name could be renamed to `xxXx`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` @@ -73,9 +73,5 @@ invalidExportAlias.js:7:15 lint/style/useNamingConvention ━━━━━━━ > 7 │ export { Y as snake_case } from "" │ ^^^^^^^^^^ - i The name could be renamed to `snakeCase`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportNamespace.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportNamespace.js.snap index 0ad1e97b7326..1964b4abd5ea 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportNamespace.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidExportNamespace.js.snap @@ -29,8 +29,6 @@ invalidExportNamespace.js:5:13 lint/style/useNamingConvention ━━━━━━ 6 │ 7 │ export * as snake_case from "" - i The name could be renamed to `constantCase`. - ``` @@ -46,8 +44,6 @@ invalidExportNamespace.js:7:13 lint/style/useNamingConvention ━━━━━━ 8 │ 9 │ export * as Unknown_Style from "" - i The name could be renamed to `snakeCase`. - ``` @@ -62,9 +58,5 @@ invalidExportNamespace.js:9:13 lint/style/useNamingConvention ━━━━━━ │ ^^^^^^^^^^^^^ 10 │ - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js index d8ff98b6ef72..a3c695b6f363 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js @@ -1,7 +1,9 @@ -function PERSON() {} - function special_function() {} function Unknown_Style() {} -const g = function SPECIAL_FUNCTION() {} \ No newline at end of file +(function() { + function PERSON() {} + + const g = function SPECIAL_FUNCTION() {} +})() diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js.snap index 96fc6a853bf9..0262ef5ed15a 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunction.js.snap @@ -4,34 +4,35 @@ expression: invalidFunction.js --- # Input ```jsx -function PERSON() {} - function special_function() {} function Unknown_Style() {} -const g = function SPECIAL_FUNCTION() {} +(function() { + function PERSON() {} + + const g = function SPECIAL_FUNCTION() {} +})() + ``` # Diagnostics ``` invalidFunction.js:1:10 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This function name should be in camelCase or PascalCase. + ! This global function name should be in camelCase or PascalCase or UPPERCASE. - > 1 │ function PERSON() {} - │ ^^^^^^ + > 1 │ function special_function() {} + │ ^^^^^^^^^^^^^^^^ 2 │ - 3 │ function special_function() {} - - i The name could be renamed to `person`. + 3 │ function Unknown_Style() {} i Safe fix: Rename this symbol in camelCase. - 1 │ - function·PERSON()·{} - 1 │ + function·person()·{} - 2 2 │ - 3 3 │ function special_function() {} + 1 │ - function·special_function()·{} + 1 │ + function·specialFunction()·{} + 2 2 │ + 3 3 │ function Unknown_Style() {} ``` @@ -39,75 +40,70 @@ invalidFunction.js:1:10 lint/style/useNamingConvention FIXABLE ━━━━━ ``` invalidFunction.js:3:10 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This function name should be in camelCase or PascalCase. + ! This global function name should be in camelCase or PascalCase or UPPERCASE. - 1 │ function PERSON() {} + 1 │ function special_function() {} 2 │ - > 3 │ function special_function() {} - │ ^^^^^^^^^^^^^^^^ + > 3 │ function Unknown_Style() {} + │ ^^^^^^^^^^^^^ 4 │ - 5 │ function Unknown_Style() {} - - i The name could be renamed to `specialFunction`. + 5 │ (function() { i Safe fix: Rename this symbol in camelCase. - 1 1 │ function PERSON() {} - 2 2 │ - 3 │ - function·special_function()·{} - 3 │ + function·specialFunction()·{} - 4 4 │ - 5 5 │ function Unknown_Style() {} + 1 1 │ function special_function() {} + 2 2 │ + 3 │ - function·Unknown_Style()·{} + 3 │ + function·unknownStyle()·{} + 4 4 │ + 5 5 │ (function() { ``` ``` -invalidFunction.js:5:10 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidFunction.js:6:14 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This function name should be in camelCase or PascalCase. - 3 │ function special_function() {} - 4 │ - > 5 │ function Unknown_Style() {} - │ ^^^^^^^^^^^^^ - 6 │ - 7 │ const g = function SPECIAL_FUNCTION() {} - - i The name could be renamed to `unknownStyle`. + 5 │ (function() { + > 6 │ function PERSON() {} + │ ^^^^^^ + 7 │ + 8 │ const g = function SPECIAL_FUNCTION() {} i Safe fix: Rename this symbol in camelCase. - 3 3 │ function special_function() {} - 4 4 │ - 5 │ - function·Unknown_Style()·{} - 5 │ + function·unknownStyle()·{} - 6 6 │ - 7 7 │ const g = function SPECIAL_FUNCTION() {} + 4 4 │ + 5 5 │ (function() { + 6 │ - ····function·PERSON()·{} + 6 │ + ····function·person()·{} + 7 7 │ + 8 8 │ const g = function SPECIAL_FUNCTION() {} ``` ``` -invalidFunction.js:7:20 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidFunction.js:8:24 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This function name should be in camelCase or PascalCase. - 5 │ function Unknown_Style() {} - 6 │ - > 7 │ const g = function SPECIAL_FUNCTION() {} - │ ^^^^^^^^^^^^^^^^ - - i The name could be renamed to `specialFunction`. + 6 │ function PERSON() {} + 7 │ + > 8 │ const g = function SPECIAL_FUNCTION() {} + │ ^^^^^^^^^^^^^^^^ + 9 │ })() + 10 │ i Safe fix: Rename this symbol in camelCase. - 5 5 │ function Unknown_Style() {} - 6 6 │ - 7 │ - const·g·=·function·SPECIAL_FUNCTION()·{} - 7 │ + const·g·=·function·specialFunction()·{} + 6 6 │ function PERSON() {} + 7 7 │ + 8 │ - ····const·g·=·function·SPECIAL_FUNCTION()·{} + 8 │ + ····const·g·=·function·specialFunction()·{} + 9 9 │ })() + 10 10 │ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunctionParameter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunctionParameter.js.snap index 88f0a4f20b15..7d6ff9323bf8 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunctionParameter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidFunctionParameter.js.snap @@ -9,14 +9,12 @@ function f(_snake_case, CONSTANT_CASE) {} # Diagnostics ``` -invalidFunctionParameter.js:1:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidFunctionParameter.js:1:13 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This function parameter name trimmed as `snake_case` should be in camelCase or PascalCase. + ! This function parameter name part should be in camelCase or PascalCase. > 1 │ function f(_snake_case, CONSTANT_CASE) {} - │ ^^^^^^^^^^^ - - i The name could be renamed to `_snakeCase`. + │ ^^^^^^^^^^ i Safe fix: Rename this symbol in camelCase. @@ -34,8 +32,6 @@ invalidFunctionParameter.js:1:25 lint/style/useNamingConvention FIXABLE ━━ > 1 │ function f(_snake_case, CONSTANT_CASE) {} │ ^^^^^^^^^^^^^ - i The name could be renamed to `constantCase`. - i Safe fix: Rename this symbol in camelCase. - function·f(_snake_case,·CONSTANT_CASE)·{} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportAlias.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportAlias.js.snap index e245902428d7..4b3288650b46 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportAlias.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportAlias.js.snap @@ -25,14 +25,15 @@ import snake_case from "" ``` invalidImportAlias.js:1:15 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This import alias name should be in camelCase or PascalCase or CONSTANT_CASE. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. > 1 │ import { X as XxXX } from "" │ ^^^^ 2 │ 3 │ import { Y as snake_case } from "" - i The name could be renamed to `xxXx`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. i Safe fix: Rename this symbol in camelCase. @@ -56,8 +57,6 @@ invalidImportAlias.js:3:15 lint/style/useNamingConvention FIXABLE ━━━━ 4 │ 5 │ import { X as XxXX } from "" - i The name could be renamed to `snakeCase`. - i Safe fix: Rename this symbol in camelCase. 1 1 │ import { X as XxXX } from "" @@ -73,7 +72,7 @@ invalidImportAlias.js:3:15 lint/style/useNamingConvention FIXABLE ━━━━ ``` invalidImportAlias.js:5:15 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This import alias name should be in camelCase or PascalCase or CONSTANT_CASE. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 3 │ import { Y as snake_case } from "" 4 │ @@ -82,7 +81,8 @@ invalidImportAlias.js:5:15 lint/style/useNamingConvention FIXABLE ━━━━ 6 │ 7 │ import { Y as snake_case } from "" - i The name could be renamed to `xxXx`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. i Safe fix: Rename this symbol in camelCase. @@ -108,8 +108,6 @@ invalidImportAlias.js:7:15 lint/style/useNamingConvention FIXABLE ━━━━ 8 │ 9 │ import XxXX from "" - i The name could be renamed to `snakeCase`. - i Safe fix: Rename this symbol in camelCase. 5 5 │ import { X as XxXX } from "" @@ -125,7 +123,7 @@ invalidImportAlias.js:7:15 lint/style/useNamingConvention FIXABLE ━━━━ ``` invalidImportAlias.js:9:8 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This import alias name should be in camelCase or PascalCase or CONSTANT_CASE. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 7 │ import { Y as snake_case } from "" 8 │ @@ -134,7 +132,8 @@ invalidImportAlias.js:9:8 lint/style/useNamingConvention FIXABLE ━━━━ 10 │ 11 │ import snake_case from "" - i The name could be renamed to `xxXx`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. i Safe fix: Rename this symbol in camelCase. @@ -160,8 +159,6 @@ invalidImportAlias.js:11:8 lint/style/useNamingConvention FIXABLE ━━━━ 12 │ 13 │ import XxXX from "" - i The name could be renamed to `snakeCase`. - i Safe fix: Rename this symbol in camelCase. 9 9 │ import XxXX from "" @@ -177,7 +174,7 @@ invalidImportAlias.js:11:8 lint/style/useNamingConvention FIXABLE ━━━━ ``` invalidImportAlias.js:13:8 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This import alias name should be in camelCase or PascalCase or CONSTANT_CASE. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 11 │ import snake_case from "" 12 │ @@ -186,7 +183,8 @@ invalidImportAlias.js:13:8 lint/style/useNamingConvention FIXABLE ━━━━ 14 │ 15 │ import snake_case from "" - i The name could be renamed to `xxXx`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. i Safe fix: Rename this symbol in camelCase. @@ -210,8 +208,6 @@ invalidImportAlias.js:15:8 lint/style/useNamingConvention FIXABLE ━━━━ > 15 │ import snake_case from "" │ ^^^^^^^^^^ - i The name could be renamed to `snakeCase`. - i Safe fix: Rename this symbol in camelCase. 13 13 │ import XxXX from "" @@ -221,5 +217,3 @@ invalidImportAlias.js:15:8 lint/style/useNamingConvention FIXABLE ━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportNamespace.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportNamespace.js.snap index b7e87a30d2c3..226a79c6f15c 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportNamespace.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidImportNamespace.js.snap @@ -29,8 +29,6 @@ invalidImportNamespace.js:5:13 lint/style/useNamingConvention FIXABLE ━━ 6 │ 7 │ import * as snake_case from "" - i The name could be renamed to `constantCase`. - i Safe fix: Rename this symbol in camelCase. 3 3 │ import * as PascalCase from "" @@ -55,8 +53,6 @@ invalidImportNamespace.js:7:13 lint/style/useNamingConvention FIXABLE ━━ 8 │ 9 │ import * as Unknown_Style from "" - i The name could be renamed to `snakeCase`. - i Safe fix: Rename this symbol in camelCase. 5 5 │ import * as CONSTANT_CASE from "" @@ -80,8 +76,6 @@ invalidImportNamespace.js:9:13 lint/style/useNamingConvention FIXABLE ━━ │ ^^^^^^^^^^^^^ 10 │ - i The name could be renamed to `unknownStyle`. - i Safe fix: Rename this symbol in camelCase. 7 7 │ import * as snake_case from "" @@ -92,5 +86,3 @@ invalidImportNamespace.js:9:13 lint/style/useNamingConvention FIXABLE ━━ ``` - - 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 abdd5492039b..a24c9277437a 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 @@ -25,8 +25,6 @@ invalidIndexParameter.ts:2:6 lint/style/useNamingConvention FIXABLE ━━━ 3 │ 4 │ [CONSTANT_CASE: number]: unknown - i The name could be renamed to `pascalCase`. - i Safe fix: Rename this symbol in camelCase. 1 1 │ export interface X { @@ -50,8 +48,6 @@ invalidIndexParameter.ts:4:6 lint/style/useNamingConvention FIXABLE ━━━ 5 │ 6 │ [snake_case: symbol]: unknown - i The name could be renamed to `constantCase`. - i Safe fix: Rename this symbol in camelCase. 2 2 │ [PascalCase: string]: unknown @@ -75,8 +71,6 @@ invalidIndexParameter.ts:6:6 lint/style/useNamingConvention FIXABLE ━━━ │ ^^^^^^^^^^ 7 │ } - i The name could be renamed to `snakeCase`. - i Safe fix: Rename this symbol in camelCase. 4 4 │ [CONSTANT_CASE: number]: unknown @@ -87,5 +81,3 @@ invalidIndexParameter.ts:6:6 lint/style/useNamingConvention FIXABLE ━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidInterface.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidInterface.ts.snap index 16d978bfd7ba..fa28ac83eb81 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidInterface.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidInterface.ts.snap @@ -36,8 +36,6 @@ invalidInterface.ts:1:18 lint/style/useNamingConvention ━━━━━━━━ 2 │ 3 │ export interface CONSTANT_CASE {} - i The name could be renamed to `Xxx`. - ``` @@ -53,8 +51,6 @@ invalidInterface.ts:3:18 lint/style/useNamingConvention ━━━━━━━━ 4 │ 5 │ export interface camelCase {} - i The name could be renamed to `ConstantCase`. - ``` @@ -70,8 +66,6 @@ invalidInterface.ts:5:18 lint/style/useNamingConvention ━━━━━━━━ 6 │ 7 │ export interface snake_case {} - i The name could be renamed to `CamelCase`. - ``` @@ -87,8 +81,6 @@ invalidInterface.ts:7:18 lint/style/useNamingConvention ━━━━━━━━ 8 │ 9 │ export interface Unknown_Style {} - i The name could be renamed to `SnakeCase`. - ``` @@ -104,25 +96,21 @@ invalidInterface.ts:9:18 lint/style/useNamingConvention ━━━━━━━━ 10 │ 11 │ interface _XXX {} - i The name could be renamed to `UnknownStyle`. - ``` ``` -invalidInterface.ts:11:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidInterface.ts:11:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This interface name trimmed as `XXX` should be in PascalCase. + ! This interface name part should be in PascalCase. 9 │ export interface Unknown_Style {} 10 │ > 11 │ interface _XXX {} - │ ^^^^ + │ ^^^ 12 │ 13 │ interface _CONSTANT_CASE {} - i The name could be renamed to `_Xxx`. - i Safe fix: Rename this symbol in PascalCase. 9 9 │ export interface Unknown_Style {} @@ -136,19 +124,17 @@ invalidInterface.ts:11:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidInterface.ts:13:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidInterface.ts:13:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This interface name trimmed as `CONSTANT_CASE` should be in PascalCase. + ! This interface name part should be in PascalCase. 11 │ interface _XXX {} 12 │ > 13 │ interface _CONSTANT_CASE {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 14 │ 15 │ interface _camelCase {} - i The name could be renamed to `_ConstantCase`. - i Safe fix: Rename this symbol in PascalCase. 11 11 │ interface _XXX {} @@ -162,19 +148,17 @@ invalidInterface.ts:13:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidInterface.ts:15:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidInterface.ts:15:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This interface name trimmed as `camelCase` should be in PascalCase. + ! This interface name part should be in PascalCase. 13 │ interface _CONSTANT_CASE {} 14 │ > 15 │ interface _camelCase {} - │ ^^^^^^^^^^ + │ ^^^^^^^^^ 16 │ 17 │ interface _snake_case {} - i The name could be renamed to `_CamelCase`. - i Safe fix: Rename this symbol in PascalCase. 13 13 │ interface _CONSTANT_CASE {} @@ -188,19 +172,17 @@ invalidInterface.ts:15:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidInterface.ts:17:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidInterface.ts:17:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This interface name trimmed as `snake_case` should be in PascalCase. + ! This interface name part should be in PascalCase. 15 │ interface _camelCase {} 16 │ > 17 │ interface _snake_case {} - │ ^^^^^^^^^^^ + │ ^^^^^^^^^^ 18 │ 19 │ interface _Unknown_Style {} - i The name could be renamed to `_SnakeCase`. - i Safe fix: Rename this symbol in PascalCase. 15 15 │ interface _camelCase {} @@ -214,16 +196,14 @@ invalidInterface.ts:17:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidInterface.ts:19:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidInterface.ts:19:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This interface name trimmed as `Unknown_Style` should be in PascalCase. + ! This interface name part should be in PascalCase. 17 │ interface _snake_case {} 18 │ > 19 │ interface _Unknown_Style {} - │ ^^^^^^^^^^^^^^ - - i The name could be renamed to `_UnknownStyle`. + │ ^^^^^^^^^^^^^ i Safe fix: Rename this symbol in PascalCase. @@ -234,5 +214,3 @@ invalidInterface.ts:19:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidLocalVariable.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidLocalVariable.js.snap index 324e9b75290d..edeaff4e631b 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidLocalVariable.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidLocalVariable.js.snap @@ -26,7 +26,7 @@ export function f() { ``` invalidLocalVariable.js:2:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This local const name should be in camelCase or PascalCase. + ! This const name should be in camelCase or PascalCase. 1 │ export default function () { > 2 │ const CONSTANT_CASE_CONST = 0 @@ -34,8 +34,6 @@ invalidLocalVariable.js:2:11 lint/style/useNamingConvention FIXABLE ━━━ 3 │ 4 │ let CONSTANT_CASE_LET - i The name could be renamed to `constantCaseConst`. - i Safe fix: Rename this symbol in camelCase. 1 1 │ export default function () { @@ -50,7 +48,7 @@ invalidLocalVariable.js:2:11 lint/style/useNamingConvention FIXABLE ━━━ ``` invalidLocalVariable.js:4:9 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This local let name should be in camelCase or PascalCase. + ! This let name should be in camelCase or PascalCase. 2 │ const CONSTANT_CASE_CONST = 0 3 │ @@ -59,8 +57,6 @@ invalidLocalVariable.js:4:9 lint/style/useNamingConvention FIXABLE ━━━ 5 │ 6 │ var CONSTANT_CASE_VAR - i The name could be renamed to `constantCaseLet`. - i Safe fix: Rename this symbol in camelCase. 2 2 │ const CONSTANT_CASE_CONST = 0 @@ -76,7 +72,7 @@ invalidLocalVariable.js:4:9 lint/style/useNamingConvention FIXABLE ━━━ ``` invalidLocalVariable.js:6:9 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This local var name should be in camelCase or PascalCase. + ! This var name should be in camelCase or PascalCase. 4 │ let CONSTANT_CASE_LET 5 │ @@ -85,8 +81,6 @@ invalidLocalVariable.js:6:9 lint/style/useNamingConvention FIXABLE ━━━ 7 │ 8 │ const { prop: Unknown_Style } = obj - i The name could be renamed to `constantCaseVar`. - i Safe fix: Rename this symbol in camelCase. 4 4 │ let CONSTANT_CASE_LET @@ -102,7 +96,7 @@ invalidLocalVariable.js:6:9 lint/style/useNamingConvention FIXABLE ━━━ ``` invalidLocalVariable.js:8:19 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This local const name should be in camelCase or PascalCase. + ! This const name should be in camelCase or PascalCase. 6 │ var CONSTANT_CASE_VAR 7 │ @@ -111,8 +105,6 @@ invalidLocalVariable.js:8:19 lint/style/useNamingConvention FIXABLE ━━━ 9 │ } 10 │ - i The name could be renamed to `unknownStyle`. - i Safe fix: Rename this symbol in camelCase. 6 6 │ var CONSTANT_CASE_VAR @@ -128,7 +120,7 @@ invalidLocalVariable.js:8:19 lint/style/useNamingConvention FIXABLE ━━━ ``` invalidLocalVariable.js:12:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This local const name should be in camelCase or PascalCase. + ! This const name should be in camelCase or PascalCase. 11 │ export function f() { > 12 │ const a_var = 0; @@ -136,8 +128,6 @@ invalidLocalVariable.js:12:11 lint/style/useNamingConvention FIXABLE ━━━ 13 │ console.log(a_var); 14 │ return a_var; - i The name could be renamed to `aVar`. - i Safe fix: Rename this symbol in camelCase. 10 10 │ diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNamespace.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNamespace.ts.snap index 9d00eff0d9f9..2f07a5bfc9e0 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNamespace.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNamespace.ts.snap @@ -32,8 +32,6 @@ invalidNamespace.ts:1:18 lint/style/useNamingConvention ━━━━━━━━ 2 │ 3 │ export namespace CONSTANT_CASE {} - i The name could be renamed to `xxx`. - ``` @@ -49,8 +47,6 @@ invalidNamespace.ts:3:18 lint/style/useNamingConvention ━━━━━━━━ 4 │ 5 │ export namespace snake_case {} - i The name could be renamed to `constantCase`. - ``` @@ -66,8 +62,6 @@ invalidNamespace.ts:5:18 lint/style/useNamingConvention ━━━━━━━━ 6 │ 7 │ export namespace Unknown_Style {} - i The name could be renamed to `snakeCase`. - ``` @@ -83,25 +77,21 @@ invalidNamespace.ts:7:18 lint/style/useNamingConvention ━━━━━━━━ 8 │ 9 │ namespace _XXX {} - i The name could be renamed to `unknownStyle`. - ``` ``` -invalidNamespace.ts:9:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidNamespace.ts:9:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This namespace name trimmed as `XXX` should be in camelCase or PascalCase. + ! This namespace name part should be in camelCase or PascalCase. 7 │ export namespace Unknown_Style {} 8 │ > 9 │ namespace _XXX {} - │ ^^^^ + │ ^^^ 10 │ 11 │ namespace _CONSTANT_CASE {} - i The name could be renamed to `_xxx`. - i Safe fix: Rename this symbol in camelCase. 7 7 │ export namespace Unknown_Style {} @@ -115,19 +105,17 @@ invalidNamespace.ts:9:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidNamespace.ts:11:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidNamespace.ts:11:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This namespace name trimmed as `CONSTANT_CASE` should be in camelCase or PascalCase. + ! This namespace name part should be in camelCase or PascalCase. 9 │ namespace _XXX {} 10 │ > 11 │ namespace _CONSTANT_CASE {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 12 │ 13 │ namespace _snake_case {} - i The name could be renamed to `_constantCase`. - i Safe fix: Rename this symbol in camelCase. 9 9 │ namespace _XXX {} @@ -141,19 +129,17 @@ invalidNamespace.ts:11:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidNamespace.ts:13:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidNamespace.ts:13:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This namespace name trimmed as `snake_case` should be in camelCase or PascalCase. + ! This namespace name part should be in camelCase or PascalCase. 11 │ namespace _CONSTANT_CASE {} 12 │ > 13 │ namespace _snake_case {} - │ ^^^^^^^^^^^ + │ ^^^^^^^^^^ 14 │ 15 │ namespace _Unknown_Style {} - i The name could be renamed to `_snakeCase`. - i Safe fix: Rename this symbol in camelCase. 11 11 │ namespace _CONSTANT_CASE {} @@ -167,16 +153,14 @@ invalidNamespace.ts:13:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidNamespace.ts:15:11 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidNamespace.ts:15:12 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This namespace name trimmed as `Unknown_Style` should be in camelCase or PascalCase. + ! This namespace name part should be in camelCase or PascalCase. 13 │ namespace _snake_case {} 14 │ > 15 │ namespace _Unknown_Style {} - │ ^^^^^^^^^^^^^^ - - i The name could be renamed to `_unknownStyle`. + │ ^^^^^^^^^^^^^ i Safe fix: Rename this symbol in camelCase. @@ -187,5 +171,3 @@ invalidNamespace.ts:15:11 lint/style/useNamingConvention FIXABLE ━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNonAscii.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNonAscii.js.snap index 3b781a4631cb..1e17cf31b84a 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNonAscii.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidNonAscii.js.snap @@ -11,7 +11,7 @@ const café = 0; ``` invalidNonAscii.js:1:7 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level const name should be in ASCII because requireAscii` is set to `true`. + ! This declaration name should be in ASCII because requireAscii is set to `true`. > 1 │ const café = 0; │ ^^^^ @@ -21,5 +21,3 @@ invalidNonAscii.js:1:7 lint/style/useNamingConvention ━━━━━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectGetter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectGetter.js.snap index 7d98a24d4027..26a925453ba6 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectGetter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectGetter.js.snap @@ -25,8 +25,6 @@ invalidObjectGetter.js:2:9 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ get special_property() {}, - i The name could be renamed to `specialProperty`. - ``` @@ -42,8 +40,6 @@ invalidObjectGetter.js:4:9 lint/style/useNamingConvention ━━━━━━━ 5 │ 6 │ get Unknown_Style() {}, - i The name could be renamed to `specialProperty`. - ``` @@ -58,9 +54,5 @@ invalidObjectGetter.js:6:9 lint/style/useNamingConvention ━━━━━━━ │ ^^^^^^^^^^^^^ 7 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectMethod.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectMethod.js.snap index 42f6905be432..ede59d92362c 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectMethod.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectMethod.js.snap @@ -27,15 +27,13 @@ invalidObjectMethod.js:2:5 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ AMethod() {}, - i The name could be renamed to `method`. - ``` ``` invalidObjectMethod.js:4:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This object method name should be in camelCase. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 2 │ METHOD() {}, 3 │ @@ -44,7 +42,8 @@ invalidObjectMethod.js:4:5 lint/style/useNamingConvention ━━━━━━━ 5 │ 6 │ method_1() {}, - i The name could be renamed to `aMethod`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` @@ -61,8 +60,6 @@ invalidObjectMethod.js:6:5 lint/style/useNamingConvention ━━━━━━━ 7 │ 8 │ Unknown_Style() {}, - i The name could be renamed to `method1`. - ``` @@ -77,9 +74,5 @@ invalidObjectMethod.js:8:5 lint/style/useNamingConvention ━━━━━━━ │ ^^^^^^^^^^^^^ 9 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectProperty.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectProperty.js.snap index 74c9104c4a2b..749852b5eb8a 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectProperty.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectProperty.js.snap @@ -29,8 +29,6 @@ invalidObjectProperty.js:2:5 lint/style/useNamingConvention ━━━━━━ 3 │ 4 │ SpecialProperty: 0, - i The name could be renamed to `initialized`. - ``` @@ -46,8 +44,6 @@ invalidObjectProperty.js:4:5 lint/style/useNamingConvention ━━━━━━ 5 │ 6 │ special_property: 0, - i The name could be renamed to `specialProperty`. - ``` @@ -63,8 +59,6 @@ invalidObjectProperty.js:6:5 lint/style/useNamingConvention ━━━━━━ 7 │ 8 │ Unknown_Style: 0, - i The name could be renamed to `specialProperty`. - ``` @@ -80,8 +74,6 @@ invalidObjectProperty.js:8:5 lint/style/useNamingConvention ━━━━━━ 9 │ 10 │ Unknown_Init_Style: 0, - i The name could be renamed to `unknownStyle`. - ``` @@ -96,9 +88,5 @@ invalidObjectProperty.js:10:5 lint/style/useNamingConvention ━━━━━━ │ ^^^^^^^^^^^^^^^^^^ 11 │ } - i The name could be renamed to `unknownInitStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectSetter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectSetter.js.snap index 2e0eaf1772f4..008cb84ddedb 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectSetter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidObjectSetter.js.snap @@ -29,8 +29,6 @@ invalidObjectSetter.js:2:9 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ set PROPERTY(x) {}, - i The name could be renamed to `x`. - ``` @@ -46,8 +44,6 @@ invalidObjectSetter.js:4:9 lint/style/useNamingConvention ━━━━━━━ 5 │ 6 │ set SpecialProperty(x) {}, - i The name could be renamed to `property`. - ``` @@ -63,8 +59,6 @@ invalidObjectSetter.js:6:9 lint/style/useNamingConvention ━━━━━━━ 7 │ 8 │ set special_property(x) {}, - i The name could be renamed to `specialProperty`. - ``` @@ -80,8 +74,6 @@ invalidObjectSetter.js:8:9 lint/style/useNamingConvention ━━━━━━━ 9 │ 10 │ set Unknown_Style(x) {}, - i The name could be renamed to `specialProperty`. - ``` @@ -96,9 +88,5 @@ invalidObjectSetter.js:10:9 lint/style/useNamingConvention ━━━━━━━ │ ^^^^^^^^^^^^^ 11 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidParameterProperty.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidParameterProperty.ts.snap index 444b6173d751..9105c6c0ccd1 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidParameterProperty.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidParameterProperty.ts.snap @@ -19,7 +19,7 @@ export default class { ``` invalidParameterProperty.ts:3:18 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This parameter property name should be in camelCase. + ! This class property name should be in camelCase. 1 │ export default class { 2 │ constructor( @@ -28,15 +28,13 @@ invalidParameterProperty.ts:3:18 lint/style/useNamingConvention ━━━━━ 4 │ 5 │ protected CONSTANT_CASE: unknown, - i The name could be renamed to `pascalCase`. - ``` ``` invalidParameterProperty.ts:5:19 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This parameter property name should be in camelCase. + ! This class property name should be in camelCase. 3 │ readonly PascalCase: unknown, 4 │ @@ -45,15 +43,13 @@ invalidParameterProperty.ts:5:19 lint/style/useNamingConvention ━━━━━ 6 │ 7 │ public Unknown_Style: unknown, - i The name could be renamed to `constantCase`. - ``` ``` invalidParameterProperty.ts:7:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This parameter property name should be in camelCase. + ! This class property name should be in camelCase. 5 │ protected CONSTANT_CASE: unknown, 6 │ @@ -62,9 +58,5 @@ invalidParameterProperty.ts:7:16 lint/style/useNamingConvention ━━━━━ 8 │ ) {} 9 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js.snap index fb2b3efab9ea..c582916a1ff6 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js.snap @@ -21,7 +21,7 @@ expression: invalidSyllabary.js ``` invalidSyllabary.js:2:11 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level const name should be in camelCase or PascalCase or CONSTANT_CASE. + ! This global const name should be in camelCase or PascalCase or CONSTANT_CASE. 1 │ { > 2 │ const 안녕a하세요 = 0; @@ -60,5 +60,3 @@ invalidSyllabary.js:10:11 lint/style/useNamingConvention ━━━━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTopLevelVariable.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTopLevelVariable.ts.snap index 7a4613fbe04a..57435e8dcb08 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTopLevelVariable.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTopLevelVariable.ts.snap @@ -26,22 +26,20 @@ export const fooYPosition = 0; ``` invalidTopLevelVariable.ts:1:14 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level const name should be in camelCase or PascalCase or CONSTANT_CASE. + ! This global const name should be in camelCase or PascalCase or CONSTANT_CASE. > 1 │ export const Unknown_Style = 0 │ ^^^^^^^^^^^^^ 2 │ export const MY__CONSTANT = 0 3 │ - i The name could be renamed to `unknownStyle`. - ``` ``` invalidTopLevelVariable.ts:2:14 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level const name should be in camelCase or PascalCase or CONSTANT_CASE. + ! This global const name should be in camelCase or PascalCase or CONSTANT_CASE. 1 │ export const Unknown_Style = 0 > 2 │ export const MY__CONSTANT = 0 @@ -49,15 +47,13 @@ invalidTopLevelVariable.ts:2:14 lint/style/useNamingConvention ━━━━━ 3 │ 4 │ export var Unknown_Style_1 = 0 - i The name could be renamed to `myConstant`. - ``` ``` invalidTopLevelVariable.ts:4:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level var name should be in camelCase or PascalCase or CONSTANT_CASE. + ! This global var name should be in camelCase or PascalCase or CONSTANT_CASE. 2 │ export const MY__CONSTANT = 0 3 │ @@ -66,15 +62,13 @@ invalidTopLevelVariable.ts:4:12 lint/style/useNamingConvention ━━━━━ 5 │ 6 │ export let Unknown_Style_2 = 0 - i The name could be renamed to `unknownStyle1`. - ``` ``` invalidTopLevelVariable.ts:6:12 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level let name should be in camelCase or PascalCase. + ! This let name should be in camelCase or PascalCase. 4 │ export var Unknown_Style_1 = 0 5 │ @@ -83,15 +77,13 @@ invalidTopLevelVariable.ts:6:12 lint/style/useNamingConvention ━━━━━ 7 │ 8 │ export namespace X { - i The name could be renamed to `unknownStyle2`. - ``` ``` invalidTopLevelVariable.ts:9:18 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level const name should be in camelCase or PascalCase or CONSTANT_CASE. + ! This global const name should be in camelCase or PascalCase or CONSTANT_CASE. 8 │ export namespace X { > 9 │ export const Unknown_Style = 0 @@ -99,15 +91,13 @@ invalidTopLevelVariable.ts:9:18 lint/style/useNamingConvention ━━━━━ 10 │ 11 │ export var Unknown_Style_1 = 0 - i The name could be renamed to `unknownStyle`. - ``` ``` invalidTopLevelVariable.ts:11:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level var name should be in camelCase or PascalCase or CONSTANT_CASE. + ! This global var name should be in camelCase or PascalCase or CONSTANT_CASE. 9 │ export const Unknown_Style = 0 10 │ @@ -116,15 +106,13 @@ invalidTopLevelVariable.ts:11:16 lint/style/useNamingConvention ━━━━━ 12 │ 13 │ export let Unknown_Style_2 = 0 - i The name could be renamed to `unknownStyle1`. - ``` ``` invalidTopLevelVariable.ts:13:16 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This top-level let name should be in camelCase or PascalCase. + ! This let name should be in camelCase or PascalCase. 11 │ export var Unknown_Style_1 = 0 12 │ @@ -133,22 +121,20 @@ invalidTopLevelVariable.ts:13:16 lint/style/useNamingConvention ━━━━━ 14 │ } 15 │ - i The name could be renamed to `unknownStyle2`. - ``` ``` invalidTopLevelVariable.ts:16:14 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Two consecutive uppercase characters are not allowed in camelCase and PascalCase because strictCase is set to `true`. + ! Two consecutive uppercase characters are not allowed in camelCase because strictCase is set to `true`. 14 │ } 15 │ > 16 │ export const fooYPosition = 0; │ ^^^^^^^^^^^^ - i If you want to use consecutive uppercase characters in camelCase and PascalCase, then set the strictCase option to `false`. + i If you want to use consecutive uppercase characters in camelCase, then set the strictCase option to `false`. See the rule options for more details. diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeAlias.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeAlias.ts.snap index 78e3f874fee3..fc538564b832 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeAlias.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeAlias.ts.snap @@ -32,8 +32,6 @@ invalidTypeAlias.ts:1:13 lint/style/useNamingConvention ━━━━━━━━ 2 │ 3 │ export type CONSTANT_CASE = {} - i The name could be renamed to `CamelCase`. - ``` @@ -49,8 +47,6 @@ invalidTypeAlias.ts:3:13 lint/style/useNamingConvention ━━━━━━━━ 4 │ 5 │ export type snake_case = {} - i The name could be renamed to `ConstantCase`. - ``` @@ -66,8 +62,6 @@ invalidTypeAlias.ts:5:13 lint/style/useNamingConvention ━━━━━━━━ 6 │ 7 │ export type Unknown_Style = {} - i The name could be renamed to `SnakeCase`. - ``` @@ -83,25 +77,21 @@ invalidTypeAlias.ts:7:13 lint/style/useNamingConvention ━━━━━━━━ 8 │ 9 │ type _CONSTANT_CASE = {} - i The name could be renamed to `UnknownStyle`. - ``` ``` -invalidTypeAlias.ts:9:6 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidTypeAlias.ts:9:7 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This type alias name trimmed as `CONSTANT_CASE` should be in PascalCase. + ! This type alias name part should be in PascalCase. 7 │ export type Unknown_Style = {} 8 │ > 9 │ type _CONSTANT_CASE = {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 10 │ 11 │ type _snake_case = {} - i The name could be renamed to `_ConstantCase`. - i Safe fix: Rename this symbol in PascalCase. 7 7 │ export type Unknown_Style = {} @@ -115,19 +105,17 @@ invalidTypeAlias.ts:9:6 lint/style/useNamingConvention FIXABLE ━━━━━ ``` ``` -invalidTypeAlias.ts:11:6 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidTypeAlias.ts:11:7 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This type alias name trimmed as `snake_case` should be in PascalCase. + ! This type alias name part should be in PascalCase. 9 │ type _CONSTANT_CASE = {} 10 │ > 11 │ type _snake_case = {} - │ ^^^^^^^^^^^ + │ ^^^^^^^^^^ 12 │ 13 │ type _Unknown_Style = {} - i The name could be renamed to `_SnakeCase`. - i Safe fix: Rename this symbol in PascalCase. 9 9 │ type _CONSTANT_CASE = {} @@ -141,19 +129,17 @@ invalidTypeAlias.ts:11:6 lint/style/useNamingConvention FIXABLE ━━━━ ``` ``` -invalidTypeAlias.ts:13:6 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidTypeAlias.ts:13:7 lint/style/useNamingConvention FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This type alias name trimmed as `Unknown_Style` should be in PascalCase. + ! This type alias name part should be in PascalCase. 11 │ type _snake_case = {} 12 │ > 13 │ type _Unknown_Style = {} - │ ^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 14 │ 15 │ type str = string - i The name could be renamed to `_UnknownStyle`. - i Safe fix: Rename this symbol in PascalCase. 11 11 │ type _snake_case = {} @@ -176,8 +162,6 @@ invalidTypeAlias.ts:15:6 lint/style/useNamingConvention FIXABLE ━━━━ > 15 │ type str = string │ ^^^ - i The name could be renamed to `Str`. - i Safe fix: Rename this symbol in PascalCase. 13 13 │ type _Unknown_Style = {} @@ -187,5 +171,3 @@ invalidTypeAlias.ts:15:6 lint/style/useNamingConvention FIXABLE ━━━━ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeGetter.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeGetter.ts.snap index 7207dcccf73e..89692065932f 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeGetter.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeGetter.ts.snap @@ -25,8 +25,6 @@ invalidTypeGetter.ts:2:9 lint/style/useNamingConvention ━━━━━━━━ 3 │ 4 │ get special_property(): unknown - i The name could be renamed to `specialProperty`. - ``` @@ -42,8 +40,6 @@ invalidTypeGetter.ts:4:9 lint/style/useNamingConvention ━━━━━━━━ 5 │ 6 │ get Unknown_Style(): unknown - i The name could be renamed to `specialProperty`. - ``` @@ -58,9 +54,5 @@ invalidTypeGetter.ts:6:9 lint/style/useNamingConvention ━━━━━━━━ │ ^^^^^^^^^^^^^ 7 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeMethod.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeMethod.ts.snap index 16ccb57de429..91e11aafc509 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeMethod.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeMethod.ts.snap @@ -27,15 +27,13 @@ invalidTypeMethod.ts:2:5 lint/style/useNamingConvention ━━━━━━━━ 3 │ 4 │ AMethod(): unknown - i The name could be renamed to `method`. - ``` ``` invalidTypeMethod.ts:4:5 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! This method name should be in camelCase. + ! Two consecutive uppercase characters are not allowed in PascalCase because strictCase is set to `true`. 2 │ METHOD(): unknown 3 │ @@ -44,7 +42,8 @@ invalidTypeMethod.ts:4:5 lint/style/useNamingConvention ━━━━━━━━ 5 │ 6 │ method_1(): unknown - i The name could be renamed to `aMethod`. + i If you want to use consecutive uppercase characters in PascalCase, then set the strictCase option to `false`. + See the rule options for more details. ``` @@ -61,8 +60,6 @@ invalidTypeMethod.ts:6:5 lint/style/useNamingConvention ━━━━━━━━ 7 │ 8 │ Unknown_Style(): unknown - i The name could be renamed to `method1`. - ``` @@ -77,9 +74,5 @@ invalidTypeMethod.ts:8:5 lint/style/useNamingConvention ━━━━━━━━ │ ^^^^^^^^^^^^^ 9 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeParameter.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeParameter.ts.snap index 038c32e94909..49727ad277c3 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeParameter.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeParameter.ts.snap @@ -22,8 +22,6 @@ invalidTypeParameter.ts:1:23 lint/style/useNamingConvention ━━━━━━ 2 │ 3 │ type Mapped = { - i The name could be renamed to `L`. - ``` @@ -37,8 +35,6 @@ invalidTypeParameter.ts:1:26 lint/style/useNamingConvention ━━━━━━ 2 │ 3 │ type Mapped = { - i The name could be renamed to `L1`. - ``` @@ -52,8 +48,6 @@ invalidTypeParameter.ts:1:30 lint/style/useNamingConvention ━━━━━━ 2 │ 3 │ type Mapped = { - i The name could be renamed to `CamelCase`. - ``` @@ -67,8 +61,6 @@ invalidTypeParameter.ts:1:41 lint/style/useNamingConvention ━━━━━━ 2 │ 3 │ type Mapped = { - i The name could be renamed to `ConstantCase`. - ``` @@ -82,8 +74,6 @@ invalidTypeParameter.ts:1:56 lint/style/useNamingConvention ━━━━━━ 2 │ 3 │ type Mapped = { - i The name could be renamed to `SnakeCase`. - ``` @@ -97,8 +87,6 @@ invalidTypeParameter.ts:1:68 lint/style/useNamingConvention ━━━━━━ 2 │ 3 │ type Mapped = { - i The name could be renamed to `UnknownStyle`. - ``` @@ -112,9 +100,5 @@ invalidTypeParameter.ts:4:6 lint/style/useNamingConvention ━━━━━━━ │ ^ 5 │ } - i The name could be renamed to `K`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeProperty.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeProperty.ts.snap index f8e8173e89b1..d7774a1b321a 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeProperty.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeProperty.ts.snap @@ -31,8 +31,6 @@ invalidTypeProperty.ts:2:5 lint/style/useNamingConvention ━━━━━━━ 3 │ 4 │ SpecialProperty: unknown - i The name could be renamed to `initialized`. - ``` @@ -48,8 +46,6 @@ invalidTypeProperty.ts:4:5 lint/style/useNamingConvention ━━━━━━━ 5 │ 6 │ special_property: unknown - i The name could be renamed to `specialProperty`. - ``` @@ -65,8 +61,6 @@ invalidTypeProperty.ts:6:5 lint/style/useNamingConvention ━━━━━━━ 7 │ 8 │ Unknown_Style: unknown - i The name could be renamed to `specialProperty`. - ``` @@ -82,8 +76,6 @@ invalidTypeProperty.ts:8:5 lint/style/useNamingConvention ━━━━━━━ 9 │ 10 │ Unknown_Init_Style: unknown - i The name could be renamed to `unknownStyle`. - ``` @@ -99,8 +91,6 @@ invalidTypeProperty.ts:10:5 lint/style/useNamingConvention ━━━━━━━ 11 │ 12 │ A_CONSTANT: unknown - i The name could be renamed to `unknownInitStyle`. - ``` @@ -115,9 +105,5 @@ invalidTypeProperty.ts:12:5 lint/style/useNamingConvention ━━━━━━━ │ ^^^^^^^^^^ 13 │ } - i The name could be renamed to `aConstant`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeReadonlyProperty.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeReadonlyProperty.ts.snap index 126573a2e2ac..9e9d81df7dce 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeReadonlyProperty.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeReadonlyProperty.ts.snap @@ -29,8 +29,6 @@ invalidTypeReadonlyProperty.ts:2:14 lint/style/useNamingConvention ━━━━ 3 │ 4 │ readonly SpecialProperty: unknown - i The name could be renamed to `initialized`. - ``` @@ -46,8 +44,6 @@ invalidTypeReadonlyProperty.ts:4:14 lint/style/useNamingConvention ━━━━ 5 │ 6 │ readonly special_property: unknown - i The name could be renamed to `specialProperty`. - ``` @@ -63,8 +59,6 @@ invalidTypeReadonlyProperty.ts:6:14 lint/style/useNamingConvention ━━━━ 7 │ 8 │ readonly Unknown_Style: unknown - i The name could be renamed to `specialProperty`. - ``` @@ -80,8 +74,6 @@ invalidTypeReadonlyProperty.ts:8:14 lint/style/useNamingConvention ━━━━ 9 │ 10 │ readonly Unknown_Init_Style: unknown - i The name could be renamed to `unknownStyle`. - ``` @@ -96,9 +88,5 @@ invalidTypeReadonlyProperty.ts:10:14 lint/style/useNamingConvention ━━━━ │ ^^^^^^^^^^^^^^^^^^ 11 │ } - i The name could be renamed to `unknownInitStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeSetter.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeSetter.ts.snap index 2d013b73ac74..5f9af1b3197e 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeSetter.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidTypeSetter.ts.snap @@ -29,8 +29,6 @@ invalidTypeSetter.ts:2:9 lint/style/useNamingConvention ━━━━━━━━ 3 │ 4 │ set PROPERTY(x: unknown ) - i The name could be renamed to `x`. - ``` @@ -46,8 +44,6 @@ invalidTypeSetter.ts:4:9 lint/style/useNamingConvention ━━━━━━━━ 5 │ 6 │ set SpecialProperty(x: unknown ) - i The name could be renamed to `property`. - ``` @@ -63,8 +59,6 @@ invalidTypeSetter.ts:6:9 lint/style/useNamingConvention ━━━━━━━━ 7 │ 8 │ set special_property(x: unknown ) - i The name could be renamed to `specialProperty`. - ``` @@ -80,8 +74,6 @@ invalidTypeSetter.ts:8:9 lint/style/useNamingConvention ━━━━━━━━ 9 │ 10 │ set Unknown_Style(x: unknown ) - i The name could be renamed to `specialProperty`. - ``` @@ -96,9 +88,5 @@ invalidTypeSetter.ts:10:9 lint/style/useNamingConvention ━━━━━━━ │ ^^^^^^^^^^^^^ 11 │ } - i The name could be renamed to `unknownStyle`. - ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap index d927f5069d9f..00beeb799f84 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap @@ -11,22 +11,21 @@ expression: malformedOptions.js ``` malformedOptions.options:9:25 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - × Found an unknown value `snake_case`. + × Found an unknown value `kebab-case`. 7 │ "level": "error", 8 │ "options": { - > 9 │ "enumMemberCase": "snake_case" + > 9 │ "enumMemberCase": "kebab-case" │ ^^^^^^^^^^^^ 10 │ } 11 │ } i Accepted values: - - PascalCase - - CONSTANT_CASE - camelCase + - CONSTANT_CASE + - PascalCase + - snake_case ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.options.json index fec3ee98566f..8928f09d9cfa 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.options.json +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.options.json @@ -6,7 +6,7 @@ "useNamingConvention": { "level": "error", "options": { - "enumMemberCase": "snake_case" + "enumMemberCase": "kebab-case" } } } diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js new file mode 100644 index 000000000000..8928f09d9cfa --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "enumMemberCase": "kebab-case" + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js.snap new file mode 100644 index 000000000000..aa33c65cdd60 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.js.snap @@ -0,0 +1,138 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: malformedSelector.js +--- +# Input +```jsx +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "enumMemberCase": "kebab-case" + } + } + } + } + } +} + +``` + +# Diagnostics +``` +malformedSelector.options:11:21 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The `private` and `protected` modifiers cannot be used toghether. + + 9 │ "conventions": [ + 10 │ { + > 11 │ "selector": { + │ ^ + > 12 │ "kind": "classMember", + > 13 │ "modifiers": ["private", "protected"] + > 14 │ }, + │ ^ + 15 │ "match": ".*" + 16 │ }, { + + +``` + +``` +malformedSelector.options:17:21 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The `abstract` and `static` modifiers cannot be used toghether. + + 15 │ "match": ".*" + 16 │ }, { + > 17 │ "selector": { + │ ^ + > 18 │ "kind": "classMember", + > 19 │ "modifiers": ["abstract", "static"] + > 20 │ }, + │ ^ + 21 │ "match": ".*" + 22 │ }, { + + +``` + +``` +malformedSelector.options:23:21 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The `Private` modifier can only be used on class member kinds. + + 21 │ "match": ".*" + 22 │ }, { + > 23 │ "selector": { + │ ^ + > 24 │ "kind": "const", + > 25 │ "modifiers": ["private"] + > 26 │ }, + │ ^ + 27 │ "match": ".*" + 28 │ }, { + + +``` + +``` +malformedSelector.options:29:21 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The `readonly` modifier can only be used on class and type property kinds. + + 27 │ "match": ".*" + 28 │ }, { + > 29 │ "selector": { + │ ^ + > 30 │ "kind": "const", + > 31 │ "modifiers": ["readonly"] + > 32 │ }, + │ ^ + 33 │ "match": ".*" + 34 │ }, { + + +``` + +``` +malformedSelector.options:35:21 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The `abstract` modifier can only be used on classes and class member kinds. + + 33 │ "match": ".*" + 34 │ }, { + > 35 │ "selector": { + │ ^ + > 36 │ "kind": "interface", + > 37 │ "modifiers": ["abstract"] + > 38 │ }, + │ ^ + 39 │ "match": ".*" + 40 │ }, { + + +``` + +``` +malformedSelector.options:41:21 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The `global` scope can only be used on type and variable kinds. + + 39 │ "match": ".*" + 40 │ }, { + > 41 │ "selector": { + │ ^ + > 42 │ "kind": "classMember", + > 43 │ "scope": "global" + > 44 │ }, + │ ^ + 45 │ "match": ".*" + 46 │ } + + +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.options.json new file mode 100644 index 000000000000..07d27632cb08 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedSelector.options.json @@ -0,0 +1,53 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "selector": { + "kind": "classMember", + "modifiers": ["private", "protected"] + }, + "match": ".*" + }, { + "selector": { + "kind": "classMember", + "modifiers": ["abstract", "static"] + }, + "match": ".*" + }, { + "selector": { + "kind": "const", + "modifiers": ["private"] + }, + "match": ".*" + }, { + "selector": { + "kind": "const", + "modifiers": ["readonly"] + }, + "match": ".*" + }, { + "selector": { + "kind": "interface", + "modifiers": ["abstract"] + }, + "match": ".*" + }, { + "selector": { + "kind": "classMember", + "scope": "global" + }, + "match": ".*" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.options.json new file mode 100644 index 000000000000..b67d9d25a56c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.options.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "match": "aSepcial_CASE|(.*)" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts new file mode 100644 index 000000000000..0dd558934d01 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts @@ -0,0 +1 @@ +const aSepcial_CASE = 0; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts.snap new file mode 100644 index 000000000000..b35cfefca222 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyle.ts.snap @@ -0,0 +1,8 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: validCustomStyle.ts +--- +# Input +```ts +const aSepcial_CASE = 0; +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.options.json new file mode 100644 index 000000000000..b67d9d25a56c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.options.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "match": "aSepcial_CASE|(.*)" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts new file mode 100644 index 000000000000..0dd558934d01 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts @@ -0,0 +1 @@ +const aSepcial_CASE = 0; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts.snap new file mode 100644 index 000000000000..edb826a29cb9 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleExceptions.ts.snap @@ -0,0 +1,8 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: validCustomStyleExceptions.ts +--- +# Input +```ts +const aSepcial_CASE = 0; +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.options.json new file mode 100644 index 000000000000..e3f5ca6e63e0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.options.json @@ -0,0 +1,24 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "selector": { + "kind": "classMember", + "modifiers": ["private"] + }, + "match": "_(.*)", + "formats": ["snake_case"] + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts new file mode 100644 index 000000000000..f5123c239202 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts @@ -0,0 +1,3 @@ +class X { + private _private: number +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts.snap new file mode 100644 index 000000000000..222298ed96b3 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validCustomStyleUnderscorePrivate.ts.snap @@ -0,0 +1,10 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: validCustomStyleUnderscorePrivate.ts +--- +# Input +```ts +class X { + private _private: number +} +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectGetter.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectGetter.js.snap index 76dd8b865c56..fafdc069baf1 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectGetter.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectGetter.js.snap @@ -22,5 +22,3 @@ export default { get "@"() {}, } ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectProperty.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectProperty.js.snap index 8b4a849cf3dc..65815d3a7c93 100644 --- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectProperty.js.snap +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validObjectProperty.js.snap @@ -24,5 +24,3 @@ export default { "@": 0, } ``` - - diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js new file mode 100644 index 000000000000..e132298320ac --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js @@ -0,0 +1,41 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "custom": [ + { + "selector": { + "kind": "classProperty", + "modifiers": ["protected", "static", "readonly"] + }, + "match": ".*" + }, { + "selector": { + "kind": "typeProperty", + "modifiers": ["readonly"] + }, + "match": ".*" + }, { + "selector": { + "kind": "variableLike", + "scope": "global" + }, + "match": ".*" + }, { + "selector": { + "kind": "typeLike", + "scope": "global" + }, + "match": ".*" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js.snap new file mode 100644 index 000000000000..e4273ada11af --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.js.snap @@ -0,0 +1,49 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: wellformedSelector.js +--- +# Input +```jsx +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "custom": [ + { + "selector": { + "kind": "classProperty", + "modifiers": ["protected", "static", "readonly"] + }, + "match": ".*" + }, { + "selector": { + "kind": "typeProperty", + "modifiers": ["readonly"] + }, + "match": ".*" + }, { + "selector": { + "kind": "variableLike", + "scope": "global" + }, + "match": ".*" + }, { + "selector": { + "kind": "typeLike", + "scope": "global" + }, + "match": ".*" + } + ] + } + } + } + } + } +} + +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.options.json b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.options.json new file mode 100644 index 000000000000..199e036aa9c4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/wellformedSelector.options.json @@ -0,0 +1,41 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "selector": { + "kind": "classProperty", + "modifiers": ["protected", "static", "readonly"] + }, + "match": ".*" + }, { + "selector": { + "kind": "typeProperty", + "modifiers": ["readonly"] + }, + "match": ".*" + }, { + "selector": { + "kind": "variable", + "scope": "global" + }, + "match": ".*" + }, { + "selector": { + "kind": "typeLike", + "scope": "global" + }, + "match": ".*" + } + ] + } + } + } + } + } +} diff --git a/crates/biome_js_formatter/src/utils/format_modifiers.rs b/crates/biome_js_formatter/src/utils/format_modifiers.rs index a4b76b385d27..501aa761bf77 100644 --- a/crates/biome_js_formatter/src/utils/format_modifiers.rs +++ b/crates/biome_js_formatter/src/utils/format_modifiers.rs @@ -3,7 +3,7 @@ use crate::utils::sort_modifiers_by_precedence; use crate::{AsFormat, IntoFormat}; use biome_formatter::{format_args, write}; use biome_js_syntax::JsSyntaxKind::JS_DECORATOR; -use biome_js_syntax::{JsLanguage, Modifiers}; +use biome_js_syntax::{JsLanguage, Modifier}; use biome_rowan::{AstNode, AstNodeList, NodeOrToken}; pub(crate) struct FormatModifiers { @@ -20,7 +20,7 @@ impl Format for FormatModifiers where Node: AstNode + AsFormat + IntoFormat, List: AstNodeList, - Modifiers: for<'a> From<&'a Node>, + Modifier: for<'a> From<&'a Node>, { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let modifiers = sort_modifiers_by_precedence(&self.list); diff --git a/crates/biome_js_formatter/src/utils/mod.rs b/crates/biome_js_formatter/src/utils/mod.rs index 516f6a826e95..860751c15b4b 100644 --- a/crates/biome_js_formatter/src/utils/mod.rs +++ b/crates/biome_js_formatter/src/utils/mod.rs @@ -32,7 +32,7 @@ pub(crate) use binary_like_expression::{ use biome_formatter::{format_args, write, Buffer}; use biome_js_syntax::JsSyntaxToken; use biome_js_syntax::{ - AnyJsExpression, AnyJsStatement, JsCallExpression, JsInitializerClause, JsLanguage, Modifiers, + AnyJsExpression, AnyJsStatement, JsCallExpression, JsInitializerClause, JsLanguage, Modifier, }; use biome_rowan::{AstNode, AstNodeList}; use biome_text_size::TextSize; @@ -206,11 +206,11 @@ pub(crate) fn sort_modifiers_by_precedence(list: &List) -> Vec where Node: AstNode, List: AstNodeList, - Modifiers: for<'a> From<&'a Node>, + Modifier: for<'a> From<&'a Node>, { let mut nodes_and_modifiers = list.iter().collect::>(); - nodes_and_modifiers.sort_unstable_by_key(|node| Modifiers::from(node)); + nodes_and_modifiers.sort_unstable_by_key(|node| Modifier::from(node)); nodes_and_modifiers } diff --git a/crates/biome_js_syntax/Cargo.toml b/crates/biome_js_syntax/Cargo.toml index 6b2cd3d4da29..9c32b829d863 100644 --- a/crates/biome_js_syntax/Cargo.toml +++ b/crates/biome_js_syntax/Cargo.toml @@ -12,6 +12,7 @@ version = "0.5.7" [dependencies] biome_rowan = { workspace = true } +enumflags2 = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/biome_js_syntax/src/modifier_ext.rs b/crates/biome_js_syntax/src/modifier_ext.rs index b626a1d57806..18900860b587 100644 --- a/crates/biome_js_syntax/src/modifier_ext.rs +++ b/crates/biome_js_syntax/src/modifier_ext.rs @@ -1,93 +1,175 @@ use crate::{ AnyJsMethodModifier, AnyJsPropertyModifier, AnyTsIndexSignatureModifier, AnyTsMethodSignatureModifier, AnyTsPropertyParameterModifier, AnyTsPropertySignatureModifier, - AnyTsTypeParameterModifier, JsSyntaxKind, TsAccessibilityModifier, + AnyTsTypeParameterModifier, JsMethodModifierList, JsPropertyModifierList, JsSyntaxKind, + TsAccessibilityModifier, TsMethodSignatureModifierList, TsPropertySignatureModifierList, }; /// Helpful data structure to make the order of modifiers predictable inside the formatter -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] -pub enum Modifiers { +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[enumflags2::bitflags] +#[repr(u16)] +pub enum Modifier { // modifiers must be sorted by precedence. - Decorator, - Accessibility, - Declare, - Static, - Abstract, - Override, - Readonly, - Accessor, + Decorator = 1 << 0, + BogusAccessibility = 1 << 1, + Private = 1 << 2, + Protected = 1 << 3, + Public = 1 << 4, + Declare = 1 << 5, + Static = 1 << 6, + Abstract = 1 << 7, + Override = 1 << 8, + Readonly = 1 << 9, + Accessor = 1 << 10, } -impl From<&AnyTsIndexSignatureModifier> for Modifiers { +impl std::fmt::Display for Modifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Modifier::Decorator => "decorator", + Modifier::BogusAccessibility => "accessibility", + Modifier::Private => "private", + Modifier::Protected => "protected", + Modifier::Public => "public", + Modifier::Declare => "declare", + Modifier::Static => "static", + Modifier::Abstract => "abstract", + Modifier::Override => "override", + Modifier::Readonly => "readonly", + Modifier::Accessor => "accessor", + } + ) + } +} + +impl From<&AnyTsIndexSignatureModifier> for Modifier { fn from(modifier: &AnyTsIndexSignatureModifier) -> Self { match modifier { - AnyTsIndexSignatureModifier::JsStaticModifier(_) => Modifiers::Static, - AnyTsIndexSignatureModifier::TsReadonlyModifier(_) => Modifiers::Readonly, + AnyTsIndexSignatureModifier::JsStaticModifier(_) => Modifier::Static, + AnyTsIndexSignatureModifier::TsReadonlyModifier(_) => Modifier::Readonly, } } } -impl From<&AnyJsMethodModifier> for Modifiers { +impl From<&AnyJsMethodModifier> for Modifier { fn from(modifier: &AnyJsMethodModifier) -> Self { match modifier { - AnyJsMethodModifier::JsDecorator(_) => Modifiers::Decorator, - AnyJsMethodModifier::JsStaticModifier(_) => Modifiers::Static, - AnyJsMethodModifier::TsAccessibilityModifier(_) => Modifiers::Accessibility, - AnyJsMethodModifier::TsOverrideModifier(_) => Modifiers::Override, + AnyJsMethodModifier::JsDecorator(_) => Modifier::Decorator, + AnyJsMethodModifier::JsStaticModifier(_) => Modifier::Static, + AnyJsMethodModifier::TsAccessibilityModifier(accessibility) => accessibility.into(), + AnyJsMethodModifier::TsOverrideModifier(_) => Modifier::Override, } } } -impl From<&AnyTsMethodSignatureModifier> for Modifiers { +impl From<&AnyTsMethodSignatureModifier> for Modifier { fn from(modifier: &AnyTsMethodSignatureModifier) -> Self { match modifier { - AnyTsMethodSignatureModifier::JsDecorator(_) => Modifiers::Decorator, - AnyTsMethodSignatureModifier::JsStaticModifier(_) => Modifiers::Static, - AnyTsMethodSignatureModifier::TsAbstractModifier(_) => Modifiers::Abstract, - AnyTsMethodSignatureModifier::TsAccessibilityModifier(_) => Modifiers::Accessibility, - AnyTsMethodSignatureModifier::TsOverrideModifier(_) => Modifiers::Override, + AnyTsMethodSignatureModifier::JsDecorator(_) => Modifier::Decorator, + AnyTsMethodSignatureModifier::JsStaticModifier(_) => Modifier::Static, + AnyTsMethodSignatureModifier::TsAbstractModifier(_) => Modifier::Abstract, + AnyTsMethodSignatureModifier::TsAccessibilityModifier(accessibility) => { + accessibility.into() + } + AnyTsMethodSignatureModifier::TsOverrideModifier(_) => Modifier::Override, } } } -impl From<&AnyJsPropertyModifier> for Modifiers { +impl From<&AnyJsPropertyModifier> for Modifier { fn from(modifier: &AnyJsPropertyModifier) -> Self { match modifier { - AnyJsPropertyModifier::JsDecorator(_) => Modifiers::Decorator, - AnyJsPropertyModifier::JsStaticModifier(_) => Modifiers::Static, - AnyJsPropertyModifier::JsAccessorModifier(_) => Modifiers::Accessor, - AnyJsPropertyModifier::TsAccessibilityModifier(_) => Modifiers::Accessibility, - AnyJsPropertyModifier::TsOverrideModifier(_) => Modifiers::Override, - AnyJsPropertyModifier::TsReadonlyModifier(_) => Modifiers::Readonly, + AnyJsPropertyModifier::JsDecorator(_) => Modifier::Decorator, + AnyJsPropertyModifier::JsStaticModifier(_) => Modifier::Static, + AnyJsPropertyModifier::JsAccessorModifier(_) => Modifier::Accessor, + AnyJsPropertyModifier::TsAccessibilityModifier(accessibility) => accessibility.into(), + AnyJsPropertyModifier::TsOverrideModifier(_) => Modifier::Override, + AnyJsPropertyModifier::TsReadonlyModifier(_) => Modifier::Readonly, } } } -impl From<&AnyTsPropertyParameterModifier> for Modifiers { +impl From<&AnyTsPropertyParameterModifier> for Modifier { fn from(modifier: &AnyTsPropertyParameterModifier) -> Self { match modifier { - AnyTsPropertyParameterModifier::TsAccessibilityModifier(_) => Modifiers::Accessibility, - AnyTsPropertyParameterModifier::TsOverrideModifier(_) => Modifiers::Override, - AnyTsPropertyParameterModifier::TsReadonlyModifier(_) => Modifiers::Readonly, + AnyTsPropertyParameterModifier::TsAccessibilityModifier(accessibility) => { + accessibility.into() + } + AnyTsPropertyParameterModifier::TsOverrideModifier(_) => Modifier::Override, + AnyTsPropertyParameterModifier::TsReadonlyModifier(_) => Modifier::Readonly, } } } -impl From<&AnyTsPropertySignatureModifier> for Modifiers { +impl From<&AnyTsPropertySignatureModifier> for Modifier { fn from(modifier: &AnyTsPropertySignatureModifier) -> Self { match modifier { - AnyTsPropertySignatureModifier::JsDecorator(_) => Modifiers::Decorator, - AnyTsPropertySignatureModifier::TsAccessibilityModifier(_) => Modifiers::Accessibility, - AnyTsPropertySignatureModifier::TsDeclareModifier(_) => Modifiers::Declare, - AnyTsPropertySignatureModifier::JsStaticModifier(_) => Modifiers::Static, - AnyTsPropertySignatureModifier::JsAccessorModifier(_) => Modifiers::Accessor, - AnyTsPropertySignatureModifier::TsAbstractModifier(_) => Modifiers::Abstract, - AnyTsPropertySignatureModifier::TsOverrideModifier(_) => Modifiers::Override, - AnyTsPropertySignatureModifier::TsReadonlyModifier(_) => Modifiers::Readonly, + AnyTsPropertySignatureModifier::JsDecorator(_) => Modifier::Decorator, + AnyTsPropertySignatureModifier::TsAccessibilityModifier(accessibility) => { + accessibility.into() + } + AnyTsPropertySignatureModifier::TsDeclareModifier(_) => Modifier::Declare, + AnyTsPropertySignatureModifier::JsStaticModifier(_) => Modifier::Static, + AnyTsPropertySignatureModifier::JsAccessorModifier(_) => Modifier::Accessor, + AnyTsPropertySignatureModifier::TsAbstractModifier(_) => Modifier::Abstract, + AnyTsPropertySignatureModifier::TsOverrideModifier(_) => Modifier::Override, + AnyTsPropertySignatureModifier::TsReadonlyModifier(_) => Modifier::Readonly, + } + } +} + +impl From<&TsAccessibilityModifier> for Modifier { + fn from(value: &TsAccessibilityModifier) -> Self { + if let Ok(modifier_token) = value.modifier_token() { + match modifier_token.kind() { + JsSyntaxKind::PRIVATE_KW => Self::Private, + JsSyntaxKind::PROTECTED_KW => Self::Protected, + JsSyntaxKind::PUBLIC_KW => Self::Public, + _ => Self::BogusAccessibility, + } + } else { + Self::BogusAccessibility } } } +impl From<&JsMethodModifierList> for enumflags2::BitFlags { + fn from(value: &JsMethodModifierList) -> Self { + value + .into_iter() + .map(|m| Modifier::from(&m)) + .fold(Self::empty(), |acc, m| acc | m) + } +} +impl From<&JsPropertyModifierList> for enumflags2::BitFlags { + fn from(value: &JsPropertyModifierList) -> 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 + .into_iter() + .map(|m| Modifier::from(&m)) + .fold(Self::empty(), |acc, m| acc | m) + } +} +impl From<&TsMethodSignatureModifierList> for enumflags2::BitFlags { + fn from(value: &TsMethodSignatureModifierList) -> Self { + value + .into_iter() + .map(|m| Modifier::from(&m)) + .fold(Self::empty(), |acc, m| acc | m) + } +} + /// Helpful data structure to make the order of type parameter modifiers predictable inside the formatter #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] pub enum TypeParameterModifiers { diff --git a/crates/biome_service/src/file_handlers/javascript.rs b/crates/biome_service/src/file_handlers/javascript.rs index 451c6c1aa16f..72612c6dec7a 100644 --- a/crates/biome_service/src/file_handlers/javascript.rs +++ b/crates/biome_service/src/file_handlers/javascript.rs @@ -742,7 +742,7 @@ fn rename( match node.try_into() { Ok(node) => { let mut batch = root.begin(); - let result = batch.rename_any_renamable_node(&model, node, &new_name); + let result = batch.rename_any_renamable_node(&model, &node, &new_name); if !result { Err(WorkspaceError::RenameError(RenameError::CannotBeRenamed { original_name: original_name.to_string(), diff --git a/crates/biome_service/tests/invalid/naming_convention_incorrect_options.json.snap b/crates/biome_service/tests/invalid/naming_convention_incorrect_options.json.snap index 55b7d413b180..a9f789a7cfc6 100644 --- a/crates/biome_service/tests/invalid/naming_convention_incorrect_options.json.snap +++ b/crates/biome_service/tests/invalid/naming_convention_incorrect_options.json.snap @@ -17,7 +17,5 @@ naming_convention_incorrect_options.json:9:7 deserialize ━━━━━━━ - strictCase - requireAscii + - conventions - enumMemberCase - - - diff --git a/crates/biome_string_case/src/lib.rs b/crates/biome_string_case/src/lib.rs index ebb4d9288dff..19dbf1516692 100644 --- a/crates/biome_string_case/src/lib.rs +++ b/crates/biome_string_case/src/lib.rs @@ -3,31 +3,57 @@ /// Represents the [Case] of a string. /// /// Note that some cases are superset of others. -/// For example, `Case::Camel` includes `Case::Lower`. -/// See [Case::is_compatible_with] for more details. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] +/// For example, a name in [Case::Lower] is also in [Case::Camel], [Case::Kebab] , and [Case::Snake]. +/// Thus [Case::Camel], [Case::Kebab], and [Case::Snake] are superset of [Case::Lower]. +/// `Case::Unknown` is a superset of all [Case]. +/// +/// The relation between cases is depicted in the following diagram. +/// The arrow means "is subset of". +/// +/// ```svgbob +/// ┌──► Pascal ────────────┐ +/// NumberableCapital ─┤ │ +/// └──► Upper ─► Constant ─┤ +/// ├──► Unknown +/// ┌──► Camel ─────────────┤ +/// Lower ─┤ │ +/// └──► Kebab ─────────────┤ +/// │ │ +/// └──► Snake ─────────────┤ +/// │ +/// Uni ─────────────────────────┘ +/// ``` +/// +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[repr(u16)] pub enum Case { - /// Unknown case - #[default] - Unknown, - /// camelCase - Camel, - // CONSTANT_CASE - Constant, - /// kebab-case - Kebab, - /// lowercase - Lower, + /// Alphanumeric Characters that cannot be in lowercase or uppercase (numbers and syllabary) + Uni = 1 << 0, /// A, B1, C42 - NumberableCapital, + NumberableCapital = 1 << 1, + /// UPPERCASE + Upper = Case::NumberableCapital as u16 | 1 << 2, + // CONSTANT_CASE + Constant = Case::Upper as u16 | 1 << 3, /// PascalCase - Pascal, + Pascal = Case::NumberableCapital as u16 | 1 << 4, + /// lowercase + Lower = 1 << 5, /// snake_case - Snake, - /// Alphanumeric Characters that cannot be in lowercase or uppercase (numbers and syllabary) - Uni, - /// UPPERCASE - Upper, + Snake = Case::Lower as u16 | 1 << 6, + /// kebab-case + Kebab = Case::Lower as u16 | 1 << 7, + // camelCase + Camel = Case::Lower as u16 | 1 << 8, + /// Unknown case + #[default] + Unknown = Case::Camel as u16 + | Case::Kebab as u16 + | Case::Snake as u16 + | Case::Pascal as u16 + | Case::Constant as u16 + | Case::Uni as u16 + | 1 << 9, } impl Case { @@ -136,57 +162,6 @@ impl Case { result } - /// Returns `true` if a name that respects `self` also respects `other`. - /// - /// For example, a name in [Case::Lower] is also in [Case::Camel], [Case::Kebab] , and [Case::Snake]. - /// Thus [Case::Lower] is compatible with [Case::Camel], [Case::Kebab] , and [Case::Snake]. - /// - /// Any [Case] is compatible with `Case::Unknown` and with itself. - /// - /// The compatibility relation between cases is depicted in the following diagram. - /// The arrow means "is compatible with". - /// - /// ```svgbob - /// ┌──► Pascal ────────────┐ - /// NumberableCapital ─┤ │ - /// └──► Upper ─► Constant ─┤ - /// ├──► Unknown - /// ┌──► Kebab ─────────────┤ - /// Lower ─┤ │ - /// └──► Camel ─────────────┤ - /// │ - /// Uni ─────────────────────────┘ - /// ``` - /// - /// ``` - /// use biome_string_case::Case; - /// - /// assert!(Case::Lower.is_compatible_with(Case::Camel)); - /// assert!(Case::Lower.is_compatible_with(Case::Kebab)); - /// assert!(Case::Lower.is_compatible_with(Case::Lower)); - /// assert!(Case::Lower.is_compatible_with(Case::Snake)); - /// - /// assert!(Case::NumberableCapital.is_compatible_with(Case::Constant)); - /// assert!(Case::NumberableCapital.is_compatible_with(Case::Pascal)); - /// assert!(Case::NumberableCapital.is_compatible_with(Case::Upper)); - /// - /// assert!(Case::Upper.is_compatible_with(Case::Constant)); - /// ``` - pub fn is_compatible_with(self, other: Case) -> bool { - self == other - || matches!(other, Case::Unknown) - || matches!((self, other), |( - Case::Lower, - Case::Camel | Case::Kebab | Case::Snake, - )| ( - Case::NumberableCapital, - Case::Constant | Case::Pascal | Case::Upper - ) | ( - Case::Upper, - Case::Constant - )) - } - /// Convert `value` to the `self` [Case]. /// /// ``` @@ -296,6 +271,140 @@ impl std::fmt::Display for Case { } } +/// Represents a set of cases. +/// +/// An instance of [Cases] supports the binary operators `|` to unionize two sets or add a new [Case]. +/// +/// Note that some [Case] are already sets of [Case]. +/// For example, [Case::Unknown] is a set that includes all [Case]. +/// So adding [Case::Unknown] to a [Cases] will superseed all other cases. +/// +/// A [Cases] is iterable. +/// A Cases iterator doesn't yield a [Case] that is covered by another [Case] in the set. +/// See [CasesIterator] for more details. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct Cases(u16); + +impl Cases { + /// Create an empty set. + /// + /// You can also obtain an empty alias using [Cases::default()]. + pub const fn empty() -> Self { + Self(0) + } + + /// Returns `true` if the set is empty. + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + /// Returns `true` if all cases of `other` are contained in the current set. + /// + /// ``` + /// use biome_string_case::{Cases, Case}; + /// + /// let camel_or_kebab = (Case::Camel | Case::Kebab); + /// + /// assert!(camel_or_kebab.contains(Case::Camel)); + /// assert!(camel_or_kebab.contains(camel_or_kebab)); + /// ``` + pub fn contains(self, other: impl Into) -> bool { + let other = other.into(); + self.0 & other.0 == other.0 + } +} + +impl IntoIterator for Cases { + type Item = Case; + type IntoIter = CasesIterator; + fn into_iter(self) -> Self::IntoIter { + CasesIterator { rest: self } + } +} + +impl FromIterator for Cases { + fn from_iter>(iter: T) -> Self { + iter.into_iter() + .fold(Self::empty(), |result, case| result | case) + } +} + +impl From for Cases { + fn from(value: Case) -> Self { + Self(value as u16) + } +} + +impl> core::ops::BitOr for Cases { + type Output = Cases; + fn bitor(self, rhs: Rhs) -> Self::Output { + Self(self.0 | rhs.into().0) + } +} +impl core::ops::BitOr for Case { + type Output = Cases; + fn bitor(self, rhs: Self) -> Self::Output { + Cases::from(self) | rhs + } +} +impl> core::ops::BitOrAssign for Cases { + fn bitor_assign(&mut self, rhs: Rhs) { + self.0 |= rhs.into().0; + } +} + +/// An iterator of [Cases]. +/// +/// The iterator doesn't yield a [Case] that is covered by another [Case] in the iterated set. +/// For example, if the set includes [Case::Constant] and [Case::Upper], +/// the iterator only yields [Case::Constant] because [Case::Constant] covers [Case::Upper]. +/// +/// ``` +/// use biome_string_case::{Cases, Case}; +/// +/// let cases = Case::Camel | Case::Kebab; +/// assert_eq!(cases.into_iter().collect::>().as_slice(), &[Case::Camel, Case::Kebab]); +/// +/// let cases = Case::Camel | Case::Kebab | Case::Lower; +/// assert_eq!(cases.into_iter().collect::>().as_slice(), &[Case::Camel, Case::Kebab]); +/// ``` +#[derive(Clone, Debug)] +pub struct CasesIterator { + rest: Cases, +} +impl Iterator for CasesIterator { + type Item = Case; + + fn next(&mut self) -> Option { + if self.rest.is_empty() { + None + } else { + let leading_bit_index = 15u16 - (self.rest.0.leading_zeros() as u16); + let case = LEADING_BIT_INDEX_TO_CASE[leading_bit_index as usize]; + self.rest.0 &= !(case as u16); + Some(case) + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(6)) + } +} +impl std::iter::FusedIterator for CasesIterator {} + +const LEADING_BIT_INDEX_TO_CASE: [Case; 10] = [ + Case::Uni, + Case::NumberableCapital, + Case::Upper, + Case::Constant, + Case::Pascal, + Case::Lower, + Case::Snake, + Case::Kebab, + Case::Camel, + Case::Unknown, +]; + #[cfg(test)] mod tests { use super::*; @@ -352,116 +461,138 @@ mod tests { } #[test] - fn test_case_is_compatible() { - assert!(Case::Unknown.is_compatible_with(Case::Unknown)); - assert!(!Case::Unknown.is_compatible_with(Case::Camel)); - assert!(!Case::Unknown.is_compatible_with(Case::Constant)); - assert!(!Case::Unknown.is_compatible_with(Case::Kebab)); - assert!(!Case::Unknown.is_compatible_with(Case::Lower)); - assert!(!Case::Unknown.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Unknown.is_compatible_with(Case::Pascal)); - assert!(!Case::Unknown.is_compatible_with(Case::Snake)); - assert!(!Case::Unknown.is_compatible_with(Case::Uni)); - assert!(!Case::Unknown.is_compatible_with(Case::Upper)); - - assert!(Case::Camel.is_compatible_with(Case::Unknown)); - assert!(Case::Camel.is_compatible_with(Case::Camel)); - assert!(!Case::Camel.is_compatible_with(Case::Constant)); - assert!(!Case::Camel.is_compatible_with(Case::Kebab)); - assert!(!Case::Camel.is_compatible_with(Case::Lower)); - assert!(!Case::Camel.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Camel.is_compatible_with(Case::Pascal)); - assert!(!Case::Camel.is_compatible_with(Case::Snake)); - assert!(!Case::Camel.is_compatible_with(Case::Uni)); - assert!(!Case::Camel.is_compatible_with(Case::Upper)); - - assert!(Case::Constant.is_compatible_with(Case::Unknown)); - assert!(!Case::Constant.is_compatible_with(Case::Camel)); - assert!(Case::Constant.is_compatible_with(Case::Constant)); - assert!(!Case::Constant.is_compatible_with(Case::Kebab)); - assert!(!Case::Constant.is_compatible_with(Case::Lower)); - assert!(!Case::Constant.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Constant.is_compatible_with(Case::Pascal)); - assert!(!Case::Constant.is_compatible_with(Case::Snake)); - assert!(!Case::Constant.is_compatible_with(Case::Uni)); - assert!(!Case::Constant.is_compatible_with(Case::Upper)); - - assert!(Case::Kebab.is_compatible_with(Case::Unknown)); - assert!(!Case::Kebab.is_compatible_with(Case::Camel)); - assert!(!Case::Kebab.is_compatible_with(Case::Constant)); - assert!(Case::Kebab.is_compatible_with(Case::Kebab)); - assert!(!Case::Kebab.is_compatible_with(Case::Lower)); - assert!(!Case::Kebab.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Kebab.is_compatible_with(Case::Pascal)); - assert!(!Case::Kebab.is_compatible_with(Case::Snake)); - assert!(!Case::Kebab.is_compatible_with(Case::Uni)); - assert!(!Case::Kebab.is_compatible_with(Case::Upper)); - - assert!(Case::Lower.is_compatible_with(Case::Unknown)); - assert!(Case::Lower.is_compatible_with(Case::Camel)); - assert!(!Case::Lower.is_compatible_with(Case::Constant)); - assert!(Case::Lower.is_compatible_with(Case::Kebab)); - assert!(Case::Lower.is_compatible_with(Case::Lower)); - assert!(!Case::Lower.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Lower.is_compatible_with(Case::Pascal)); - assert!(Case::Lower.is_compatible_with(Case::Snake)); - assert!(!Case::Lower.is_compatible_with(Case::Uni)); - assert!(!Case::Lower.is_compatible_with(Case::Upper)); - - assert!(Case::NumberableCapital.is_compatible_with(Case::Unknown)); - assert!(!Case::NumberableCapital.is_compatible_with(Case::Camel)); - assert!(Case::NumberableCapital.is_compatible_with(Case::Constant)); - assert!(!Case::NumberableCapital.is_compatible_with(Case::Kebab)); - assert!(!Case::NumberableCapital.is_compatible_with(Case::Lower)); - assert!(Case::NumberableCapital.is_compatible_with(Case::NumberableCapital)); - assert!(Case::NumberableCapital.is_compatible_with(Case::Pascal)); - assert!(!Case::NumberableCapital.is_compatible_with(Case::Snake)); - assert!(!Case::NumberableCapital.is_compatible_with(Case::Uni)); - assert!(Case::NumberableCapital.is_compatible_with(Case::Upper)); - - assert!(Case::Pascal.is_compatible_with(Case::Unknown)); - assert!(!Case::Pascal.is_compatible_with(Case::Camel)); - assert!(!Case::Pascal.is_compatible_with(Case::Constant)); - assert!(!Case::Pascal.is_compatible_with(Case::Kebab)); - assert!(!Case::Pascal.is_compatible_with(Case::Lower)); - assert!(!Case::Pascal.is_compatible_with(Case::NumberableCapital)); - assert!(Case::Pascal.is_compatible_with(Case::Pascal)); - assert!(!Case::Pascal.is_compatible_with(Case::Snake)); - assert!(!Case::Pascal.is_compatible_with(Case::Uni)); - assert!(!Case::Pascal.is_compatible_with(Case::Upper)); - - assert!(Case::Snake.is_compatible_with(Case::Unknown)); - assert!(!Case::Snake.is_compatible_with(Case::Camel)); - assert!(!Case::Snake.is_compatible_with(Case::Constant)); - assert!(!Case::Snake.is_compatible_with(Case::Kebab)); - assert!(!Case::Snake.is_compatible_with(Case::Lower)); - assert!(!Case::Snake.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Snake.is_compatible_with(Case::Pascal)); - assert!(Case::Snake.is_compatible_with(Case::Snake)); - assert!(!Case::Snake.is_compatible_with(Case::Uni)); - assert!(!Case::Snake.is_compatible_with(Case::Upper)); - - assert!(Case::Uni.is_compatible_with(Case::Unknown)); - assert!(!Case::Uni.is_compatible_with(Case::Camel)); - assert!(!Case::Uni.is_compatible_with(Case::Constant)); - assert!(!Case::Uni.is_compatible_with(Case::Kebab)); - assert!(!Case::Uni.is_compatible_with(Case::Lower)); - assert!(!Case::Uni.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Uni.is_compatible_with(Case::Pascal)); - assert!(!Case::Uni.is_compatible_with(Case::Snake)); - assert!(Case::Uni.is_compatible_with(Case::Uni)); - assert!(!Case::Uni.is_compatible_with(Case::Upper)); - - assert!(Case::Upper.is_compatible_with(Case::Unknown)); - assert!(!Case::Upper.is_compatible_with(Case::Camel)); - assert!(Case::Upper.is_compatible_with(Case::Constant)); - assert!(!Case::Upper.is_compatible_with(Case::Kebab)); - assert!(!Case::Upper.is_compatible_with(Case::Lower)); - assert!(!Case::Upper.is_compatible_with(Case::NumberableCapital)); - assert!(!Case::Upper.is_compatible_with(Case::Pascal)); - assert!(!Case::Upper.is_compatible_with(Case::Snake)); - assert!(!Case::Upper.is_compatible_with(Case::Uni)); - assert!(Case::Upper.is_compatible_with(Case::Upper)); + fn test_cases_contains() { + // Individual cases + assert!(Cases::from(Case::Unknown).contains(Case::Unknown)); + assert!(!Cases::from(Case::Camel).contains(Case::Unknown)); + assert!(!Cases::from(Case::Constant).contains(Case::Unknown)); + assert!(!Cases::from(Case::Kebab).contains(Case::Unknown)); + assert!(!Cases::from(Case::Lower).contains(Case::Unknown)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Unknown)); + assert!(!Cases::from(Case::Pascal).contains(Case::Unknown)); + assert!(!Cases::from(Case::Snake).contains(Case::Unknown)); + assert!(!Cases::from(Case::Uni).contains(Case::Unknown)); + assert!(!Cases::from(Case::Upper).contains(Case::Unknown)); + + assert!(Cases::from(Case::Unknown).contains(Case::Camel)); + assert!(Cases::from(Case::Camel).contains(Case::Camel)); + assert!(!Cases::from(Case::Constant).contains(Case::Camel)); + assert!(!Cases::from(Case::Kebab).contains(Case::Camel)); + assert!(!Cases::from(Case::Lower).contains(Case::Camel)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Camel)); + assert!(!Cases::from(Case::Pascal).contains(Case::Camel)); + assert!(!Cases::from(Case::Snake).contains(Case::Camel)); + assert!(!Cases::from(Case::Uni).contains(Case::Camel)); + assert!(!Cases::from(Case::Upper).contains(Case::Camel)); + + assert!(Cases::from(Case::Unknown).contains(Case::Constant)); + assert!(!Cases::from(Case::Camel).contains(Case::Constant)); + assert!(Cases::from(Case::Constant).contains(Case::Constant)); + assert!(!Cases::from(Case::Kebab).contains(Case::Constant)); + assert!(!Cases::from(Case::Lower).contains(Case::Constant)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Constant)); + assert!(!Cases::from(Case::Pascal).contains(Case::Constant)); + assert!(!Cases::from(Case::Snake).contains(Case::Constant)); + assert!(!Cases::from(Case::Uni).contains(Case::Constant)); + assert!(!Cases::from(Case::Upper).contains(Case::Constant)); + + assert!(Cases::from(Case::Unknown).contains(Case::Kebab)); + assert!(!Cases::from(Case::Camel).contains(Case::Kebab)); + assert!(!Cases::from(Case::Constant).contains(Case::Kebab)); + assert!(Cases::from(Case::Kebab).contains(Case::Kebab)); + assert!(!Cases::from(Case::Lower).contains(Case::Kebab)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Kebab)); + assert!(!Cases::from(Case::Pascal).contains(Case::Kebab)); + assert!(!Cases::from(Case::Snake).contains(Case::Kebab)); + assert!(!Cases::from(Case::Uni).contains(Case::Kebab)); + assert!(!Cases::from(Case::Upper).contains(Case::Kebab)); + + assert!(Cases::from(Case::Unknown).contains(Case::Lower)); + assert!(Cases::from(Case::Camel).contains(Case::Lower)); + assert!(!Cases::from(Case::Constant).contains(Case::Lower)); + assert!(Cases::from(Case::Kebab).contains(Case::Lower)); + assert!(Cases::from(Case::Lower).contains(Case::Lower)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Lower)); + assert!(!Cases::from(Case::Pascal).contains(Case::Lower)); + assert!(Cases::from(Case::Snake).contains(Case::Lower)); + assert!(!Cases::from(Case::Uni).contains(Case::Lower)); + assert!(!Cases::from(Case::Upper).contains(Case::Lower)); + + assert!(Cases::from(Case::Unknown).contains(Case::NumberableCapital)); + assert!(!Cases::from(Case::Camel).contains(Case::NumberableCapital)); + assert!(Cases::from(Case::Constant).contains(Case::NumberableCapital)); + assert!(!Cases::from(Case::Kebab).contains(Case::NumberableCapital)); + assert!(!Cases::from(Case::Lower).contains(Case::NumberableCapital)); + assert!(Cases::from(Case::NumberableCapital).contains(Case::NumberableCapital)); + assert!(Cases::from(Case::Pascal).contains(Case::NumberableCapital)); + assert!(!Cases::from(Case::Snake).contains(Case::NumberableCapital)); + assert!(!Cases::from(Case::Uni).contains(Case::NumberableCapital)); + assert!(Cases::from(Case::Upper).contains(Case::NumberableCapital)); + + assert!(Cases::from(Case::Unknown).contains(Case::Pascal)); + assert!(!Cases::from(Case::Camel).contains(Case::Pascal)); + assert!(!Cases::from(Case::Constant).contains(Case::Pascal)); + assert!(!Cases::from(Case::Kebab).contains(Case::Pascal)); + assert!(!Cases::from(Case::Lower).contains(Case::Pascal)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Pascal)); + assert!(Cases::from(Case::Pascal).contains(Case::Pascal)); + assert!(!Cases::from(Case::Snake).contains(Case::Pascal)); + assert!(!Cases::from(Case::Uni).contains(Case::Pascal)); + assert!(!Cases::from(Case::Upper).contains(Case::Pascal)); + + assert!(Cases::from(Case::Unknown).contains(Case::Snake)); + assert!(!Cases::from(Case::Camel).contains(Case::Snake)); + assert!(!Cases::from(Case::Constant).contains(Case::Snake)); + assert!(!Cases::from(Case::Kebab).contains(Case::Snake)); + assert!(!Cases::from(Case::Lower).contains(Case::Snake)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Snake)); + assert!(!Cases::from(Case::Pascal).contains(Case::Snake)); + assert!(Cases::from(Case::Snake).contains(Case::Snake)); + assert!(!Cases::from(Case::Uni).contains(Case::Snake)); + assert!(!Cases::from(Case::Upper).contains(Case::Snake)); + + assert!(Cases::from(Case::Unknown).contains(Case::Uni)); + assert!(!Cases::from(Case::Camel).contains(Case::Uni)); + assert!(!Cases::from(Case::Constant).contains(Case::Uni)); + assert!(!Cases::from(Case::Kebab).contains(Case::Uni)); + assert!(!Cases::from(Case::Lower).contains(Case::Uni)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Uni)); + assert!(!Cases::from(Case::Pascal).contains(Case::Uni)); + assert!(!Cases::from(Case::Snake).contains(Case::Uni)); + assert!(Cases::from(Case::Uni).contains(Case::Uni)); + assert!(!Cases::from(Case::Upper).contains(Case::Uni)); + + assert!(Cases::from(Case::Unknown).contains(Case::Upper)); + assert!(!Cases::from(Case::Camel).contains(Case::Upper)); + assert!(Cases::from(Case::Constant).contains(Case::Upper)); + assert!(!Cases::from(Case::Kebab).contains(Case::Upper)); + assert!(!Cases::from(Case::Lower).contains(Case::Upper)); + assert!(!Cases::from(Case::NumberableCapital).contains(Case::Upper)); + assert!(!Cases::from(Case::Pascal).contains(Case::Upper)); + assert!(!Cases::from(Case::Snake).contains(Case::Upper)); + assert!(!Cases::from(Case::Uni).contains(Case::Upper)); + assert!(Cases::from(Case::Upper).contains(Case::Upper)); + + // Set of cases + assert!((Case::Camel | Case::Kebab | Case::Snake).contains(Case::Camel)); + assert!((Case::Camel | Case::Kebab | Case::Snake).contains(Case::Kebab)); + assert!((Case::Camel | Case::Kebab | Case::Snake).contains(Case::Snake)); + assert!((Case::Camel | Case::Kebab | Case::Snake).contains(Case::Lower)); + assert!(!(Case::Camel | Case::Kebab | Case::Snake).contains(Case::Unknown)); + assert!(!(Case::Camel | Case::Kebab | Case::Snake).contains(Case::Constant)); + assert!(!(Case::Camel | Case::Kebab | Case::Snake).contains(Case::Upper)); + assert!(!(Case::Camel | Case::Kebab | Case::Snake).contains(Case::NumberableCapital)); + assert!(!(Case::Camel | Case::Kebab | Case::Snake).contains(Case::Uni)); + + assert!((Case::Constant | Case::Upper).contains(Case::Constant)); + assert!((Case::Constant | Case::Upper).contains(Case::Upper)); + assert!((Case::Constant | Case::Upper).contains(Case::NumberableCapital)); + assert!(!(Case::Constant | Case::Upper).contains(Case::Unknown)); + assert!(!(Case::Constant | Case::Upper).contains(Case::Camel)); + assert!(!(Case::Constant | Case::Upper).contains(Case::Kebab)); + assert!(!(Case::Constant | Case::Upper).contains(Case::Snake)); + assert!(!(Case::Constant | Case::Upper).contains(Case::Lower)); + assert!(!(Case::Constant | Case::Upper).contains(Case::Uni)); } #[test] @@ -523,4 +654,134 @@ mod tests { assert_eq!(Case::Unknown.convert("Unknown_Style"), "Unknown_Style"); } + + #[test] + fn test_cases_iter() { + fn vec(value: impl Into) -> Vec { + value.into().into_iter().collect::>() + } + + assert_eq!(vec(Cases::empty()).as_slice(), &[]); + assert_eq!(vec(Case::Unknown).as_slice(), &[Case::Unknown]); + assert_eq!(vec(Case::Camel).as_slice(), &[Case::Camel]); + assert_eq!(vec(Case::Kebab).as_slice(), &[Case::Kebab]); + assert_eq!(vec(Case::Snake).as_slice(), &[Case::Snake]); + assert_eq!(vec(Case::Lower).as_slice(), &[Case::Lower]); + assert_eq!(vec(Case::Pascal).as_slice(), &[Case::Pascal]); + assert_eq!(vec(Case::Constant).as_slice(), &[Case::Constant]); + assert_eq!(vec(Case::Upper).as_slice(), &[Case::Upper]); + assert_eq!(vec(Case::Uni).as_slice(), &[Case::Uni]); + assert_eq!( + vec(Case::NumberableCapital).as_slice(), + &[Case::NumberableCapital] + ); + + assert_eq!( + vec(Case::Unknown | Case::Camel).as_slice(), + &[Case::Unknown] + ); + assert_eq!( + vec(Case::Unknown | Case::Kebab).as_slice(), + &[Case::Unknown] + ); + assert_eq!( + vec(Case::Unknown | Case::Snake).as_slice(), + &[Case::Unknown] + ); + assert_eq!( + vec(Case::Unknown | Case::Lower).as_slice(), + &[Case::Unknown] + ); + assert_eq!( + vec(Case::Unknown | Case::Pascal).as_slice(), + &[Case::Unknown] + ); + assert_eq!( + vec(Case::Unknown | Case::Constant).as_slice(), + &[Case::Unknown] + ); + assert_eq!( + vec(Case::Unknown | Case::Upper).as_slice(), + &[Case::Unknown] + ); + assert_eq!( + vec(Case::Unknown | Case::NumberableCapital).as_slice(), + &[Case::Unknown] + ); + assert_eq!(vec(Case::Unknown | Case::Uni).as_slice(), &[Case::Unknown]); + assert_eq!( + vec(Case::Unknown | Case::Pascal | Case::Camel).as_slice(), + &[Case::Unknown] + ); + + assert_eq!(vec(Case::Camel | Case::Lower).as_slice(), &[Case::Camel]); + assert_eq!(vec(Case::Kebab | Case::Lower).as_slice(), &[Case::Kebab]); + assert_eq!(vec(Case::Snake | Case::Lower).as_slice(), &[Case::Snake]); + + assert_eq!( + vec(Case::Constant | Case::Upper).as_slice(), + &[Case::Constant] + ); + + assert_eq!( + vec(Case::Pascal | Case::NumberableCapital).as_slice(), + &[Case::Pascal] + ); + assert_eq!( + vec(Case::Constant | Case::NumberableCapital).as_slice(), + &[Case::Constant] + ); + assert_eq!( + vec(Case::Upper | Case::NumberableCapital).as_slice(), + &[Case::Upper] + ); + + assert_eq!( + vec(Case::Pascal | Case::Camel).as_slice(), + &[Case::Camel, Case::Pascal] + ); + assert_eq!( + vec(Case::NumberableCapital | Case::Uni).as_slice(), + &[Case::NumberableCapital, Case::Uni] + ); + + assert_eq!( + vec(Case::Pascal + | Case::Constant + | Case::Camel + | Case::Kebab + | Case::Snake + | Case::Uni) + .as_slice(), + &[ + Case::Camel, + Case::Kebab, + Case::Snake, + Case::Pascal, + Case::Constant, + Case::Uni + ] + ); + } + + #[test] + fn test_leading_bit_to_case() { + for (i, case) in LEADING_BIT_INDEX_TO_CASE.iter().enumerate() { + assert_eq!(i as u16, 15u16 - (*case as u16).leading_zeros() as u16) + } + } + + #[test] + fn test_size_hint_upper_limit() { + let mut cases = Cases::empty(); + let mut max_count = 0; + for case in LEADING_BIT_INDEX_TO_CASE { + let count = (cases | case).into_iter().count(); + if count >= max_count { + cases |= case; + max_count = count; + } + } + assert_eq!(cases.into_iter().size_hint().1, Some(max_count)); + } } diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index e901995e152e..79e751d7e09f 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1738,10 +1738,14 @@ export interface FilenamingConventionOptions { * Rule's options. */ export interface NamingConventionOptions { + /** + * Custom conventions. + */ + conventions: Convention[]; /** * Allowed cases for _TypeScript_ `enum` member names. */ - enumMemberCase: EnumMemberCase; + enumMemberCase: Format; /** * If `false`, then non-ASCII characters are allowed. */ @@ -1779,10 +1783,28 @@ For example, for React's `useRef()` hook the value would be `true`, while for `u } export type ConsistentArrayType = "shorthand" | "generic"; export type FilenameCases = FilenameCase[]; +export interface Convention { + /** + * String cases to enforce + */ + formats: Formats; + /** + * Regular expression to enforce + */ + match?: Regex; + /** + * Declarations concerned by this convention + */ + selector: Selector; +} /** - * Supported cases for TypeScript `enum` member names. + * Supported cases. */ -export type EnumMemberCase = "PascalCase" | "CONSTANT_CASE" | "camelCase"; +export type Format = + | "camelCase" + | "CONSTANT_CASE" + | "PascalCase" + | "snake_case"; export type StableHookResult = boolean | number[]; /** * Supported cases for file names. @@ -1793,6 +1815,69 @@ export type FilenameCase = | "kebab-case" | "PascalCase" | "snake_case"; +export type Formats = Format[]; +export type Regex = string; +export interface Selector { + /** + * Declaration kind + */ + kind: Kind; + /** + * Modifiers used on the declaration + */ + modifiers: Modifiers; + /** + * Scope of the declaration + */ + scope: Scope; +} +export type Kind = + | "class" + | "enum" + | "interface" + | "enumMember" + | "importNamespace" + | "exportNamespace" + | "variable" + | "const" + | "let" + | "using" + | "var" + | "catchParameter" + | "indexParameter" + | "exportAlias" + | "importAlias" + | "classGetter" + | "classSetter" + | "classMethod" + | "objectLiteralProperty" + | "objectLiteralGetter" + | "objectLiteralSetter" + | "objectLiteralMethod" + | "typeAlias" + | "any" + | "typeLike" + | "function" + | "namespaceLike" + | "namespace" + | "functionParameter" + | "typeParameter" + | "classMember" + | "classProperty" + | "objectLiteralMember" + | "typeMember" + | "typeGetter" + | "typeProperty" + | "typeSetter" + | "typeMethod"; +export type Modifiers = RestrictedModifier[]; +export type Scope = "any" | "global"; +export type RestrictedModifier = + | "abstract" + | "private" + | "protected" + | "readonly" + | "static"; export interface RegisterProjectFolderParams { path?: string; setAsCurrentWorkspace: boolean; diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 5c7ddb32e9b2..998bd9f01190 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -565,6 +565,24 @@ }, "additionalProperties": false }, + "Convention": { + "type": "object", + "properties": { + "formats": { + "description": "String cases to enforce", + "allOf": [{ "$ref": "#/definitions/Formats" }] + }, + "match": { + "description": "Regular expression to enforce", + "anyOf": [{ "$ref": "#/definitions/Regex" }, { "type": "null" }] + }, + "selector": { + "description": "Declarations concerned by this convention", + "allOf": [{ "$ref": "#/definitions/Selector" }] + } + }, + "additionalProperties": false + }, "Correctness": { "description": "A list of rules that belong to this group", "type": "object", @@ -919,22 +937,6 @@ "type": "object", "additionalProperties": false }, - "EnumMemberCase": { - "description": "Supported cases for TypeScript `enum` member names.", - "oneOf": [ - { - "description": "PascalCase", - "type": "string", - "enum": ["PascalCase"] - }, - { - "description": "CONSTANT_CASE", - "type": "string", - "enum": ["CONSTANT_CASE"] - }, - { "description": "camelCase", "type": "string", "enum": ["camelCase"] } - ] - }, "FilenameCase": { "description": "Supported cases for file names.", "oneOf": [ @@ -1016,6 +1018,16 @@ }, "additionalProperties": false }, + "Format": { + "description": "Supported cases.", + "type": "string", + "enum": ["camelCase", "CONSTANT_CASE", "PascalCase", "snake_case"] + }, + "Formats": { + "type": "array", + "items": { "$ref": "#/definitions/Format" }, + "uniqueItems": true + }, "FormatterConfiguration": { "description": "Generic options applied to all files", "type": "object", @@ -1341,6 +1353,109 @@ } ] }, + "Kind": { + "oneOf": [ + { + "type": "string", + "enum": [ + "class", + "enum", + "interface", + "enumMember", + "importNamespace", + "exportNamespace", + "variable", + "const", + "let", + "using", + "var", + "catchParameter", + "indexParameter", + "exportAlias", + "importAlias", + "classGetter", + "classSetter", + "classMethod", + "objectLiteralProperty", + "objectLiteralGetter", + "objectLiteralSetter", + "objectLiteralMethod", + "typeAlias" + ] + }, + { "description": "All kinds", "type": "string", "enum": ["any"] }, + { + "description": "All type definitions: classes, enums, interfaces, and type aliases", + "type": "string", + "enum": ["typeLike"] + }, + { + "description": "Named function declarations and expressions", + "type": "string", + "enum": ["function"] + }, + { + "description": "TypeScript mamespaces, import and export namesapces", + "type": "string", + "enum": ["namespaceLike"] + }, + { + "description": "TypeScript mamespaces", + "type": "string", + "enum": ["namespace"] + }, + { + "description": "All function parameters, but parameter properties", + "type": "string", + "enum": ["functionParameter"] + }, + { + "description": "All generic type parameters", + "type": "string", + "enum": ["typeParameter"] + }, + { + "description": "All class members: properties, methods, getters, and setters", + "type": "string", + "enum": ["classMember"] + }, + { + "description": "All class properties, including parameter properties", + "type": "string", + "enum": ["classProperty"] + }, + { + "description": "All object literal members: properties, methods, getters, and setters", + "type": "string", + "enum": ["objectLiteralMember"] + }, + { + "description": "All members defined in type alaises and interfaces", + "type": "string", + "enum": ["typeMember"] + }, + { + "description": "All getters defined in type alaises and interfaces", + "type": "string", + "enum": ["typeGetter"] + }, + { + "description": "All properties defined in type alaises and interfaces", + "type": "string", + "enum": ["typeProperty"] + }, + { + "description": "All setters defined in type alaises and interfaces", + "type": "string", + "enum": ["typeSetter"] + }, + { + "description": "All methods defined in type alaises and interfaces", + "type": "string", + "enum": ["typeMethod"] + } + ] + }, "LineEnding": { "oneOf": [ { @@ -1388,6 +1503,11 @@ }, "additionalProperties": false }, + "Modifiers": { + "type": "array", + "items": { "$ref": "#/definitions/RestrictedModifier" }, + "uniqueItems": true + }, "NamingConventionConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -1398,9 +1518,14 @@ "description": "Rule's options.", "type": "object", "properties": { + "conventions": { + "description": "Custom conventions.", + "type": "array", + "items": { "$ref": "#/definitions/Convention" } + }, "enumMemberCase": { "description": "Allowed cases for _TypeScript_ `enum` member names.", - "allOf": [{ "$ref": "#/definitions/EnumMemberCase" }] + "allOf": [{ "$ref": "#/definitions/Format" }] }, "requireAscii": { "description": "If `false`, then non-ASCII characters are allowed.", @@ -1883,6 +2008,7 @@ }, "QuoteProperties": { "type": "string", "enum": ["asNeeded", "preserve"] }, "QuoteStyle": { "type": "string", "enum": ["double", "single"] }, + "Regex": { "type": "string" }, "RestrictedGlobalsConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -1921,6 +2047,10 @@ }, "additionalProperties": false }, + "RestrictedModifier": { + "type": "string", + "enum": ["abstract", "private", "protected", "readonly", "static"] + }, "RuleConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -2076,6 +2206,7 @@ }, "additionalProperties": false }, + "Scope": { "type": "string", "enum": ["any", "global"] }, "Security": { "description": "A list of rules that belong to this group", "type": "object", @@ -2112,6 +2243,24 @@ }, "additionalProperties": false }, + "Selector": { + "type": "object", + "properties": { + "kind": { + "description": "Declaration kind", + "allOf": [{ "$ref": "#/definitions/Kind" }] + }, + "modifiers": { + "description": "Modifiers used on the declaration", + "allOf": [{ "$ref": "#/definitions/Modifiers" }] + }, + "scope": { + "description": "Scope of the declaration", + "allOf": [{ "$ref": "#/definitions/Scope" }] + } + }, + "additionalProperties": false + }, "Semicolons": { "type": "string", "enum": ["always", "asNeeded"] }, "StableHookResult": { "oneOf": [