-
-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(graphql_analyze): implement useUniqueEnumValueNames #8597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f3e8eae
3eef250
9ae4477
0bc4ea2
45a5a43
af55b25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
| "@biomejs/biome": patch | ||
| --- | ||
|
|
||
| Added the nursery rule [`useUniqueEnumValueNames`](https://biomejs.dev/linter/rules/use-unique-enum-value-names/). Enforce unique enum value names. | ||
|
|
||
| **Invalid:** | ||
|
|
||
| ```graphql | ||
| enum A { | ||
| TEST | ||
| TesT | ||
| } | ||
| ``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| use std::collections::HashSet; | ||
|
|
||
| use biome_analyze::{ | ||
| Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, | ||
| }; | ||
| use biome_console::markup; | ||
| use biome_graphql_syntax::GraphqlEnumTypeDefinition; | ||
| use biome_rowan::{AstNodeList, TextRange}; | ||
| use biome_rule_options::use_unique_enum_value_names::UseUniqueEnumValueNamesOptions; | ||
| use biome_string_case::StrOnlyExtension; | ||
|
|
||
| declare_lint_rule! { | ||
| /// Require all enum value names to be unique. | ||
| /// | ||
| /// A GraphQL enum type is only valid if all its values are uniquely named. | ||
| /// The enum value names are case insensitive, meaning `TEST` & `Test` are seen as the same enum value name. | ||
| /// | ||
| /// ## Examples | ||
| /// | ||
| /// ### Invalid | ||
| /// | ||
| /// ```graphql,expect_diagnostic | ||
| /// enum A { | ||
| /// TEST | ||
| /// OTHER | ||
| /// TEST | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| /// ```graphql,expect_diagnostic | ||
| /// enum B { | ||
| /// TEST | ||
| /// TesT | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| /// ### Valid | ||
| /// | ||
| /// ```graphql | ||
| /// enum A { | ||
| /// TEST | ||
| /// OTHER | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| pub UseUniqueEnumValueNames { | ||
| version: "next", | ||
| name: "useUniqueEnumValueNames", | ||
| language: "graphql", | ||
| recommended: false, | ||
| sources: &[RuleSource::EslintGraphql("unique-enum-value-names").same()], | ||
| } | ||
| } | ||
|
Netail marked this conversation as resolved.
|
||
|
|
||
| impl Rule for UseUniqueEnumValueNames { | ||
| type Query = Ast<GraphqlEnumTypeDefinition>; | ||
| type State = Vec<TextRange>; | ||
| type Signals = Option<Self::State>; | ||
| type Options = UseUniqueEnumValueNamesOptions; | ||
|
|
||
| fn run(ctx: &RuleContext<Self>) -> Self::Signals { | ||
| let node = ctx.query(); | ||
| // We can't use TokenText (to minimize allocations) because of lowercasing and Cow can't be used in a HashSet | ||
| let mut found: HashSet<String> = HashSet::new(); | ||
|
|
||
| let enum_values = node.enum_values()?; | ||
| let duplicates: Vec<TextRange> = enum_values | ||
| .values() | ||
| .iter() | ||
| .filter_map(|enum_value| { | ||
| if let Some(name) = enum_value.value().ok() | ||
| && let Some(value_token) = name.value_token().ok() | ||
| { | ||
| let string = value_token.token_text().to_lowercase_cow().to_string(); | ||
| if found.insert(string) { | ||
| return None; | ||
| } else { | ||
| let range = value_token.text_range(); | ||
| return Some(range); | ||
| } | ||
| } | ||
|
|
||
| None | ||
| }) | ||
| .collect(); | ||
|
|
||
| if duplicates.is_empty() { | ||
| None | ||
| } else { | ||
| Some(duplicates) | ||
| } | ||
| } | ||
|
|
||
| fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> { | ||
| let mut diagnostic = RuleDiagnostic::new( | ||
| rule_category!(), | ||
| state.first()?, | ||
| markup! { | ||
| "Duplicate enum value name." | ||
| }, | ||
| ); | ||
|
|
||
| for range in &state[1..] { | ||
| diagnostic = diagnostic.detail( | ||
| range, | ||
| markup! { | ||
| "Another duplicate enum value." | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| Some(diagnostic.note(markup! { | ||
| "A GraphQL enum type is only valid if all its values are uniquely named. Make sure to name every enum value differently." | ||
| })) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # should generate diagnostics | ||
| enum A { | ||
| TEST | ||
| OTHER | ||
| TesT | ||
| ANOTHER | ||
| Test | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| --- | ||
| source: crates/biome_graphql_analyze/tests/spec_tests.rs | ||
| expression: invalid.graphql | ||
| --- | ||
| # Input | ||
| ```graphql | ||
| # should generate diagnostics | ||
| enum A { | ||
| TEST | ||
| OTHER | ||
| TesT | ||
| ANOTHER | ||
| Test | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| # Diagnostics | ||
| ``` | ||
| invalid.graphql:4:7 lint/nursery/useUniqueEnumValueNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
|
|
||
| i Duplicate enum value name. | ||
|
|
||
| 2 │ enum A { | ||
| 3 │ TEST | ||
| > 4 │ OTHER | ||
| │ | ||
| > 5 │ TesT | ||
| │ ^^^^ | ||
| 6 │ ANOTHER | ||
| 7 │ Test | ||
|
|
||
| i Another duplicate enum value. | ||
|
|
||
| 4 │ OTHER | ||
| 5 │ TesT | ||
| > 6 │ ANOTHER | ||
| │ | ||
| > 7 │ Test | ||
| │ ^^^^ | ||
| 8 │ } | ||
| 9 │ | ||
|
|
||
| i A GraphQL enum type is only valid if all its values are uniquely named. Make sure to name every enum value differently. | ||
|
|
||
| i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. | ||
|
|
||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # should not generate diagnostics | ||
| enum A { | ||
| TEST | ||
| OTHER | ||
| ANOTHER | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
| source: crates/biome_graphql_analyze/tests/spec_tests.rs | ||
| expression: valid.graphql | ||
| --- | ||
| # Input | ||
| ```graphql | ||
| # should not generate diagnostics | ||
| enum A { | ||
| TEST | ||
| OTHER | ||
| ANOTHER | ||
| } | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| use biome_deserialize_macros::{Deserializable, Merge}; | ||
| use serde::{Deserialize, Serialize}; | ||
| #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] | ||
| #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] | ||
| #[serde(rename_all = "camelCase", deny_unknown_fields, default)] | ||
| pub struct UseUniqueEnumValueNamesOptions {} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.