mirror of
https://github.com/denoland/deno
synced 2024-11-05 18:45:24 +00:00
220 lines
5.9 KiB
Rust
220 lines
5.9 KiB
Rust
// 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 crate::fs::files_in_subtree;
|
|
use crate::op_error::OpError;
|
|
use deno_core::ErrBox;
|
|
use dprint_plugin_typescript as dprint;
|
|
use std::fs;
|
|
use std::io::stdin;
|
|
use std::io::stdout;
|
|
use std::io::Read;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
fn is_supported(path: &Path) -> bool {
|
|
if let Some(ext) = path.extension() {
|
|
if ext == "tsx" || ext == "js" || ext == "jsx" {
|
|
true
|
|
} else if ext == "ts" {
|
|
// Currently dprint does not support d.ts files.
|
|
// https://github.com/dsherret/dprint/issues/100
|
|
!path.as_os_str().to_string_lossy().ends_with(".d.ts")
|
|
} else {
|
|
false
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn get_config() -> dprint::configuration::Configuration {
|
|
dprint::configuration::ConfigurationBuilder::new()
|
|
.line_width(80)
|
|
.indent_width(2)
|
|
.next_control_flow_position(
|
|
dprint::configuration::NextControlFlowPosition::SameLine,
|
|
)
|
|
.binary_expression_operator_position(
|
|
dprint::configuration::OperatorPosition::SameLine,
|
|
)
|
|
.build()
|
|
}
|
|
|
|
fn check_source_files(
|
|
config: dprint::configuration::Configuration,
|
|
paths: Vec<PathBuf>,
|
|
) -> Result<(), ErrBox> {
|
|
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 dprint::format_text(&file_path_str, &file_contents, &config) {
|
|
Ok(None) => {
|
|
// nothing to format, pass
|
|
}
|
|
Ok(Some(formatted_text)) => {
|
|
if formatted_text != file_contents {
|
|
not_formatted_files.push(file_path);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error checking: {}", &file_path_str);
|
|
eprintln!(" {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
if not_formatted_files.is_empty() {
|
|
Ok(())
|
|
} else {
|
|
let f = if not_formatted_files.len() == 1 {
|
|
"file"
|
|
} else {
|
|
"files"
|
|
};
|
|
Err(
|
|
OpError::other(format!(
|
|
"Found {} not formatted {}",
|
|
not_formatted_files.len(),
|
|
f,
|
|
))
|
|
.into(),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn format_source_files(
|
|
config: dprint::configuration::Configuration,
|
|
paths: Vec<PathBuf>,
|
|
) -> Result<(), ErrBox> {
|
|
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)?;
|
|
// TODO(ry) dprint seems to panic unnecessarally sometimes. Until it matures
|
|
// we'll use a catch_unwind to avoid passing it on to our users.
|
|
let catch_unwind_result = std::panic::catch_unwind(|| {
|
|
dprint::format_text(&file_path_str, &file_contents, &config)
|
|
});
|
|
if let Ok(dprint_result) = catch_unwind_result {
|
|
match dprint_result {
|
|
Ok(None) => {
|
|
// nothing to format, pass
|
|
}
|
|
Ok(Some(formatted_text)) => {
|
|
if formatted_text != file_contents {
|
|
println!("{}", file_path_str);
|
|
fs::write(&file_path, formatted_text)?;
|
|
not_formatted_files.push(file_path);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error formatting: {}", &file_path_str);
|
|
eprintln!(" {}", e);
|
|
}
|
|
}
|
|
} else {
|
|
eprintln!("dprint panic {}", file_path_str);
|
|
}
|
|
}
|
|
|
|
let f = if not_formatted_files.len() == 1 {
|
|
"file"
|
|
} else {
|
|
"files"
|
|
};
|
|
debug!("Formatted {} {}", not_formatted_files.len(), f);
|
|
Ok(())
|
|
}
|
|
|
|
/// Format JavaScript/TypeScript files.
|
|
///
|
|
/// First argument supports globs, and if it is `None`
|
|
/// then the current directory is recursively walked.
|
|
pub fn format(args: Vec<String>, check: bool) -> Result<(), ErrBox> {
|
|
if args.len() == 1 && args[0] == "-" {
|
|
return format_stdin(check);
|
|
}
|
|
|
|
let mut target_files: Vec<PathBuf> = vec![];
|
|
|
|
if args.is_empty() {
|
|
target_files.extend(files_in_subtree(
|
|
std::env::current_dir().unwrap(),
|
|
is_supported,
|
|
));
|
|
} else {
|
|
for arg in args {
|
|
let p = PathBuf::from(arg);
|
|
if p.is_dir() {
|
|
target_files.extend(files_in_subtree(p, is_supported));
|
|
} else {
|
|
target_files.push(p);
|
|
};
|
|
}
|
|
}
|
|
let config = get_config();
|
|
if check {
|
|
check_source_files(config, target_files)?;
|
|
} else {
|
|
format_source_files(config, target_files)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Format stdin and write result to stdout.
|
|
/// Treats input as TypeScript.
|
|
/// Compatible with `--check` flag.
|
|
fn format_stdin(check: bool) -> Result<(), ErrBox> {
|
|
let mut source = String::new();
|
|
if stdin().read_to_string(&mut source).is_err() {
|
|
return Err(OpError::other("Failed to read from stdin".to_string()).into());
|
|
}
|
|
let config = get_config();
|
|
|
|
match dprint::format_text("_stdin.ts", &source, &config) {
|
|
Ok(None) => unreachable!(),
|
|
Ok(Some(formatted_text)) => {
|
|
if check {
|
|
if formatted_text != source {
|
|
println!("Not formatted stdin");
|
|
}
|
|
} else {
|
|
stdout().write_all(formatted_text.as_bytes())?;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
return Err(OpError::other(e).into());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_supported() {
|
|
assert!(!is_supported(Path::new("tests/subdir/redirects")));
|
|
assert!(!is_supported(Path::new("README.md")));
|
|
assert!(!is_supported(Path::new("lib/typescript.d.ts")));
|
|
assert!(is_supported(Path::new("cli/tests/001_hello.js")));
|
|
assert!(is_supported(Path::new("cli/tests/002_hello.ts")));
|
|
assert!(is_supported(Path::new("foo.jsx")));
|
|
assert!(is_supported(Path::new("foo.tsx")));
|
|
}
|
|
|
|
#[test]
|
|
fn check_tests_dir() {
|
|
// Because of cli/tests/error_syntax.js the following should fail but not
|
|
// crash.
|
|
let r = format(vec!["./tests".to_string()], true);
|
|
assert!(r.is_err());
|
|
}
|