Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_json_analyze): noDuplicateKeys (#4592)
Browse files Browse the repository at this point in the history
* feat(rome_json_analyze): noDuplicateKeys

* codegen and dependencies

* rebase

* chore: clippy and codegen

* code suggestions

* code suggestions

* clippy

* codegen

* change rule name
  • Loading branch information
ematipico committed Jun 21, 2023
1 parent a005f2b commit 50eb45f
Show file tree
Hide file tree
Showing 32 changed files with 1,120 additions and 97 deletions.
1 change: 1 addition & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ A-Project:
A-Linter:
- crates/rome_analyze/**
- crates/rome_js_analyze/**
- crates/rome_json_analyze/**

A-Parser:
- crates/rome_parser/**
Expand Down
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ rome_json_factory = { version = "0.0.1", path = "./crates/rome_json_factory" }
tests_macros = { path = "./crates/tests_macros" }
rome_formatter_test = { path = "./crates/rome_formatter_test" }
rome_js_analyze = { path = "./crates/rome_js_analyze" }
rome_json_analyze = { path = "./crates/rome_json_analyze" }
schemars = { version = "0.8.10" }
smallvec = { version = "1.8.0", features = ["union", "const_new"] }

Expand Down
2 changes: 1 addition & 1 deletion crates/rome_analyze/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub trait RegistryVisitor<L: Language> {

/// Stores metadata information for all the rules in the registry, sorted
/// alphabetically
#[derive(Default)]
#[derive(Debug, Default)]
pub struct MetadataRegistry {
inner: BTreeSet<MetadataKey>,
}
Expand Down
1 change: 1 addition & 0 deletions crates/rome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ define_categories! {
"lint/nursery/useLiteralKeys": "https://docs.rome.tools/lint/rules/useLiteralKeys",
"lint/nursery/useSimpleNumberKeys": "https://docs.rome.tools/lint/rules/useSimpleNumberKeys",
"lint/nursery/noStaticOnlyClass": "https://docs.rome.tools/lint/rules/noStaticOnlyClass",
"lint/nursery/noDuplicateJsonKeys": "https://docs.rome.tools/lint/rules/noDuplicateJsonKeys",
"lint/nursery/useNamingConvention": "https://docs.rome.tools/lint/rules/useNamingConvention",
// Insert new nursery rule here

Expand Down
2 changes: 1 addition & 1 deletion crates/rome_js_analyze/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ rome_aria = { workspace = true }
roaring = "0.10.1"
rustc-hash = { workspace = true }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = { version = "1.0.74", features = ["raw_value"] }
serde_json = { version = "1.0.74" }
lazy_static = { workspace = true }
natord = "1.0.9"
bpaf.workspace = true
Expand Down
29 changes: 29 additions & 0 deletions crates/rome_json_analyze/Cargo.toml
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_json_syntax = { workspace = true }
rome_rowan = { workspace = true }
rome_console = { workspace = true }
rome_diagnostics = { workspace = true }
lazy_static = { workspace = true }

[dev-dependencies]
rome_deserialize = { workspace = true }
rome_json_parser = { workspace = true }
rome_json_factory = { workspace = true }
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 = { workspace = true }
4 changes: 4 additions & 0 deletions crates/rome_json_analyze/src/analyzers.rs
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 ,] } }
5 changes: 5 additions & 0 deletions crates/rome_json_analyze/src/analyzers/nursery.rs
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_json_keys;
declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_duplicate_json_keys :: NoDuplicateJsonKeys ,] } }
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic};
use rome_console::markup;
use rome_json_syntax::{JsonMemberName, JsonObjectValue, TextRange};
use rome_rowan::{AstNode, AstSeparatedList};
use std::collections::HashMap;

declare_rule! {
/// Disallow two keys with the same name inside a JSON object.
///
/// ## Examples
///
/// ### Invalid
///
/// ```json,expect_diagnostic
/// {
/// "title": "New title",
/// "title": "Second title"
/// }
/// ```
///
/// ### Valid
///
/// ```json
/// {
/// "title": "New title",
/// "secondTitle": "Second title"
/// }
/// ```
pub(crate) NoDuplicateJsonKeys {
version: "next",
name: "noDuplicateJsonKeys",
recommended: true,
}
}

pub(crate) struct DuplicatedKeys {
/// The fist key, which should be the correct one
original_key: JsonMemberName,
/// The ranges where the duplicated keys are found
duplicated_keys: Vec<TextRange>,
}

impl Rule for NoDuplicateJsonKeys {
type Query = Ast<JsonObjectValue>;
type State = DuplicatedKeys;
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let query = ctx.query();
let mut names = HashMap::<String, Vec<TextRange>>::new();
let mut original_key = None;
for (index, member) in query.json_member_list().iter().flatten().enumerate() {
let name = member.name().ok()?;
if index == 0 {
original_key = Some(name.clone());
}
let text = name.inner_string_text().ok()?;
if let Some(ranges) = names.get_mut(text.text()) {
ranges.push(name.range());
} else {
names.insert(text.text().to_string(), vec![]);
}
}

let duplicated_keys: Vec<_> = names
.into_values()
.filter(|ranges| !ranges.is_empty())
.flatten()
.collect();

if !duplicated_keys.is_empty() {
let Some(original_key) = original_key else { return None};

return Some(DuplicatedKeys {
original_key,
duplicated_keys,
});
}
None
}

fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
let DuplicatedKeys {
duplicated_keys,
original_key,
} = state;
let mut diagnostic = RuleDiagnostic::new(
rule_category!(),
original_key.range(),
markup! {
"The key "<Emphasis>{{original_key.inner_string_text().ok()?.text()}}</Emphasis>" was already declared."
},
);
for range in duplicated_keys {
diagnostic = diagnostic.detail(
range,
markup! {
"This where a duplicated key was declared again."
},
);
}
Some(diagnostic.note(
markup! {
"If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored."
},
))
}
}
5 changes: 5 additions & 0 deletions crates/rome_json_analyze/src/diagnostics.rs
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 {}
Loading

0 comments on commit 50eb45f

Please sign in to comment.