diff --git a/Cargo.lock b/Cargo.lock index 48137f53bb75..a4219b5cdbc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,8 @@ dependencies = [ "biome_diagnostics", "biome_flags", "biome_formatter", + "biome_graphql_analyze", + "biome_graphql_syntax", "biome_js_analyze", "biome_js_formatter", "biome_js_syntax", @@ -4530,6 +4532,9 @@ dependencies = [ "biome_css_analyze", "biome_css_syntax", "biome_diagnostics", + "biome_graphql_analyze", + "biome_graphql_parser", + "biome_graphql_syntax", "biome_js_analyze", "biome_js_factory", "biome_js_formatter", diff --git a/crates/biome_configuration/Cargo.toml b/crates/biome_configuration/Cargo.toml index 09a5eb2cfff5..0bd04e43d251 100644 --- a/crates/biome_configuration/Cargo.toml +++ b/crates/biome_configuration/Cargo.toml @@ -22,6 +22,8 @@ biome_deserialize_macros = { workspace = true } biome_diagnostics = { workspace = true } biome_flags = { workspace = true } biome_formatter = { workspace = true, features = ["serde"] } +biome_graphql_analyze = { workspace = true } +biome_graphql_syntax = { workspace = true } biome_js_analyze = { workspace = true } biome_js_formatter = { workspace = true, features = ["serde"] } biome_js_syntax = { workspace = true, features = ["serde", "schema"] } @@ -47,6 +49,7 @@ schema = [ "biome_formatter/serde", "biome_json_syntax/schema", "biome_css_syntax/schema", + "biome_graphql_syntax/schema", ] [dev-dependencies] diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index e5f7b95b82ed..d2b9a7ca926d 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -8,6 +8,7 @@ use biome_css_analyze::options::*; use biome_deserialize::{DeserializableValidator, DeserializationDiagnostic}; use biome_deserialize_macros::{Deserializable, Merge}; use biome_diagnostics::{Category, Severity}; +use biome_graphql_analyze::options::*; use biome_js_analyze::options::*; use biome_json_analyze::options::*; use biome_rowan::TextRange; @@ -2924,6 +2925,9 @@ pub struct Nursery { #[doc = "Require the default clause in switch statements."] #[serde(skip_serializing_if = "Option::is_none")] pub use_default_switch_clause: Option>, + #[doc = "Require specifying the reason argument when using @deprecated directive"] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_deprecated_reason: Option>, #[doc = "Enforce passing a message value when creating a built-in error."] #[serde(skip_serializing_if = "Option::is_none")] pub use_error_message: Option>, @@ -3018,6 +3022,7 @@ impl Nursery { "useConsistentGridAreas", "useDateNow", "useDefaultSwitchClause", + "useDeprecatedReason", "useErrorMessage", "useExplicitLengthCheck", "useFocusableInteractive", @@ -3074,9 +3079,9 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3128,6 +3133,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3324,71 +3330,76 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_error_message.as_ref() { + if let Some(rule) = self.use_deprecated_reason.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_error_message.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_import_extensions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_import_extensions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_semantic_elements.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_semantic_elements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_throw_only_error.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_only_error.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_top_level_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } + if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3573,71 +3584,76 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_error_message.as_ref() { + if let Some(rule) = self.use_deprecated_reason.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_error_message.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_import_extensions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_import_extensions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_semantic_elements.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_semantic_elements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_throw_only_error.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_only_error.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_top_level_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } + if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3818,6 +3834,10 @@ impl Nursery { .use_default_switch_clause .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "useDeprecatedReason" => self + .use_deprecated_reason + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "useErrorMessage" => self .use_error_message .as_ref() diff --git a/crates/biome_service/tests/workspace.rs b/crates/biome_service/tests/workspace.rs index 57d5a56b4dcf..2435c39ebd5b 100644 --- a/crates/biome_service/tests/workspace.rs +++ b/crates/biome_service/tests/workspace.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod test { + use biome_analyze::RuleCategories; + use biome_configuration::linter::{RuleGroup, RuleSelector}; use biome_fs::BiomePath; use biome_js_syntax::{JsFileSource, TextSize}; use biome_service::file_handlers::DocumentFileSource; @@ -185,7 +187,7 @@ mod test { content: r#"type Query { me: User } - + type User { id: ID name: String @@ -202,4 +204,35 @@ type User { assert!(syntax.starts_with("GraphqlRoot")) } + + #[test] + fn correctly_pulls_lint_diagnostics() { + let workspace = create_server(); + + let graphql_file = FileGuard::open( + workspace.as_ref(), + OpenFileParams { + path: BiomePath::new("file.graphql"), + content: r#"query { + member @deprecated(abc: 123) +}"# + .into(), + version: 0, + document_file_source: None, + }, + ) + .unwrap(); + let result = graphql_file.pull_diagnostics( + RuleCategories::all(), + 10, + vec![RuleSelector::Rule( + RuleGroup::Nursery, + "useDeprecatedReason", + )], + vec![], + ); + assert!(result.is_ok()); + let diagnostics = result.unwrap().diagnostics; + assert_eq!(diagnostics.len(), 1) + } } diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index ace1bf54035a..6a41839cc895 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1122,6 +1122,10 @@ export interface Nursery { * Require the default clause in switch statements. */ useDefaultSwitchClause?: RuleConfiguration_for_Null; + /** + * Require specifying the reason argument when using @deprecated directive + */ + useDeprecatedReason?: RuleConfiguration_for_Null; /** * Enforce passing a message value when creating a built-in error. */ diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 359cd91b994d..f87a93fcece5 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1920,6 +1920,13 @@ { "type": "null" } ] }, + "useDeprecatedReason": { + "description": "Require specifying the reason argument when using @deprecated directive", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "useErrorMessage": { "description": "Enforce passing a message value when creating a built-in error.", "anyOf": [ diff --git a/xtask/codegen/Cargo.toml b/xtask/codegen/Cargo.toml index f3fb1e763c1b..11b748891909 100644 --- a/xtask/codegen/Cargo.toml +++ b/xtask/codegen/Cargo.toml @@ -16,27 +16,30 @@ ureq = "2.9.7" walkdir = "2.5.0" xtask = { path = '../', version = "0.0" } -biome_analyze = { workspace = true, optional = true } -biome_cli = { workspace = true, optional = true } -biome_configuration = { workspace = true, optional = true } -biome_css_analyze = { workspace = true, optional = true } -biome_css_syntax = { workspace = true, optional = true } -biome_diagnostics = { workspace = true, optional = true } -biome_js_analyze = { workspace = true, optional = true } -biome_js_factory = { workspace = true, optional = true } -biome_js_formatter = { workspace = true, optional = true } -biome_js_parser = { workspace = true, optional = true } -biome_js_syntax = { workspace = true, optional = true } -biome_json_analyze = { workspace = true, optional = true } -biome_json_formatter = { workspace = true, optional = true } -biome_json_parser = { workspace = true, optional = true } -biome_json_syntax = { workspace = true, optional = true } -biome_rowan = { workspace = true, optional = true } -biome_service = { workspace = true, features = ["schema"], optional = true } -biome_string_case = { workspace = true } -biome_ungrammar = { workspace = true } -schemars = { workspace = true, optional = true } -serde_json = { workspace = true, optional = true } +biome_analyze = { workspace = true, optional = true } +biome_cli = { workspace = true, optional = true } +biome_configuration = { workspace = true, optional = true } +biome_css_analyze = { workspace = true, optional = true } +biome_css_syntax = { workspace = true, optional = true } +biome_diagnostics = { workspace = true, optional = true } +biome_graphql_analyze = { workspace = true, optional = true } +biome_graphql_parser = { workspace = true, optional = true } +biome_graphql_syntax = { workspace = true, optional = true } +biome_js_analyze = { workspace = true, optional = true } +biome_js_factory = { workspace = true, optional = true } +biome_js_formatter = { workspace = true, optional = true } +biome_js_parser = { workspace = true, optional = true } +biome_js_syntax = { workspace = true, optional = true } +biome_json_analyze = { workspace = true, optional = true } +biome_json_formatter = { workspace = true, optional = true } +biome_json_parser = { workspace = true, optional = true } +biome_json_syntax = { workspace = true, optional = true } +biome_rowan = { workspace = true, optional = true } +biome_service = { workspace = true, features = ["schema"], optional = true } +biome_string_case = { workspace = true } +biome_ungrammar = { workspace = true } +schemars = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } [features] configuration = [ @@ -47,6 +50,8 @@ configuration = [ "biome_json_syntax", "biome_css_analyze", "biome_css_syntax", + "biome_graphql_analyze", + "biome_graphql_syntax", "biome_rowan", "pulldown-cmark", ] diff --git a/xtask/codegen/src/generate_configuration.rs b/xtask/codegen/src/generate_configuration.rs index 225eded5b695..87d572f9452d 100644 --- a/xtask/codegen/src/generate_configuration.rs +++ b/xtask/codegen/src/generate_configuration.rs @@ -2,6 +2,7 @@ use biome_analyze::{ FixKind, GroupCategory, Queryable, RegistryVisitor, Rule, RuleCategory, RuleGroup, RuleMetadata, }; use biome_css_syntax::CssLanguage; +use biome_graphql_syntax::GraphqlLanguage; use biome_js_syntax::JsLanguage; use biome_json_syntax::JsonLanguage; use biome_string_case::Case; @@ -81,10 +82,31 @@ pub(crate) fn generate_rules_configuration(mode: Mode) -> Result<()> { } } + impl RegistryVisitor for LintRulesVisitor { + fn record_category>(&mut self) { + if matches!(C::CATEGORY, RuleCategory::Lint) { + C::record_groups(self); + } + } + + fn record_rule(&mut self) + where + R: Rule + 'static, + R::Query: Queryable, + ::Output: Clone, + { + self.groups + .entry(::NAME) + .or_insert_with(BTreeMap::new) + .insert(R::METADATA.name, R::METADATA); + } + } + let mut visitor = LintRulesVisitor::default(); biome_js_analyze::visit_registry(&mut visitor); biome_json_analyze::visit_registry(&mut visitor); biome_css_analyze::visit_registry(&mut visitor); + biome_graphql_analyze::visit_registry(&mut visitor); let LintRulesVisitor { groups } = visitor; @@ -140,6 +162,7 @@ pub(crate) fn generate_rules_configuration(mode: Mode) -> Result<()> { use biome_js_analyze::options::*; use biome_json_analyze::options::*; use biome_css_analyze::options::*; + use biome_graphql_analyze::options::*; use biome_rowan::TextRange; use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize};