diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e6ad3e3c32c..df9e50e44468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -352,6 +352,8 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - [noUselessFragments](https://biomejs.dev/linter/rules/no-useless-fragments/) now handles `JsxAttributeInitializerClause`, ensuring that fragments inside expressions like ` />` are preserved. ([#4208](https://github.com/biomejs/biome/issues/4208)). Contributed by @MaxtuneLee +- [useSortedClasses](https://biomejs.dev/linter/rules/use-sorted-classes/) now suggests code fixes that match the JSX quote style of the formatter ([#4855](https://github.com/biomejs/biome/issues/4855)). Contributed by @lucasweng + ### Parser #### Bug fixes diff --git a/crates/biome_analyze/src/context.rs b/crates/biome_analyze/src/context.rs index c2fbf3701d12..dc5027c562e4 100644 --- a/crates/biome_analyze/src/context.rs +++ b/crates/biome_analyze/src/context.rs @@ -17,6 +17,7 @@ pub struct RuleContext<'a, R: Rule> { file_path: &'a Path, options: &'a R::Options, preferred_quote: &'a PreferredQuote, + preferred_jsx_quote: &'a PreferredQuote, jsx_runtime: Option, } @@ -33,6 +34,7 @@ where file_path: &'a Path, options: &'a R::Options, preferred_quote: &'a PreferredQuote, + preferred_jsx_quote: &'a PreferredQuote, jsx_runtime: Option, ) -> Result { let rule_key = RuleKey::rule::(); @@ -45,6 +47,7 @@ where file_path, options, preferred_quote, + preferred_jsx_quote, jsx_runtime, }) } @@ -168,6 +171,11 @@ where self.preferred_quote } + /// Returns the preferred JSX quote that should be used when providing code actions + pub fn as_preferred_jsx_quote(&self) -> &PreferredQuote { + self.preferred_jsx_quote + } + /// Attempts to retrieve a service from the current context /// /// ```no_test diff --git a/crates/biome_analyze/src/options.rs b/crates/biome_analyze/src/options.rs index 8d01cdf10767..3394012ff1b6 100644 --- a/crates/biome_analyze/src/options.rs +++ b/crates/biome_analyze/src/options.rs @@ -65,6 +65,9 @@ pub struct AnalyzerConfiguration { /// Allows to choose a different quote when applying fixes inside the lint rules pub preferred_quote: PreferredQuote, + /// Allows to choose a different JSX quote when applying fixes inside the lint rules + pub preferred_jsx_quote: PreferredQuote, + /// Indicates the type of runtime or transformation used for interpreting JSX. pub jsx_runtime: Option, } @@ -117,6 +120,10 @@ impl AnalyzerOptions { pub fn preferred_quote(&self) -> &PreferredQuote { &self.configuration.preferred_quote } + + pub fn preferred_jsx_quote(&self) -> &PreferredQuote { + &self.configuration.preferred_jsx_quote + } } #[derive(Debug, Default)] diff --git a/crates/biome_analyze/src/registry.rs b/crates/biome_analyze/src/registry.rs index dd4121be19ac..0111d3c24b0f 100644 --- a/crates/biome_analyze/src/registry.rs +++ b/crates/biome_analyze/src/registry.rs @@ -399,6 +399,7 @@ impl RegistryRule { let query_result = ::unwrap_match(params.services, query_result); let globals = params.options.globals(); let preferred_quote = params.options.preferred_quote(); + let preferred_jsx_quote = params.options.preferred_jsx_quote(); let jsx_runtime = params.options.jsx_runtime(); let options = params.options.rule_options::().unwrap_or_default(); let ctx = match RuleContext::new( @@ -409,6 +410,7 @@ impl RegistryRule { ¶ms.options.file_path, &options, preferred_quote, + preferred_jsx_quote, jsx_runtime, ) { Ok(ctx) => ctx, diff --git a/crates/biome_analyze/src/signals.rs b/crates/biome_analyze/src/signals.rs index 93ab77a16469..d95c60f6bcd8 100644 --- a/crates/biome_analyze/src/signals.rs +++ b/crates/biome_analyze/src/signals.rs @@ -346,6 +346,7 @@ where fn diagnostic(&self) -> Option { let globals = self.options.globals(); let preferred_quote = self.options.preferred_quote(); + let preferred_jsx_quote = self.options.preferred_jsx_quote(); let options = self.options.rule_options::().unwrap_or_default(); let ctx = RuleContext::new( &self.query_result, @@ -355,6 +356,7 @@ where &self.options.file_path, &options, preferred_quote, + preferred_jsx_quote, self.options.jsx_runtime(), ) .ok()?; @@ -386,6 +388,7 @@ where &self.options.file_path, &options, self.options.preferred_quote(), + self.options.preferred_jsx_quote(), self.options.jsx_runtime(), ) .ok(); @@ -435,6 +438,7 @@ where &self.options.file_path, &options, self.options.preferred_quote(), + self.options.preferred_jsx_quote(), self.options.jsx_runtime(), ) .ok(); diff --git a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs index 8b4e7f7fa196..52095273b493 100644 --- a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs @@ -239,7 +239,7 @@ impl Rule for UseSortedClasses { mutation.replace_node(string_literal.clone(), replacement); } AnyClassStringLike::JsxString(jsx_string_node) => { - let replacement = jsx_string(if ctx.as_preferred_quote().is_double() { + let replacement = jsx_string(if ctx.as_preferred_jsx_quote().is_double() { js_string_literal(state) } else { js_string_literal_single_quotes(state) diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.jsx new file mode 100644 index 000000000000..5fd54a5d8d88 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.jsx @@ -0,0 +1,2 @@ +
Hello
; +
Hello
; diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.jsx.snap new file mode 100644 index 000000000000..831cb319c324 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.jsx.snap @@ -0,0 +1,52 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: issue_4855.jsx +snapshot_kind: text +--- +# Input +```jsx +
Hello
; +
Hello
; + +``` + +# Diagnostics +``` +issue_4855.jsx:1:12 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + > 1 │
Hello
; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 2 │
Hello
; + 3 │ + + i Unsafe fix: Sort the classes. + + 1 │ - Hello; + 1 │ + Hello; + 2 2 │
Hello
; + 3 3 │ + + +``` + +``` +issue_4855.jsx:2:12 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 1 │
Hello
; + > 2 │
Hello
; + │ ^^^^^^^^^^^^^^^^ + 3 │ + + i Unsafe fix: Sort the classes. + + 1 1 │
Hello
; + 2 │ - Hello; + 2 │ + Hello; + 3 3 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.options.json b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.options.json new file mode 100644 index 000000000000..2c87a76000c4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/issue_4855.options.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "javascript": { + "formatter": { + "quoteStyle": "single", + "jsxQuoteStyle": "double" + } + }, + "linter": { + "rules": { + "nursery": { + "useSortedClasses": { + "level": "error", + "options": { + "attributes": ["customClassAttribute"], + "functions": ["clsx", "tw", "tw.*"] + } + } + } + } + } +} diff --git a/crates/biome_service/src/file_handlers/css.rs b/crates/biome_service/src/file_handlers/css.rs index e76ce0c130fa..9a45cf12ef5d 100644 --- a/crates/biome_service/src/file_handlers/css.rs +++ b/crates/biome_service/src/file_handlers/css.rs @@ -147,6 +147,7 @@ impl ServiceLanguage for CssLanguage { .unwrap_or_default(), globals: Vec::new(), preferred_quote, + preferred_jsx_quote: Default::default(), jsx_runtime: None, }; diff --git a/crates/biome_service/src/file_handlers/javascript.rs b/crates/biome_service/src/file_handlers/javascript.rs index 6d682af11efd..310e60624b0c 100644 --- a/crates/biome_service/src/file_handlers/javascript.rs +++ b/crates/biome_service/src/file_handlers/javascript.rs @@ -210,6 +210,19 @@ impl ServiceLanguage for JsLanguage { ) }) .unwrap_or_default(); + let preferred_jsx_quote = global + .and_then(|global| { + global.languages.javascript.formatter.jsx_quote_style.map( + |quote_style: QuoteStyle| { + if quote_style == QuoteStyle::Single { + PreferredQuote::Single + } else { + PreferredQuote::Double + } + }, + ) + }) + .unwrap_or_default(); let mut jsx_runtime = None; let mut globals = Vec::new(); @@ -276,6 +289,7 @@ impl ServiceLanguage for JsLanguage { .unwrap_or_default(), globals, preferred_quote, + preferred_jsx_quote, jsx_runtime, }; diff --git a/crates/biome_service/src/file_handlers/json.rs b/crates/biome_service/src/file_handlers/json.rs index 94fa7ce424c5..be5d7e1d0f02 100644 --- a/crates/biome_service/src/file_handlers/json.rs +++ b/crates/biome_service/src/file_handlers/json.rs @@ -140,6 +140,7 @@ impl ServiceLanguage for JsonLanguage { .unwrap_or_default(), globals: vec![], preferred_quote: PreferredQuote::Double, + preferred_jsx_quote: Default::default(), jsx_runtime: Default::default(), }; AnalyzerOptions { diff --git a/crates/biome_test_utils/src/lib.rs b/crates/biome_test_utils/src/lib.rs index 6191e2eccc95..9670b69e9c1e 100644 --- a/crates/biome_test_utils/src/lib.rs +++ b/crates/biome_test_utils/src/lib.rs @@ -42,6 +42,7 @@ pub fn create_analyzer_options( rules: AnalyzerRules::default(), globals: vec![], preferred_quote: PreferredQuote::Double, + preferred_jsx_quote: PreferredQuote::Double, jsx_runtime: Some(JsxRuntime::Transparent), }; let options_file = input_file.with_extension("options.json");