summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-01-30 03:16:48 +0100
committerGitHub <noreply@github.com>2020-01-29 21:16:48 -0500
commit73a3cc21d0e7ceec1275078db125f57ce97725fb (patch)
tree17d845d5d125d3ba8ecefa49e57e02e9c088c68b /cli
parentf0a6062012c4e09553877c9feedb3fbc3d4625b9 (diff)
feat: dprint formatter (#3820)
* rewrite fmt_test in Rust, remove tools/fmt_test.py * remove //std/prettier
Diffstat (limited to 'cli')
-rw-r--r--cli/Cargo.toml2
-rw-r--r--cli/flags.rs360
-rw-r--r--cli/fmt.rs162
-rw-r--r--cli/lib.rs8
-rw-r--r--cli/tests/badly_formatted.js4
-rw-r--r--cli/tests/integration_tests.rs40
6 files changed, 263 insertions, 313 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index f37825371..a643e51ba 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -35,7 +35,9 @@ brotli2 = "0.3.2"
clap = "2.33.0"
dirs = "2.0.2"
dlopen = "0.1.8"
+dprint-plugin-typescript = "0.3.0-alpha.1"
futures = { version = "0.3.1", features = [ "compat", "io-compat" ] }
+glob = "0.3.0"
http = "0.2.0"
indexmap = "1.3.0"
lazy_static = "1.4.0"
diff --git a/cli/flags.rs b/cli/flags.rs
index 10709b780..eaf9b7fc5 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -28,8 +28,6 @@ macro_rules! std_url {
};
}
-/// Used for `deno fmt <files>...` subcommand
-const PRETTIER_URL: &str = std_url!("prettier/main.ts");
/// Used for `deno install...` subcommand
const INSTALLER_URL: &str = std_url!("installer/mod.ts");
/// Used for `deno test...` subcommand
@@ -41,6 +39,10 @@ pub enum DenoSubcommand {
Completions,
Eval,
Fetch,
+ Format {
+ check: bool,
+ files: Option<Vec<String>>,
+ },
Help,
Info,
Install,
@@ -230,60 +232,19 @@ fn types_parse(flags: &mut DenoFlags, _matches: &clap::ArgMatches) {
}
fn fmt_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
- flags.subcommand = DenoSubcommand::Run;
- flags.allow_read = true;
- flags.allow_write = true;
- flags.argv.push(PRETTIER_URL.to_string());
-
- let files: Vec<String> = matches
- .values_of("files")
- .unwrap()
- .map(String::from)
- .collect();
- flags.argv.extend(files);
-
- if !matches.is_present("stdout") {
- // `deno fmt` writes to the files by default
- flags.argv.push("--write".to_string());
- }
-
- let prettier_flags = [
- ["0", "check"],
- ["1", "prettierrc"],
- ["1", "ignore-path"],
- ["1", "print-width"],
- ["1", "tab-width"],
- ["0", "use-tabs"],
- ["0", "no-semi"],
- ["0", "single-quote"],
- ["1", "quote-props"],
- ["0", "jsx-single-quote"],
- ["0", "jsx-bracket-same-line"],
- ["0", "trailing-comma"],
- ["0", "no-bracket-spacing"],
- ["1", "arrow-parens"],
- ["1", "prose-wrap"],
- ["1", "end-of-line"],
- ];
-
- for opt in &prettier_flags {
- let t = opt[0];
- let keyword = opt[1];
-
- if matches.is_present(&keyword) {
- if t == "0" {
- flags.argv.push(format!("--{}", keyword));
- } else {
- if keyword == "prettierrc" {
- flags.argv.push("--config".to_string());
- } else {
- flags.argv.push(format!("--{}", keyword));
- }
- flags
- .argv
- .push(matches.value_of(keyword).unwrap().to_string());
- }
+ let maybe_files = match matches.values_of("files") {
+ Some(f) => {
+ let files: Vec<String> = f.map(String::from).collect();
+ Some(files)
}
+ None => None,
+ };
+
+ let check = matches.is_present("check");
+
+ flags.subcommand = DenoSubcommand::Format {
+ check,
+ files: maybe_files,
}
}
@@ -556,155 +517,28 @@ The declaration file could be saved and used for typing information.",
fn fmt_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("fmt")
- .about("Format files")
- .long_about(
-"Auto-format JavaScript/TypeScript source code using Prettier
+ .about("Format files")
+ .long_about(
+ "Auto-format JavaScript/TypeScript source code
-Automatically downloads Prettier dependencies on first run.
+ deno fmt
- deno fmt myfile1.ts myfile2.ts",
- )
- .arg(
- Arg::with_name("check")
- .long("check")
- .help("Check if the source files are formatted.")
- .takes_value(false),
- )
- .arg(
- Arg::with_name("prettierrc")
- .long("prettierrc")
- .value_name("auto|disable|FILE")
- .help("Specify the configuration file of the prettier.
- auto: Auto detect prettier configuration file in current working dir.
- disable: Disable load configuration file.
- FILE: Load specified prettier configuration file. support .json/.toml/.js/.ts file
- ")
- .takes_value(true)
- .require_equals(true)
- .default_value("auto")
- )
- .arg(
- Arg::with_name("ignore-path")
- .long("ignore-path")
- .value_name("auto|disable|FILE")
- .help("Path to a file containing patterns that describe files to ignore.
- auto: Auto detect .pretierignore file in current working dir.
- disable: Disable load .prettierignore file.
- FILE: Load specified prettier ignore file.
- ")
- .takes_value(true)
- .require_equals(true)
- .default_value("auto")
- )
- .arg(
- Arg::with_name("stdout")
- .long("stdout")
- .help("Output formated code to stdout")
- .takes_value(false),
- )
- .arg(
- Arg::with_name("print-width")
- .long("print-width")
- .value_name("int")
- .help("Specify the line length that the printer will wrap on.")
- .takes_value(true)
- .require_equals(true)
- )
- .arg(
- Arg::with_name("tab-width")
- .long("tab-width")
- .value_name("int")
- .help("Specify the number of spaces per indentation-level.")
- .takes_value(true)
- .require_equals(true)
- )
- .arg(
- Arg::with_name("use-tabs")
- .long("use-tabs")
- .help("Indent lines with tabs instead of spaces.")
- .takes_value(false)
- )
- .arg(
- Arg::with_name("no-semi")
- .long("no-semi")
- .help("Print semicolons at the ends of statements.")
- .takes_value(false)
- )
- .arg(
- Arg::with_name("single-quote")
- .long("single-quote")
- .help("Use single quotes instead of double quotes.")
- .takes_value(false)
- )
- .arg(
- Arg::with_name("quote-props")
- .long("quote-props")
- .value_name("as-needed|consistent|preserve")
- .help("Change when properties in objects are quoted.")
- .takes_value(true)
- .possible_values(&["as-needed", "consistent", "preserve"])
- .require_equals(true)
- )
- .arg(
- Arg::with_name("jsx-single-quote")
- .long("jsx-single-quote")
- .help("Use single quotes instead of double quotes in JSX.")
- .takes_value(false)
- )
- .arg(
- Arg::with_name("jsx-bracket-same-line")
- .long("jsx-bracket-same-line")
- .help(
- "Put the > of a multi-line JSX element at the end of the last line
-instead of being alone on the next line (does not apply to self closing elements)."
- )
- .takes_value(false)
- )
- .arg(
- Arg::with_name("trailing-comma")
- .long("trailing-comma")
- .help("Print trailing commas wherever possible when multi-line.")
- .takes_value(false)
- )
- .arg(
- Arg::with_name("no-bracket-spacing")
- .long("no-bracket-spacing")
- .help("Print spaces between brackets in object literals.")
- .takes_value(false)
- )
- .arg(
- Arg::with_name("arrow-parens")
- .long("arrow-parens")
- .value_name("avoid|always")
- .help("Include parentheses around a sole arrow function parameter.")
- .takes_value(true)
- .possible_values(&["avoid", "always"])
- .require_equals(true)
- )
- .arg(
- Arg::with_name("prose-wrap")
- .long("prose-wrap")
- .value_name("always|never|preserve")
- .help("How to wrap prose.")
- .takes_value(true)
- .possible_values(&["always", "never", "preserve"])
- .require_equals(true)
- )
- .arg(
- Arg::with_name("end-of-line")
- .long("end-of-line")
- .value_name("auto|lf|crlf|cr")
- .help("Which end of line characters to apply.")
- .takes_value(true)
- .possible_values(&["auto", "lf", "crlf", "cr"])
- .require_equals(true)
- )
- .arg(
- Arg::with_name("files")
- .takes_value(true)
- .multiple(true)
- .required(true),
- )
+ deno fmt myfile1.ts myfile2.ts
+
+ deno fmt --check",
+ )
+ .arg(
+ Arg::with_name("check")
+ .long("check")
+ .help("Check if the source files are formatted.")
+ .takes_value(false),
+ )
+ .arg(
+ Arg::with_name("files")
+ .takes_value(true)
+ .multiple(true)
+ .required(false),
+ )
}
fn repl_subcommand<'a, 'b>() -> App<'a, 'b> {
@@ -1439,20 +1273,24 @@ mod tests {
assert_eq!(
r.unwrap(),
DenoFlags {
- subcommand: DenoSubcommand::Run,
- allow_write: true,
- allow_read: true,
- argv: svec![
- "deno",
- PRETTIER_URL,
- "script_1.ts",
- "script_2.ts",
- "--write",
- "--config",
- "auto",
- "--ignore-path",
- "auto"
- ],
+ subcommand: DenoSubcommand::Format {
+ check: false,
+ files: Some(svec!["script_1.ts", "script_2.ts"])
+ },
+ argv: svec!["deno"],
+ ..DenoFlags::default()
+ }
+ );
+
+ let r = flags_from_vec_safe(svec!["deno", "fmt", "--check"]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Format {
+ check: true,
+ files: None
+ },
+ argv: svec!["deno"],
..DenoFlags::default()
}
);
@@ -1668,36 +1506,6 @@ mod tests {
}
#[test]
- fn fmt_stdout() {
- let r = flags_from_vec_safe(svec![
- "deno",
- "fmt",
- "--stdout",
- "script_1.ts",
- "script_2.ts"
- ]);
- assert_eq!(
- r.unwrap(),
- DenoFlags {
- subcommand: DenoSubcommand::Run,
- argv: svec![
- "deno",
- PRETTIER_URL,
- "script_1.ts",
- "script_2.ts",
- "--config",
- "auto",
- "--ignore-path",
- "auto"
- ],
- allow_write: true,
- allow_read: true,
- ..DenoFlags::default()
- }
- );
- }
-
- #[test]
fn default_to_run() {
let r = flags_from_vec_safe(svec!["deno", "script.ts"]);
assert_eq!(
@@ -2126,66 +1934,6 @@ mod tests {
}
#[test]
- fn fmt_args() {
- let r = flags_from_vec_safe(svec![
- "deno",
- "fmt",
- "--check",
- "--prettierrc=auto",
- "--print-width=100",
- "--tab-width=4",
- "--use-tabs",
- "--no-semi",
- "--single-quote",
- "--arrow-parens=always",
- "--prose-wrap=preserve",
- "--end-of-line=crlf",
- "--quote-props=preserve",
- "--jsx-single-quote",
- "--jsx-bracket-same-line",
- "--ignore-path=.prettier-ignore",
- "script.ts"
- ]);
- assert_eq!(
- r.unwrap(),
- DenoFlags {
- subcommand: DenoSubcommand::Run,
- argv: svec![
- "deno",
- PRETTIER_URL,
- "script.ts",
- "--write",
- "--check",
- "--config",
- "auto",
- "--ignore-path",
- ".prettier-ignore",
- "--print-width",
- "100",
- "--tab-width",
- "4",
- "--use-tabs",
- "--no-semi",
- "--single-quote",
- "--quote-props",
- "preserve",
- "--jsx-single-quote",
- "--jsx-bracket-same-line",
- "--arrow-parens",
- "always",
- "--prose-wrap",
- "preserve",
- "--end-of-line",
- "crlf"
- ],
- allow_write: true,
- allow_read: true,
- ..DenoFlags::default()
- }
- );
- }
-
- #[test]
fn test_with_exclude() {
let r = flags_from_vec_safe(svec![
"deno",
diff --git a/cli/fmt.rs b/cli/fmt.rs
new file mode 100644
index 000000000..986be9fe1
--- /dev/null
+++ b/cli/fmt.rs
@@ -0,0 +1,162 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+//! This module provides file formating utilities using
+//! [`dprint`](https://github.com/dsherret/dprint).
+//!
+//! At the moment it is only consumed using CLI but in
+//! the future it can be easily extended to provide
+//! the same functions as ops available in JS runtime.
+
+use dprint_plugin_typescript::format_text;
+use dprint_plugin_typescript::Configuration;
+use dprint_plugin_typescript::ConfigurationBuilder;
+use glob;
+use regex::Regex;
+use std::fs;
+use std::path::Path;
+use std::path::PathBuf;
+use std::time::Instant;
+
+lazy_static! {
+ static ref TYPESCRIPT_LIB: Regex = Regex::new(".d.ts$").unwrap();
+ static ref TYPESCRIPT: Regex = Regex::new(".tsx?$").unwrap();
+ static ref JAVASCRIPT: Regex = Regex::new(".jsx?$").unwrap();
+}
+
+fn is_supported(path: &Path) -> bool {
+ let path_str = path.to_string_lossy();
+ !TYPESCRIPT_LIB.is_match(&path_str)
+ && (TYPESCRIPT.is_match(&path_str) || JAVASCRIPT.is_match(&path_str))
+}
+
+fn get_config() -> Configuration {
+ ConfigurationBuilder::new()
+ .line_width(80)
+ .indent_width(2)
+ .build()
+}
+
+fn get_supported_files(paths: Vec<PathBuf>) -> Vec<PathBuf> {
+ let mut files_to_check = vec![];
+
+ for path in paths {
+ if is_supported(&path) {
+ files_to_check.push(path.to_owned());
+ }
+ }
+
+ files_to_check
+}
+
+fn check_source_files(config: Configuration, paths: Vec<PathBuf>) {
+ let start = Instant::now();
+ let mut not_formatted_files = vec![];
+
+ for file_path in paths {
+ let file_path_str = file_path.to_string_lossy();
+ let file_contents = fs::read_to_string(&file_path).unwrap();
+ match format_text(&file_path_str, &file_contents, &config) {
+ Ok(None) => {
+ // nothing to format, pass
+ }
+ Ok(Some(formatted_text)) => {
+ if formatted_text != file_contents {
+ println!("Not formatted {}", file_path_str);
+ not_formatted_files.push(file_path);
+ }
+ }
+ Err(e) => {
+ eprintln!("Error checking: {}", &file_path_str);
+ eprintln!(" {}", e);
+ }
+ }
+ }
+
+ let duration = Instant::now() - start;
+
+ if !not_formatted_files.is_empty() {
+ let f = if not_formatted_files.len() == 1 {
+ "file"
+ } else {
+ "files"
+ };
+
+ eprintln!(
+ "Found {} not formatted {} in {:?}",
+ not_formatted_files.len(),
+ f,
+ duration
+ );
+ std::process::exit(1);
+ }
+}
+
+fn format_source_files(config: Configuration, paths: Vec<PathBuf>) {
+ let start = Instant::now();
+ let mut not_formatted_files = vec![];
+
+ for file_path in paths {
+ let file_path_str = file_path.to_string_lossy();
+ let file_contents = fs::read_to_string(&file_path).unwrap();
+ match format_text(&file_path_str, &file_contents, &config) {
+ Ok(None) => {
+ // nothing to format, pass
+ }
+ Ok(Some(formatted_text)) => {
+ if formatted_text != file_contents {
+ println!("Formatting {}", file_path_str);
+ fs::write(&file_path, formatted_text).unwrap();
+ not_formatted_files.push(file_path);
+ }
+ }
+ Err(e) => {
+ eprintln!("Error formatting: {}", &file_path_str);
+ eprintln!(" {}", e);
+ }
+ }
+ }
+
+ let duration = Instant::now() - start;
+ let f = if not_formatted_files.len() == 1 {
+ "file"
+ } else {
+ "files"
+ };
+ eprintln!(
+ "Formatted {} {} in {:?}",
+ not_formatted_files.len(),
+ f,
+ duration
+ );
+}
+
+fn get_matching_files(glob_paths: Vec<String>) -> Vec<PathBuf> {
+ let mut target_files = Vec::with_capacity(128);
+
+ for path in glob_paths {
+ let files = glob::glob(&path)
+ .expect("Failed to execute glob.")
+ .filter_map(Result::ok);
+ target_files.extend(files);
+ }
+
+ target_files
+}
+
+/// Format JavaScript/TypeScript files.
+///
+/// First argument supports globs, and if it is `None`
+/// then the current directory is recursively walked.
+pub fn format_files(maybe_files: Option<Vec<String>>, check: bool) {
+ // TODO: improve glob to look for tsx?/jsx? files only
+ let glob_paths = maybe_files.unwrap_or_else(|| vec!["**/*".to_string()]);
+ let matching_files = get_matching_files(glob_paths);
+ let matching_files = get_supported_files(matching_files);
+ let config = get_config();
+
+ if check {
+ check_source_files(config, matching_files);
+ } else {
+ format_source_files(config, matching_files);
+ }
+}
diff --git a/cli/lib.rs b/cli/lib.rs
index 56705289f..fe20cd135 100644
--- a/cli/lib.rs
+++ b/cli/lib.rs
@@ -28,6 +28,7 @@ pub mod diagnostics;
mod disk_cache;
mod file_fetcher;
pub mod flags;
+mod fmt;
pub mod fmt_errors;
mod fs;
mod global_state;
@@ -422,6 +423,10 @@ fn run_script(flags: DenoFlags) {
}
}
+fn format_command(files: Option<Vec<String>>, check: bool) {
+ fmt::format_files(files, check);
+}
+
pub fn main() {
#[cfg(windows)]
ansi_term::enable_ansi_support().ok(); // For Windows 10
@@ -442,11 +447,12 @@ pub fn main() {
};
log::set_max_level(log_level.to_level_filter());
- match flags.subcommand {
+ match flags.clone().subcommand {
DenoSubcommand::Bundle => bundle_command(flags),
DenoSubcommand::Completions => {}
DenoSubcommand::Eval => eval_command(flags),
DenoSubcommand::Fetch => fetch_command(flags),
+ DenoSubcommand::Format { check, files } => format_command(files, check),
DenoSubcommand::Info => info_command(flags),
DenoSubcommand::Repl => run_repl(flags),
DenoSubcommand::Run => run_script(flags),
diff --git a/cli/tests/badly_formatted.js b/cli/tests/badly_formatted.js
index 17e3e6be0..b646a95a3 100644
--- a/cli/tests/badly_formatted.js
+++ b/cli/tests/badly_formatted.js
@@ -1,4 +1,4 @@
-console.log(
- "Hello World"
+console.log("Hello World"
+
)
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 7f318119a..501ff1713 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -29,12 +29,44 @@ fn fetch_test() {
drop(g);
}
-// TODO(#2933): Rewrite this test in rust.
#[test]
fn fmt_test() {
- let g = util::http_server();
- util::run_python_script("tools/fmt_test.py");
- drop(g);
+ use tempfile::TempDir;
+
+ let t = TempDir::new().expect("tempdir fail");
+ let fixed = util::root_path().join("cli/tests/badly_formatted_fixed.js");
+ let badly_formatted_original =
+ util::root_path().join("cli/tests/badly_formatted.js");
+ let badly_formatted = t.path().join("badly_formatted.js");
+ let badly_formatted_str = badly_formatted.to_str().unwrap();
+ std::fs::copy(&badly_formatted_original, &badly_formatted)
+ .expect("Failed to copy file");
+
+ let status = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("fmt")
+ .arg("--check")
+ .arg(badly_formatted_str)
+ .spawn()
+ .expect("Failed to spawn script")
+ .wait()
+ .expect("Failed to wait for child process");
+
+ assert_eq!(Some(1), status.code());
+
+ let status = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("fmt")
+ .arg(badly_formatted_str)
+ .spawn()
+ .expect("Failed to spawn script")
+ .wait()
+ .expect("Failed to wait for child process");
+
+ assert_eq!(Some(0), status.code());
+ let expected = std::fs::read_to_string(fixed).unwrap();
+ let actual = std::fs::read_to_string(badly_formatted).unwrap();
+ assert_eq!(expected, actual);
}
#[test]