dprint_core/plugins/
plugin_handler.rs

1use anyhow::Result;
2use serde::Deserialize;
3use serde::Serialize;
4
5#[cfg(feature = "async_runtime")]
6use crate::async_runtime::FutureExt;
7#[cfg(feature = "async_runtime")]
8use crate::async_runtime::LocalBoxFuture;
9
10use crate::configuration::ConfigKeyMap;
11use crate::configuration::ConfigKeyValue;
12use crate::configuration::ConfigurationDiagnostic;
13use crate::configuration::GlobalConfiguration;
14use crate::plugins::PluginInfo;
15
16use super::FileMatchingInfo;
17
18pub trait CancellationToken: Send + Sync + std::fmt::Debug {
19  fn is_cancelled(&self) -> bool;
20  #[cfg(feature = "async_runtime")]
21  fn wait_cancellation(&self) -> LocalBoxFuture<'static, ()>;
22}
23
24#[cfg(feature = "async_runtime")]
25impl CancellationToken for tokio_util::sync::CancellationToken {
26  fn is_cancelled(&self) -> bool {
27    self.is_cancelled()
28  }
29
30  fn wait_cancellation(&self) -> LocalBoxFuture<'static, ()> {
31    let token = self.clone();
32    async move { token.cancelled().await }.boxed_local()
33  }
34}
35
36/// A cancellation token that always says it's not cancelled.
37#[derive(Debug)]
38pub struct NullCancellationToken;
39
40impl CancellationToken for NullCancellationToken {
41  fn is_cancelled(&self) -> bool {
42    false
43  }
44
45  #[cfg(feature = "async_runtime")]
46  fn wait_cancellation(&self) -> LocalBoxFuture<'static, ()> {
47    // never resolves
48    Box::pin(std::future::pending())
49  }
50}
51
52pub type FormatRange = Option<std::ops::Range<usize>>;
53
54/// A formatting error where the plugin cannot recover.
55///
56/// Return one of these to signal to the dprint CLI that
57/// it should recreate the plugin.
58#[derive(Debug)]
59pub struct CriticalFormatError(pub anyhow::Error);
60
61impl std::fmt::Display for CriticalFormatError {
62  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63    self.0.fmt(f)
64  }
65}
66
67impl std::error::Error for CriticalFormatError {
68  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
69    self.0.source()
70  }
71}
72
73#[derive(Debug, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct CheckConfigUpdatesMessage {
76  /// dprint versions < 0.47 won't have this set
77  #[serde(default)]
78  pub old_version: Option<String>,
79  pub config: ConfigKeyMap,
80}
81
82#[cfg(feature = "process")]
83#[derive(Debug)]
84pub struct HostFormatRequest {
85  pub file_path: std::path::PathBuf,
86  pub file_bytes: Vec<u8>,
87  /// Range to format.
88  pub range: FormatRange,
89  pub override_config: ConfigKeyMap,
90  pub token: std::sync::Arc<dyn CancellationToken>,
91}
92
93#[cfg(feature = "wasm")]
94#[derive(Debug)]
95pub struct SyncHostFormatRequest<'a> {
96  pub file_path: &'a std::path::Path,
97  pub file_bytes: &'a [u8],
98  /// Range to format.
99  pub range: FormatRange,
100  pub override_config: &'a ConfigKeyMap,
101}
102
103/// `Ok(Some(text))` - Changes due to the format.
104/// `Ok(None)` - No changes.
105/// `Err(err)` - Error formatting. Use a `CriticalError` to signal that the plugin can't recover.
106pub type FormatResult = Result<Option<Vec<u8>>>;
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct RawFormatConfig {
110  pub plugin: ConfigKeyMap,
111  pub global: GlobalConfiguration,
112}
113
114/// A unique configuration id used for formatting.
115#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
116pub struct FormatConfigId(u32);
117
118impl std::fmt::Display for FormatConfigId {
119  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120    write!(f, "${}", self.0)
121  }
122}
123
124impl FormatConfigId {
125  pub fn from_raw(raw: u32) -> FormatConfigId {
126    FormatConfigId(raw)
127  }
128
129  pub fn uninitialized() -> FormatConfigId {
130    FormatConfigId(0)
131  }
132
133  pub fn as_raw(&self) -> u32 {
134    self.0
135  }
136}
137
138#[cfg(feature = "process")]
139pub struct FormatRequest<TConfiguration> {
140  pub file_path: std::path::PathBuf,
141  pub file_bytes: Vec<u8>,
142  pub config_id: FormatConfigId,
143  pub config: std::sync::Arc<TConfiguration>,
144  /// Range to format.
145  pub range: FormatRange,
146  pub token: std::sync::Arc<dyn CancellationToken>,
147}
148
149#[cfg(feature = "wasm")]
150pub struct SyncFormatRequest<'a, TConfiguration> {
151  pub file_path: &'a std::path::Path,
152  pub file_bytes: Vec<u8>,
153  pub config_id: FormatConfigId,
154  pub config: &'a TConfiguration,
155  /// Range to format.
156  pub range: FormatRange,
157  pub token: &'a dyn CancellationToken,
158}
159
160#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
161#[serde(untagged)]
162pub enum ConfigChangePathItem {
163  /// String property name.
164  String(String),
165  /// Number if an index in an array.
166  Number(usize),
167}
168
169impl From<String> for ConfigChangePathItem {
170  fn from(value: String) -> Self {
171    Self::String(value)
172  }
173}
174
175impl From<usize> for ConfigChangePathItem {
176  fn from(value: usize) -> Self {
177    Self::Number(value)
178  }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct ConfigChange {
184  /// The path to make modifications at.
185  pub path: Vec<ConfigChangePathItem>,
186  #[serde(flatten)]
187  pub kind: ConfigChangeKind,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(tag = "kind", content = "value")]
192pub enum ConfigChangeKind {
193  /// Adds an object property or array element.
194  Add(ConfigKeyValue),
195  /// Overwrites an existing value at the provided path.
196  Set(ConfigKeyValue),
197  /// Removes the value at the path.
198  Remove,
199}
200
201#[derive(Clone, Serialize)]
202#[serde(rename_all = "camelCase")]
203pub struct PluginResolveConfigurationResult<T>
204where
205  T: Clone + Serialize,
206{
207  /// Information about what files are matched for the provided configuration.
208  pub file_matching: FileMatchingInfo,
209
210  /// The configuration diagnostics.
211  pub diagnostics: Vec<ConfigurationDiagnostic>,
212
213  /// The configuration derived from the unresolved configuration
214  /// that can be used to format a file.
215  pub config: T,
216}
217
218/// Trait for implementing a process plugin.
219#[cfg(feature = "process")]
220#[crate::async_runtime::async_trait(?Send)]
221pub trait AsyncPluginHandler: 'static {
222  type Configuration: Serialize + Clone + Send + Sync;
223
224  /// Gets the plugin's plugin info.
225  fn plugin_info(&self) -> PluginInfo;
226  /// Gets the plugin's license text.
227  fn license_text(&self) -> String;
228  /// Resolves configuration based on the provided config map and global configuration.
229  async fn resolve_config(&self, config: ConfigKeyMap, global_config: GlobalConfiguration) -> PluginResolveConfigurationResult<Self::Configuration>;
230  /// Updates the config key map. This will be called after the CLI has upgraded the
231  /// plugin in `dprint config update`.
232  async fn check_config_updates(&self, _message: CheckConfigUpdatesMessage) -> Result<Vec<ConfigChange>> {
233    Ok(Vec::new())
234  }
235  /// Formats the provided file text based on the provided file path and configuration.
236  async fn format(
237    &self,
238    request: FormatRequest<Self::Configuration>,
239    format_with_host: impl FnMut(HostFormatRequest) -> LocalBoxFuture<'static, FormatResult> + 'static,
240  ) -> FormatResult;
241}
242
243/// Trait for implementing a Wasm plugin.
244#[cfg(feature = "wasm")]
245pub trait SyncPluginHandler<TConfiguration: Clone + serde::Serialize> {
246  /// Resolves configuration based on the provided config map and global configuration.
247  fn resolve_config(&mut self, config: ConfigKeyMap, global_config: &GlobalConfiguration) -> PluginResolveConfigurationResult<TConfiguration>;
248  /// Gets the plugin's plugin info.
249  fn plugin_info(&mut self) -> PluginInfo;
250  /// Gets the plugin's license text.
251  fn license_text(&mut self) -> String;
252  /// Updates the config key map. This will be called after the CLI has upgraded the
253  /// plugin in `dprint config update`.
254  fn check_config_updates(&self, message: CheckConfigUpdatesMessage) -> Result<Vec<ConfigChange>>;
255  /// Formats the provided file text based on the provided file path and configuration.
256  fn format(&mut self, request: SyncFormatRequest<TConfiguration>, format_with_host: impl FnMut(SyncHostFormatRequest) -> FormatResult) -> FormatResult;
257}