This repository has been archived by the owner on Aug 31, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 664
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rome_json_analyze): noDuplicateKeys
- Loading branch information
Showing
28 changed files
with
1,025 additions
and
93 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
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
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,29 @@ | ||
[package] | ||
name = "rome_json_analyze" | ||
version = "0.0.0" | ||
edition.workspace = true | ||
authors.workspace = true | ||
repository.workspace = true | ||
license.workspace = true | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
rome_analyze = { workspace = true } | ||
rome_rowan = { workspace = true } | ||
rome_json_syntax = { workspace = true } | ||
rome_json_factory = { workspace = true } | ||
rome_json_parser = { workspace = true } | ||
rome_console = { workspace = true } | ||
rome_diagnostics = { workspace = true } | ||
rome_deserialize = { workspace = true } | ||
lazy_static = { workspace = true } | ||
|
||
[dev-dependencies] | ||
tests_macros = { workspace = true } | ||
rome_text_edit = { workspace = true } | ||
insta = { workspace = true, features = ["glob"] } | ||
countme = { workspace = true, features = ["enable"] } | ||
similar = "2.1.0" | ||
json_comments = "0.2.1" | ||
rome_service = { path = "../rome_service"} |
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(crate) mod nursery; | ||
::rome_analyze::declare_category! { pub (crate) Analyzers { 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,5 @@ | ||
//! Generated file, do not edit by hand, see `xtask/codegen` | ||
|
||
use rome_analyze::declare_group; | ||
pub(crate) mod no_duplicate_keys; | ||
declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_duplicate_keys :: NoDuplicateKeys ,] } } |
76 changes: 76 additions & 0 deletions
76
crates/rome_json_analyze/src/analyzers/nursery/no_duplicate_keys.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,76 @@ | ||
use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; | ||
use rome_console::markup; | ||
use rome_json_syntax::{JsonObjectValue, TextRange}; | ||
use rome_rowan::{AstNode, AstSeparatedList}; | ||
use std::collections::HashMap; | ||
|
||
declare_rule! { | ||
/// Disallow two keys with the same name inside an object. | ||
/// | ||
/// ## Examples | ||
/// | ||
/// ### Invalid | ||
/// | ||
/// ```json,expect_diagnostic | ||
/// { | ||
/// "title": "New title", | ||
/// "title": "Second title" | ||
/// } | ||
/// ``` | ||
pub(crate) NoDuplicateKeys { | ||
version: "next", | ||
name: "noDuplicateKeys", | ||
recommended: true, | ||
} | ||
} | ||
|
||
impl Rule for NoDuplicateKeys { | ||
type Query = Ast<JsonObjectValue>; | ||
type State = (String, TextRange); | ||
type Signals = Vec<Self::State>; | ||
type Options = (); | ||
|
||
fn run(ctx: &RuleContext<Self>) -> Self::Signals { | ||
let query = ctx.query(); | ||
let mut names = HashMap::<String, Vec<TextRange>>::new(); | ||
for member in query.json_member_list().iter().flatten() { | ||
if let Ok(name) = member.name() { | ||
if let Ok(text) = name.inner_string_text() { | ||
if let Some(ranges) = names.get_mut(text.text()) { | ||
ranges.push(name.range()); | ||
} else { | ||
names.insert(text.text().to_string(), vec![]); | ||
} | ||
} | ||
} | ||
} | ||
|
||
let names: Vec<_> = names | ||
.into_iter() | ||
.filter(|(_, ranges)| !ranges.is_empty()) | ||
.flat_map(|(string, ranges)| { | ||
ranges.into_iter().map(move |range| (string.clone(), range)) | ||
}) | ||
.collect(); | ||
|
||
names | ||
} | ||
|
||
fn diagnostic(_ctx: &RuleContext<Self>, (name, range): &Self::State) -> Option<RuleDiagnostic> { | ||
Some( | ||
RuleDiagnostic::new( | ||
rule_category!(), | ||
range, | ||
markup! { | ||
"The key "<Emphasis>{{name}}</Emphasis>" was already declared" | ||
}, | ||
) | ||
.detail( | ||
range, | ||
markup! { | ||
"Remove or rename the key" | ||
}, | ||
), | ||
) | ||
} | ||
} |
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,5 @@ | ||
use rome_diagnostics::Diagnostic; | ||
|
||
#[derive(Clone, Debug, PartialEq, Eq, Diagnostic)] | ||
#[diagnostic(category = "suppressions/parse")] | ||
pub struct SuppressionDiagnostic {} |
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,169 @@ | ||
mod analyzers; | ||
mod diagnostics; | ||
mod registry; | ||
|
||
use crate::diagnostics::SuppressionDiagnostic; | ||
pub use crate::registry::visit_registry; | ||
use rome_analyze::{ | ||
AnalysisFilter, AnalyzerOptions, AnalyzerSignal, ControlFlow, LanguageRoot, MatchQueryParams, | ||
MetadataRegistry, RuleRegistry, SuppressionKind, | ||
}; | ||
use rome_diagnostics::Error; | ||
use rome_json_syntax::JsonLanguage; | ||
|
||
/// 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<JsonLanguage>, | ||
filter: AnalysisFilter, | ||
options: &'a AnalyzerOptions, | ||
emit_signal: F, | ||
) -> (Option<B>, Vec<Error>) | ||
where | ||
F: FnMut(&dyn AnalyzerSignal<JsonLanguage>) -> 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<JsonLanguage>, | ||
filter: AnalysisFilter, | ||
inspect_matcher: V, | ||
options: &'a AnalyzerOptions, | ||
mut emit_signal: F, | ||
) -> (Option<B>, Vec<Error>) | ||
where | ||
V: FnMut(&MatchQueryParams<JsonLanguage>) + 'a, | ||
F: FnMut(&dyn AnalyzerSignal<JsonLanguage>) -> ControlFlow<B> + 'a, | ||
B: 'a, | ||
{ | ||
fn parse_linter_suppression_comment( | ||
_text: &str, | ||
) -> Vec<Result<SuppressionKind, SuppressionDiagnostic>> { | ||
vec![] | ||
} | ||
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 = rome_analyze::Analyzer::new( | ||
metadata(), | ||
rome_analyze::InspectMatcher::new(registry, inspect_matcher), | ||
parse_linter_suppression_comment, | ||
|_| {}, | ||
&mut emit_signal, | ||
); | ||
|
||
for ((phase, _), visitor) in visitors { | ||
analyzer.add_visitor(phase, visitor); | ||
} | ||
|
||
( | ||
analyzer.run(rome_analyze::AnalyzerContext { | ||
root: root.clone(), | ||
range: filter.range, | ||
services, | ||
options, | ||
}), | ||
diagnostics, | ||
) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use rome_analyze::{AnalyzerOptions, Never, RuleFilter}; | ||
use rome_console::fmt::{Formatter, Termcolor}; | ||
use rome_console::{markup, Markup}; | ||
use rome_diagnostics::termcolor::NoColor; | ||
use rome_diagnostics::{Diagnostic, DiagnosticExt, PrintDiagnostic, Severity}; | ||
use rome_json_parser::parse_json; | ||
use rome_json_syntax::TextRange; | ||
use std::slice; | ||
|
||
use crate::{analyze, AnalysisFilter, ControlFlow}; | ||
|
||
#[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#"{ | ||
"foo": "", | ||
"foo": "", | ||
"foo": "", | ||
"bar": "", | ||
"bar": "" | ||
} | ||
"#; | ||
|
||
let parsed = parse_json(SOURCE); | ||
|
||
let mut error_ranges: Vec<TextRange> = Vec::new(); | ||
let rule_filter = RuleFilter::Rule("nursery", "noDuplicateKeys"); | ||
let options = AnalyzerOptions::default(); | ||
analyze( | ||
&parsed.tree().value().unwrap(), | ||
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,7 @@ | ||
//! Generated file, do not edit by hand, see `xtask/codegen` | ||
|
||
use rome_analyze::RegistryVisitor; | ||
use rome_json_syntax::JsonLanguage; | ||
pub fn visit_registry<V: RegistryVisitor<JsonLanguage>>(registry: &mut V) { | ||
registry.record_category::<crate::analyzers::Analyzers>(); | ||
} |
Oops, something went wrong.