From e5238358b84973d98c14d6950bb725691e7a0615 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 19 Apr 2024 15:45:50 +0100 Subject: [PATCH] fix(linter): correctly compute duplicate entries (#2525) --- CHANGELOG.md | 2 +- Cargo.toml | 289 +++++++++--------- crates/biome_json_analyze/Cargo.toml | 32 +- crates/biome_json_analyze/src/lib.rs | 14 +- .../lint/nursery/no_duplicate_json_keys.rs | 69 ++--- .../nursery/noDuplicateJsonKeys/invalid.json | 7 +- .../noDuplicateJsonKeys/invalid.json.snap | 43 ++- 7 files changed, 237 insertions(+), 219 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ca7f96850e..5b8cd1be4bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b callFunction(<>{bar}) ``` Contributed by @ematipico - +- Fix [#2366](https://github.com/biomejs/biome/issues/2366), where `noDuplicateJsonKeys` incorrectly computed the kes to highlight. Contributed by @ematipico #### Enhancements - The rule `noMisplacedAssertions` now considers valid calling `expect` inside `waitFor`: diff --git a/Cargo.toml b/Cargo.toml index 96fccface62b..532220cae75d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,190 +1,191 @@ [workspace] # Use the newer version of the cargo resolver # https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions -members = ["crates/*", "xtask/bench", "xtask/codegen", "xtask/coverage", "xtask/libs_bench", "xtask/contributors"] +members = ["crates/*", "xtask/bench", "xtask/codegen", "xtask/coverage", "xtask/libs_bench", "xtask/contributors"] resolver = "2" [workspace.lints.rust] absolute_paths_not_starting_with_crate = "warn" -dead_code = "warn" -trivial_numeric_casts = "warn" -unused_import_braces = "warn" -unused_lifetimes = "warn" -unused_macro_rules = "warn" +dead_code = "warn" +trivial_numeric_casts = "warn" +unused_import_braces = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" [workspace.lints.clippy] -cargo_common_metadata = "allow" +cargo_common_metadata = "allow" multiple_crate_versions = "allow" # pedantic -checked_conversions = "warn" -cloned_instead_of_copied = "warn" -copy_iterator = "warn" -dbg_macro = "warn" -doc_link_with_quotes = "warn" -empty_enum = "warn" -expl_impl_clone_on_copy = "warn" -explicit_into_iter_loop = "warn" -filter_map_next = "warn" -flat_map_option = "warn" -fn_params_excessive_bools = "warn" +checked_conversions = "warn" +cloned_instead_of_copied = "warn" +copy_iterator = "warn" +dbg_macro = "warn" +doc_link_with_quotes = "warn" +empty_enum = "warn" +expl_impl_clone_on_copy = "warn" +explicit_into_iter_loop = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +fn_params_excessive_bools = "warn" from_iter_instead_of_collect = "warn" -implicit_clone = "warn" -implicit_hasher = "warn" -index_refutable_slice = "warn" -inefficient_to_string = "warn" -invalid_upcast_comparisons = "warn" -iter_not_returning_iterator = "warn" -large_stack_arrays = "warn" -large_types_passed_by_value = "warn" -macro_use_imports = "warn" -manual_ok_or = "warn" -manual_string_new = "warn" -map_flatten = "warn" -map_unwrap_or = "warn" +implicit_clone = "warn" +implicit_hasher = "warn" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +invalid_upcast_comparisons = "warn" +iter_not_returning_iterator = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +macro_use_imports = "warn" +manual_ok_or = "warn" +manual_string_new = "warn" +map_flatten = "warn" +map_unwrap_or = "warn" mismatching_type_param_order = "warn" -mut_mut = "warn" -naive_bytecount = "warn" -needless_bitwise_bool = "warn" -needless_continue = "warn" -needless_for_each = "warn" +mut_mut = "warn" +naive_bytecount = "warn" +needless_bitwise_bool = "warn" +needless_continue = "warn" +needless_for_each = "warn" no_effect_underscore_binding = "warn" -ref_binding_to_reference = "warn" -ref_option_ref = "warn" -stable_sort_primitive = "warn" -unnecessary_box_returns = "warn" -unnecessary_join = "warn" -unnested_or_patterns = "warn" -unreadable_literal = "warn" -verbose_bit_mask = "warn" -zero_sized_map_values = "warn" +ref_binding_to_reference = "warn" +ref_option_ref = "warn" +stable_sort_primitive = "warn" +unnecessary_box_returns = "warn" +unnecessary_join = "warn" +unnested_or_patterns = "warn" +unreadable_literal = "warn" +verbose_bit_mask = "warn" +zero_sized_map_values = "warn" # restriction -empty_drop = "warn" -float_cmp_const = "warn" -get_unwrap = "warn" -infinite_loop = "warn" -lossy_float_literal = "warn" -mem_forget = "warn" -rc_buffer = "warn" -rc_mutex = "warn" +empty_drop = "warn" +float_cmp_const = "warn" +get_unwrap = "warn" +infinite_loop = "warn" +lossy_float_literal = "warn" +mem_forget = "warn" +rc_buffer = "warn" +rc_mutex = "warn" rest_pat_in_fully_bound_structs = "warn" -verbose_file_reads = "warn" +verbose_file_reads = "warn" [workspace.package] -authors = ["Biome Developers and Contributors"] +authors = ["Biome Developers and Contributors"] categories = ["development-tools", "web-programming"] -edition = "2021" -homepage = "https://biomejs.dev/" -keywords = ["parser", "linter", "formatter"] -license = "MIT OR Apache-2.0" +edition = "2021" +homepage = "https://biomejs.dev/" +keywords = ["parser", "linter", "formatter"] +license = "MIT OR Apache-2.0" repository = "https://github.com/biomejs/biome" [profile.release-with-debug] -debug = true +debug = true inherits = "release" [workspace.dependencies] # publish -biome_analyze = { version = "0.5.7", path = "./crates/biome_analyze" } -biome_aria = { version = "0.5.7", path = "./crates/biome_aria" } -biome_aria_metadata = { version = "0.5.7", path = "./crates/biome_aria_metadata" } -biome_console = { version = "0.5.7", path = "./crates/biome_console" } -biome_control_flow = { version = "0.5.7", path = "./crates/biome_control_flow" } -biome_css_analyze = { version = "0.5.7", path = "./crates/biome_css_analyze" } -biome_css_factory = { version = "0.5.7", path = "./crates/biome_css_factory" } -biome_css_formatter = { version = "0.5.7", path = "./crates/biome_css_formatter" } -biome_css_parser = { version = "0.5.7", path = "./crates/biome_css_parser" } -biome_css_syntax = { version = "0.5.7", path = "./crates/biome_css_syntax" } -biome_deserialize = { version = "0.5.7", path = "./crates/biome_deserialize" } -biome_deserialize_macros = { version = "0.5.7", path = "./crates/biome_deserialize_macros" } -biome_diagnostics = { version = "0.5.7", path = "./crates/biome_diagnostics" } +biome_analyze = { version = "0.5.7", path = "./crates/biome_analyze" } +biome_aria = { version = "0.5.7", path = "./crates/biome_aria" } +biome_aria_metadata = { version = "0.5.7", path = "./crates/biome_aria_metadata" } +biome_console = { version = "0.5.7", path = "./crates/biome_console" } +biome_control_flow = { version = "0.5.7", path = "./crates/biome_control_flow" } +biome_css_analyze = { version = "0.5.7", path = "./crates/biome_css_analyze" } +biome_css_factory = { version = "0.5.7", path = "./crates/biome_css_factory" } +biome_css_formatter = { version = "0.5.7", path = "./crates/biome_css_formatter" } +biome_css_parser = { version = "0.5.7", path = "./crates/biome_css_parser" } +biome_css_syntax = { version = "0.5.7", path = "./crates/biome_css_syntax" } +biome_deserialize = { version = "0.5.7", path = "./crates/biome_deserialize" } +biome_deserialize_macros = { version = "0.5.7", path = "./crates/biome_deserialize_macros" } +biome_diagnostics = { version = "0.5.7", path = "./crates/biome_diagnostics" } biome_diagnostics_categories = { version = "0.5.7", path = "./crates/biome_diagnostics_categories" } -biome_diagnostics_macros = { version = "0.5.7", path = "./crates/biome_diagnostics_macros" } -biome_formatter = { version = "0.5.7", path = "./crates/biome_formatter" } -biome_fs = { version = "0.5.7", path = "./crates/biome_fs" } -biome_graphql_factory = { version = "0.1.0", path = "./crates/biome_graphql_factory" } -biome_graphql_syntax = { version = "0.1.0", path = "./crates/biome_graphql_syntax" } -biome_grit_factory = { version = "0.5.7", path = "./crates/biome_grit_factory" } -biome_grit_parser = { version = "0.1.0", path = "./crates/biome_grit_parser" } -biome_grit_patterns = { version = "0.0.1", path = "./crates/biome_grit_patterns" } -biome_grit_syntax = { version = "0.5.7", path = "./crates/biome_grit_syntax" } -biome_html_factory = { version = "0.5.7", path = "./crates/biome_html_factory" } -biome_html_syntax = { version = "0.5.7", path = "./crates/biome_html_syntax" } -biome_js_analyze = { version = "0.5.7", path = "./crates/biome_js_analyze" } -biome_js_factory = { version = "0.5.7", path = "./crates/biome_js_factory" } -biome_js_formatter = { version = "0.5.7", path = "./crates/biome_js_formatter" } -biome_js_parser = { version = "0.5.7", path = "./crates/biome_js_parser" } -biome_js_semantic = { version = "0.5.7", path = "./crates/biome_js_semantic" } -biome_js_syntax = { version = "0.5.7", path = "./crates/biome_js_syntax" } -biome_json_analyze = { version = "0.5.7", path = "./crates/biome_json_analyze" } -biome_json_factory = { version = "0.5.7", path = "./crates/biome_json_factory" } -biome_json_formatter = { version = "0.5.7", path = "./crates/biome_json_formatter" } -biome_json_parser = { version = "0.5.7", path = "./crates/biome_json_parser" } -biome_json_syntax = { version = "0.5.7", path = "./crates/biome_json_syntax" } -biome_yaml_factory = { version = "0.0.1", path = "./crates/biome_yaml_factory" } -biome_yaml_parser = { version = "0.0.1", path = "./crates/biome_yaml_parser" } -biome_yaml_syntax = { version = "0.0.1", path = "./crates/biome_yaml_syntax" } +biome_diagnostics_macros = { version = "0.5.7", path = "./crates/biome_diagnostics_macros" } +biome_formatter = { version = "0.5.7", path = "./crates/biome_formatter" } +biome_fs = { version = "0.5.7", path = "./crates/biome_fs" } +biome_graphql_factory = { version = "0.1.0", path = "./crates/biome_graphql_factory" } +biome_graphql_syntax = { version = "0.1.0", path = "./crates/biome_graphql_syntax" } +biome_grit_factory = { version = "0.5.7", path = "./crates/biome_grit_factory" } +biome_grit_parser = { version = "0.1.0", path = "./crates/biome_grit_parser" } +biome_grit_patterns = { version = "0.0.1", path = "./crates/biome_grit_patterns" } +biome_grit_syntax = { version = "0.5.7", path = "./crates/biome_grit_syntax" } +biome_html_factory = { version = "0.5.7", path = "./crates/biome_html_factory" } +biome_html_syntax = { version = "0.5.7", path = "./crates/biome_html_syntax" } +biome_js_analyze = { version = "0.5.7", path = "./crates/biome_js_analyze" } +biome_js_factory = { version = "0.5.7", path = "./crates/biome_js_factory" } +biome_js_formatter = { version = "0.5.7", path = "./crates/biome_js_formatter" } +biome_js_parser = { version = "0.5.7", path = "./crates/biome_js_parser" } +biome_js_semantic = { version = "0.5.7", path = "./crates/biome_js_semantic" } +biome_js_syntax = { version = "0.5.7", path = "./crates/biome_js_syntax" } +biome_json_analyze = { version = "0.5.7", path = "./crates/biome_json_analyze" } +biome_json_factory = { version = "0.5.7", path = "./crates/biome_json_factory" } +biome_json_formatter = { version = "0.5.7", path = "./crates/biome_json_formatter" } +biome_json_parser = { version = "0.5.7", path = "./crates/biome_json_parser" } +biome_json_syntax = { version = "0.5.7", path = "./crates/biome_json_syntax" } +biome_yaml_factory = { version = "0.0.1", path = "./crates/biome_yaml_factory" } +biome_yaml_parser = { version = "0.0.1", path = "./crates/biome_yaml_parser" } +biome_yaml_syntax = { version = "0.0.1", path = "./crates/biome_yaml_syntax" } -biome_markup = { version = "0.5.7", path = "./crates/biome_markup" } -biome_parser = { version = "0.5.7", path = "./crates/biome_parser" } -biome_project = { version = "0.5.7", path = "./crates/biome_project" } -biome_rowan = { version = "0.5.7", path = "./crates/biome_rowan" } -biome_string_case = { version = "0.5.7", path = "./crates/biome_string_case" } -biome_suppression = { version = "0.5.7", path = "./crates/biome_suppression" } -biome_text_edit = { version = "0.5.7", path = "./crates/biome_text_edit" } -biome_text_size = { version = "0.5.7", path = "./crates/biome_text_size" } +biome_markup = { version = "0.5.7", path = "./crates/biome_markup" } +biome_parser = { version = "0.5.7", path = "./crates/biome_parser" } +biome_project = { version = "0.5.7", path = "./crates/biome_project" } +biome_rowan = { version = "0.5.7", path = "./crates/biome_rowan" } +biome_string_case = { version = "0.5.7", path = "./crates/biome_string_case" } +biome_suppression = { version = "0.5.7", path = "./crates/biome_suppression" } +biome_text_edit = { version = "0.5.7", path = "./crates/biome_text_edit" } +biome_text_size = { version = "0.5.7", path = "./crates/biome_text_size" } biome_unicode_table = { version = "0.5.7", path = "./crates/biome_unicode_table" } # not publish -biome_cli = { path = "./crates/biome_cli" } -biome_configuration = { path = "./crates/biome_configuration" } -biome_flags = { path = "./crates/biome_flags" } +biome_cli = { path = "./crates/biome_cli" } +biome_configuration = { path = "./crates/biome_configuration" } +biome_flags = { path = "./crates/biome_flags" } biome_formatter_test = { path = "./crates/biome_formatter_test" } -biome_lsp = { path = "./crates/biome_lsp" } -biome_migrate = { path = "./crates/biome_migrate" } -biome_service = { path = "./crates/biome_service" } -biome_test_utils = { path = "./crates/biome_test_utils" } -biome_ungrammar = { path = "./crates/biome_ungrammar" } -tests_macros = { path = "./crates/tests_macros" } +biome_lsp = { path = "./crates/biome_lsp" } +biome_migrate = { path = "./crates/biome_migrate" } +biome_service = { path = "./crates/biome_service" } +biome_test_utils = { path = "./crates/biome_test_utils" } +biome_ungrammar = { path = "./crates/biome_ungrammar" } +tests_macros = { path = "./crates/tests_macros" } # Crates needed in the workspace -bitflags = "2.5.0" -bpaf = { version = "0.9.9", features = ["derive"] } -countme = "3.0.1" -crossbeam = "0.8.4" -dashmap = "5.4.0" -ignore = "0.4.21" -indexmap = "1.9.3" -insta = "1.38.0" -lazy_static = "1.4.0" -oxc_resolver = "1.4.0" -proc-macro2 = "1.0.80" -quickcheck = "1.0.3" -quickcheck_macros = "1.0.0" -quote = "1.0.36" -rayon = "1.8.1" -regex = "1.10.4" -rustc-hash = "1.1.0" -schemars = { version = "0.8.12" } -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.115" -similar = "2.5.0" -smallvec = { version = "1.10.0", features = ["union", "const_new"] } -syn = "1.0.109" -tokio = { version = "1.36.0" } -tracing = { version = "0.1.37", default-features = false, features = ["std"] } +bitflags = "2.5.0" +bpaf = { version = "0.9.9", features = ["derive"] } +countme = "3.0.1" +crossbeam = "0.8.4" +dashmap = "5.4.0" +ignore = "0.4.21" +indexmap = "1.9.3" +insta = "1.38.0" +lazy_static = "1.4.0" +oxc_resolver = "1.4.0" +proc-macro2 = "1.0.80" +quickcheck = "1.0.3" +quickcheck_macros = "1.0.0" +quote = "1.0.36" +rayon = "1.8.1" +regex = "1.10.4" +rustc-hash = "1.1.0" +schemars = { version = "0.8.12" } +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.115" +similar = "2.5.0" +smallvec = { version = "1.10.0", features = ["union", "const_new"] } +syn = "1.0.109" +tokio = { version = "1.36.0" } +tracing = { version = "0.1.37", default-features = false, features = ["std"] } tracing-subscriber = "0.3.18" -unicode-bom = "2.0.3" +unicode-bom = "2.0.3" +itertools = "0.12.1" [profile.dev.package.biome_wasm] -debug = true +debug = true opt-level = "s" [profile.test.package.biome_wasm] -debug = true +debug = true opt-level = "s" [profile.release.package.biome_wasm] -debug = false +debug = false opt-level = 3 diff --git a/crates/biome_json_analyze/Cargo.toml b/crates/biome_json_analyze/Cargo.toml index a645de57ec1f..e26aa47ab560 100644 --- a/crates/biome_json_analyze/Cargo.toml +++ b/crates/biome_json_analyze/Cargo.toml @@ -1,31 +1,31 @@ [package] -authors.workspace = true +authors.workspace = true categories.workspace = true -description = "Biome's JSON linter" -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -name = "biome_json_analyze" +description = "Biome's JSON linter" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "biome_json_analyze" repository.workspace = true -version = "0.5.7" +version = "0.5.7" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -biome_analyze = { workspace = true } -biome_console = { workspace = true } +biome_analyze = { workspace = true } +biome_console = { workspace = true } biome_diagnostics = { workspace = true } biome_json_syntax = { workspace = true } -biome_rowan = { workspace = true } -lazy_static = { workspace = true } -rustc-hash = { workspace = true } +biome_rowan = { workspace = true } +lazy_static = { workspace = true } +rustc-hash = { workspace = true } [dev-dependencies] biome_json_parser = { path = "../biome_json_parser" } -biome_test_utils = { path = "../biome_test_utils" } -insta = { workspace = true, features = ["glob"] } -tests_macros = { path = "../tests_macros" } +biome_test_utils = { path = "../biome_test_utils" } +insta = { workspace = true, features = ["glob"] } +tests_macros = { path = "../tests_macros" } [lints] workspace = true diff --git a/crates/biome_json_analyze/src/lib.rs b/crates/biome_json_analyze/src/lib.rs index 2189e5bada2e..81bd47c7f7b3 100644 --- a/crates/biome_json_analyze/src/lib.rs +++ b/crates/biome_json_analyze/src/lib.rs @@ -120,19 +120,17 @@ mod tests { String::from_utf8(buffer).unwrap() } - const SOURCE: &str = r#"{ - "foo": "", - "foo": "", - "foo": "", - "bar": "", - "bar": "" -} + const SOURCE: &str = r#" { + "loreum": "", + "loreum": "", + "loreum": "" + } "#; let parsed = parse_json(SOURCE, JsonParserOptions::default()); let mut error_ranges: Vec = Vec::new(); - let rule_filter = RuleFilter::Rule("nursery", "noDuplicateKeys"); + let rule_filter = RuleFilter::Rule("nursery", "noDuplicateJsonKeys"); let options = AnalyzerOptions::default(); analyze( &parsed.tree(), diff --git a/crates/biome_json_analyze/src/lint/nursery/no_duplicate_json_keys.rs b/crates/biome_json_analyze/src/lint/nursery/no_duplicate_json_keys.rs index 4f8ef6efaeaf..7cbd8d85ce39 100644 --- a/crates/biome_json_analyze/src/lint/nursery/no_duplicate_json_keys.rs +++ b/crates/biome_json_analyze/src/lint/nursery/no_duplicate_json_keys.rs @@ -33,66 +33,51 @@ declare_rule! { } } -pub 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, -} - impl Rule for NoDuplicateJsonKeys { type Query = Ast; - type State = DuplicatedKeys; - type Signals = Option; + type State = (JsonMemberName, Vec); + type Signals = Vec; type Options = (); fn run(ctx: &RuleContext) -> Self::Signals { let query = ctx.query(); - let mut names = FxHashMap::>::default(); - 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 mut names = FxHashMap::>::default(); + let mut keys_found = FxHashMap::::default(); + for member in query.json_member_list().iter().flatten() { + let name = member.name(); + + if let Ok(name) = name { + let text = name.inner_string_text(); + if let Ok(text) = text { + if let Some(original_member) = keys_found.get(text.text()) { + if let Some(ranges) = names.get_mut(original_member) { + ranges.push(name.range()); + } else { + names.insert(original_member.clone(), vec![name.range()]); + } + } else { + keys_found.insert(text.to_string(), name); + } + } } } - let duplicated_keys: Vec<_> = names - .into_values() - .filter(|ranges| !ranges.is_empty()) - .flatten() - .collect(); - - if !duplicated_keys.is_empty() { - let original_key = original_key?; + let duplicated_keys: Vec<_> = names.into_iter().collect(); - return Some(DuplicatedKeys { - original_key, - duplicated_keys, - }); - } - None + duplicated_keys } fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { - let DuplicatedKeys { - duplicated_keys, - original_key, - } = state; + let (original, ranges) = state; + let name = original.inner_string_text().ok()?; let mut diagnostic = RuleDiagnostic::new( rule_category!(), - original_key.range(), + original.range(), markup! { - "The key "{{original_key.inner_string_text().ok()?.text()}}" was already declared." + "The key "{name.text()}" was already declared." }, ); - for range in duplicated_keys { + for range in ranges { diagnostic = diagnostic.detail( range, markup! { diff --git a/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json b/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json index a5c10881e859..244eec37af79 100644 --- a/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json +++ b/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json @@ -2,5 +2,10 @@ "foo": "", "foo": "", "foo": "", - "foo": "" + "foo": "", + "new": { + "lorem": "", + "ipsum": "", + "lorem": "" + } } diff --git a/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json.snap b/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json.snap index e160c6f65984..de51da3d71a2 100644 --- a/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json.snap +++ b/crates/biome_json_analyze/tests/specs/nursery/noDuplicateJsonKeys/invalid.json.snap @@ -8,7 +8,12 @@ expression: invalid.json "foo": "", "foo": "", "foo": "", - "foo": "" + "foo": "", + "new": { + "lorem": "", + "ipsum": "", + "lorem": "" + } } ``` @@ -32,7 +37,7 @@ invalid.json:2:2 lint/nursery/noDuplicateJsonKeys ━━━━━━━━━━ > 3 │ "foo": "", │ ^^^^^ 4 │ "foo": "", - 5 │ "foo": "" + 5 │ "foo": "", i This where a duplicated key was declared again. @@ -40,21 +45,45 @@ invalid.json:2:2 lint/nursery/noDuplicateJsonKeys ━━━━━━━━━━ 3 │ "foo": "", > 4 │ "foo": "", │ ^^^^^ - 5 │ "foo": "" - 6 │ } + 5 │ "foo": "", + 6 │ "new": { i This where a duplicated key was declared again. 3 │ "foo": "", 4 │ "foo": "", - > 5 │ "foo": "" + > 5 │ "foo": "", │ ^^^^^ - 6 │ } - 7 │ + 6 │ "new": { + 7 │ "lorem": "", i If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored. ``` +``` +invalid.json:7:3 lint/nursery/noDuplicateJsonKeys ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ! The key lorem was already declared. + + 5 │ "foo": "", + 6 │ "new": { + > 7 │ "lorem": "", + │ ^^^^^^^ + 8 │ "ipsum": "", + 9 │ "lorem": "" + + i This where a duplicated key was declared again. + + 7 │ "lorem": "", + 8 │ "ipsum": "", + > 9 │ "lorem": "" + │ ^^^^^^^ + 10 │ } + 11 │ } + + i If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored. + + +```