rust_analyzer/config/
patch_old_style.rs

1//! See [`patch_json_for_outdated_configs`]
2use serde_json::{Value, json};
3
4/// This function patches the json config to the new expected keys.
5/// That is we try to load old known config keys here and convert them to the new ones.
6/// See https://github.com/rust-lang/rust-analyzer/pull/12010
7///
8/// We already have an alias system for simple cases, but if we make structural changes
9/// the alias infra fails down.
10pub(super) fn patch_json_for_outdated_configs(json: &mut Value) {
11    let copy = json.clone();
12
13    macro_rules! patch {
14        ($(
15            $($src:ident).+ -> $($dst:ident).+ ;
16        )+) => { $(
17            match copy.pointer(concat!($("/", stringify!($src)),+)).cloned() {
18                Some(Value::Object(_)) | None => (),
19                Some(it) => {
20                    let mut last = it;
21                    for segment in [$(stringify!($dst)),+].into_iter().rev() {
22                        last = Value::Object(serde_json::Map::from_iter(std::iter::once((segment.to_owned(), last))));
23                    }
24
25                    merge(json, last);
26                },
27            }
28        )+ };
29    }
30
31    patch! {
32        assist.allowMergingIntoGlobImports -> imports.merge.glob;
33        assist.exprFillDefault -> assist.expressionFillDefault;
34        assist.importEnforceGranularity -> imports.granularity.enforce;
35        assist.importGranularity -> imports.granularity.group;
36        assist.importMergeBehavior -> imports.granularity.group;
37        assist.importMergeBehaviour -> imports.granularity.group;
38        assist.importGroup -> imports.group.enable;
39        assist.importPrefix -> imports.prefix;
40        primeCaches.enable -> cachePriming.enable;
41        cache.warmup -> cachePriming.enable;
42        cargo.loadOutDirsFromCheck -> cargo.buildScripts.enable;
43        cargo.runBuildScripts -> cargo.buildScripts.enable;
44        cargo.runBuildScriptsCommand -> cargo.buildScripts.overrideCommand;
45        cargo.useRustcWrapperForBuildScripts -> cargo.buildScripts.useRustcWrapper;
46        diagnostics.enableExperimental -> diagnostics.experimental.enable;
47        experimental.procAttrMacros -> procMacro.attributes.enable;
48        highlighting.strings -> semanticHighlighting.strings.enable;
49        highlightRelated.breakPoints -> semanticHighlighting.breakPoints.enable;
50        highlightRelated.exitPoints -> semanticHighlighting.exitPoints.enable;
51        highlightRelated.yieldPoints -> semanticHighlighting.yieldPoints.enable;
52        highlightRelated.references -> semanticHighlighting.references.enable;
53        hover.documentation -> hover.documentation.enable;
54        hover.linksInHover -> hover.links.enable;
55        hoverActions.linksInHover -> hover.links.enable;
56        hoverActions.debug -> hover.actions.debug.enable;
57        hoverActions.enable -> hover.actions.enable;
58        hoverActions.gotoTypeDef -> hover.actions.gotoTypeDef.enable;
59        hoverActions.implementations -> hover.actions.implementations.enable;
60        hoverActions.references -> hover.actions.references.enable;
61        hoverActions.run -> hover.actions.run.enable;
62        inlayHints.chainingHints -> inlayHints.chainingHints.enable;
63        inlayHints.closureReturnTypeHints -> inlayHints.closureReturnTypeHints.enable;
64        inlayHints.hideNamedConstructorHints -> inlayHints.typeHints.hideNamedConstructorHints;
65        inlayHints.parameterHints -> inlayHints.parameterHints.enable;
66        inlayHints.reborrowHints -> inlayHints.reborrowHints.enable;
67        inlayHints.typeHints -> inlayHints.typeHints.enable;
68        lruCapacity -> lru.capacity;
69        runnables.cargoExtraArgs -> runnables.extraArgs ;
70        runnables.overrideCargo -> runnables.command ;
71        rustcSource -> rustc.source;
72        rustfmt.enableRangeFormatting -> rustfmt.rangeFormatting.enable;
73    }
74
75    // completion.snippets -> completion.snippets.custom;
76    if let Some(Value::Object(obj)) = copy.pointer("/completion/snippets").cloned()
77        && (obj.len() != 1 || obj.get("custom").is_none())
78    {
79        merge(
80            json,
81            json! {{
82                "completion": {
83                    "snippets": {
84                        "custom": obj
85                    },
86                },
87            }},
88        );
89    }
90
91    // callInfo_full -> signatureInfo_detail, signatureInfo_documentation_enable
92    if let Some(Value::Bool(b)) = copy.pointer("/callInfo/full") {
93        let sig_info = match b {
94            true => json!({ "signatureInfo": {
95                "documentation": {"enable": true}},
96                "detail": "full"
97            }),
98            false => json!({ "signatureInfo": {
99                "documentation": {"enable": false}},
100                "detail": "parameters"
101            }),
102        };
103        merge(json, sig_info);
104    }
105
106    // cargo_allFeatures, cargo_features -> cargo_features
107    if let Some(Value::Bool(true)) = copy.pointer("/cargo/allFeatures") {
108        merge(json, json!({ "cargo": { "features": "all" } }));
109    }
110
111    // checkOnSave_allFeatures, checkOnSave_features -> check_features
112    if let Some(Value::Bool(true)) = copy.pointer("/checkOnSave/allFeatures") {
113        merge(json, json!({ "check": { "features": "all" } }));
114    }
115
116    // completion_addCallArgumentSnippets completion_addCallParenthesis -> completion_callable_snippets
117    'completion: {
118        let res = match (
119            copy.pointer("/completion/addCallArgumentSnippets"),
120            copy.pointer("/completion/addCallParenthesis"),
121        ) {
122            (Some(Value::Bool(true)), Some(Value::Bool(true))) => json!("fill_arguments"),
123            (_, Some(Value::Bool(true))) => json!("add_parentheses"),
124            (Some(Value::Bool(false)), Some(Value::Bool(false))) => json!("none"),
125            (_, _) => break 'completion,
126        };
127        merge(json, json!({ "completion": { "callable": {"snippets": res }} }));
128    }
129
130    // We need to do this due to the checkOnSave_enable -> checkOnSave change, as that key now can either be an object or a bool
131    // checkOnSave_* -> check_*
132    if let Some(Value::Object(obj)) = copy.pointer("/checkOnSave") {
133        // checkOnSave_enable -> checkOnSave
134        if let Some(b @ Value::Bool(_)) = obj.get("enable") {
135            merge(json, json!({ "checkOnSave": b }));
136        }
137        merge(json, json!({ "check": obj }));
138    }
139}
140
141fn merge(dst: &mut Value, src: Value) {
142    match (dst, src) {
143        (Value::Object(dst), Value::Object(src)) => {
144            for (k, v) in src {
145                merge(dst.entry(k).or_insert(v.clone()), v)
146            }
147        }
148        (dst, src) => *dst = src,
149    }
150}
151
152#[test]
153fn check_on_save_patching() {
154    let mut json = json!({ "checkOnSave": { "overrideCommand": "foo" }});
155    patch_json_for_outdated_configs(&mut json);
156    assert_eq!(
157        json,
158        json!({ "checkOnSave": { "overrideCommand": "foo" }, "check": { "overrideCommand": "foo" }})
159    );
160}
161
162#[test]
163fn check_on_save_patching_enable() {
164    let mut json = json!({ "checkOnSave": { "enable": true, "overrideCommand": "foo" }});
165    patch_json_for_outdated_configs(&mut json);
166    assert_eq!(
167        json,
168        json!({ "checkOnSave": true, "check": { "enable": true, "overrideCommand": "foo" }})
169    );
170}