Skip to content

Commit

Permalink
Auto merge of #17115 - leviska:json_is_not_rust_better_names, r=Veykril
Browse files Browse the repository at this point in the history
Try to generate more meaningful names in json converter

I just found out about rust-analyzer json converter, but I think it would be more convenient, if names were more useful, like using the names of the keys.

Let's look at some realistic arbitrary json:

```json
{
    "user": {
        "address": {
            "street": "Main St",
            "house": 3
        },
        "email": "example@example.com"
    }
}
```
I think, new generated code is much easier to read and to edit, than the old:
```rust
// Old
struct Struct1{ house: i64, street: String }
struct Struct2{ address: Struct1, email: String }
struct Struct3{ user: Struct2 }

// New
struct Address1{ house: i64, street: String }
struct User1{ address: Address1, email: String }
struct Root1{ user: User1 }
```

Ideally, if we drop the numbers, I can see it being usable just as is (may be rename root)
```rust
struct Address{ house: i64, street: String }
struct User{ address: Address, email: String }
struct Root{ user: User }
```

Sadly, we can't just drop them, because there can be multiple fields (recursive) with the same name, and we can't just easily retroactively add numbers if the name has 2 instances due to parsing being single pass.
We could ignore the `1` and add number only if it's > 1, but I will leave this open to discussion and right now made it the simpler way

In sum, even with numbers, I think this PR still helps in readability
  • Loading branch information
bors committed Apr 21, 2024
2 parents 7baabfc + 029c710 commit 3077e69
Showing 1 changed file with 66 additions and 18 deletions.
84 changes: 66 additions & 18 deletions crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use ide_db::{
helpers::mod_path_to_ast,
imports::insert_use::{insert_use, ImportScope},
source_change::SourceChangeBuilder,
RootDatabase,
FxHashMap, RootDatabase,
};
use itertools::Itertools;
use stdx::{format_to, never};
Expand All @@ -22,15 +22,22 @@ use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsConfig, Severity};
#[derive(Default)]
struct State {
result: String,
struct_counts: usize,
has_serialize: bool,
has_deserialize: bool,
names: FxHashMap<String, usize>,
}

impl State {
fn generate_new_name(&mut self) -> ast::Name {
self.struct_counts += 1;
make::name(&format!("Struct{}", self.struct_counts))
fn generate_new_name(&mut self, name: &str) -> ast::Name {
let name = stdx::to_camel_case(name);
let count = if let Some(count) = self.names.get_mut(&name) {
*count += 1;
*count
} else {
self.names.insert(name.clone(), 1);
1
};
make::name(&format!("{}{}", name, count))
}

fn serde_derive(&self) -> String {
Expand All @@ -52,36 +59,42 @@ impl State {
}
}

fn build_struct(&mut self, value: &serde_json::Map<String, serde_json::Value>) -> ast::Type {
let name = self.generate_new_name();
fn build_struct(
&mut self,
name: &str,
value: &serde_json::Map<String, serde_json::Value>,
) -> ast::Type {
let name = self.generate_new_name(name);
let ty = make::ty(&name.to_string());
let strukt = make::struct_(
None,
name,
None,
make::record_field_list(value.iter().sorted_unstable_by_key(|x| x.0).map(
|(name, value)| make::record_field(None, make::name(name), self.type_of(value)),
|(name, value)| {
make::record_field(None, make::name(name), self.type_of(name, value))
},
))
.into(),
);
format_to!(self.result, "{}{}\n", self.serde_derive(), strukt);
ty
}

fn type_of(&mut self, value: &serde_json::Value) -> ast::Type {
fn type_of(&mut self, name: &str, value: &serde_json::Value) -> ast::Type {
match value {
serde_json::Value::Null => make::ty_unit(),
serde_json::Value::Bool(_) => make::ty("bool"),
serde_json::Value::Number(it) => make::ty(if it.is_i64() { "i64" } else { "f64" }),
serde_json::Value::String(_) => make::ty("String"),
serde_json::Value::Array(it) => {
let ty = match it.iter().next() {
Some(x) => self.type_of(x),
Some(x) => self.type_of(name, x),
None => make::ty_placeholder(),
};
make::ty(&format!("Vec<{ty}>"))
}
serde_json::Value::Object(x) => self.build_struct(x),
serde_json::Value::Object(x) => self.build_struct(name, x),
}
}
}
Expand Down Expand Up @@ -113,7 +126,7 @@ pub(crate) fn json_in_items(
let serialize_resolved = scope_resolve("::serde::Serialize");
state.has_deserialize = deserialize_resolved.is_some();
state.has_serialize = serialize_resolved.is_some();
state.build_struct(&it);
state.build_struct("Root", &it);
edit.insert(range.start(), state.result);
acc.push(
Diagnostic::new(
Expand Down Expand Up @@ -218,7 +231,7 @@ mod tests {
}
#[derive(Serialize)]
struct Struct1{ bar: f64, bay: i64, baz: (), r#box: bool, foo: String }
struct Root1{ bar: f64, bay: i64, baz: (), r#box: bool, foo: String }
"#,
);
Expand All @@ -237,9 +250,44 @@ mod tests {
}
"#,
r#"
struct Struct3{ }
struct Struct2{ kind: String, value: Struct3 }
struct Struct1{ bar: Struct2, foo: String }
struct Value1{ }
struct Bar1{ kind: String, value: Value1 }
struct Root1{ bar: Bar1, foo: String }
"#,
);
}

#[test]
fn naming() {
check_fix(
r#"
{$0
"user": {
"address": {
"street": "Main St",
"house": 3
},
"email": "example@example.com"
},
"another_user": {
"user": {
"address": {
"street": "Main St",
"house": 3
},
"email": "example@example.com"
}
}
}
"#,
r#"
struct Address1{ house: i64, street: String }
struct User1{ address: Address1, email: String }
struct AnotherUser1{ user: User1 }
struct Address2{ house: i64, street: String }
struct User2{ address: Address2, email: String }
struct Root1{ another_user: AnotherUser1, user: User2 }
"#,
);
Expand Down Expand Up @@ -276,9 +324,9 @@ mod tests {
use serde::Deserialize;
#[derive(Serialize, Deserialize)]
struct Struct2{ x: i64, y: i64 }
struct OfObject1{ x: i64, y: i64 }
#[derive(Serialize, Deserialize)]
struct Struct1{ empty: Vec<_>, nested: Vec<Vec<Vec<i64>>>, of_object: Vec<Struct2>, of_string: Vec<String> }
struct Root1{ empty: Vec<_>, nested: Vec<Vec<Vec<i64>>>, of_object: Vec<OfObject1>, of_string: Vec<String> }
"#,
);
Expand Down

0 comments on commit 3077e69

Please sign in to comment.