use std::borrow::Cow;
use std::path::PathBuf;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use colored::Colorize;
use dprint_core::types::ErrBox;
use crate::cache::{Cache, CreateCacheItemOptions};
use crate::environment::Environment;
use crate::configuration::{self, get_global_config, get_plugin_config_map};
use crate::plugins::{InitializedPlugin, Plugin, PluginResolver, InitializedPluginPool, PluginPools};
use crate::utils::{get_table_text, get_difference, pretty_print_json_text};
use super::{CliArgs, SubCommand, EditorServiceInfo};
use super::configuration::{resolve_config_from_args, ResolvedConfig};
use super::incremental::IncrementalFile;
const BOM_CHAR: char = '\u{FEFF}';
pub async fn run_cli<TEnvironment : Environment>(
args: CliArgs,
environment: &TEnvironment,
cache: &Cache<TEnvironment>,
plugin_resolver: &PluginResolver<TEnvironment>,
plugin_pools: Arc<PluginPools<TEnvironment>>,
) -> Result<(), ErrBox> {
if let SubCommand::Help(help_text) = &args.sub_command {
return output_help(&args, cache, environment, plugin_resolver, help_text).await;
}
if args.sub_command == SubCommand::Version {
return output_version(environment).await;
}
if args.sub_command == SubCommand::License {
return output_license(&args, cache, environment, plugin_resolver).await;
}
if args.sub_command == SubCommand::EditorInfo {
return output_editor_info(&args, cache, environment, plugin_resolver).await;
}
if let SubCommand::EditorService(info) = &args.sub_command {
return run_editor_service(&args, cache, environment, plugin_resolver, plugin_pools, info).await;
}
if args.sub_command == SubCommand::ClearCache {
let cache_dir = environment.get_cache_dir()?; environment.remove_dir_all(&cache_dir)?;
environment.log(&format!("Deleted {}", cache_dir.display()));
return Ok(());
}
if args.sub_command == SubCommand::Init {
return init_config_file(environment, &args.config).await;
}
let config = resolve_config_from_args(&args, cache, environment).await?;
let project_type_result = check_project_type_diagnostic(&config);
let plugins = resolve_plugins(&config, environment, plugin_resolver).await?;
if plugins.is_empty() {
return err!("No formatting plugins found. Ensure at least one is specified in the 'plugins' array of the configuration file.");
}
if let SubCommand::StdInFmt(stdin_fmt) = &args.sub_command {
plugin_pools.set_plugins(plugins);
return output_stdin_format(&PathBuf::from(&stdin_fmt.file_name), &stdin_fmt.file_text, environment, plugin_pools).await;
}
if args.sub_command == SubCommand::OutputResolvedConfig {
return output_resolved_config(plugins, environment);
}
let file_paths = resolve_file_paths(&config, &args, environment)?;
let file_paths_by_plugin = get_file_paths_by_plugin(&plugins, file_paths);
plugin_pools.set_plugins(plugins);
if args.sub_command == SubCommand::OutputFilePaths {
output_file_paths(file_paths_by_plugin.values().flat_map(|x| x.iter()), environment);
return Ok(());
}
if file_paths_by_plugin.is_empty() {
return err!("No files found to format with the specified plugins. You may want to try using `dprint output-file-paths` to see which files it's finding.");
}
project_type_result?;
if args.sub_command == SubCommand::OutputFormatTimes {
return output_format_times(file_paths_by_plugin, environment, plugin_pools).await;
}
let incremental_file = get_incremental_file(&args, &config, &cache, &plugin_pools, &environment);
if args.sub_command == SubCommand::Check {
check_files(file_paths_by_plugin, environment, plugin_pools, incremental_file).await
} else if args.sub_command == SubCommand::Fmt {
format_files(file_paths_by_plugin, environment, plugin_pools, incremental_file).await
} else {
unreachable!()
}
}
fn get_file_paths_by_plugin(plugins: &Vec<Box<dyn Plugin>>, file_paths: Vec<PathBuf>) -> HashMap<String, Vec<PathBuf>> {
let mut file_paths_by_plugin: HashMap<String, Vec<PathBuf>> = HashMap::new();
for file_path in file_paths.into_iter() {
if let Some(file_extension) = crate::utils::get_lowercase_file_extension(&file_path) {
if let Some(plugin) = plugins.iter().filter(|p| p.file_extensions().contains(&file_extension)).next() {
if let Some(file_paths) = file_paths_by_plugin.get_mut(plugin.name()) {
file_paths.push(file_path);
} else {
file_paths_by_plugin.insert(String::from(plugin.name()), vec![file_path]);
}
}
}
}
file_paths_by_plugin
}
async fn output_version<'a, TEnvironment: Environment>(environment: &TEnvironment) -> Result<(), ErrBox> {
environment.log(&format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")));
Ok(())
}
async fn output_help<TEnvironment: Environment>(
args: &CliArgs,
cache: &Cache<TEnvironment>,
environment: &TEnvironment,
plugin_resolver: &PluginResolver<TEnvironment>,
help_text: &str,
) -> Result<(), ErrBox> {
environment.log(help_text);
let plugins_result = get_plugins_from_args(args, cache, environment, plugin_resolver).await;
match plugins_result {
Ok(plugins) => {
if !plugins.is_empty() {
let plugin_texts = get_table_text(plugins.iter().map(|plugin| (plugin.name(), plugin.help_url())).collect(), 4);
environment.log("\nPLUGINS HELP:");
for plugin_text in plugin_texts {
environment.log(&format!(" {}", plugin_text));
}
}
}
Err(err) => {
log_verbose!(environment, "Error getting plugins for help. {}", err.to_string());
}
}
Ok(())
}
async fn output_license<TEnvironment: Environment>(
args: &CliArgs,
cache: &Cache<TEnvironment>,
environment: &TEnvironment,
plugin_resolver: &PluginResolver<TEnvironment>,
) -> Result<(), ErrBox> {
environment.log("==== DPRINT CLI LICENSE ====");
environment.log(std::str::from_utf8(include_bytes!("../../LICENSE"))?);
for plugin in get_plugins_from_args(args, cache, environment, plugin_resolver).await? {
environment.log(&format!("\n==== {} LICENSE ====", plugin.name().to_uppercase()));
let initialized_plugin = plugin.initialize()?;
environment.log(&initialized_plugin.get_license_text()?);
}
Ok(())
}
async fn output_editor_info<TEnvironment: Environment>(
args: &CliArgs,
cache: &Cache<TEnvironment>,
environment: &TEnvironment,
plugin_resolver: &PluginResolver<TEnvironment>,
) -> Result<(), ErrBox> {
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct EditorInfo {
pub schema_version: u32,
pub plugins: Vec<EditorPluginInfo>,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct EditorPluginInfo {
name: String,
file_extensions: Vec<String>,
}
let mut plugins = Vec::new();
for plugin in get_plugins_from_args(args, cache, environment, plugin_resolver).await? {
plugins.push(EditorPluginInfo {
name: plugin.name().to_string(),
file_extensions: plugin.file_extensions().iter().map(|ext| ext.to_string()).collect(),
});
}
environment.log_silent(&serde_json::to_string(&EditorInfo {
schema_version: 2,
plugins,
})?);
Ok(())
}
async fn run_editor_service<TEnvironment: Environment>(
args: &CliArgs,
cache: &Cache<TEnvironment>,
environment: &TEnvironment,
plugin_resolver: &PluginResolver<TEnvironment>,
plugin_pools: Arc<PluginPools<TEnvironment>>,
editor_service_info: &EditorServiceInfo,
) -> Result<(), ErrBox> {
use dprint_core::plugins::process::{StdInOutReaderWriter, start_parent_process_checker_thread};
let _handle = start_parent_process_checker_thread("editor-service".to_string(), editor_service_info.parent_pid);
let mut stdin = environment.stdin();
let mut stdout = environment.stdout();
let mut reader_writer = StdInOutReaderWriter::new(&mut stdin, &mut stdout);
let mut past_config: Option<ResolvedConfig> = None;
loop {
let message_kind = reader_writer.read_u32()?;
match message_kind {
0 => return Ok(()),
1 => {
let file_path = reader_writer.read_path_buf()?;
let config = resolve_config_from_args(&args, cache, environment).await?;
let file_paths = resolve_file_paths(&config, &args, environment)?;
match environment.canonicalize(&file_path) {
Ok(resolved_file_path) => {
reader_writer.send_u32(if file_paths.contains(&resolved_file_path) { 1 } else { 0 })?;
}
Err(err) => {
environment.log_error(&format!("Error canonicalizing file {}: {}", file_path.display(), err.to_string()));
reader_writer.send_u32(0)?; },
}
},
2 => {
let file_path = PathBuf::from(reader_writer.read_string()?);
let file_text = reader_writer.read_string()?;
let result = format_text(args, cache, environment, plugin_resolver, &plugin_pools, &past_config, &file_path, &file_text).await;
match result {
Ok((formatted_text, config)) => {
if formatted_text == file_text {
reader_writer.send_u32(0)?; } else {
reader_writer.send_u32(1)?; reader_writer.send_string(&formatted_text)?;
}
past_config.replace(config);
},
Err(err) => {
reader_writer.send_u32(2)?; reader_writer.send_string(&err.to_string())?;
}
}
},
_ => {
environment.log_error(&format!("Unknown message kind: {}", message_kind));
}
}
}
async fn format_text<'a, TEnvironment: Environment>(
args: &CliArgs,
cache: &Cache<TEnvironment>,
environment: &TEnvironment,
plugin_resolver: &PluginResolver<TEnvironment>,
plugin_pools: &Arc<PluginPools<TEnvironment>>,
past_config: &Option<ResolvedConfig>,
file_path: &PathBuf,
file_text: &'a str,
) -> Result<(Cow<'a, str>, ResolvedConfig), ErrBox> {
let config = resolve_config_from_args(&args, cache, environment).await?;
let has_config_changed = past_config.is_none() || *past_config.as_ref().unwrap() != config;
if has_config_changed {
plugin_pools.drop_plugins(); let plugins = resolve_plugins(&config, environment, plugin_resolver).await?;
plugin_pools.set_plugins(plugins);
}
let formatted_text = format_with_plugin_pools(&file_path, &file_text, &plugin_pools).await?;
Ok((formatted_text, config))
}
}
async fn get_plugins_from_args<TEnvironment : Environment>(
args: &CliArgs,
cache: &Cache<TEnvironment>,
environment: &TEnvironment,
plugin_resolver: &PluginResolver<TEnvironment>,
) -> Result<Vec<Box<dyn Plugin>>, ErrBox> {
match resolve_config_from_args(args, cache, environment).await {
Ok(config) => {
let plugins = resolve_plugins(&config, environment, plugin_resolver).await?;
Ok(plugins)
},
Err(_) => {
Ok(Vec::new())
}
}
}
fn output_file_paths<'a>(file_paths: impl Iterator<Item=&'a PathBuf>, environment: &impl Environment) {
for file_path in file_paths {
environment.log(&file_path.display().to_string())
}
}
fn output_resolved_config(
plugins: Vec<Box<dyn Plugin>>,
environment: &impl Environment,
) -> Result<(), ErrBox> {
for plugin in plugins {
let config_key = String::from(plugin.config_key());
let initialized_plugin = plugin.initialize()?;
output_plugin_config_diagnostics(plugin.name(), &initialized_plugin, environment)?;
let text = initialized_plugin.get_resolved_config()?;
let pretty_text = pretty_print_json_text(&text)?;
environment.log(&format!("{}: {}", config_key, pretty_text));
}
Ok(())
}
async fn init_config_file(environment: &impl Environment, config_arg: &Option<String>) -> Result<(), ErrBox> {
let config_file_path = get_config_path(environment, config_arg)?;
return if !environment.path_exists(&config_file_path) {
environment.write_file(&config_file_path, &configuration::get_init_config_file_text(environment).await?)?;
environment.log(&format!("Created {}", config_file_path.display()));
Ok(())
} else {
err!("Configuration file '{}' already exists.", config_file_path.display())
};
fn get_config_path(environment: &impl Environment, config_arg: &Option<String>) -> Result<PathBuf, ErrBox> {
return Ok(if let Some(config_arg) = config_arg.as_ref() {
PathBuf::from(config_arg)
} else if use_config_dir(environment)? {
PathBuf::from("./config/.dprintrc.json")
} else {
PathBuf::from("./.dprintrc.json")
});
fn use_config_dir(environment: &impl Environment) -> Result<bool, ErrBox> {
if environment.path_exists(&PathBuf::from("./config")) {
let prompt_message = "Would you like to create the .dprintrc.json in the ./config directory?";
let options = get_table_text(vec![
("Yes", "Create it in the ./config directory."),
("No", "Create it in the current working directory.")
], 2);
Ok(environment.get_selection(prompt_message, &options)? == 0)
} else {
Ok(false)
}
}
}
}
async fn output_stdin_format<TEnvironment: Environment>(
file_name: &PathBuf,
file_text: &str,
environment: &TEnvironment,
plugin_pools: Arc<PluginPools<TEnvironment>>,
) -> Result<(), ErrBox> {
let formatted_text = format_with_plugin_pools(file_name, file_text, &plugin_pools).await?;
environment.log_silent(&formatted_text);
Ok(())
}
async fn format_with_plugin_pools<'a, TEnvironment: Environment>(
file_name: &PathBuf,
file_text: &'a str,
plugin_pools: &Arc<PluginPools<TEnvironment>>,
) -> Result<Cow<'a, str>, ErrBox> {
let ext = match file_name.extension() {
Some(ext) => ext.to_string_lossy().to_string(),
None => return err!("Could not find extension for {}", file_name.display()),
};
Ok(if let Some(plugin_name) = plugin_pools.get_plugin_name_from_extension(&ext) {
let plugin_pool = plugin_pools.get_pool(&plugin_name).unwrap();
let initialized_plugin = plugin_pool.take_or_create_no_sempahore()?;
let result = initialized_plugin.format_text(file_name, file_text, &HashMap::new());
plugin_pool.release_no_semaphore(initialized_plugin);
Cow::Owned(result?) } else {
Cow::Borrowed(file_text)
})
}
async fn check_files<TEnvironment : Environment>(
file_paths_by_plugin: HashMap<String, Vec<PathBuf>>,
environment: &TEnvironment,
plugin_pools: Arc<PluginPools<TEnvironment>>,
incremental_file: Option<Arc<IncrementalFile<TEnvironment>>>,
) -> Result<(), ErrBox> {
let not_formatted_files_count = Arc::new(AtomicUsize::new(0));
run_parallelized(file_paths_by_plugin, environment, plugin_pools, incremental_file, {
let not_formatted_files_count = not_formatted_files_count.clone();
move |file_path, file_text, formatted_text, _, _, environment| {
if formatted_text != file_text {
not_formatted_files_count.fetch_add(1, Ordering::SeqCst);
match get_difference(&file_text, &formatted_text) {
Ok(difference_text) => {
environment.log(&format!(
"{} {}:\n{}\n--",
"from".bold().red().to_string(),
file_path.display(),
difference_text,
));
}
Err(err) => {
environment.log(&format!(
"{} {}:\nError getting difference, but this file needs formatting.\n\nError message: {}\n--",
"from".bold().red().to_string(),
file_path.display(),
err.to_string().red().to_string(),
));
}
}
}
Ok(None)
}
}).await?;
let not_formatted_files_count = not_formatted_files_count.load(Ordering::SeqCst);
if not_formatted_files_count == 0 {
Ok(())
} else {
let f = if not_formatted_files_count == 1 { "file" } else { "files" };
err!("Found {} not formatted {}.", not_formatted_files_count.to_string().bold().to_string(), f)
}
}
async fn format_files<TEnvironment : Environment>(
file_paths_by_plugin: HashMap<String, Vec<PathBuf>>,
environment: &TEnvironment,
plugin_pools: Arc<PluginPools<TEnvironment>>,
incremental_file: Option<Arc<IncrementalFile<TEnvironment>>>,
) -> Result<(), ErrBox> {
let formatted_files_count = Arc::new(AtomicUsize::new(0));
let files_count: usize = file_paths_by_plugin.values().map(|x| x.len()).sum();
run_parallelized(file_paths_by_plugin, environment, plugin_pools, incremental_file.clone(), {
let formatted_files_count = formatted_files_count.clone();
move |_, file_text, formatted_text, had_bom, _, _| {
if formatted_text != file_text {
let new_text = if had_bom {
format!("{}{}", BOM_CHAR, formatted_text)
} else {
formatted_text
};
formatted_files_count.fetch_add(1, Ordering::SeqCst);
Ok(Some(new_text))
} else {
Ok(None)
}
}
}).await?;
let formatted_files_count = formatted_files_count.load(Ordering::SeqCst);
if formatted_files_count > 0 {
let suffix = if files_count == 1 { "file" } else { "files" };
environment.log(&format!("Formatted {} {}.", formatted_files_count.to_string().bold().to_string(), suffix));
}
if let Some(incremental_file) = &incremental_file {
incremental_file.write();
}
Ok(())
}
async fn output_format_times<TEnvironment : Environment>(
file_paths_by_plugin: HashMap<String, Vec<PathBuf>>,
environment: &TEnvironment,
plugin_pools: Arc<PluginPools<TEnvironment>>,
) -> Result<(), ErrBox> {
let durations: Arc<Mutex<Vec<(PathBuf, u128)>>> = Arc::new(Mutex::new(Vec::new()));
run_parallelized(file_paths_by_plugin, environment, plugin_pools, None, {
let durations = durations.clone();
move |file_path, _, _, _, start_instant, _| {
let duration = start_instant.elapsed().as_millis();
let mut durations = durations.lock().unwrap();
durations.push((file_path.to_owned(), duration));
Ok(None)
}
}).await?;
let mut durations = durations.lock().unwrap();
durations.sort_by_key(|k| k.1);
for (file_path, duration) in durations.iter() {
environment.log(&format!("{}ms - {}", duration, file_path.display()));
}
Ok(())
}
async fn run_parallelized<F, TEnvironment : Environment>(
file_paths_by_plugin: HashMap<String, Vec<PathBuf>>,
environment: &TEnvironment,
plugin_pools: Arc<PluginPools<TEnvironment>>,
incremental_file: Option<Arc<IncrementalFile<TEnvironment>>>,
f: F,
) -> Result<(), ErrBox> where F: Fn(&PathBuf, &str, String, bool, Instant, &TEnvironment) -> Result<Option<String>, ErrBox> + Send + 'static + Clone {
let error_count = Arc::new(AtomicUsize::new(0));
let handles = file_paths_by_plugin.into_iter().map(|(plugin_name, file_paths)| {
let plugin_pools = plugin_pools.clone();
let environment = environment.to_owned();
let f = f.clone();
let error_count = error_count.clone();
let incremental_file = incremental_file.clone();
tokio::task::spawn(async move {
let result = inner_run(&plugin_name, file_paths, plugin_pools, incremental_file, &environment, f, error_count.clone()).await;
if let Err(err) = result {
environment.log_error(&format!("[{}]: {}", plugin_name, err.to_string()));
error_count.fetch_add(1, Ordering::SeqCst);
}
})
});
let result = futures::future::try_join_all(handles).await;
if let Err(err) = result {
return err!(
"A panic occurred in a dprint plugin. You may want to run in verbose mode (--verbose) to help figure out where it failed then report this as a bug.\n Error: {}",
err.to_string()
);
}
let error_count = error_count.load(Ordering::SeqCst);
return if error_count == 0 {
Ok(())
} else {
err!("Had {0} error(s) formatting.", error_count)
};
#[inline]
async fn inner_run<F, TEnvironment : Environment>(
plugin_name: &str,
file_paths: Vec<PathBuf>,
plugin_pools: Arc<PluginPools<TEnvironment>>,
incremental_file: Option<Arc<IncrementalFile<TEnvironment>>>,
environment: &TEnvironment,
f: F,
error_count: Arc<AtomicUsize>,
) -> Result<(), ErrBox> where F: Fn(&PathBuf, &str, String, bool, Instant, &TEnvironment) -> Result<Option<String>, ErrBox> + Send + 'static + Clone {
let plugin_pool = plugin_pools.get_pool(plugin_name).expect("Could not get the plugin pool.");
let plugin = plugin_pool.initialize_first().await?;
output_plugin_config_diagnostics(&plugin_name, &plugin, environment)?;
plugin_pool.release(plugin);
let max_concurrent_files_semaphore = Arc::new(tokio::sync::Semaphore::new(8));
let handles = file_paths.into_iter().map(|file_path| {
let environment = environment.to_owned();
let f = f.clone();
let plugin_pool = plugin_pool.clone();
let error_count = error_count.clone();
let max_concurrent_files_semaphore = max_concurrent_files_semaphore.clone();
let incremental_file = incremental_file.clone();
tokio::task::spawn(async move {
let permit = max_concurrent_files_semaphore.acquire().await;
match run_for_file_path(&file_path, &environment, incremental_file, plugin_pool, f).await {
Err(err) => {
environment.log_error(&format!("Error formatting {}. Message: {}", file_path.display(), err.to_string()));
error_count.fetch_add(1, Ordering::SeqCst);
},
_ => {}
}
std::mem::drop(permit);
})
}).collect::<Vec<_>>();
futures::future::try_join_all(handles).await?;
plugin_pools.release(plugin_name);
Ok(())
}
#[inline]
async fn run_for_file_path<F, TEnvironment : Environment>(
file_path: &PathBuf,
environment: &TEnvironment,
incremental_file: Option<Arc<IncrementalFile<TEnvironment>>>,
plugin_pool: Arc<InitializedPluginPool<TEnvironment>>,
f: F
) -> Result<(), ErrBox> where F: Fn(&PathBuf, &str, String, bool, Instant, &TEnvironment) -> Result<Option<String>, ErrBox> + Send + 'static + Clone {
let file_text = environment.read_file_async(&file_path).await?;
let had_bom = file_text.starts_with(BOM_CHAR);
let file_text = if had_bom {
&file_text[BOM_CHAR.len_utf8()..]
} else {
&file_text
};
if let Some(incremental_file) = &incremental_file {
if incremental_file.is_file_same(file_path, &file_text) {
log_verbose!(environment, "No change: {}", file_path.display());
return Ok(());
}
}
let (start_instant, formatted_text) = {
let initialized_plugin = loop {
let result = plugin_pool.try_take().await?;
if let Some(result) = result {
break result;
} else {
let plugin_pool = plugin_pool.clone();
tokio::task::spawn_blocking(move || {
plugin_pool.create_pool_item().expect("Expected to create the plugin.");
});
}
};
let start_instant = Instant::now();
let format_text_result = initialized_plugin.format_text(file_path, file_text, &HashMap::new());
log_verbose!(environment, "Formatted file: {} in {}ms", file_path.display(), start_instant.elapsed().as_millis());
plugin_pool.release(initialized_plugin); (start_instant, format_text_result?)
};
if let Some(incremental_file) = &incremental_file {
incremental_file.update_file(file_path, &formatted_text);
}
let result = f(&file_path, file_text, formatted_text, had_bom, start_instant, &environment)?;
if let Some(result) = result {
environment.write_file_async(&file_path, &result).await?;
}
Ok(())
}
}
async fn resolve_plugins<TEnvironment: Environment>(
config: &ResolvedConfig,
environment: &TEnvironment,
plugin_resolver: &PluginResolver<TEnvironment>,
) -> Result<Vec<Box<dyn Plugin>>, ErrBox> {
let plugins = plugin_resolver.resolve_plugins(config.plugins.clone()).await?;
let mut config_map = config.config_map.clone();
let mut plugins_with_config = Vec::new();
for plugin in plugins.into_iter() {
plugins_with_config.push((
get_plugin_config_map(&plugin, &mut config_map)?,
plugin
));
}
let global_config = get_global_config(config_map, environment)?;
let mut plugins = Vec::new();
for (plugin_config, plugin) in plugins_with_config {
let mut plugin = plugin;
plugin.set_config(plugin_config, global_config.clone());
plugins.push(plugin);
}
return Ok(plugins);
}
fn check_project_type_diagnostic(config: &ResolvedConfig) -> Result<(), ErrBox> {
if let Some(diagnostic) = configuration::handle_project_type_diagnostic(&config.project_type) {
return err!("{}", diagnostic.message);
}
Ok(())
}
fn resolve_file_paths(config: &ResolvedConfig, args: &CliArgs, environment: &impl Environment) -> Result<Vec<PathBuf>, ErrBox> {
let mut file_patterns = get_file_patterns(config, args);
let absolute_paths = take_absolute_paths(&mut file_patterns, environment);
let mut file_paths = environment.glob(&config.base_path, &file_patterns)?;
file_paths.extend(absolute_paths);
return Ok(file_paths);
fn get_file_patterns(config: &ResolvedConfig, args: &CliArgs) -> Vec<String> {
let mut file_patterns = Vec::new();
file_patterns.extend(if args.file_patterns.is_empty() {
config.includes.clone() } else {
args.file_patterns.clone()
});
file_patterns.extend(if args.exclude_file_patterns.is_empty() {
config.excludes.clone()
} else {
args.exclude_file_patterns.clone()
}.into_iter().map(|exclude| if exclude.starts_with("!") { exclude } else { format!("!{}", exclude) }));
if !args.allow_node_modules {
let node_modules_exclude = String::from("!**/node_modules");
if !file_patterns.contains(&node_modules_exclude) {
file_patterns.push(node_modules_exclude);
}
}
for file_pattern in file_patterns.iter_mut() {
if file_pattern.starts_with("./") {
*file_pattern = String::from(&file_pattern[2..]);
}
if file_pattern.starts_with("!./") {
*file_pattern = format!("!{}", &file_pattern[3..]);
}
}
file_patterns
}
fn take_absolute_paths(file_patterns: &mut Vec<String>, environment: &impl Environment) -> Vec<PathBuf> {
let len = file_patterns.len();
let mut file_paths = Vec::new();
for i in (0..len).rev() {
if is_absolute_path(&file_patterns[i], environment) {
file_paths.push(PathBuf::from(file_patterns.swap_remove(i))); }
}
file_paths
}
fn is_absolute_path(file_pattern: &str, environment: &impl Environment) -> bool {
return !has_glob_chars(file_pattern)
&& environment.is_absolute_path(&PathBuf::from(file_pattern));
fn has_glob_chars(text: &str) -> bool {
for c in text.chars() {
match c {
'*' | '{' | '}' | '[' | ']' | '!' => return true,
_ => {}
}
}
false
}
}
}
fn output_plugin_config_diagnostics(plugin_name: &str, plugin: &Box<dyn InitializedPlugin>, environment: &impl Environment) -> Result<(), ErrBox> {
let mut diagnostic_count = 0;
for diagnostic in plugin.get_config_diagnostics()? {
environment.log_error(&format!("[{}]: {}", plugin_name, diagnostic.message));
diagnostic_count += 1;
}
if diagnostic_count > 0 {
err!("Error initializing from configuration file. Had {} diagnostic(s).", diagnostic_count)
} else {
Ok(())
}
}
fn get_incremental_file<TEnvironment: Environment>(
args: &CliArgs,
config: &ResolvedConfig,
cache: &Cache<TEnvironment>,
plugin_pools: &PluginPools<TEnvironment>,
environment: &TEnvironment,
) -> Option<Arc<IncrementalFile<TEnvironment>>> {
if args.incremental || config.incremental {
let base_path = match environment.canonicalize(&config.base_path) {
Ok(base_path) => base_path,
Err(err) => {
environment.log_error(&format!("Could not canonicalize base path for incremental feature. {}", err));
return None;
}
};
let key = format!("incremental_cache:{}", base_path.to_string_lossy());
let cache_item = if let Some(cache_item) = cache.get_cache_item(&key) {
cache_item
} else {
let cache_item = cache.create_cache_item(CreateCacheItemOptions {
key,
extension: "incremental",
bytes: None,
meta_data: None,
});
match cache_item {
Ok(cache_item) => cache_item,
Err(err) => {
environment.log_error(&format!("Could not create cache item for incremental feature. {}", err));
return None;
}
}
};
let file_path = cache.resolve_cache_item_file_path(&cache_item);
Some(Arc::new(IncrementalFile::new(file_path, plugin_pools.get_plugins_hash(), environment.clone(), base_path)))
} else {
None
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use colored::Colorize;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
use std::sync::Arc;
use std::io::{Read, Write};
use crate::cache::Cache;
use crate::environment::{Environment, TestEnvironment};
use crate::configuration::*;
use crate::plugins::{PluginsDropper, PluginPools, CompilationResult, PluginResolver, PluginCache};
use dprint_core::types::ErrBox;
use dprint_core::plugins::process::StdInOutReaderWriter;
use crate::utils::get_difference;
use super::run_cli;
use super::super::{parse_args, TestStdInReader};
async fn run_test_cli(args: Vec<&str>, environment: &TestEnvironment) -> Result<(), ErrBox> {
run_test_cli_with_stdin(args, environment, TestStdInReader::new()).await
}
async fn run_test_cli_with_stdin(
args: Vec<&str>,
environment: &TestEnvironment,
stdin_reader: TestStdInReader, ) -> Result<(), ErrBox> {
let mut args: Vec<String> = args.into_iter().map(String::from).collect();
args.insert(0, String::from(""));
environment.set_wasm_compile_result(COMPILATION_RESULT.clone());
let cache = Arc::new(Cache::new(environment.clone()).unwrap());
let plugin_cache = Arc::new(PluginCache::new(environment.clone())?);
let plugin_pools = Arc::new(PluginPools::new(environment.clone()));
let _plugins_dropper = PluginsDropper::new(plugin_pools.clone());
let plugin_resolver = PluginResolver::new(environment.clone(), plugin_cache, plugin_pools.clone());
let args = parse_args(args, &stdin_reader)?;
environment.set_silent(args.is_silent_output());
environment.set_verbose(args.verbose);
run_cli(args, environment, &cache, &plugin_resolver, plugin_pools).await
}
#[tokio::test]
async fn it_should_output_version_with_no_plugins() {
let environment = TestEnvironment::new();
run_test_cli(vec!["--version"], &environment).await.unwrap();
let logged_messages = environment.take_logged_messages();
assert_eq!(logged_messages, vec![format!("dprint {}", env!("CARGO_PKG_VERSION"))]);
}
#[tokio::test]
async fn it_should_output_version_and_ignore_plugins() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
run_test_cli(vec!["--version"], &environment).await.unwrap();
let logged_messages = environment.take_logged_messages();
assert_eq!(logged_messages, vec![format!("dprint {}", env!("CARGO_PKG_VERSION"))]);
}
#[tokio::test]
async fn it_should_output_help_with_no_plugins() {
let environment = TestEnvironment::new();
run_test_cli(vec!["--help"], &environment).await.unwrap();
let logged_messages = environment.take_logged_messages();
assert_eq!(logged_messages, vec![get_expected_help_text()]);
}
#[tokio::test]
async fn it_should_output_help_no_sub_commands() {
let environment = TestEnvironment::new();
run_test_cli(vec![], &environment).await.unwrap();
let logged_messages = environment.take_logged_messages();
assert_eq!(logged_messages, vec![get_expected_help_text()]);
}
#[tokio::test]
async fn it_should_output_help_with_plugins() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
run_test_cli(vec!["--help"], &environment).await.unwrap();
let logged_messages = environment.take_logged_messages();
assert_eq!(logged_messages, vec![
get_expected_help_text(),
"\nPLUGINS HELP:",
" test-plugin https://dprint.dev/plugins/test",
" test-process-plugin https://dprint.dev/plugins/test-process"
]);
}
#[tokio::test]
async fn it_should_output_resolved_config() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
run_test_cli(vec!["output-resolved-config"], &environment).await.unwrap();
let logged_messages = environment.take_logged_messages();
assert_eq!(logged_messages, vec![
"test-plugin: {\n \"ending\": \"formatted\",\n \"lineWidth\": 120\n}",
"testProcessPlugin: {\n \"ending\": \"formatted_process\",\n \"lineWidth\": 120\n}",
]);
}
#[tokio::test]
async fn it_should_output_resolved_file_paths() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "const t=4;").unwrap();
environment.write_file(&PathBuf::from("/file2.txt"), "const t=4;").unwrap();
environment.write_file(&PathBuf::from("/file3.txt_ps"), "const t=4;").unwrap();
run_test_cli(vec!["output-file-paths", "**/*.*"], &environment).await.unwrap();
let mut logged_messages = environment.take_logged_messages();
logged_messages.sort();
assert_eq!(logged_messages, vec!["/file.txt", "/file2.txt", "/file3.txt_ps"]);
}
#[tokio::test]
async fn it_should_not_output_file_paths_not_supported_by_plugins() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/file.ts"), "const t=4;").unwrap();
environment.write_file(&PathBuf::from("/file2.ts"), "const t=4;").unwrap();
run_test_cli(vec!["output-file-paths", "**/*.*"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().len(), 0);
}
#[tokio::test]
async fn it_should_output_format_times() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "const t=4;").unwrap();
environment.write_file(&PathBuf::from("/file2.txt"), "const t=4;").unwrap();
environment.write_file(&PathBuf::from("/file3.txt_ps"), "const t=4;").unwrap();
run_test_cli(vec!["output-format-times", "**/*.*"], &environment).await.unwrap();
let logged_messages = environment.take_logged_messages();
assert_eq!(logged_messages.len(), 3); }
#[tokio::test]
async fn it_should_format_file() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file.txt");
environment.write_file(&file_path1, "text").unwrap();
run_test_cli(vec!["fmt", "/file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text_formatted");
}
#[tokio::test]
async fn it_should_format_files() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file.txt");
environment.write_file(&file_path1, "text").unwrap();
let file_path2 = PathBuf::from("/file.txt_ps");
environment.write_file(&file_path2, "text2").unwrap();
run_test_cli(vec!["fmt", "/file.*"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2_formatted_process");
}
#[tokio::test]
async fn it_should_format_files_with_local_plugin() {
let environment = get_test_environment_with_local_wasm_plugin();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": ["/plugins/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["help"], &environment).await.unwrap(); environment.clear_logs();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "text").unwrap();
run_test_cli(vec!["fmt", "/file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "text_formatted");
}
#[tokio::test]
async fn it_should_handle_wasm_plugin_erroring() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "should_error").unwrap(); let error_message = run_test_cli(vec!["fmt", "/file.txt"], &environment).await.err().unwrap();
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors(), vec![String::from("Error formatting /file.txt. Message: Did error.")]);
assert_eq!(error_message.to_string(), "Had 1 error(s) formatting.");
}
#[tokio::test]
async fn it_should_handle_process_plugin_erroring() {
let environment = get_initialized_test_environment_with_remote_process_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt_ps");
environment.write_file(&file_path, "should_error").unwrap(); let error_message = run_test_cli(vec!["fmt", "/file.txt_ps"], &environment).await.err().unwrap();
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors(), vec![String::from("Error formatting /file.txt_ps. Message: Did error.")]);
assert_eq!(error_message.to_string(), "Had 1 error(s) formatting.");
}
#[tokio::test]
async fn it_should_format_calling_process_plugin_with_wasm_plugin_and_no_plugin_exists() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "plugin: format this text").unwrap();
run_test_cli(vec!["fmt", "/file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "format this text");
}
#[tokio::test]
async fn it_should_format_calling_process_plugin_with_wasm_plugin_and_process_plugin_exists() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "plugin: format this text").unwrap();
run_test_cli(vec!["fmt", "/file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "format this text_formatted_process");
}
#[tokio::test]
async fn it_should_format_calling_process_plugin_with_wasm_plugin_using_additional_plugin_specified_config() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
environment.write_file(&file_path1, "plugin-config: format this text").unwrap();
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path2, "plugin: format this text").unwrap();
run_test_cli(vec!["fmt", "/*.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "format this text_custom_config");
assert_eq!(environment.read_file(&file_path2).unwrap(), "format this text_formatted_process");
}
#[tokio::test]
async fn it_should_error_calling_process_plugin_with_wasm_plugin_and_process_plugin_errors() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "plugin: should_error").unwrap();
let error_message = run_test_cli(vec!["fmt", "/file.txt"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), "Had 1 error(s) formatting.");
assert_eq!(environment.take_logged_errors(), vec![String::from("Error formatting /file.txt. Message: Did error.")]);
}
#[tokio::test]
async fn it_should_format_calling_other_plugin_with_process_plugin_and_no_plugin_exists() {
let environment = get_initialized_test_environment_with_remote_process_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt_ps");
environment.write_file(&file_path, "plugin: format this text").unwrap();
run_test_cli(vec!["fmt", "/file.txt_ps"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "format this text");
}
#[tokio::test]
async fn it_should_format_calling_wasm_plugin_with_process_plugin_and_wasm_plugin_exists() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt_ps");
environment.write_file(&file_path, "plugin: format this text").unwrap();
run_test_cli(vec!["fmt", "/file.txt_ps"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "format this text_formatted");
}
#[tokio::test]
async fn it_should_format_calling_wasm_plugin_with_process_plugin_using_additional_plugin_specified_config() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt_ps");
environment.write_file(&file_path1, "plugin-config: format this text").unwrap();
let file_path2 = PathBuf::from("/file2.txt_ps");
environment.write_file(&file_path2, "plugin: format this text").unwrap();
run_test_cli(vec!["fmt", "*.txt_ps"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "format this text_custom_config");
assert_eq!(environment.read_file(&file_path2).unwrap(), "format this text_formatted");
}
#[tokio::test]
async fn it_should_error_calling_wasm_plugin_with_process_plugin_and_wasm_plugin_errors() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt_ps");
environment.write_file(&file_path, "plugin: should_error").unwrap();
let error_message = run_test_cli(vec!["fmt", "/file.txt_ps"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), "Had 1 error(s) formatting.");
assert_eq!(environment.take_logged_errors(), vec![String::from("Error formatting /file.txt_ps. Message: Did error.")]);
}
#[tokio::test]
async fn it_should_format_when_specifying_dot_slash_paths() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "text").unwrap();
run_test_cli(vec!["fmt", "./file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "text_formatted");
}
#[tokio::test]
async fn it_should_exclude_a_specified_dot_slash_path() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "text").unwrap();
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path2, "text").unwrap();
run_test_cli(vec!["fmt", "./**/*.txt", "--excludes", "./file2.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "text_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text");
}
#[tokio::test]
async fn it_should_ignore_files_in_node_modules_by_default() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/node_modules/file.txt"), "").unwrap();
environment.write_file(&PathBuf::from("/test/node_modules/file.txt"), "").unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "").unwrap();
run_test_cli(vec!["fmt", "**/*.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_not_ignore_files_in_node_modules_when_allowed() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/node_modules/file.txt"), "const t=4;").unwrap();
environment.write_file(&PathBuf::from("/test/node_modules/file.txt"), "const t=4;").unwrap();
run_test_cli(vec!["fmt", "--allow-node-modules", "**/*.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_format_files_with_config() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt_ps");
let plugin_file_checksum = get_process_plugin_checksum(&environment).await;
environment.write_file(&PathBuf::from("/config.json"), &format!(r#"{{
"projectType": "openSource",
"test-plugin": {{
"ending": "custom-formatted"
}},
"testProcessPlugin": {{
"ending": "custom-formatted2"
}},
"plugins": [
"https://plugins.dprint.dev/test-plugin.wasm",
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}}"#, plugin_file_checksum)).unwrap();
environment.write_file(&file_path1, "text").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
run_test_cli(vec!["fmt", "--config", "/config.json", "/file1.txt", "/file2.txt_ps"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text_custom-formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2_custom-formatted2");
}
#[tokio::test]
async fn it_should_format_files_with_config_using_c() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
environment.write_file(&file_path1, "text").unwrap();
environment.write_file(&PathBuf::from("/config.json"), r#"{
"projectType": "openSource",
"test-plugin": { "ending": "custom-formatted" },
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt", "-c", "/config.json", "/file1.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text_custom-formatted");
}
#[tokio::test]
async fn it_should_error_when_config_file_does_not_exist() {
let environment = TestEnvironment::new();
environment.write_file(&PathBuf::from("/test.txt"), "test").unwrap();
let error_message = run_test_cli(vec!["fmt", "**/*.txt"], &environment).await.err().unwrap();
assert_eq!(
error_message.to_string(),
concat!(
"No config file found at ./.dprintrc.json. Did you mean to create (dprint init) or specify one (--config <path>)?\n",
" Error: Could not find file at path ./.dprintrc.json"
)
);
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_support_config_file_urls() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt");
environment.add_remote_file("https://dprint.dev/test.json", r#"{
"projectType": "openSource",
"test-plugin": {
"ending": "custom-formatted"
},
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#.as_bytes());
environment.write_file(&file_path1, "text").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
run_test_cli(vec!["fmt", "--config", "https://dprint.dev/test.json", "/file1.txt", "/file2.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text_custom-formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2_custom-formatted");
}
#[tokio::test]
async fn it_should_error_on_wasm_plugin_config_diagnostic() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"test-plugin": { "non-existent": 25 },
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.write_file(&PathBuf::from("/test.txt"), "test").unwrap();
let error_message = run_test_cli(vec!["fmt", "**/*.txt"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), "Had 1 error(s) formatting.");
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors(), vec![
"[test-plugin]: Unknown property in configuration: non-existent",
"[test-plugin]: Error initializing from configuration file. Had 1 diagnostic(s)."
]);
}
#[tokio::test]
async fn it_should_error_on_process_plugin_config_diagnostic() {
let environment = get_initialized_test_environment_with_remote_process_plugin().await.unwrap();
let plugin_file_checksum = get_process_plugin_checksum(&environment).await;
environment.write_file(&PathBuf::from("./.dprintrc.json"), &format!(r#"{{
"projectType": "openSource",
"testProcessPlugin": {{ "non-existent": 25 }},
"plugins": [
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}}"#, plugin_file_checksum)).unwrap();
environment.write_file(&PathBuf::from("/test.txt_ps"), "test").unwrap();
let error_message = run_test_cli(vec!["fmt", "**/*.txt_ps"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), "Had 1 error(s) formatting.");
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors(), vec![
"[test-process-plugin]: Unknown property in configuration: non-existent",
"[test-process-plugin]: Error initializing from configuration file. Had 1 diagnostic(s)."
]);
}
#[tokio::test]
async fn it_should_error_when_no_plugins_specified() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": []
}"#).unwrap();
environment.write_file(&PathBuf::from("/test.txt"), "test").unwrap();
let error_message = run_test_cli(vec!["fmt", "**/*.txt"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), "No formatting plugins found. Ensure at least one is specified in the 'plugins' array of the configuration file.");
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_use_plugins_specified_in_cli_args() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": ["https://plugins.dprint.dev/other.wasm"]
}"#).unwrap();
environment.write_file(&PathBuf::from("/test.txt"), "test").unwrap();
run_test_cli(vec!["fmt", "**/*.txt", "--plugins", "https://plugins.dprint.dev/test-plugin.wasm"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_allow_using_no_config_when_plugins_specified() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.remove_file(&PathBuf::from("./.dprintrc.json")).unwrap();
environment.write_file(&PathBuf::from("/test.txt"), "test").unwrap();
run_test_cli(vec!["fmt", "**/*.txt", "--plugins", "https://plugins.dprint.dev/test-plugin.wasm"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_error_when_no_files_match_glob() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let error_message = run_test_cli(vec!["fmt", "**/*.txt"], &environment).await.err().unwrap();
assert_eq!(
error_message.to_string(),
concat!(
"No files found to format with the specified plugins. ",
"You may want to try using `dprint output-file-paths` to see which files it's finding."
)
);
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[cfg(target_os = "windows")]
#[tokio::test]
async fn it_should_format_absolute_paths_on_windows() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("E:\\file1.txt");
environment.set_cwd("D:\\test\\other\\");
environment.write_file(&file_path, "text1").unwrap();
environment.write_file(&PathBuf::from("D:\\test\\other\\.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt", "--", "E:\\file1.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "text1_formatted");
}
#[cfg(target_os = "linux")]
#[tokio::test]
async fn it_should_format_absolute_paths_on_linux() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("/asdf/file1.txt");
environment.set_cwd("/test/other/");
environment.write_file(&file_path, "text1").unwrap();
environment.write_file(&PathBuf::from("/test/other/.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt", "--", "/asdf/file1.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "text1_formatted");
}
#[tokio::test]
async fn it_should_format_files_with_config_includes() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path1, "text1").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2_formatted");
}
#[cfg(target_os = "windows")]
#[tokio::test]
async fn it_should_format_files_with_config_includes_when_using_back_slashes() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
environment.write_file(&file_path1, "text1").unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**\\*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
}
#[tokio::test]
async fn it_should_override_config_includes_with_cli_includes() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path1, "text1").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt", "/file1.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2");
}
#[tokio::test]
async fn it_should_override_config_excludes_with_cli_excludes() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path1, "text1").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"excludes": ["/file1.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt", "--excludes", "/file2.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2");
}
#[tokio::test]
async fn it_should_override_config_includes_and_excludes_with_cli() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path1, "text1").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["/file2.txt"],
"excludes": ["/file1.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt", "/file1.txt", "--excludes", "/file2.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2");
}
#[tokio::test]
async fn it_should_format_files_with_config_excludes() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path1, "text1").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"excludes": ["/file2.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2");
}
#[tokio::test]
async fn it_should_format_files_with_config_in_config_sub_dir() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.remove_file(&PathBuf::from("./.dprintrc.json")).unwrap();
let file_path1 = PathBuf::from("/file1.txt");
let file_path2 = PathBuf::from("/file2.txt");
environment.write_file(&file_path1, "text1").unwrap();
environment.write_file(&file_path2, "text2").unwrap();
environment.write_file(&PathBuf::from("./config/.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["fmt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_plural_formatted_text(2)]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
assert_eq!(environment.read_file(&file_path2).unwrap(), "text2_formatted");
}
#[tokio::test]
async fn it_should_format_using_config_in_ancestor_directory() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.set_cwd("/test/other/");
let file_path = PathBuf::from("/test/other/file.txt");
environment.write_file(&file_path, "text").unwrap();
run_test_cli(vec!["fmt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "text_formatted");
}
#[tokio::test]
async fn it_should_format_using_config_in_ancestor_directory_config_folder() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.remove_file(&PathBuf::from("./.dprintrc.json")).unwrap();
environment.write_file(&PathBuf::from("./config/.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.set_cwd("/test/other/");
let file_path = PathBuf::from("/test/other/file.txt");
environment.write_file(&file_path, "text").unwrap();
run_test_cli(vec!["fmt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "text_formatted");
}
#[tokio::test]
async fn it_should_format_incrementally_when_specified_on_cli() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
let file_path1 = PathBuf::from("/file1.txt");
environment.write_file(&file_path1, "text1").unwrap();
run_test_cli(vec!["fmt", "--incremental"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
environment.clear_logs();
run_test_cli(vec!["fmt", "--incremental", "--verbose"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().iter().any(|msg| msg.contains("No change: /file1.txt")), true);
environment.write_file(&file_path1, "asdf").unwrap();
environment.clear_logs();
run_test_cli(vec!["fmt", "--incremental"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "asdf_formatted");
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"indentWidth": 2,
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.clear_logs();
run_test_cli(vec!["fmt", "--incremental"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().iter().any(|msg| msg.contains("No change: /file1.txt")), false);
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"indentWidth": 2,
"test-plugin": {
"ending": "custom-formatted",
"line_width": 80
},
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.clear_logs();
run_test_cli(vec!["fmt", "--incremental"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "asdf_formatted_custom-formatted");
for _ in 1..4 {
environment.clear_logs();
run_test_cli(vec!["fmt", "--incremental"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().iter().any(|msg| msg.contains("No change: /file1.txt")), false);
}
environment.clear_logs();
environment.set_cwd("/test/other/");
run_test_cli(vec!["fmt", "--incremental", "--verbose"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().iter().any(|msg| msg.contains("No change: /file1.txt")), true);
}
#[tokio::test]
async fn it_should_format_incrementally_when_specified_via_config() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"incremental": true,
"includes": ["**/*.txt"],
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
let file_path1 = PathBuf::from("/file1.txt");
environment.write_file(&file_path1, "text1").unwrap();
run_test_cli(vec!["fmt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path1).unwrap(), "text1_formatted");
environment.clear_logs();
run_test_cli(vec!["fmt", "--verbose"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().iter().any(|msg| msg.contains("No change: /file1.txt")), true);
}
#[tokio::test]
async fn it_should_error_when_missing_project_type() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.write_file(&PathBuf::from("/file1.txt"), "text1_formatted").unwrap();
let error_message = run_test_cli(vec!["fmt", "/file1.txt"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string().find("The 'projectType' property").is_some(), true);
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_not_output_when_no_files_need_formatting() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "text_formatted").unwrap();
run_test_cli(vec!["fmt", "/file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_not_output_when_no_files_need_formatting_for_check() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "text_formatted").unwrap();
run_test_cli(vec!["check", "/file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages().len(), 0);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_output_when_a_file_need_formatting_for_check() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "const t=4;").unwrap();
let error_message = run_test_cli(vec!["check", "/file.txt"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), get_singular_check_text());
assert_eq!(environment.take_logged_messages(), vec![
format!(
"{}\n{}\n--",
format!("{} /file.txt:", "from".bold().red().to_string()),
get_difference("const t=4;", "const t=4;_formatted").unwrap(),
),
]);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_output_when_files_need_formatting_for_check() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/file1.txt"), "const t=4;").unwrap();
environment.write_file(&PathBuf::from("/file2.txt"), "const t=5;").unwrap();
let error_message = run_test_cli(vec!["check", "/file1.txt", "/file2.txt"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), get_plural_check_text(2));
let mut logged_messages = environment.take_logged_messages();
logged_messages.sort(); assert_eq!(logged_messages, vec![
format!(
"{}\n{}\n--",
format!("{} /file1.txt:", "from".bold().red().to_string()),
get_difference("const t=4;", "const t=4;_formatted").unwrap(),
),
format!(
"{}\n{}\n--",
format!("{} /file2.txt:", "from".bold().red().to_string()),
get_difference("const t=5;", "const t=5;_formatted").unwrap(),
),
]);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_initialize() {
let environment = TestEnvironment::new();
environment.add_remote_file(crate::plugins::REMOTE_INFO_URL, r#"{
"schemaVersion": 1,
"pluginSystemSchemaVersion": 3,
"latest": [{
"name": "dprint-plugin-typescript",
"version": "0.17.2",
"url": "https://plugins.dprint.dev/typescript-0.17.2.wasm",
"fileExtensions": ["ts"],
"configKey": "typescript",
"configExcludes": []
}, {
"name": "dprint-plugin-jsonc",
"version": "0.2.3",
"url": "https://plugins.dprint.dev/json-0.2.3.wasm",
"fileExtensions": ["json"],
"configKey": "json",
"configExcludes": []
}]
}"#.as_bytes());
let expected_text = get_init_config_file_text(&environment).await.unwrap();
environment.clear_logs();
run_test_cli(vec!["init"], &environment).await.unwrap();
assert_eq!(environment.take_logged_errors(), vec![
"What kind of project will dprint be formatting?\n\nMore information: https://dprint.dev/sponsor\n",
"Select plugins (use the spacebar to select/deselect and then press enter when finished):"
]);
assert_eq!(environment.take_logged_messages(), vec![
"Created ./.dprintrc.json"
]);
assert_eq!(environment.read_file(&PathBuf::from("./.dprintrc.json")).unwrap(), expected_text);
}
#[tokio::test]
async fn it_should_initialize_with_specified_config_path() {
let environment = TestEnvironment::new();
environment.add_remote_file(crate::plugins::REMOTE_INFO_URL, r#"{
"schemaVersion": 1,
"pluginSystemSchemaVersion": 3,
"latest": [{
"name": "dprint-plugin-typescript",
"version": "0.17.2",
"url": "https://plugins.dprint.dev/typescript-0.17.2.wasm",
"fileExtensions": ["json"],
"configKey": "typescript",
"configExcludes": []
}]
}"#.as_bytes());
let expected_text = get_init_config_file_text(&environment).await.unwrap();
environment.clear_logs();
run_test_cli(vec!["init", "--config", "./test.config.json"], &environment).await.unwrap();
assert_eq!(environment.take_logged_errors(), vec![
"What kind of project will dprint be formatting?\n\nMore information: https://dprint.dev/sponsor\n",
"Select plugins (use the spacebar to select/deselect and then press enter when finished):"
]);
assert_eq!(environment.take_logged_messages(), vec![
"Created ./test.config.json"
]);
assert_eq!(environment.read_file(&PathBuf::from("./test.config.json")).unwrap(), expected_text);
}
#[tokio::test]
async fn it_should_error_when_config_file_exists_on_initialize() {
let environment = TestEnvironment::new();
environment.write_file(&PathBuf::from("./.dprintrc.json"), "{}").unwrap();
let error_message = run_test_cli(vec!["init"], &environment).await.err().unwrap();
assert_eq!(error_message.to_string(), "Configuration file './.dprintrc.json' already exists.");
}
#[tokio::test]
async fn it_should_ask_to_initialize_in_config_dir() {
let environment = TestEnvironment::new();
environment.write_file(&PathBuf::from("./config"), "").unwrap(); let expected_text = get_init_config_file_text(&environment).await.unwrap();
environment.clear_logs();
run_test_cli(vec!["init"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![
"Created ./config/.dprintrc.json"
]);
assert_eq!(environment.take_logged_errors(), vec![
"Would you like to create the .dprintrc.json in the ./config directory?",
"What kind of project will dprint be formatting?\n\nMore information: https://dprint.dev/sponsor\n",
"There was a problem getting the latest plugin info. The created config file may not be as helpful of a starting point. Error: Could not find file at url https://plugins.dprint.dev/info.json"
]);
assert_eq!(environment.read_file(&PathBuf::from("./config/.dprintrc.json")).unwrap(), expected_text);
}
#[tokio::test]
async fn it_should_ask_to_initialize_in_config_dir_and_handle_no() {
let environment = TestEnvironment::new();
environment.write_file(&PathBuf::from("./config"), "").unwrap(); environment.set_selection_result(1);
let expected_text = get_init_config_file_text(&environment).await.unwrap();
environment.clear_logs();
run_test_cli(vec!["init"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![
"Created ./.dprintrc.json"
]);
assert_eq!(environment.take_logged_errors(), vec![
"Would you like to create the .dprintrc.json in the ./config directory?",
"What kind of project will dprint be formatting?\n\nMore information: https://dprint.dev/sponsor\n",
"There was a problem getting the latest plugin info. The created config file may not be as helpful of a starting point. Error: Could not find file at url https://plugins.dprint.dev/info.json"
]);
assert_eq!(environment.read_file(&PathBuf::from("./.dprintrc.json")).unwrap(), expected_text);
}
#[tokio::test]
async fn it_should_clear_cache_directory() {
let environment = TestEnvironment::new();
run_test_cli(vec!["clear-cache"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec!["Deleted /cache"]);
assert_eq!(environment.is_dir_deleted(&PathBuf::from("/cache")), true);
}
#[tokio::test]
async fn it_should_handle_bom() {
let environment = get_initialized_test_environment_with_remote_wasm_plugin().await.unwrap();
let file_path = PathBuf::from("/file.txt");
environment.write_file(&file_path, "\u{FEFF}text").unwrap();
run_test_cli(vec!["fmt", "/file.txt"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![get_singular_formatted_text()]);
assert_eq!(environment.take_logged_errors().len(), 0);
assert_eq!(environment.read_file(&file_path).unwrap(), "\u{FEFF}text_formatted");
}
#[tokio::test]
async fn it_should_output_license_for_sub_command_with_no_plugins() {
let environment = TestEnvironment::new();
run_test_cli(vec!["license"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![
"==== DPRINT CLI LICENSE ====",
std::str::from_utf8(include_bytes!("../../LICENSE")).unwrap()
]);
}
#[tokio::test]
async fn it_should_output_license_for_sub_command_with_plugins() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
run_test_cli(vec!["license"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![
"==== DPRINT CLI LICENSE ====",
std::str::from_utf8(include_bytes!("../../LICENSE")).unwrap(),
"\n==== TEST-PLUGIN LICENSE ====",
r#"Copyright 2020 David Sherret. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"#,
"\n==== TEST-PROCESS-PLUGIN LICENSE ====",
"License text."
]);
}
#[tokio::test]
async fn it_should_output_editor_plugin_info() {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_process_plugin(&environment);
setup_test_environment_with_remote_wasm_plugin(&environment);
let plugin_file_checksum = get_process_plugin_checksum(&environment).await;
environment.write_file(&PathBuf::from("./.dprintrc.json"), &format!(r#"{{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-plugin.wasm",
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}}"#, plugin_file_checksum)).unwrap();
run_test_cli(vec!["editor-info"], &environment).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec![
r#"{"schemaVersion":2,"plugins":[{"name":"test-plugin","fileExtensions":["txt"]},{"name":"test-process-plugin","fileExtensions":["txt_ps"]}]}"#
]);
}
struct EditorServiceCommunicator {
stdin: Box<dyn Write + Send>,
stdout: Box<dyn Read + Send>,
}
impl EditorServiceCommunicator {
pub fn new(
stdin: Box<dyn Write + Send>,
stdout: Box<dyn Read + Send>,
) -> Self {
EditorServiceCommunicator {
stdin,
stdout,
}
}
pub fn check_file(&mut self, file_path: &PathBuf) -> Result<bool, ErrBox> {
let mut reader_writer = self.get_reader_writer();
reader_writer.send_u32(1)?;
reader_writer.send_path_buf(file_path)?;
Ok(reader_writer.read_u32()? == 1)
}
pub fn format_text(&mut self, file_path: &PathBuf, file_text: &str) -> Result<Option<String>, ErrBox> {
let mut reader_writer = self.get_reader_writer();
reader_writer.send_u32(2)?;
reader_writer.send_path_buf(file_path)?;
reader_writer.send_string(file_text)?;
let result = reader_writer.read_u32()?;
match result {
0 => Ok(None),
1 => Ok(Some(reader_writer.read_string()?)),
2 => err!("{}", reader_writer.read_string()?),
_ => err!("Unknown result: {}", result),
}
}
pub fn exit(&mut self) {
let mut reader_writer = self.get_reader_writer();
reader_writer.send_u32(0).unwrap();
}
fn get_reader_writer(&mut self) -> StdInOutReaderWriter<Box<dyn Read + Send>, Box<dyn Write + Send>> {
StdInOutReaderWriter::new(&mut self.stdout, &mut self.stdin)
}
}
#[tokio::test]
async fn it_should_format_for_editor_service() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.{txt,ts}"],
"plugins": [
"https://plugins.dprint.dev/test-plugin.wasm",
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}"#).unwrap();
let txt_file_path = PathBuf::from("/file.txt");
environment.write_file(&txt_file_path, "").unwrap();
let ts_file_path = PathBuf::from("/file.ts");
environment.write_file(&ts_file_path, "").unwrap();
let other_ext_path = PathBuf::from("/file.asdf");
environment.write_file(&other_ext_path, "").unwrap();
let stdin = environment.stdin_writer();
let stdout = environment.stdout_reader();
let result = tokio::task::spawn_blocking({
let environment = environment.clone();
move || {
let mut communicator = EditorServiceCommunicator::new(stdin, stdout);
assert_eq!(communicator.check_file(&txt_file_path).unwrap(), true);
assert_eq!(communicator.check_file(&PathBuf::from("/non-existent.txt")).unwrap(), false);
assert_eq!(communicator.check_file(&other_ext_path).unwrap(), false);
assert_eq!(communicator.check_file(&ts_file_path).unwrap(), true);
assert_eq!(communicator.format_text(&txt_file_path, "testing").unwrap().unwrap(), "testing_formatted");
assert_eq!(communicator.format_text(&txt_file_path, "testing_formatted").unwrap().is_none(), true); assert_eq!(communicator.format_text(&other_ext_path, "testing").unwrap().is_none(), true); assert_eq!(communicator.format_text(&txt_file_path, "plugin: format this text").unwrap().unwrap(), "format this text_formatted_process");
assert_eq!(communicator.format_text(&txt_file_path, "should_error").err().unwrap().to_string(), "Did error.");
assert_eq!(communicator.format_text(&txt_file_path, "plugin: should_error").err().unwrap().to_string(), "Did error.");
assert_eq!(communicator.format_text(&PathBuf::from("/file.txt_ps"), "testing").unwrap().unwrap(), "testing_formatted_process");
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"includes": ["**/*.txt"],
"test-plugin": {
"ending": "new_ending"
},
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
assert_eq!(communicator.check_file(&ts_file_path).unwrap(), false); assert_eq!(communicator.check_file(&txt_file_path).unwrap(), true); assert_eq!(communicator.format_text(&txt_file_path, "testing").unwrap().unwrap(), "testing_new_ending");
communicator.exit();
}
});
let pid = std::process::id().to_string();
run_test_cli(vec!["editor-service", "--parent-pid", &pid], &environment).await.unwrap();
result.await.unwrap();
}
#[tokio::test]
async fn it_should_format_for_stdin_fmt() {
let environment = get_test_environment_with_remote_wasm_plugin();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "").unwrap();
let test_std_in = TestStdInReader::new_with_text("text");
run_test_cli_with_stdin(vec!["stdin-fmt", "--file-name", "file.txt"], &environment, test_std_in).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec!["text_formatted"]);
assert_eq!(environment.take_logged_errors().len(), 0);
}
#[tokio::test]
async fn it_should_stdin_fmt_calling_other_plugin() {
let environment = get_initialized_test_environment_with_remote_wasm_and_process_plugin().await.unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "").unwrap();
let plugin_file_checksum = get_process_plugin_checksum(&environment).await;
environment.write_file(&PathBuf::from("./.dprintrc.json"), &format!(r#"{{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-plugin.wasm",
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}}"#, plugin_file_checksum)).unwrap();
let test_std_in = TestStdInReader::new_with_text("plugin: format this text");
run_test_cli_with_stdin(vec!["stdin-fmt", "--file-name", "file.txt"], &environment, test_std_in).await.unwrap();
assert_eq!(environment.take_logged_messages(), vec!["format this text_formatted_process"]);
}
#[tokio::test]
async fn it_should_handle_error_for_stdin_fmt() {
let environment = get_test_environment_with_remote_wasm_plugin();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
environment.write_file(&PathBuf::from("/file.txt"), "").unwrap();
let test_std_in = TestStdInReader::new_with_text("should_error");
let error_message = run_test_cli_with_stdin(vec!["stdin-fmt", "--file-name", "file.txt"], &environment, test_std_in).await.err().unwrap();
assert_eq!(error_message.to_string(), "Did error.");
}
#[tokio::test]
async fn it_should_error_if_process_plugin_has_no_checksum_in_config() {
let environment = get_initialized_test_environment_with_remote_process_plugin().await.unwrap();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-process.exe-plugin"
]
}"#).unwrap();
environment.write_file(&PathBuf::from("test.txt_ps"), "").unwrap();
let error_message = run_test_cli(vec!["fmt", "*.*"], &environment).await.err().unwrap();
assert_eq!(
error_message.to_string(),
concat!(
"The plugin 'https://plugins.dprint.dev/test-process.exe-plugin' must have a checksum specified for security reasons ",
"since it is not a WASM plugin. You may specify one by writing \"https://plugins.dprint.dev/test-process.exe-plugin@checksum-goes-here\" ",
"when providing the url in the configuration file. Check the plugin's release notes for what ",
"the checksum is or calculate it yourself if you trust the source (it's SHA-256)."
)
);
}
#[tokio::test]
async fn it_should_error_if_process_plugin_has_wrong_checksum_in_config() {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_process_plugin(&environment);
let actual_plugin_file_checksum = get_process_plugin_checksum(&environment).await;
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-process.exe-plugin@asdf"
]
}"#).unwrap();
environment.write_file(&PathBuf::from("test.txt_ps"), "").unwrap();
let error_message = run_test_cli(vec!["fmt", "*.*"], &environment).await.err().unwrap();
assert_eq!(
error_message.to_string(),
format!(
"Error resolving plugin https://plugins.dprint.dev/test-process.exe-plugin: The checksum {} did not match the expected checksum of asdf.",
actual_plugin_file_checksum,
)
);
assert_eq!(environment.take_logged_errors(), vec![format!(
"Error getting plugin from cache. Forgetting from cache and retrying. Message: The checksum {} did not match the expected checksum of asdf.",
actual_plugin_file_checksum
)]);
}
#[tokio::test]
async fn it_should_error_if_wasm_plugin_has_wrong_checksum_in_config() {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_wasm_plugin(&environment);
let actual_plugin_file_checksum = get_wasm_plugin_checksum();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-plugin.wasm@asdf"
]
}"#).unwrap();
environment.write_file(&PathBuf::from("test.txt"), "").unwrap();
let error_message = run_test_cli(vec!["fmt", "*.*"], &environment).await.err().unwrap();
assert_eq!(
error_message.to_string(),
format!(
"Error resolving plugin https://plugins.dprint.dev/test-plugin.wasm: The checksum {} did not match the expected checksum of asdf.",
actual_plugin_file_checksum,
)
);
assert_eq!(environment.take_logged_errors(), vec![format!(
"Error getting plugin from cache. Forgetting from cache and retrying. Message: The checksum {} did not match the expected checksum of asdf.",
actual_plugin_file_checksum
)]);
}
#[tokio::test]
async fn it_should_not_error_if_wasm_plugin_has_correct_checksum_in_config() {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_wasm_plugin(&environment);
let actual_plugin_file_checksum = get_wasm_plugin_checksum();
environment.write_file(&PathBuf::from("./.dprintrc.json"), &format!(r#"{{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-plugin.wasm@{}"
]
}}"#, actual_plugin_file_checksum)).unwrap();
environment.write_file(&PathBuf::from("test.txt"), "text").unwrap();
run_test_cli(vec!["fmt", "*.*"], &environment).await.unwrap();
assert_eq!(environment.read_file(&PathBuf::from("test.txt")).unwrap(), "text_formatted");
assert_eq!(environment.take_logged_errors(), vec!["Compiling https://plugins.dprint.dev/test-plugin.wasm"]);
}
#[tokio::test]
async fn it_should_error_if_process_plugin_has_wrong_checksum_in_file_for_zip() {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_process_plugin(&environment);
write_process_plugin_file(&environment, "asdf");
environment.write_file(&PathBuf::from("./.dprintrc.json"), &format!(r#"{{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}}"#, get_process_plugin_checksum(&environment).await)).unwrap();
let actual_plugin_zip_file_checksum = get_process_plugin_zip_checksum(&environment).await;
environment.write_file(&PathBuf::from("test.txt_ps"), "").unwrap();
let error_message = run_test_cli(vec!["fmt", "*.*"], &environment).await.err().unwrap();
assert_eq!(
error_message.to_string(),
format!(
"Error resolving plugin https://plugins.dprint.dev/test-process.exe-plugin: The checksum {} did not match the expected checksum of asdf.",
actual_plugin_zip_file_checksum,
)
);
assert_eq!(environment.take_logged_errors(), vec![format!(
"Error getting plugin from cache. Forgetting from cache and retrying. Message: The checksum {} did not match the expected checksum of asdf.",
actual_plugin_zip_file_checksum
)]);
}
fn get_singular_formatted_text() -> String {
format!("Formatted {} file.", "1".bold().to_string())
}
fn get_plural_formatted_text(count: usize) -> String {
format!("Formatted {} files.", count.to_string().bold().to_string())
}
fn get_singular_check_text() -> String {
format!("Found {} not formatted file.", "1".bold().to_string())
}
fn get_plural_check_text(count: usize) -> String {
format!("Found {} not formatted files.", count.to_string().bold().to_string())
}
fn get_expected_help_text() -> &'static str {
concat!("dprint ", env!("CARGO_PKG_VERSION"), r#"
Copyright 2020 by David Sherret
Auto-formats source code based on the specified plugins.
USAGE:
dprint <SUBCOMMAND> [OPTIONS] [--] [files]...
SUBCOMMANDS:
init Initializes a configuration file in the current directory.
fmt Formats the source files and writes the result to the file system.
check Checks for any files that haven't been formatted.
output-file-paths Prints the resolved file paths for the plugins based on the args and configuration.
output-resolved-config Prints the resolved configuration for the plugins based on the args and configuration.
output-format-times Prints the amount of time it takes to format each file. Use this for debugging.
clear-cache Deletes the plugin cache directory.
license Outputs the software license.
OPTIONS:
-c, --config <config> Path or url to JSON configuration file. Defaults to .dprintrc.json in current or
ancestor directory when not provided.
--excludes <patterns>... List of files or directories or globs in quotes to exclude when formatting. This
overrides what is specified in the config file.
--allow-node-modules Allows traversing node module directories (unstable - This flag will be renamed to
be non-node specific in the future).
--incremental Only format files only when they change. This may alternatively be specified in the
configuration file.
--plugins <urls/files>... List of urls or file paths of plugins to use. This overrides what is specified in
the config file.
--verbose Prints additional diagnostic information.
-v, --version Prints the version.
ARGS:
<files>... List of files or globs in quotes to format. This overrides what is specified in the config file.
GETTING STARTED:
1. Navigate to the root directory of a code repository.
2. Run `dprint init` to create a .dprintrc.json file in that directory.
3. Modify configuration file if necessary.
4. Run `dprint fmt` or `dprint check`.
EXAMPLES:
Write formatted files to file system:
dprint fmt
Check for files that haven't been formatted:
dprint check
Specify path to config file other than the default:
dprint fmt --config path/to/config/.dprintrc.json
Search for files using the specified file patterns:
dprint fmt "**/*.{ts,tsx,js,jsx,json}""#)
}
static WASM_PLUGIN_BYTES: &'static [u8] = include_bytes!("../../../test-plugin/target/wasm32-unknown-unknown/release/test_plugin.wasm");
lazy_static! {
static ref COMPILATION_RESULT: CompilationResult = {
crate::plugins::compile_wasm(WASM_PLUGIN_BYTES).unwrap()
};
}
async fn get_initialized_test_environment_with_remote_wasm_and_process_plugin() -> Result<TestEnvironment, ErrBox> {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_wasm_plugin(&environment);
setup_test_environment_with_remote_process_plugin(&environment);
let plugin_file_checksum = get_process_plugin_checksum(&environment).await;
environment.write_file(&PathBuf::from("./.dprintrc.json"), &format!(r#"{{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-plugin.wasm",
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}}"#, plugin_file_checksum)).unwrap();
run_test_cli(vec!["help"], &environment).await.unwrap(); environment.clear_logs();
Ok(environment)
}
async fn get_process_plugin_checksum(environment: &TestEnvironment) -> String {
let plugin_file_bytes = environment.download_file("https://plugins.dprint.dev/test-process.exe-plugin").await.unwrap();
crate::utils::get_sha256_checksum(&plugin_file_bytes)
}
async fn get_process_plugin_zip_checksum(environment: &TestEnvironment) -> String {
let plugin_file_bytes = environment.download_file("https://github.com/dprint/test-process-plugin/releases/0.1.0/test-process-plugin.zip").await.unwrap();
crate::utils::get_sha256_checksum(&plugin_file_bytes)
}
fn get_wasm_plugin_checksum() -> String {
crate::utils::get_sha256_checksum(WASM_PLUGIN_BYTES)
}
async fn get_initialized_test_environment_with_remote_process_plugin() -> Result<TestEnvironment, ErrBox> {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_process_plugin(&environment);
let plugin_file_checksum = get_process_plugin_checksum(&environment).await;
environment.write_file(&PathBuf::from("./.dprintrc.json"), &format!(r#"{{
"projectType": "openSource",
"plugins": [
"https://plugins.dprint.dev/test-process.exe-plugin@{}"
]
}}"#, plugin_file_checksum)).unwrap();
run_test_cli(vec!["help"], &environment).await.unwrap(); environment.clear_logs();
Ok(environment)
}
async fn get_initialized_test_environment_with_remote_wasm_plugin() -> Result<TestEnvironment, ErrBox> {
let environment = get_test_environment_with_remote_wasm_plugin();
environment.write_file(&PathBuf::from("./.dprintrc.json"), r#"{
"projectType": "openSource",
"plugins": ["https://plugins.dprint.dev/test-plugin.wasm"]
}"#).unwrap();
run_test_cli(vec!["help"], &environment).await.unwrap(); environment.clear_logs();
Ok(environment)
}
fn get_test_environment_with_remote_wasm_plugin() -> TestEnvironment {
let environment = TestEnvironment::new();
setup_test_environment_with_remote_wasm_plugin(&environment);
environment
}
fn get_test_environment_with_local_wasm_plugin() -> TestEnvironment {
let environment = TestEnvironment::new();
environment.write_file_bytes(&PathBuf::from("/plugins/test-plugin.wasm"), WASM_PLUGIN_BYTES).unwrap();
environment
}
fn setup_test_environment_with_remote_wasm_plugin(environment: &TestEnvironment) {
environment.add_remote_file("https://plugins.dprint.dev/test-plugin.wasm", WASM_PLUGIN_BYTES);
}
#[cfg(target_os="windows")]
static PROCESS_PLUGIN_EXE_BYTES: &'static [u8] = include_bytes!("../../../../target/release/test-process-plugin.exe");
#[cfg(not(target_os="windows"))]
static PROCESS_PLUGIN_EXE_BYTES: &'static [u8] = include_bytes!("../../../../target/release/test-process-plugin");
fn setup_test_environment_with_remote_process_plugin(environment: &TestEnvironment) {
let buf: Vec<u8> = Vec::new();
let w = std::io::Cursor::new(buf);
let mut zip = zip::ZipWriter::new(w);
let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
zip.start_file(if cfg!(target_os="windows") { "test-process-plugin.exe" } else { "test-process-plugin" }, options).unwrap();
zip.write(PROCESS_PLUGIN_EXE_BYTES).unwrap();
let result = zip.finish().unwrap().into_inner();
let zip_file_checksum = crate::utils::get_sha256_checksum(&result);
environment.add_remote_file_bytes(
"https://github.com/dprint/test-process-plugin/releases/0.1.0/test-process-plugin.zip",
Bytes::from(result),
);
write_process_plugin_file(environment, &zip_file_checksum);
}
fn write_process_plugin_file(environment: &TestEnvironment, zip_checksum: &str) {
environment.add_remote_file_bytes(
"https://plugins.dprint.dev/test-process.exe-plugin",
Bytes::from(format!(r#"{{
"schemaVersion": 1,
"name": "test-process-plugin",
"version": "0.1.0",
"windows-x86_64": {{
"reference": "https://github.com/dprint/test-process-plugin/releases/0.1.0/test-process-plugin.zip",
"checksum": "{0}"
}},
"linux-x86_64": {{
"reference": "https://github.com/dprint/test-process-plugin/releases/0.1.0/test-process-plugin.zip",
"checksum": "{0}"
}},
"mac-x86_64": {{
"reference": "https://github.com/dprint/test-process-plugin/releases/0.1.0/test-process-plugin.zip",
"checksum": "{0}"
}}
}}"#, zip_checksum))
);
}
}