dprint_plugin_json/configuration/
builder.rs

1use dprint_core::configuration::{ConfigKeyMap, ConfigKeyValue, GlobalConfiguration, NewLineKind};
2
3use super::*;
4
5/// Formatting configuration builder.
6///
7/// # Example
8///
9/// ```
10/// use dprint_plugin_json::configuration::*;
11///
12/// let config = ConfigurationBuilder::new()
13///     .line_width(80)
14///     .build();
15/// ```
16#[derive(Default)]
17pub struct ConfigurationBuilder {
18  pub(super) config: ConfigKeyMap,
19  global_config: Option<GlobalConfiguration>,
20}
21
22impl ConfigurationBuilder {
23  /// Constructs a new configuration builder.
24  pub fn new() -> ConfigurationBuilder {
25    Default::default()
26  }
27
28  /// Gets the final configuration that can be used to format a file.
29  pub fn build(&self) -> Configuration {
30    if let Some(global_config) = &self.global_config {
31      resolve_config(self.config.clone(), global_config).config
32    } else {
33      let config = self.config.clone();
34      resolve_config(config, &GlobalConfiguration::default()).config
35    }
36  }
37
38  /// Set the global configuration.
39  pub fn global_config(&mut self, global_config: GlobalConfiguration) -> &mut Self {
40    self.global_config = Some(global_config);
41    self
42  }
43
44  /// The width of a line the printer will try to stay under. Note that the printer may exceed this width in certain cases.
45  /// Default: 120
46  pub fn line_width(&mut self, value: u32) -> &mut Self {
47    self.insert("lineWidth", (value as i32).into())
48  }
49
50  /// Whether to use tabs (true) or spaces (false).
51  ///
52  /// Default: `false`
53  pub fn use_tabs(&mut self, value: bool) -> &mut Self {
54    self.insert("useTabs", value.into())
55  }
56
57  /// The number of columns for an indent.
58  ///
59  /// Default: `2`
60  pub fn indent_width(&mut self, value: u8) -> &mut Self {
61    self.insert("indentWidth", (value as i32).into())
62  }
63
64  /// The kind of newline to use.
65  /// Default: `NewLineKind::LineFeed`
66  pub fn new_line_kind(&mut self, value: NewLineKind) -> &mut Self {
67    self.insert("newLineKind", value.to_string().into())
68  }
69
70  /// The kind of newline to use.
71  /// Default: true
72  pub fn comment_line_force_space_after_slashes(&mut self, value: bool) -> &mut Self {
73    self.insert("commentLine.forceSpaceAfterSlashes", value.into())
74  }
75
76  /// The text to use for an ignore comment (ex. `// dprint-ignore`).
77  ///
78  /// Default: `"dprint-ignore"`
79  pub fn ignore_node_comment_text(&mut self, value: &str) -> &mut Self {
80    self.insert("ignoreNodeCommentText", value.into())
81  }
82
83  /// Whether to make objects and arrays collapse to a single line when below the line width.
84  /// Default: false
85  pub fn prefer_single_line(&mut self, value: bool) -> &mut Self {
86    self.insert("preferSingleLine", value.into())
87  }
88
89  /// Whether to make arrays collapse to a single line when below the line width.
90  /// Default: false
91  pub fn array_prefer_single_line(&mut self, value: bool) -> &mut Self {
92    self.insert("array.preferSingleLine", value.into())
93  }
94
95  /// Whether to make ojects collapse to a single line when below the line width.
96  /// Default: false
97  pub fn object_prefer_single_line(&mut self, value: bool) -> &mut Self {
98    self.insert("object.preferSingleLine", value.into())
99  }
100
101  /// Whether to use trailing commas.
102  ///
103  /// Default: `TrailingCommaKind::Jsonc`
104  pub fn trailing_commas(&mut self, value: TrailingCommaKind) -> &mut Self {
105    self.insert("trailingCommas", value.to_string().into())
106  }
107
108  /// When `trailingCommas` is `jsonc`, treat these files as JSONC and use trailing commas.
109  ///
110  /// Ex. `vec!["tsconfig.json".to_string(), ".vscode/settings.json".to_string()]`
111  pub fn json_trailing_comma_files(&mut self, value: Vec<String>) -> &mut Self {
112    self.insert(
113      "jsonTrailingCommaFiles",
114      ConfigKeyValue::Array(value.into_iter().map(|v| v.into()).collect()),
115    )
116  }
117
118  /// Sets the configuration to what is used in Deno.
119  pub fn deno(&mut self) -> &mut Self {
120    self
121      .line_width(80)
122      .ignore_node_comment_text("deno-fmt-ignore")
123      .comment_line_force_space_after_slashes(false)
124      .trailing_commas(TrailingCommaKind::Never)
125  }
126
127  #[cfg(test)]
128  pub(super) fn get_inner_config(&self) -> ConfigKeyMap {
129    self.config.clone()
130  }
131
132  fn insert(&mut self, name: &str, value: ConfigKeyValue) -> &mut Self {
133    self.config.insert(String::from(name), value);
134    self
135  }
136}
137
138#[cfg(test)]
139mod tests {
140  use dprint_core::configuration::resolve_global_config;
141  use dprint_core::configuration::NewLineKind;
142
143  use super::*;
144
145  #[test]
146  fn check_all_values_set() {
147    let mut config = ConfigurationBuilder::new();
148    config
149      .new_line_kind(NewLineKind::CarriageReturnLineFeed)
150      .line_width(90)
151      .use_tabs(true)
152      .indent_width(4)
153      .new_line_kind(NewLineKind::CarriageReturnLineFeed)
154      .comment_line_force_space_after_slashes(false)
155      .prefer_single_line(true)
156      .array_prefer_single_line(true)
157      .object_prefer_single_line(false)
158      .trailing_commas(TrailingCommaKind::Always)
159      .json_trailing_comma_files(vec!["tsconfig.json".to_string(), ".vscode/settings.json".to_string()])
160      .ignore_node_comment_text("deno-fmt-ignore");
161
162    let inner_config = config.get_inner_config();
163    assert_eq!(inner_config.len(), 11);
164    let diagnostics = resolve_config(inner_config, &GlobalConfiguration::default()).diagnostics;
165    assert_eq!(diagnostics.len(), 0);
166  }
167
168  #[test]
169  fn handle_global_config() {
170    let mut global_config = ConfigKeyMap::new();
171    global_config.insert(String::from("lineWidth"), 90.into());
172    global_config.insert(String::from("newLineKind"), "crlf".into());
173    global_config.insert(String::from("useTabs"), true.into());
174    let global_config = resolve_global_config(&mut global_config).config;
175    let mut config_builder = ConfigurationBuilder::new();
176    let config = config_builder.global_config(global_config).build();
177    assert_eq!(config.line_width, 90);
178    assert_eq!(config.new_line_kind == NewLineKind::CarriageReturnLineFeed, true);
179  }
180
181  #[test]
182  fn use_json_defaults_when_global_not_set() {
183    let global_config = GlobalConfiguration::default();
184    let mut config_builder = ConfigurationBuilder::new();
185    let config = config_builder.global_config(global_config).build();
186    assert_eq!(config.indent_width, 2); // this is different
187    assert_eq!(config.new_line_kind == NewLineKind::LineFeed, true);
188  }
189
190  #[test]
191  fn support_deno_config() {
192    let mut config_builder = ConfigurationBuilder::new();
193    let config = config_builder.deno().build();
194    assert_eq!(config.indent_width, 2);
195    assert_eq!(config.line_width, 80);
196    assert_eq!(config.new_line_kind == NewLineKind::LineFeed, true);
197    assert_eq!(config.use_tabs, false);
198    assert_eq!(config.comment_line_force_space_after_slashes, false);
199    assert_eq!(config.ignore_node_comment_text, "deno-fmt-ignore");
200    assert_eq!(config.array_prefer_single_line, false);
201    assert_eq!(config.object_prefer_single_line, false);
202  }
203
204  #[test]
205  fn support_prefer_single_line_config() {
206    let mut config_builder = ConfigurationBuilder::new();
207    let config = config_builder.prefer_single_line(true).build();
208    assert_eq!(config.array_prefer_single_line, true);
209    assert_eq!(config.object_prefer_single_line, true);
210  }
211}