-
-
Notifications
You must be signed in to change notification settings - Fork 527
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(lint): initialise
biome_graphql_analyze
- Loading branch information
Showing
15 changed files
with
695 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
[package] | ||
authors.workspace = true | ||
categories.workspace = true | ||
description = "Biome's GraphQL linter" | ||
edition.workspace = true | ||
homepage.workspace = true | ||
keywords.workspace = true | ||
license.workspace = true | ||
name = "biome_graphql_analyze" | ||
repository.workspace = true | ||
version = "0.0.1" | ||
|
||
[dependencies] | ||
biome_analyze = { workspace = true } | ||
biome_console = { workspace = true } | ||
biome_deserialize = { workspace = true } | ||
biome_deserialize_macros = { workspace = true } | ||
biome_diagnostics = { workspace = true } | ||
biome_graphql_syntax = { workspace = true } | ||
biome_rowan = { workspace = true } | ||
biome_suppression = { workspace = true } | ||
lazy_static = { workspace = true } | ||
schemars = { workspace = true, optional = true } | ||
serde = { workspace = true, features = ["derive"] } | ||
|
||
[dev-dependencies] | ||
biome_graphql_parser = { path = "../biome_graphql_parser" } | ||
biome_test_utils = { path = "../biome_test_utils" } | ||
insta = { workspace = true, features = ["glob"] } | ||
tests_macros = { path = "../tests_macros" } | ||
|
||
[features] | ||
schema = ["schemars", "biome_deserialize/schema"] | ||
|
||
[lints] | ||
workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
mod lint; | ||
pub mod options; | ||
mod registry; | ||
mod suppression_action; | ||
|
||
pub use crate::registry::visit_registry; | ||
use crate::suppression_action::GraphqlSuppressionAction; | ||
use biome_analyze::{ | ||
AnalysisFilter, AnalyzerOptions, AnalyzerSignal, ControlFlow, LanguageRoot, MatchQueryParams, | ||
MetadataRegistry, RuleRegistry, SuppressionKind, | ||
}; | ||
use biome_diagnostics::{category, Error}; | ||
use biome_graphql_syntax::GraphqlLanguage; | ||
use biome_suppression::{parse_suppression_comment, SuppressionDiagnostic}; | ||
|
||
/// Return the static [MetadataRegistry] for the JSON analyzer rules | ||
pub fn metadata() -> &'static MetadataRegistry { | ||
lazy_static::lazy_static! { | ||
static ref METADATA: MetadataRegistry = { | ||
let mut metadata = MetadataRegistry::default(); | ||
visit_registry(&mut metadata); | ||
metadata | ||
}; | ||
} | ||
|
||
&METADATA | ||
} | ||
|
||
/// Run the analyzer on the provided `root`: this process will use the given `filter` | ||
/// to selectively restrict analysis to specific rules / a specific source range, | ||
/// then call `emit_signal` when an analysis rule emits a diagnostic or action | ||
pub fn analyze<'a, F, B>( | ||
root: &LanguageRoot<GraphqlLanguage>, | ||
filter: AnalysisFilter, | ||
options: &'a AnalyzerOptions, | ||
emit_signal: F, | ||
) -> (Option<B>, Vec<Error>) | ||
where | ||
F: FnMut(&dyn AnalyzerSignal<GraphqlLanguage>) -> ControlFlow<B> + 'a, | ||
B: 'a, | ||
{ | ||
analyze_with_inspect_matcher(root, filter, |_| {}, options, emit_signal) | ||
} | ||
|
||
/// Run the analyzer on the provided `root`: this process will use the given `filter` | ||
/// to selectively restrict analysis to specific rules / a specific source range, | ||
/// then call `emit_signal` when an analysis rule emits a diagnostic or action. | ||
/// Additionally, this function takes a `inspect_matcher` function that can be | ||
/// used to inspect the "query matches" emitted by the analyzer before they are | ||
/// processed by the lint rules registry | ||
pub fn analyze_with_inspect_matcher<'a, V, F, B>( | ||
root: &LanguageRoot<GraphqlLanguage>, | ||
filter: AnalysisFilter, | ||
inspect_matcher: V, | ||
options: &'a AnalyzerOptions, | ||
mut emit_signal: F, | ||
) -> (Option<B>, Vec<Error>) | ||
where | ||
V: FnMut(&MatchQueryParams<GraphqlLanguage>) + 'a, | ||
F: FnMut(&dyn AnalyzerSignal<GraphqlLanguage>) -> ControlFlow<B> + 'a, | ||
B: 'a, | ||
{ | ||
fn parse_linter_suppression_comment( | ||
text: &str, | ||
) -> Vec<Result<SuppressionKind, SuppressionDiagnostic>> { | ||
let mut result = Vec::new(); | ||
|
||
for comment in parse_suppression_comment(text) { | ||
let categories = match comment { | ||
Ok(comment) => { | ||
if comment.is_legacy { | ||
result.push(Ok(SuppressionKind::Deprecated)); | ||
} | ||
comment.categories | ||
} | ||
Err(err) => { | ||
result.push(Err(err)); | ||
continue; | ||
} | ||
}; | ||
|
||
for (key, value) in categories { | ||
if key == category!("lint") { | ||
if let Some(value) = value { | ||
result.push(Ok(SuppressionKind::MaybeLegacy(value))); | ||
} else { | ||
result.push(Ok(SuppressionKind::Everything)); | ||
} | ||
} else { | ||
let category = key.name(); | ||
if let Some(rule) = category.strip_prefix("lint/") { | ||
result.push(Ok(SuppressionKind::Rule(rule))); | ||
} | ||
} | ||
} | ||
} | ||
|
||
result | ||
} | ||
|
||
let mut registry = RuleRegistry::builder(&filter, root); | ||
visit_registry(&mut registry); | ||
|
||
let (registry, services, diagnostics, visitors) = registry.build(); | ||
|
||
// Bail if we can't parse a rule option | ||
if !diagnostics.is_empty() { | ||
return (None, diagnostics); | ||
} | ||
|
||
let mut analyzer = biome_analyze::Analyzer::new( | ||
metadata(), | ||
biome_analyze::InspectMatcher::new(registry, inspect_matcher), | ||
parse_linter_suppression_comment, | ||
Box::new(GraphqlSuppressionAction), | ||
&mut emit_signal, | ||
); | ||
|
||
for ((phase, _), visitor) in visitors { | ||
analyzer.add_visitor(phase, visitor); | ||
} | ||
|
||
( | ||
analyzer.run(biome_analyze::AnalyzerContext { | ||
root: root.clone(), | ||
range: filter.range, | ||
services, | ||
options, | ||
}), | ||
diagnostics, | ||
) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::analyze; | ||
use biome_analyze::{AnalysisFilter, AnalyzerOptions, ControlFlow, Never, RuleFilter}; | ||
use biome_console::fmt::{Formatter, Termcolor}; | ||
use biome_console::{markup, Markup}; | ||
use biome_diagnostics::termcolor::NoColor; | ||
use biome_diagnostics::{Diagnostic, DiagnosticExt, PrintDiagnostic, Severity}; | ||
use biome_graphql_parser::parse_graphql; | ||
use biome_rowan::TextRange; | ||
use std::slice; | ||
|
||
#[ignore] | ||
#[test] | ||
fn quick_test() { | ||
fn markup_to_string(markup: Markup) -> String { | ||
let mut buffer = Vec::new(); | ||
let mut write = Termcolor(NoColor::new(&mut buffer)); | ||
let mut fmt = Formatter::new(&mut write); | ||
fmt.write_markup(markup).unwrap(); | ||
|
||
String::from_utf8(buffer).unwrap() | ||
} | ||
|
||
const SOURCE: &str = r#" "#; | ||
|
||
let parsed = parse_graphql(SOURCE); | ||
|
||
let mut error_ranges: Vec<TextRange> = Vec::new(); | ||
let rule_filter = RuleFilter::Rule("nursery", "noUnknownPseudoClassSelector"); | ||
let options = AnalyzerOptions::default(); | ||
analyze( | ||
&parsed.tree(), | ||
AnalysisFilter { | ||
enabled_rules: Some(slice::from_ref(&rule_filter)), | ||
..AnalysisFilter::default() | ||
}, | ||
&options, | ||
|signal| { | ||
if let Some(diag) = signal.diagnostic() { | ||
error_ranges.push(diag.location().span.unwrap()); | ||
let error = diag | ||
.with_severity(Severity::Warning) | ||
.with_file_path("ahahah") | ||
.with_file_source_code(SOURCE); | ||
let text = markup_to_string(markup! { | ||
{PrintDiagnostic::verbose(&error)} | ||
}); | ||
eprintln!("{text}"); | ||
} | ||
|
||
for action in signal.actions() { | ||
let new_code = action.mutation.commit(); | ||
eprintln!("{new_code}"); | ||
} | ||
|
||
ControlFlow::<Never>::Continue(()) | ||
}, | ||
); | ||
|
||
assert_eq!(error_ranges.as_slice(), &[]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
//! Generated file, do not edit by hand, see `xtask/codegen` | ||
pub mod nursery; | ||
::biome_analyze::declare_category! { pub Lint { kind : Lint , groups : [self :: nursery :: Nursery ,] } } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//! Generated file, do not edit by hand, see `xtask/codegen` | ||
use biome_analyze::declare_lint_group; | ||
|
||
pub mod use_dummy_rule; | ||
|
||
declare_lint_group! { | ||
pub Nursery { | ||
name : "nursery" , | ||
rules : [ | ||
self :: use_dummy_rule :: UseDummyRule , | ||
] | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
crates/biome_graphql_analyze/src/lint/nursery/use_dummy_rule.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule}; | ||
use biome_graphql_syntax::GraphqlRoot; | ||
|
||
declare_rule! { | ||
/// Dummy rule | ||
/// | ||
/// ## Examples | ||
/// | ||
/// ### Invalid | ||
/// | ||
/// ```json,expect_diagnostic | ||
/// { | ||
/// "title": "New title", | ||
/// "title": "Second title" | ||
/// } | ||
/// ``` | ||
/// | ||
/// ### Valid | ||
/// | ||
/// ```json | ||
/// { | ||
/// "title": "New title", | ||
/// "secondTitle": "Second title" | ||
/// } | ||
/// ``` | ||
pub UseDummyRule { | ||
version: "next", | ||
name: "useDummyRule", | ||
language: "graphql", | ||
} | ||
} | ||
|
||
impl Rule for UseDummyRule { | ||
type Query = Ast<GraphqlRoot>; | ||
type State = (); | ||
type Signals = Option<Self::State>; | ||
type Options = (); | ||
|
||
fn run(_ctx: &RuleContext<Self>) -> Self::Signals { | ||
None | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
//! Generated file, do not edit by hand, see `xtask/codegen` | ||
use crate::lint; | ||
|
||
pub type UseDummyRule = | ||
<lint::nursery::use_dummy_rule::UseDummyRule as biome_analyze::Rule>::Options; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
//! Generated file, do not edit by hand, see `xtask/codegen` | ||
use biome_analyze::RegistryVisitor; | ||
use biome_graphql_syntax::GraphqlLanguage; | ||
pub fn visit_registry<V: RegistryVisitor<GraphqlLanguage>>(registry: &mut V) { | ||
registry.record_category::<crate::lint::Lint>(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use biome_analyze::{ApplySuppression, SuppressionAction}; | ||
use biome_graphql_syntax::GraphqlLanguage; | ||
use biome_rowan::{BatchMutation, SyntaxToken}; | ||
|
||
pub(crate) struct GraphqlSuppressionAction; | ||
|
||
impl SuppressionAction for GraphqlSuppressionAction { | ||
type Language = GraphqlLanguage; | ||
|
||
fn find_token_to_apply_suppression( | ||
&self, | ||
_original_token: SyntaxToken<Self::Language>, | ||
) -> Option<ApplySuppression<Self::Language>> { | ||
// TODO: property implement. Look for the JsSuppressionAction for an example | ||
None | ||
} | ||
|
||
fn apply_suppression( | ||
&self, | ||
_mutation: &mut BatchMutation<Self::Language>, | ||
_apply_suppression: ApplySuppression<Self::Language>, | ||
_suppression_text: &str, | ||
) { | ||
unreachable!("find_token_to_apply_suppression return None") | ||
} | ||
} |
Oops, something went wrong.