diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d8321c5a948..7e87a7808678 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -199,6 +199,27 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom
Contributed by @Conaclos
+- [useNamingConvention](https://biomejs.dev/linter/rules/use-naming-convention) now supports [unicase](https://en.wikipedia.org/wiki/Unicase) letters ([#1786](https://github.com/biomejs/biome/issues/1786)).
+
+ [unicase](https://en.wikipedia.org/wiki/Unicase) letters have a single case: they are neither uppercase nor lowercase.
+ Previously, Biome reported names in unicase as invalid.
+ It now accepts a name in unicase everywhere.
+
+ The following code is now accepted:
+
+ ```js
+ const 안녕하세요 = { 안녕하세요: 0 };
+ ```
+
+ We still reject a name that mixes unicase characters with lowercase or uppercase characters:
+ The following names are rejected:
+
+ ```js
+ const A안녕하세요 = { a안녕하세요: 0 };
+ ```
+
+ Contributed by @Conaclos
+
#### Bug fixes
- Fix [#1651](https://github.com/biomejs/biome/issues/1651). [noVar](https://biomejs.dev/linter/rules/no-var/) now ignores TsGlobalDeclaration. Contributed by @vasucp1207
diff --git a/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs b/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs
index f594c4e0e60c..89f6ed39cfb1 100644
--- a/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs
+++ b/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs
@@ -304,7 +304,8 @@ impl Rule for UseNamingConvention {
}
let trimmed_name = trim_underscore_dollar(name);
let actual_case = Case::identify(trimmed_name, options.strict_case);
- if trimmed_name.is_empty()
+ if actual_case == Case::Uni
+ || trimmed_name.is_empty()
|| allowed_cases
.iter()
.any(|&expected_style| actual_case.is_compatible_with(expected_style))
@@ -314,7 +315,8 @@ impl Rule for UseNamingConvention {
}
let preferred_case = element.allowed_cases(ctx.options())[0];
let new_trimmed_name = preferred_case.convert(trimmed_name);
- let suggested_name = name.replace(trimmed_name, &new_trimmed_name);
+ let suggested_name = (trimmed_name != new_trimmed_name)
+ .then(|| name.replacen(trimmed_name, &new_trimmed_name, 1));
Some(State {
element,
suggested_name,
@@ -357,26 +359,33 @@ impl Rule for UseNamingConvention {
}));
}
}
-
- Some(RuleDiagnostic::new(
+ 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}"."
},
- ).note(markup! {
- "The name could be renamed to `"{suggested_name}"`."
- }))
+ );
+ Some(if let Some(suggested_name) = suggested_name {
+ diagnostic.note(markup! {
+ "The name could be renamed to `"{suggested_name}"`."
+ })
+ } else {
+ diagnostic
+ })
}
fn action(ctx: &RuleContext, state: &Self::State) -> Option {
- let node = ctx.query();
- let model = ctx.model();
- let mut mutation = ctx.root().begin();
let State {
element,
suggested_name,
} = state;
+ let Some(suggested_name) = suggested_name 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) {
@@ -452,7 +461,7 @@ impl AnyIdentifierBindingLike {
#[derive(Debug)]
pub(crate) struct State {
element: Named,
- suggested_name: String,
+ suggested_name: Option,
}
/// Rule's options.
diff --git a/crates/biome_js_analyze/src/utils/case.rs b/crates/biome_js_analyze/src/utils/case.rs
index b254b81669ed..1f22d14a58e4 100644
--- a/crates/biome_js_analyze/src/utils/case.rs
+++ b/crates/biome_js_analyze/src/utils/case.rs
@@ -22,6 +22,8 @@ pub enum Case {
Pascal,
/// snake_case
Snake,
+ /// Alphanumeric Characters that cannot be in lowercase or uppercase (numbers and syllabary)
+ Uni,
/// UPPERCASE
Upper,
}
@@ -44,7 +46,7 @@ impl Case {
/// assert_eq!(Case::identify("aHttpServer", /* no effect */ true), Case::Camel);
/// assert_eq!(Case::identify("aHTTPServer", true), Case::Unknown);
/// assert_eq!(Case::identify("aHTTPServer", false), Case::Camel);
- /// assert_eq!(Case::identify("v8Engine", true), Case::Camel);
+ /// assert_eq!(Case::identify("v8Engine", /* no effect */ true), Case::Camel);
///
/// assert_eq!(Case::identify("HTTP_SERVER", /* no effect */ true), Case::Constant);
/// assert_eq!(Case::identify("V8_ENGINE", /* no effect */ true), Case::Constant);
@@ -59,27 +61,32 @@ impl Case {
/// assert_eq!(Case::identify("HttpServer", /* no effect */ true), Case::Pascal);
/// assert_eq!(Case::identify("HTTPServer", true), Case::Unknown);
/// assert_eq!(Case::identify("HTTPServer", false), Case::Pascal);
- /// assert_eq!(Case::identify("V8Engine", true), Case::Pascal);
+ /// assert_eq!(Case::identify("V8Engine", /* no effect */ true), Case::Pascal);
///
/// assert_eq!(Case::identify("http_server", /* no effect */ true), Case::Snake);
///
/// assert_eq!(Case::identify("HTTPSERVER", /* no effect */ true), Case::Upper);
///
+ /// assert_eq!(Case::identify("100", /* no effect */ true), Case::Uni);
+ /// assert_eq!(Case::identify("안녕하세요", /* no effect */ true), Case::Uni);
+ ///
/// assert_eq!(Case::identify("", /* no effect */ true), Case::Unknown);
/// assert_eq!(Case::identify("_", /* no effect */ true), Case::Unknown);
+ /// assert_eq!(Case::identify("안녕하세요abc", /* no effect */ true), Case::Unknown);
/// ```
pub fn identify(value: &str, strict: bool) -> Case {
let mut chars = value.chars();
let Some(first_char) = chars.next() else {
return Case::Unknown;
};
- if !first_char.is_alphanumeric() {
- return Case::Unknown;
- }
let mut result = if first_char.is_uppercase() {
Case::NumberableCapital
- } else {
+ } else if first_char.is_lowercase() {
Case::Lower
+ } else if first_char.is_alphanumeric() {
+ Case::Uni
+ } else {
+ return Case::Unknown;
};
let mut previous_char = first_char;
let mut has_consecutive_uppercase = false;
@@ -92,9 +99,7 @@ impl Case {
'_' => match result {
Case::Constant | Case::NumberableCapital | Case::Upper => Case::Constant,
Case::Lower | Case::Snake => Case::Snake,
- Case::Camel | Case::Kebab | Case::Pascal | Case::Unknown => {
- return Case::Unknown
- }
+ _ => return Case::Unknown,
},
_ if current_char.is_uppercase() => {
has_consecutive_uppercase |= previous_char.is_uppercase();
@@ -105,16 +110,20 @@ impl Case {
Case::Camel | Case::Constant | Case::Pascal => result,
Case::Lower => Case::Camel,
Case::NumberableCapital | Case::Upper => Case::Upper,
- Case::Kebab | Case::Snake | Case::Unknown => return Case::Unknown,
+ _ => return Case::Unknown,
}
}
_ if current_char.is_lowercase() => match result {
Case::Camel | Case::Kebab | Case::Lower | Case::Snake => result,
Case::Pascal | Case::NumberableCapital => Case::Pascal,
Case::Upper if !strict || !has_consecutive_uppercase => Case::Pascal,
- Case::Constant | Case::Upper | Case::Unknown => return Case::Unknown,
+ _ => return Case::Unknown,
+ },
+ _ if current_char.is_numeric() => result,
+ _ if current_char.is_alphabetic() => match result {
+ Case::Uni => Case::Uni,
+ _ => return Case::Unknown,
},
- _ if current_char.is_numeric() => result, // Figures don't change the case.
_ => return Case::Unknown,
};
previous_char = current_char;
@@ -129,6 +138,21 @@ impl Case {
///
/// 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 ─────────────────────────┘
+ /// ```
+ ///
/// ### Examples
///
/// ```
@@ -144,24 +168,6 @@ impl Case {
/// assert!(Case::NumberableCapital.is_compatible_with(Case::Upper));
///
/// assert!(Case::Upper.is_compatible_with(Case::Constant));
- ///
- /// assert!(Case::Camel.is_compatible_with(Case::Unknown));
- /// assert!(Case::Constant.is_compatible_with(Case::Unknown));
- /// assert!(Case::Kebab.is_compatible_with(Case::Unknown));
- /// assert!(Case::Lower.is_compatible_with(Case::Unknown));
- /// assert!(Case::NumberableCapital.is_compatible_with(Case::Unknown));
- /// assert!(Case::Pascal.is_compatible_with(Case::Unknown));
- /// assert!(Case::Snake.is_compatible_with(Case::Unknown));
- /// assert!(Case::Upper.is_compatible_with(Case::Unknown));
- ///
- /// assert!(Case::Camel.is_compatible_with(Case::Camel));
- /// assert!(Case::Constant.is_compatible_with(Case::Constant));
- /// assert!(Case::Kebab.is_compatible_with(Case::Kebab));
- /// assert!(Case::Lower.is_compatible_with(Case::Lower));
- /// assert!(Case::NumberableCapital.is_compatible_with(Case::NumberableCapital));
- /// assert!(Case::Pascal.is_compatible_with(Case::Pascal));
- /// assert!(Case::Upper.is_compatible_with(Case::Upper));
- /// assert!(Case::Unknown.is_compatible_with(Case::Unknown));
/// ```
pub fn is_compatible_with(self, other: Case) -> bool {
self == other
@@ -203,32 +209,25 @@ impl Case {
/// assert_eq!(Case::Snake.convert("HttpServer"), "http_server");
///
/// assert_eq!(Case::Upper.convert("Http_SERVER"), "HTTPSERVER");
- ///
- /// assert_eq!(Case::Unknown.convert("_"), "_");
/// ```
pub fn convert(self, value: &str) -> String {
if value.is_empty() || matches!(self, Case::Unknown) {
return value.to_string();
}
let mut word_separator = matches!(self, Case::Pascal);
- let last_i = value.len() - 1;
let mut output = String::with_capacity(value.len());
- let mut first_alphanumeric_i = 0;
for ((i, current), next) in value
.char_indices()
.zip(value.chars().skip(1).map(Some).chain(Some(None)))
{
- if (i == 0 || (i == last_i)) && (current == '_' || current == '$') {
- output.push(current);
- first_alphanumeric_i = 1;
- continue;
- }
- if !current.is_alphanumeric() {
+ if !current.is_alphanumeric()
+ || (matches!(self, Case::Uni) && (current.is_lowercase() || current.is_uppercase()))
+ {
word_separator = true;
continue;
}
if let Some(next) = next {
- if i != first_alphanumeric_i && current.is_uppercase() && next.is_lowercase() {
+ if i != 0 && current.is_uppercase() && next.is_lowercase() {
word_separator = true;
}
}
@@ -239,6 +238,7 @@ impl Case {
| Case::NumberableCapital
| Case::Pascal
| Case::Unknown
+ | Case::Uni
| Case::Upper => (),
Case::Constant | Case::Snake => {
output.push('_');
@@ -258,11 +258,12 @@ impl Case {
}
Case::Constant | Case::Upper => output.extend(current.to_uppercase()),
Case::NumberableCapital => {
- if i == first_alphanumeric_i {
+ if i == 0 {
output.extend(current.to_uppercase());
}
}
Case::Kebab | Case::Snake | Case::Lower => output.extend(current.to_lowercase()),
+ Case::Uni => output.extend(Some(current)),
Case::Unknown => (),
}
word_separator = false;
@@ -287,6 +288,7 @@ impl std::fmt::Display for Case {
Case::NumberableCapital => "numberable capital case",
Case::Pascal => "PascalCase",
Case::Snake => "snake_case",
+ Case::Uni => "unicase",
Case::Upper => "UPPERCASE",
};
write!(f, "{}", repr)
@@ -297,6 +299,159 @@ impl std::fmt::Display for Case {
mod tests {
use super::*;
+ #[test]
+ fn test_case_identify() {
+ let no_effect = true;
+
+ assert_eq!(Case::identify("aHttpServer", no_effect), Case::Camel);
+ assert_eq!(Case::identify("aHTTPServer", true), Case::Unknown);
+ assert_eq!(Case::identify("aHTTPServer", false), Case::Camel);
+ assert_eq!(Case::identify("v8Engine", no_effect), Case::Camel);
+
+ assert_eq!(Case::identify("HTTP_SERVER", no_effect), Case::Constant);
+ assert_eq!(Case::identify("V8_ENGINE", no_effect), Case::Constant);
+
+ assert_eq!(Case::identify("http-server", no_effect), Case::Kebab);
+
+ assert_eq!(Case::identify("httpserver", no_effect), Case::Lower);
+
+ assert_eq!(Case::identify("T", no_effect), Case::NumberableCapital);
+ assert_eq!(Case::identify("T1", no_effect), Case::NumberableCapital);
+
+ assert_eq!(Case::identify("HttpServer", no_effect), Case::Pascal);
+ assert_eq!(Case::identify("HTTPServer", true), Case::Unknown);
+ assert_eq!(Case::identify("HTTPServer", false), Case::Pascal);
+ assert_eq!(Case::identify("V8Engine", true), Case::Pascal);
+
+ assert_eq!(Case::identify("http_server", no_effect), Case::Snake);
+
+ assert_eq!(Case::identify("HTTPSERVER", no_effect), Case::Upper);
+
+ assert_eq!(Case::identify("100", no_effect), Case::Uni);
+ assert_eq!(Case::identify("안녕하세요", no_effect), Case::Uni);
+
+ assert_eq!(Case::identify("", no_effect), Case::Unknown);
+ assert_eq!(Case::identify("_", no_effect), Case::Unknown);
+ assert_eq!(Case::identify("안녕하세요ABC", no_effect), Case::Unknown);
+ assert_eq!(Case::identify("안녕하세요abc", no_effect), Case::Unknown);
+ assert_eq!(Case::identify("안녕하세요_ABC", no_effect), Case::Unknown);
+ assert_eq!(Case::identify("안녕하세요_abc", no_effect), Case::Unknown);
+ assert_eq!(Case::identify("안녕하세요-abc", no_effect), Case::Unknown);
+ }
+
+ #[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));
+ }
+
#[test]
fn test_case_convert() {
assert_eq!(Case::Camel.convert("camelCase"), "camelCase");
@@ -351,6 +506,9 @@ mod tests {
assert_eq!(Case::Upper.convert("snake_case"), "SNAKECASE");
assert_eq!(Case::Upper.convert("Unknown_Style"), "UNKNOWNSTYLE");
+ assert_eq!(Case::Uni.convert("안녕하세요"), "안녕하세요");
+ assert_eq!(Case::Uni.convert("a안b녕c하_세D요E"), "안녕하세요");
+
assert_eq!(Case::Unknown.convert("Unknown_Style"), "Unknown_Style");
}
}
diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js
new file mode 100644
index 000000000000..25c32f84cafa
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js
@@ -0,0 +1,11 @@
+{
+ const 안녕a하세요 = 0;
+}
+{
+ const x = {
+ 안녕하세요a: 0,
+ }
+}
+{
+ class A안녕하세요 {}
+}
\ No newline at end of file
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
new file mode 100644
index 000000000000..fb2b3efab9ea
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/invalidSyllabary.js.snap
@@ -0,0 +1,64 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalidSyllabary.js
+---
+# Input
+```jsx
+{
+ const 안녕a하세요 = 0;
+}
+{
+ const x = {
+ 안녕하세요a: 0,
+ }
+}
+{
+ class A안녕하세요 {}
+}
+```
+
+# Diagnostics
+```
+invalidSyllabary.js:2:11 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ ! This top-level const name should be in camelCase or PascalCase or CONSTANT_CASE.
+
+ 1 │ {
+ > 2 │ const 안녕a하세요 = 0;
+ │ ^^^^^^^^^^^
+ 3 │ }
+ 4 │ {
+
+
+```
+
+```
+invalidSyllabary.js:6:9 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ ! This object property name should be in camelCase.
+
+ 4 │ {
+ 5 │ const x = {
+ > 6 │ 안녕하세요a: 0,
+ │ ^^^^^^^^^^^
+ 7 │ }
+ 8 │ }
+
+
+```
+
+```
+invalidSyllabary.js:10:11 lint/style/useNamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ ! This class name should be in PascalCase.
+
+ 8 │ }
+ 9 │ {
+ > 10 │ class A안녕하세요 {}
+ │ ^^^^^^^^^^^
+ 11 │ }
+
+
+```
+
+
diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validSyllabary.js b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validSyllabary.js
new file mode 100644
index 000000000000..29700252665d
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validSyllabary.js
@@ -0,0 +1,11 @@
+{
+ const 안녕하세요 = 0;
+}
+{
+ const x = {
+ 안녕하세요: 0,
+ }
+}
+{
+ class 안녕하세요 {}
+}
\ No newline at end of file
diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validSyllabary.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validSyllabary.js.snap
new file mode 100644
index 000000000000..d76c4d1ba598
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/validSyllabary.js.snap
@@ -0,0 +1,20 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: validSyllabary.js
+---
+# Input
+```jsx
+{
+ const 안녕하세요 = 0;
+}
+{
+ const x = {
+ 안녕하세요: 0,
+ }
+}
+{
+ class 안녕하세요 {}
+}
+```
+
+
diff --git a/website/src/content/docs/internals/changelog.mdx b/website/src/content/docs/internals/changelog.mdx
index 1a770bb923e6..64a0ad1f6a0a 100644
--- a/website/src/content/docs/internals/changelog.mdx
+++ b/website/src/content/docs/internals/changelog.mdx
@@ -205,6 +205,27 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom
Contributed by @Conaclos
+- [useNamingConvention](https://biomejs.dev/linter/rules/use-naming-convention) now supports [unicase](https://en.wikipedia.org/wiki/Unicase) letters ([#1786](https://github.com/biomejs/biome/issues/1786)).
+
+ [unicase](https://en.wikipedia.org/wiki/Unicase) letters have a single case: they are neither uppercase nor lowercase.
+ Previously, Biome reported names in unicase as invalid.
+ It now accepts a name in unicase everywhere.
+
+ The following code is now accepted:
+
+ ```js
+ const 안녕하세요 = { 안녕하세요: 0 };
+ ```
+
+ We still reject a name that mixes unicase characters with lowercase or uppercase characters:
+ The following names are rejected:
+
+ ```js
+ const A안녕하세요 = { a안녕하세요: 0 };
+ ```
+
+ Contributed by @Conaclos
+
#### Bug fixes
- Fix [#1651](https://github.com/biomejs/biome/issues/1651). [noVar](https://biomejs.dev/linter/rules/no-var/) now ignores TsGlobalDeclaration. Contributed by @vasucp1207