dprint_plugin_json/configuration/
resolve_config.rs

1use super::builder::ConfigurationBuilder;
2use super::types::TrailingCommaKind;
3use super::Configuration;
4use dprint_core::configuration::*;
5
6/// Resolves configuration from a collection of key value strings.
7///
8/// # Example
9///
10/// ```
11/// use dprint_core::configuration::ConfigKeyMap;
12/// use dprint_core::configuration::resolve_global_config;
13/// use dprint_plugin_json::configuration::resolve_config;
14///
15/// let mut config_map = ConfigKeyMap::new(); // get a collection of key value pairs from somewhere
16/// let global_config_result = resolve_global_config(&mut config_map);
17///
18/// // check global_config_result.diagnostics here...
19///
20/// let jsonc_config_map = ConfigKeyMap::new(); // get a collection of k/v pairs from somewhere
21/// let config_result = resolve_config(
22///     jsonc_config_map,
23///     &global_config_result.config
24/// );
25///
26/// // check config_result.diagnostics here and use config_result.config
27/// ```
28pub fn resolve_config(
29  config: ConfigKeyMap,
30  global_config: &GlobalConfiguration,
31) -> ResolveConfigurationResult<Configuration> {
32  let mut diagnostics = Vec::new();
33  let mut config = config;
34
35  if get_value(&mut config, "deno", false, &mut diagnostics) {
36    fill_deno_config(&mut config);
37  }
38
39  let prefer_single_line = get_value(&mut config, "preferSingleLine", false, &mut diagnostics);
40
41  let resolved_config = Configuration {
42    line_width: get_value(
43      &mut config,
44      "lineWidth",
45      global_config
46        .line_width
47        .unwrap_or(RECOMMENDED_GLOBAL_CONFIGURATION.line_width),
48      &mut diagnostics,
49    ),
50    use_tabs: get_value(
51      &mut config,
52      "useTabs",
53      global_config
54        .use_tabs
55        .unwrap_or(RECOMMENDED_GLOBAL_CONFIGURATION.use_tabs),
56      &mut diagnostics,
57    ),
58    indent_width: get_value(
59      &mut config,
60      "indentWidth",
61      global_config.indent_width.unwrap_or(2),
62      &mut diagnostics,
63    ),
64    new_line_kind: get_value(
65      &mut config,
66      "newLineKind",
67      global_config
68        .new_line_kind
69        .unwrap_or(RECOMMENDED_GLOBAL_CONFIGURATION.new_line_kind),
70      &mut diagnostics,
71    ),
72    comment_line_force_space_after_slashes: get_value(
73      &mut config,
74      "commentLine.forceSpaceAfterSlashes",
75      true,
76      &mut diagnostics,
77    ),
78    ignore_node_comment_text: get_value(
79      &mut config,
80      "ignoreNodeCommentText",
81      String::from("dprint-ignore"),
82      &mut diagnostics,
83    ),
84    array_prefer_single_line: get_value(
85      &mut config,
86      "array.preferSingleLine",
87      prefer_single_line,
88      &mut diagnostics,
89    ),
90    object_prefer_single_line: get_value(
91      &mut config,
92      "object.preferSingleLine",
93      prefer_single_line,
94      &mut diagnostics,
95    ),
96    trailing_commas: get_value(
97      &mut config,
98      "trailingCommas",
99      TrailingCommaKind::Jsonc,
100      &mut diagnostics,
101    ),
102    json_trailing_comma_files: get_trailing_comma_files(&mut config, "jsonTrailingCommaFiles", &mut diagnostics),
103  };
104
105  diagnostics.extend(get_unknown_property_diagnostics(config));
106
107  ResolveConfigurationResult {
108    config: resolved_config,
109    diagnostics,
110  }
111}
112
113fn fill_deno_config(config: &mut ConfigKeyMap) {
114  for (key, value) in ConfigurationBuilder::new().deno().config.iter() {
115    if !config.contains_key(key) {
116      config.insert(key.clone(), value.clone());
117    }
118  }
119}
120
121fn get_trailing_comma_files(
122  config: &mut ConfigKeyMap,
123  key: &str,
124  diagnostics: &mut Vec<ConfigurationDiagnostic>,
125) -> Vec<String> {
126  let mut entries = Vec::with_capacity(0);
127  if let Some(values) = config.shift_remove(key) {
128    if let ConfigKeyValue::Array(values) = values {
129      entries = Vec::with_capacity(values.len() * 2);
130      for (i, value) in values.into_iter().enumerate() {
131        if let ConfigKeyValue::String(value) = value {
132          if value.starts_with("./") {
133            diagnostics.push(ConfigurationDiagnostic {
134              property_name: key.to_string(),
135              message: format!(
136                "Element at index {} starting with dot slash (./) is not supported. Remove the leading dot slash.",
137                i
138              ),
139            });
140          } else if value.chars().any(|c| matches!(c, '\\' | '/')) {
141            let value = if value.starts_with('/') || value.starts_with('\\') {
142              value
143            } else {
144              format!("/{}", value)
145            };
146            entries.push(value.replace('/', "\\"));
147            entries.push(value.replace('\\', "/"));
148          } else {
149            entries.push(format!("/{}", value));
150            entries.push(format!("\\{}", value));
151          }
152        } else {
153          diagnostics.push(ConfigurationDiagnostic {
154            property_name: key.to_string(),
155            message: format!("Expected element at index {} to be a string.", i),
156          });
157        }
158      }
159    } else {
160      diagnostics.push(ConfigurationDiagnostic {
161        property_name: key.to_string(),
162        message: "Expected an array.".to_string(),
163      });
164    }
165  }
166  entries
167}
168
169#[cfg(test)]
170mod test {
171  use dprint_core::configuration::ConfigKeyMap;
172  use dprint_core::configuration::ConfigKeyValue;
173  use dprint_core::configuration::GlobalConfiguration;
174
175  use super::resolve_config;
176
177  #[test]
178  fn json_trailing_comma_files() {
179    let global_config = GlobalConfiguration::default();
180    {
181      let result = resolve_config(
182        ConfigKeyMap::from([(
183          "jsonTrailingCommaFiles".to_string(),
184          ConfigKeyValue::Array(vec![ConfigKeyValue::String("test.json".to_string())]),
185        )]),
186        &global_config,
187      );
188      assert!(result.diagnostics.is_empty());
189      assert_eq!(
190        result.config.json_trailing_comma_files,
191        vec!["/test.json".to_string(), "\\test.json".to_string(),]
192      );
193    }
194    {
195      let result = resolve_config(
196        ConfigKeyMap::from([(
197          "jsonTrailingCommaFiles".to_string(),
198          ConfigKeyValue::Array(vec![ConfigKeyValue::String("./test.json".to_string())]),
199        )]),
200        &global_config,
201      );
202      assert_eq!(
203        result.diagnostics[0].message,
204        "Element at index 0 starting with dot slash (./) is not supported. Remove the leading dot slash."
205      );
206    }
207    {
208      let result = resolve_config(
209        ConfigKeyMap::from([(
210          "jsonTrailingCommaFiles".to_string(),
211          ConfigKeyValue::Array(vec![ConfigKeyValue::Number(5)]),
212        )]),
213        &global_config,
214      );
215      assert_eq!(
216        result.diagnostics[0].message,
217        "Expected element at index 0 to be a string."
218      );
219    }
220  }
221}