2022-01-07 22:09:52 -05:00
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
2020-01-29 21:16:48 -05:00
2020-09-16 09:38:38 -04:00
//! This module provides file formatting utilities using
2020-10-22 21:21:02 -04:00
//! [`dprint-plugin-typescript`](https://github.com/dprint/dprint-plugin-typescript).
2020-01-29 21:16:48 -05:00
//!
//! 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.
2022-06-27 16:54:09 -04:00
use crate ::args ::Flags ;
use crate ::args ::FmtConfig ;
use crate ::args ::FmtFlags ;
use crate ::args ::FmtOptionsConfig ;
use crate ::args ::ProseWrap ;
2020-05-23 09:22:08 -04:00
use crate ::colors ;
2022-04-19 22:14:00 -04:00
use crate ::deno_dir ::DenoDir ;
2020-05-23 09:22:08 -04:00
use crate ::diff ::diff ;
2020-11-22 15:45:44 -05:00
use crate ::file_watcher ;
2021-05-10 02:06:13 -04:00
use crate ::file_watcher ::ResolutionResult ;
2022-03-29 13:33:00 -04:00
use crate ::fs_util ::collect_files ;
use crate ::fs_util ::get_extension ;
2021-11-24 15:14:19 -05:00
use crate ::fs_util ::specifier_to_file_path ;
2020-08-03 17:39:48 -04:00
use crate ::text_encoding ;
2021-09-07 10:39:32 -04:00
use deno_ast ::ParsedSource ;
2022-01-04 17:02:56 -05:00
use deno_core ::anyhow ::bail ;
2021-12-09 20:24:37 -05:00
use deno_core ::anyhow ::Context ;
2020-09-14 12:48:57 -04:00
use deno_core ::error ::generic_error ;
use deno_core ::error ::AnyError ;
2020-09-21 12:36:37 -04:00
use deno_core ::futures ;
2022-03-29 13:33:00 -04:00
use deno_core ::parking_lot ::Mutex ;
2021-03-26 12:34:25 -04:00
use log ::debug ;
use log ::info ;
2020-01-29 21:16:48 -05:00
use std ::fs ;
2020-02-09 05:19:05 -05:00
use std ::io ::stdin ;
use std ::io ::stdout ;
use std ::io ::Read ;
use std ::io ::Write ;
2020-01-29 21:16:48 -05:00
use std ::path ::Path ;
use std ::path ::PathBuf ;
2022-03-29 13:33:00 -04:00
use std ::sync ::atomic ::AtomicUsize ;
use std ::sync ::atomic ::Ordering ;
use std ::sync ::Arc ;
2020-01-29 21:16:48 -05:00
2022-04-19 22:14:00 -04:00
use super ::incremental_cache ::IncrementalCache ;
2020-05-04 15:17:15 -04:00
/// Format JavaScript/TypeScript files.
2020-07-30 12:09:08 -04:00
pub async fn format (
2022-02-11 14:04:31 -05:00
flags : & Flags ,
2021-09-13 16:06:45 -04:00
fmt_flags : FmtFlags ,
2021-09-13 14:19:10 -04:00
maybe_fmt_config : Option < FmtConfig > ,
2022-04-19 22:14:00 -04:00
deno_dir : & DenoDir ,
2020-09-14 12:48:57 -04:00
) -> Result < ( ) , AnyError > {
2021-09-13 16:06:45 -04:00
let FmtFlags {
files ,
ignore ,
check ,
..
} = fmt_flags . clone ( ) ;
2021-09-13 14:19:10 -04:00
// First, prepare final configuration.
// Collect included and ignored files. CLI flags take precendence
// over config file, ie. if there's `files.ignore` in config file
// and `--ignore` CLI flag, only the flag value is taken into account.
2021-09-13 16:06:45 -04:00
let mut include_files = files . clone ( ) ;
2021-09-13 14:19:10 -04:00
let mut exclude_files = ignore ;
if let Some ( fmt_config ) = maybe_fmt_config . as_ref ( ) {
if include_files . is_empty ( ) {
2021-11-24 15:14:19 -05:00
include_files = fmt_config
. files
. include
. iter ( )
. filter_map ( | s | specifier_to_file_path ( s ) . ok ( ) )
. collect ::< Vec < _ > > ( ) ;
2021-09-13 14:19:10 -04:00
}
if exclude_files . is_empty ( ) {
2021-11-24 15:14:19 -05:00
exclude_files = fmt_config
. files
. exclude
. iter ( )
. filter_map ( | s | specifier_to_file_path ( s ) . ok ( ) )
. collect ::< Vec < _ > > ( ) ;
2021-09-13 14:19:10 -04:00
}
}
2021-10-30 13:20:56 -04:00
if include_files . is_empty ( ) {
include_files = [ std ::env ::current_dir ( ) ? ] . to_vec ( ) ;
}
2021-09-13 16:06:45 -04:00
// Now do the same for options
let fmt_options = resolve_fmt_options (
& fmt_flags ,
maybe_fmt_config . map ( | c | c . options ) . unwrap_or_default ( ) ,
) ;
2021-09-13 14:19:10 -04:00
2022-06-22 19:17:49 -04:00
let fmt_predicate = | path : & Path | {
is_supported_ext_fmt ( path )
& & ! contains_git ( path )
& & ! contains_node_modules ( path )
} ;
2022-03-29 14:57:42 -04:00
2021-05-10 02:06:13 -04:00
let resolver = | changed : Option < Vec < PathBuf > > | {
let files_changed = changed . is_some ( ) ;
2021-10-30 03:59:53 -04:00
2022-03-29 14:57:42 -04:00
let result = collect_files ( & include_files , & exclude_files , fmt_predicate )
. map ( | files | {
let refmt_files = if let Some ( paths ) = changed {
if check {
files
. iter ( )
. any ( | path | paths . contains ( path ) )
. then ( | | files )
. unwrap_or_else ( | | [ ] . to_vec ( ) )
2021-09-13 14:19:10 -04:00
} else {
2021-11-29 09:17:57 -05:00
files
2022-03-29 14:57:42 -04:00
. into_iter ( )
. filter ( | path | paths . contains ( path ) )
. collect ::< Vec < _ > > ( )
}
} else {
files
} ;
( refmt_files , fmt_options . clone ( ) )
} ) ;
2021-10-30 03:59:53 -04:00
2021-09-13 14:19:10 -04:00
let paths_to_watch = include_files . clone ( ) ;
2021-05-10 02:06:13 -04:00
async move {
2021-11-29 09:17:57 -05:00
if files_changed
& & matches! ( result , Ok ( ( ref files , _ ) ) if files . is_empty ( ) )
{
2021-05-10 02:06:13 -04:00
ResolutionResult ::Ignore
2021-02-19 07:18:16 -05:00
} else {
2021-05-10 02:06:13 -04:00
ResolutionResult ::Restart {
paths_to_watch ,
result ,
}
2021-02-19 07:18:16 -05:00
}
2021-05-10 02:06:13 -04:00
}
2020-11-22 15:45:44 -05:00
} ;
2021-09-13 14:19:10 -04:00
let operation = | ( paths , fmt_options ) : ( Vec < PathBuf > , FmtOptionsConfig ) | async move {
2022-04-19 22:14:00 -04:00
let incremental_cache = Arc ::new ( IncrementalCache ::new (
& deno_dir . fmt_incremental_cache_db_file_path ( ) ,
& fmt_options ,
& paths ,
) ) ;
2021-09-07 10:39:32 -04:00
if check {
2022-04-19 22:14:00 -04:00
check_source_files ( paths , fmt_options , incremental_cache . clone ( ) ) . await ? ;
2021-09-07 10:39:32 -04:00
} else {
2022-04-19 22:14:00 -04:00
format_source_files ( paths , fmt_options , incremental_cache . clone ( ) )
. await ? ;
2020-11-22 15:45:44 -05:00
}
2022-04-19 22:14:00 -04:00
incremental_cache . wait_completion ( ) . await ;
2021-09-07 10:39:32 -04:00
Ok ( ( ) )
2020-11-22 15:45:44 -05:00
} ;
2022-01-31 11:39:39 -05:00
if flags . watch . is_some ( ) {
file_watcher ::watch_func (
resolver ,
operation ,
file_watcher ::PrintConfig {
job_name : " Fmt " . to_string ( ) ,
clear_screen : ! flags . no_clear_screen ,
} ,
)
. await ? ;
2020-05-04 15:17:15 -04:00
} else {
2022-03-29 14:57:42 -04:00
let files = collect_files ( & include_files , & exclude_files , fmt_predicate )
. and_then ( | files | {
if files . is_empty ( ) {
Err ( generic_error ( " No target files found. " ) )
} else {
Ok ( files )
}
} ) ? ;
2021-10-30 03:59:53 -04:00
operation ( ( files , fmt_options . clone ( ) ) ) . await ? ;
2020-05-04 15:17:15 -04:00
}
2020-11-22 15:45:44 -05:00
Ok ( ( ) )
2020-03-25 17:24:26 -04:00
}
2021-09-05 10:22:45 -04:00
/// Formats markdown (using <https://github.com/dprint/dprint-plugin-markdown>) and its code blocks
2021-01-19 12:39:35 -05:00
/// (ts/tsx, js/jsx).
2021-09-13 14:19:10 -04:00
fn format_markdown (
file_text : & str ,
fmt_options : & FmtOptionsConfig ,
2022-03-29 13:33:00 -04:00
) -> Result < Option < String > , AnyError > {
2021-09-13 14:19:10 -04:00
let markdown_config = get_resolved_markdown_config ( fmt_options ) ;
2021-01-19 12:39:35 -05:00
dprint_plugin_markdown ::format_text (
2021-07-30 09:03:41 -04:00
file_text ,
2021-09-13 14:19:10 -04:00
& markdown_config ,
2021-06-06 12:42:12 -04:00
move | tag , text , line_width | {
2021-01-19 12:39:35 -05:00
let tag = tag . to_lowercase ( ) ;
if matches! (
tag . as_str ( ) ,
2021-02-18 11:31:32 -05:00
" ts "
| " tsx "
| " js "
| " jsx "
2022-06-09 19:55:04 -04:00
| " cjs "
| " cts "
| " mjs "
| " mts "
2021-02-18 11:31:32 -05:00
| " javascript "
| " typescript "
| " json "
| " jsonc "
2021-01-19 12:39:35 -05:00
) {
// It's important to tell dprint proper file extension, otherwise
// it might parse the file twice.
let extension = match tag . as_str ( ) {
" javascript " = > " js " ,
" typescript " = > " ts " ,
rest = > rest ,
} ;
2021-02-18 11:31:32 -05:00
if matches! ( extension , " json " | " jsonc " ) {
2021-09-13 14:19:10 -04:00
let mut json_config = get_resolved_json_config ( fmt_options ) ;
2021-02-18 11:31:32 -05:00
json_config . line_width = line_width ;
2022-03-29 13:33:00 -04:00
dprint_plugin_json ::format_text ( text , & json_config )
2021-02-18 11:31:32 -05:00
} else {
let fake_filename =
PathBuf ::from ( format! ( " deno_fmt_stdin. {} " , extension ) ) ;
2021-09-13 14:19:10 -04:00
let mut codeblock_config =
get_resolved_typescript_config ( fmt_options ) ;
2021-02-18 11:31:32 -05:00
codeblock_config . line_width = line_width ;
dprint_plugin_typescript ::format_text (
& fake_filename ,
2021-07-30 09:03:41 -04:00
text ,
2021-02-18 11:31:32 -05:00
& codeblock_config ,
)
}
2021-01-19 12:39:35 -05:00
} else {
2022-03-29 13:33:00 -04:00
Ok ( None )
2021-01-19 12:39:35 -05:00
}
2021-06-06 12:42:12 -04:00
} ,
2021-01-19 12:39:35 -05:00
)
}
2021-02-18 11:31:32 -05:00
/// Formats JSON and JSONC using the rules provided by .deno()
2021-09-05 10:22:45 -04:00
/// of configuration builder of <https://github.com/dprint/dprint-plugin-json>.
2022-04-26 18:39:47 -04:00
/// See <https://github.com/dprint/dprint-plugin-json/blob/cfa1052dbfa0b54eb3d814318034cdc514c813d7/src/configuration/builder.rs#L87> for configuration.
2021-12-07 19:21:04 -05:00
pub fn format_json (
2021-09-13 14:19:10 -04:00
file_text : & str ,
fmt_options : & FmtOptionsConfig ,
2022-03-29 13:33:00 -04:00
) -> Result < Option < String > , AnyError > {
2021-09-13 14:19:10 -04:00
let config = get_resolved_json_config ( fmt_options ) ;
2022-01-04 17:02:56 -05:00
dprint_plugin_json ::format_text ( file_text , & config )
2021-02-18 11:31:32 -05:00
}
2021-05-18 02:35:46 -04:00
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, or MD file.
pub fn format_file (
file_path : & Path ,
file_text : & str ,
2022-04-19 22:14:00 -04:00
fmt_options : & FmtOptionsConfig ,
2022-03-29 13:33:00 -04:00
) -> Result < Option < String > , AnyError > {
2022-02-24 20:03:12 -05:00
let ext = get_extension ( file_path ) . unwrap_or_default ( ) ;
2021-09-23 12:19:25 -04:00
if matches! (
ext . as_str ( ) ,
" md " | " mkd " | " mkdn " | " mdwn " | " mdown " | " markdown "
) {
2022-04-19 22:14:00 -04:00
format_markdown ( file_text , fmt_options )
2021-05-18 02:35:46 -04:00
} else if matches! ( ext . as_str ( ) , " json " | " jsonc " ) {
2022-04-19 22:14:00 -04:00
format_json ( file_text , fmt_options )
2021-05-18 02:35:46 -04:00
} else {
2022-04-19 22:14:00 -04:00
let config = get_resolved_typescript_config ( fmt_options ) ;
2021-09-13 14:19:10 -04:00
dprint_plugin_typescript ::format_text ( file_path , file_text , & config )
2021-05-18 02:35:46 -04:00
}
}
2021-10-21 10:18:18 -04:00
pub fn format_parsed_source (
2021-09-13 14:19:10 -04:00
parsed_source : & ParsedSource ,
fmt_options : FmtOptionsConfig ,
2022-03-29 13:33:00 -04:00
) -> Result < Option < String > , AnyError > {
2021-10-21 10:18:18 -04:00
dprint_plugin_typescript ::format_parsed_source (
parsed_source ,
2021-09-13 14:19:10 -04:00
& get_resolved_typescript_config ( & fmt_options ) ,
2021-09-07 10:39:32 -04:00
)
}
2021-09-13 14:19:10 -04:00
async fn check_source_files (
paths : Vec < PathBuf > ,
fmt_options : FmtOptionsConfig ,
2022-04-19 22:14:00 -04:00
incremental_cache : Arc < IncrementalCache > ,
2021-09-13 14:19:10 -04:00
) -> Result < ( ) , AnyError > {
2020-04-23 19:01:15 -04:00
let not_formatted_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-09-09 10:45:31 -04:00
let checked_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-05-23 09:22:08 -04:00
// prevent threads outputting at the same time
let output_lock = Arc ::new ( Mutex ::new ( 0 ) ) ;
2020-04-23 19:01:15 -04:00
run_parallelized ( paths , {
let not_formatted_files_count = not_formatted_files_count . clone ( ) ;
2020-09-09 10:45:31 -04:00
let checked_files_count = checked_files_count . clone ( ) ;
2020-04-23 19:01:15 -04:00
move | file_path | {
2020-09-09 10:45:31 -04:00
checked_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
2020-05-28 13:35:24 -04:00
let file_text = read_file_contents ( & file_path ) ? . text ;
2021-05-18 02:35:46 -04:00
2022-04-19 22:14:00 -04:00
// skip checking the file if we know it's formatted
if incremental_cache . is_file_same ( & file_path , & file_text ) {
return Ok ( ( ) ) ;
}
match format_file ( & file_path , & file_text , & fmt_options ) {
2022-03-29 13:33:00 -04:00
Ok ( Some ( formatted_text ) ) = > {
not_formatted_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
let _g = output_lock . lock ( ) ;
let diff = diff ( & file_text , & formatted_text ) ;
info! ( " " ) ;
info! ( " {} {}: " , colors ::bold ( " from " ) , file_path . display ( ) ) ;
info! ( " {} " , diff ) ;
2020-04-23 19:01:15 -04:00
}
2022-04-19 22:14:00 -04:00
Ok ( None ) = > {
// When checking formatting, only update the incremental cache when
// the file is the same since we don't bother checking for stable
// formatting here. Additionally, ensure this is done during check
// so that CIs that cache the DENO_DIR will get the benefit of
// incremental formatting
incremental_cache . update_file ( & file_path , & file_text ) ;
}
2020-04-23 19:01:15 -04:00
Err ( e ) = > {
2022-06-18 12:44:43 -04:00
not_formatted_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
2022-03-29 13:33:00 -04:00
let _g = output_lock . lock ( ) ;
2020-04-28 15:17:40 -04:00
eprintln! ( " Error checking: {} " , file_path . to_string_lossy ( ) ) ;
2020-04-23 19:01:15 -04:00
eprintln! ( " {} " , e ) ;
2020-01-29 21:16:48 -05:00
}
}
2020-04-23 19:01:15 -04:00
Ok ( ( ) )
2020-01-29 21:16:48 -05:00
}
2020-04-23 19:01:15 -04:00
} )
. await ? ;
2020-01-29 21:16:48 -05:00
2020-04-23 19:01:15 -04:00
let not_formatted_files_count =
2020-09-08 05:58:17 -04:00
not_formatted_files_count . load ( Ordering ::Relaxed ) ;
2020-09-09 10:45:31 -04:00
let checked_files_count = checked_files_count . load ( Ordering ::Relaxed ) ;
let checked_files_str =
format! ( " {} {} " , checked_files_count , files_str ( checked_files_count ) ) ;
2020-04-23 19:01:15 -04:00
if not_formatted_files_count = = 0 {
2020-09-20 07:49:22 -04:00
info! ( " Checked {} " , checked_files_str ) ;
2020-02-13 16:02:18 -05:00
Ok ( ( ) )
} else {
2020-09-09 10:45:31 -04:00
let not_formatted_files_str = files_str ( not_formatted_files_count ) ;
2020-09-14 12:48:57 -04:00
Err ( generic_error ( format! (
2020-09-09 10:45:31 -04:00
" Found {} not formatted {} in {} " ,
not_formatted_files_count , not_formatted_files_str , checked_files_str ,
2020-08-25 18:22:15 -04:00
) ) )
2020-01-29 21:16:48 -05:00
}
}
2021-09-13 14:19:10 -04:00
async fn format_source_files (
paths : Vec < PathBuf > ,
fmt_options : FmtOptionsConfig ,
2022-04-19 22:14:00 -04:00
incremental_cache : Arc < IncrementalCache > ,
2021-09-13 14:19:10 -04:00
) -> Result < ( ) , AnyError > {
2020-04-23 19:01:15 -04:00
let formatted_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-09-09 10:45:31 -04:00
let checked_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-04-23 19:01:15 -04:00
let output_lock = Arc ::new ( Mutex ::new ( 0 ) ) ; // prevent threads outputting at the same time
run_parallelized ( paths , {
let formatted_files_count = formatted_files_count . clone ( ) ;
2020-09-09 10:45:31 -04:00
let checked_files_count = checked_files_count . clone ( ) ;
2020-04-23 19:01:15 -04:00
move | file_path | {
2020-09-09 10:45:31 -04:00
checked_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
2020-05-28 13:35:24 -04:00
let file_contents = read_file_contents ( & file_path ) ? ;
2021-05-18 02:35:46 -04:00
2022-04-19 22:14:00 -04:00
// skip formatting the file if we know it's formatted
if incremental_cache . is_file_same ( & file_path , & file_contents . text ) {
return Ok ( ( ) ) ;
}
match format_ensure_stable (
& file_path ,
& file_contents . text ,
& fmt_options ,
format_file ,
) {
2022-03-29 13:33:00 -04:00
Ok ( Some ( formatted_text ) ) = > {
2022-04-19 22:14:00 -04:00
incremental_cache . update_file ( & file_path , & formatted_text ) ;
2022-03-29 13:33:00 -04:00
write_file_contents (
& file_path ,
FileContents {
had_bom : file_contents . had_bom ,
text : formatted_text ,
} ,
) ? ;
formatted_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
let _g = output_lock . lock ( ) ;
info! ( " {} " , file_path . to_string_lossy ( ) ) ;
2020-04-23 19:01:15 -04:00
}
2022-04-19 22:14:00 -04:00
Ok ( None ) = > {
incremental_cache . update_file ( & file_path , & file_contents . text ) ;
}
2020-04-23 19:01:15 -04:00
Err ( e ) = > {
2022-03-29 13:33:00 -04:00
let _g = output_lock . lock ( ) ;
2020-04-28 15:17:40 -04:00
eprintln! ( " Error formatting: {} " , file_path . to_string_lossy ( ) ) ;
2020-04-23 19:01:15 -04:00
eprintln! ( " {} " , e ) ;
2020-01-29 21:16:48 -05:00
}
}
2020-04-23 19:01:15 -04:00
Ok ( ( ) )
2020-01-29 21:16:48 -05:00
}
2020-04-23 19:01:15 -04:00
} )
. await ? ;
2020-09-08 05:58:17 -04:00
let formatted_files_count = formatted_files_count . load ( Ordering ::Relaxed ) ;
2020-04-08 10:31:48 -04:00
debug! (
" Formatted {} {} " ,
2020-04-23 19:01:15 -04:00
formatted_files_count ,
files_str ( formatted_files_count ) ,
2020-04-08 10:31:48 -04:00
) ;
2020-09-09 10:45:31 -04:00
let checked_files_count = checked_files_count . load ( Ordering ::Relaxed ) ;
2020-09-20 07:49:22 -04:00
info! (
2020-09-09 10:45:31 -04:00
" Checked {} {} " ,
checked_files_count ,
files_str ( checked_files_count )
) ;
2020-02-26 05:50:53 -05:00
Ok ( ( ) )
2020-01-29 21:16:48 -05:00
}
2022-04-19 22:14:00 -04:00
/// When storing any formatted text in the incremental cache, we want
/// to ensure that anything stored when formatted will have itself as
/// the output as well. This is to prevent "double format" issues where
/// a user formats their code locally and it fails on the CI afterwards.
fn format_ensure_stable (
file_path : & Path ,
file_text : & str ,
fmt_options : & FmtOptionsConfig ,
fmt_func : impl Fn (
& Path ,
& str ,
& FmtOptionsConfig ,
) -> Result < Option < String > , AnyError > ,
) -> Result < Option < String > , AnyError > {
let formatted_text = fmt_func ( file_path , file_text , fmt_options ) ? ;
match formatted_text {
Some ( mut current_text ) = > {
let mut count = 0 ;
loop {
match fmt_func ( file_path , & current_text , fmt_options ) {
Ok ( Some ( next_pass_text ) ) = > {
// just in case
if next_pass_text = = current_text {
return Ok ( Some ( next_pass_text ) ) ;
}
current_text = next_pass_text ;
}
Ok ( None ) = > {
return Ok ( Some ( current_text ) ) ;
}
Err ( err ) = > {
panic! (
concat! (
" Formatting succeeded initially, but failed when ensuring a " ,
" stable format. This indicates a bug in the formatter where " ,
" the text it produces is not syntatically correct. As a temporary " ,
" workfaround you can ignore this file. \n \n {:#} "
) ,
err ,
)
}
}
count + = 1 ;
if count = = 5 {
panic! (
concat! (
" Formatting not stable. Bailed after {} tries. This indicates a bug " ,
" in the formatter where it formats the file differently each time. As a " ,
" temporary workaround you can ignore this file. "
) ,
count
)
}
}
}
None = > Ok ( None ) ,
}
}
2020-02-09 05:19:05 -05:00
/// Format stdin and write result to stdout.
2021-01-19 12:39:35 -05:00
/// Treats input as TypeScript or as set by `--ext` flag.
2020-02-09 05:19:05 -05:00
/// Compatible with `--check` flag.
2021-09-13 14:19:10 -04:00
pub fn format_stdin (
2021-09-13 16:06:45 -04:00
fmt_flags : FmtFlags ,
2021-09-13 14:19:10 -04:00
fmt_options : FmtOptionsConfig ,
) -> Result < ( ) , AnyError > {
2020-02-09 05:19:05 -05:00
let mut source = String ::new ( ) ;
if stdin ( ) . read_to_string ( & mut source ) . is_err ( ) {
2022-01-04 17:02:56 -05:00
bail! ( " Failed to read from stdin " ) ;
2020-02-09 05:19:05 -05:00
}
2021-09-13 16:06:45 -04:00
let file_path = PathBuf ::from ( format! ( " _stdin. {} " , fmt_flags . ext ) ) ;
let fmt_options = resolve_fmt_options ( & fmt_flags , fmt_options ) ;
2021-05-18 02:35:46 -04:00
2022-04-19 22:14:00 -04:00
let formatted_text = format_file ( & file_path , & source , & fmt_options ) ? ;
2022-01-04 17:02:56 -05:00
if fmt_flags . check {
2022-03-29 13:33:00 -04:00
if formatted_text . is_some ( ) {
2022-01-04 17:02:56 -05:00
println! ( " Not formatted stdin " ) ;
2020-02-09 05:19:05 -05:00
}
2022-01-04 17:02:56 -05:00
} else {
2022-03-29 13:33:00 -04:00
stdout ( ) . write_all ( formatted_text . unwrap_or ( source ) . as_bytes ( ) ) ? ;
2020-02-09 05:19:05 -05:00
}
2020-02-27 15:39:41 -05:00
Ok ( ( ) )
2020-01-29 21:16:48 -05:00
}
2020-02-13 16:02:18 -05:00
2020-05-04 15:17:15 -04:00
fn files_str ( len : usize ) -> & 'static str {
2020-09-20 07:49:22 -04:00
if len < = 1 {
2020-05-04 15:17:15 -04:00
" file "
} else {
" files "
}
}
2021-09-13 16:06:45 -04:00
fn resolve_fmt_options (
fmt_flags : & FmtFlags ,
options : FmtOptionsConfig ,
) -> FmtOptionsConfig {
let mut options = options ;
if let Some ( use_tabs ) = fmt_flags . use_tabs {
options . use_tabs = Some ( use_tabs ) ;
}
if let Some ( line_width ) = fmt_flags . line_width {
options . line_width = Some ( line_width . get ( ) ) ;
}
if let Some ( indent_width ) = fmt_flags . indent_width {
options . indent_width = Some ( indent_width . get ( ) ) ;
}
if let Some ( single_quote ) = fmt_flags . single_quote {
options . single_quote = Some ( single_quote ) ;
}
if let Some ( prose_wrap ) = & fmt_flags . prose_wrap {
options . prose_wrap = Some ( match prose_wrap . as_str ( ) {
" always " = > ProseWrap ::Always ,
" never " = > ProseWrap ::Never ,
" preserve " = > ProseWrap ::Preserve ,
// validators in `flags.rs` makes other values unreachable
_ = > unreachable! ( ) ,
} ) ;
}
options
}
2021-09-13 14:19:10 -04:00
fn get_resolved_typescript_config (
options : & FmtOptionsConfig ,
) -> dprint_plugin_typescript ::configuration ::Configuration {
let mut builder =
dprint_plugin_typescript ::configuration ::ConfigurationBuilder ::new ( ) ;
builder . deno ( ) ;
if let Some ( use_tabs ) = options . use_tabs {
builder . use_tabs ( use_tabs ) ;
}
2021-01-19 12:39:35 -05:00
2021-09-13 14:19:10 -04:00
if let Some ( line_width ) = options . line_width {
builder . line_width ( line_width ) ;
}
if let Some ( indent_width ) = options . indent_width {
builder . indent_width ( indent_width ) ;
}
if let Some ( single_quote ) = options . single_quote {
if single_quote {
builder . quote_style (
dprint_plugin_typescript ::configuration ::QuoteStyle ::AlwaysSingle ,
) ;
}
}
builder . build ( )
}
fn get_resolved_markdown_config (
options : & FmtOptionsConfig ,
) -> dprint_plugin_markdown ::configuration ::Configuration {
let mut builder =
dprint_plugin_markdown ::configuration ::ConfigurationBuilder ::new ( ) ;
builder . deno ( ) ;
if let Some ( line_width ) = options . line_width {
builder . line_width ( line_width ) ;
}
if let Some ( prose_wrap ) = options . prose_wrap {
builder . text_wrap ( match prose_wrap {
ProseWrap ::Always = > {
dprint_plugin_markdown ::configuration ::TextWrap ::Always
}
ProseWrap ::Never = > {
dprint_plugin_markdown ::configuration ::TextWrap ::Never
}
ProseWrap ::Preserve = > {
dprint_plugin_markdown ::configuration ::TextWrap ::Maintain
}
} ) ;
}
builder . build ( )
}
fn get_resolved_json_config (
options : & FmtOptionsConfig ,
) -> dprint_plugin_json ::configuration ::Configuration {
let mut builder =
dprint_plugin_json ::configuration ::ConfigurationBuilder ::new ( ) ;
builder . deno ( ) ;
if let Some ( use_tabs ) = options . use_tabs {
builder . use_tabs ( use_tabs ) ;
}
if let Some ( line_width ) = options . line_width {
builder . line_width ( line_width ) ;
}
if let Some ( indent_width ) = options . indent_width {
builder . indent_width ( indent_width ) ;
}
2020-05-04 15:17:15 -04:00
2021-09-13 14:19:10 -04:00
builder . build ( )
2021-02-18 11:31:32 -05:00
}
2020-05-28 13:35:24 -04:00
struct FileContents {
text : String ,
had_bom : bool ,
}
2020-09-14 12:48:57 -04:00
fn read_file_contents ( file_path : & Path ) -> Result < FileContents , AnyError > {
2021-12-09 20:24:37 -05:00
let file_bytes = fs ::read ( & file_path )
. with_context ( | | format! ( " Error reading {} " , file_path . display ( ) ) ) ? ;
2020-08-03 17:39:48 -04:00
let charset = text_encoding ::detect_charset ( & file_bytes ) ;
let file_text = text_encoding ::convert_to_utf8 ( & file_bytes , charset ) ? ;
2021-08-16 03:28:29 -04:00
let had_bom = file_text . starts_with ( text_encoding ::BOM_CHAR ) ;
2020-05-28 13:35:24 -04:00
let text = if had_bom {
2021-08-16 03:28:29 -04:00
text_encoding ::strip_bom ( & file_text ) . to_string ( )
2020-05-28 13:35:24 -04:00
} else {
2021-08-16 03:28:29 -04:00
file_text . to_string ( )
2020-05-28 13:35:24 -04:00
} ;
Ok ( FileContents { text , had_bom } )
}
fn write_file_contents (
2020-09-08 05:58:17 -04:00
file_path : & Path ,
2020-05-28 13:35:24 -04:00
file_contents : FileContents ,
2020-09-14 12:48:57 -04:00
) -> Result < ( ) , AnyError > {
2020-05-28 13:35:24 -04:00
let file_text = if file_contents . had_bom {
// add back the BOM
2021-08-16 03:28:29 -04:00
format! ( " {} {} " , text_encoding ::BOM_CHAR , file_contents . text )
2020-05-28 13:35:24 -04:00
} else {
file_contents . text
} ;
Ok ( fs ::write ( file_path , file_text ) ? )
}
2020-06-11 19:44:17 -04:00
pub async fn run_parallelized < F > (
2020-04-23 19:01:15 -04:00
file_paths : Vec < PathBuf > ,
f : F ,
2020-09-14 12:48:57 -04:00
) -> Result < ( ) , AnyError >
2020-04-23 19:01:15 -04:00
where
2020-09-14 12:48:57 -04:00
F : FnOnce ( PathBuf ) -> Result < ( ) , AnyError > + Send + 'static + Clone ,
2020-04-23 19:01:15 -04:00
{
let handles = file_paths . iter ( ) . map ( | file_path | {
let f = f . clone ( ) ;
let file_path = file_path . clone ( ) ;
tokio ::task ::spawn_blocking ( move | | f ( file_path ) )
} ) ;
let join_results = futures ::future ::join_all ( handles ) . await ;
// find the tasks that panicked and let the user know which files
let panic_file_paths = join_results
. iter ( )
. enumerate ( )
. filter_map ( | ( i , join_result ) | {
join_result
. as_ref ( )
. err ( )
. map ( | _ | file_paths [ i ] . to_string_lossy ( ) )
} )
. collect ::< Vec < _ > > ( ) ;
if ! panic_file_paths . is_empty ( ) {
panic! ( " Panic formatting: {} " , panic_file_paths . join ( " , " ) )
}
// check for any errors and if so return the first one
let mut errors = join_results . into_iter ( ) . filter_map ( | join_result | {
join_result
. ok ( )
2022-02-24 20:03:12 -05:00
. and_then ( | handle_result | handle_result . err ( ) )
2020-04-23 19:01:15 -04:00
} ) ;
if let Some ( e ) = errors . next ( ) {
Err ( e )
} else {
Ok ( ( ) )
}
}
2022-01-17 20:10:17 -05:00
/// This function is similar to is_supported_ext but adds additional extensions
/// supported by `deno fmt`.
fn is_supported_ext_fmt ( path : & Path ) -> bool {
if let Some ( ext ) = get_extension ( path ) {
matches! (
ext . as_str ( ) ,
" ts "
| " tsx "
| " js "
| " jsx "
2022-06-09 19:55:04 -04:00
| " cjs "
| " cts "
2022-01-17 20:10:17 -05:00
| " mjs "
2022-06-09 19:55:04 -04:00
| " mts "
2022-01-17 20:10:17 -05:00
| " json "
| " jsonc "
| " md "
| " mkd "
| " mkdn "
| " mdwn "
| " mdown "
| " markdown "
)
} else {
false
}
}
2022-06-22 19:17:49 -04:00
fn contains_git ( path : & Path ) -> bool {
2022-03-29 14:57:42 -04:00
path . components ( ) . any ( | c | c . as_os_str ( ) = = " .git " )
}
2022-06-22 19:17:49 -04:00
fn contains_node_modules ( path : & Path ) -> bool {
path . components ( ) . any ( | c | c . as_os_str ( ) = = " node_modules " )
}
2022-04-19 22:14:00 -04:00
#[ cfg(test) ]
mod test {
use super ::* ;
#[ test ]
fn test_is_supported_ext_fmt ( ) {
assert! ( ! is_supported_ext_fmt ( Path ::new ( " tests/subdir/redirects " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " README.md " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " readme.MD " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " readme.mkd " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " readme.mkdn " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " readme.mdwn " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " readme.mdown " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " readme.markdown " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " lib/typescript.d.ts " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " testdata/001_hello.js " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " testdata/002_hello.ts " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.jsx " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.tsx " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.TS " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.TSX " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.JS " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.JSX " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.mjs " ) ) ) ;
assert! ( ! is_supported_ext_fmt ( Path ::new ( " foo.mjsx " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.jsonc " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.JSONC " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.json " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.JsON " ) ) ) ;
}
#[ test ]
fn test_is_located_in_git ( ) {
2022-06-22 19:17:49 -04:00
assert! ( contains_git ( Path ::new ( " test/.git " ) ) ) ;
assert! ( contains_git ( Path ::new ( " .git/bad.json " ) ) ) ;
assert! ( contains_git ( Path ::new ( " test/.git/bad.json " ) ) ) ;
assert! ( ! contains_git ( Path ::new ( " test/bad.git/bad.json " ) ) ) ;
}
#[ test ]
fn test_is_located_in_node_modules ( ) {
assert! ( contains_node_modules ( Path ::new ( " test/node_modules " ) ) ) ;
assert! ( contains_node_modules ( Path ::new ( " node_modules/bad.json " ) ) ) ;
assert! ( contains_node_modules ( Path ::new (
" test/node_modules/bad.json "
) ) ) ;
assert! ( ! contains_node_modules ( Path ::new (
" test/bad.node_modules/bad.json "
) ) ) ;
2022-04-19 22:14:00 -04:00
}
#[ test ]
#[ should_panic(expected = " Formatting not stable. Bailed after 5 tries. " ) ]
fn test_format_ensure_stable_unstable_format ( ) {
format_ensure_stable (
& PathBuf ::from ( " mod.ts " ) ,
" 1 " ,
& Default ::default ( ) ,
| _ , file_text , _ | Ok ( Some ( format! ( " 1 {} " , file_text ) ) ) ,
)
. unwrap ( ) ;
}
#[ test ]
fn test_format_ensure_stable_error_first ( ) {
let err = format_ensure_stable (
& PathBuf ::from ( " mod.ts " ) ,
" 1 " ,
& Default ::default ( ) ,
| _ , _ , _ | bail! ( " Error formatting. " ) ,
)
. unwrap_err ( ) ;
assert_eq! ( err . to_string ( ) , " Error formatting. " ) ;
}
2022-03-29 14:57:42 -04:00
2022-04-19 22:14:00 -04:00
#[ test ]
#[ should_panic(expected = " Formatting succeeded initially, but failed when " ) ]
fn test_format_ensure_stable_error_second ( ) {
format_ensure_stable (
& PathBuf ::from ( " mod.ts " ) ,
" 1 " ,
& Default ::default ( ) ,
| _ , file_text , _ | {
if file_text = = " 1 " {
Ok ( Some ( " 11 " . to_string ( ) ) )
} else {
bail! ( " Error formatting. " )
}
} ,
)
. unwrap ( ) ;
}
#[ test ]
fn test_format_stable_after_two ( ) {
let result = format_ensure_stable (
& PathBuf ::from ( " mod.ts " ) ,
" 1 " ,
& Default ::default ( ) ,
| _ , file_text , _ | {
if file_text = = " 1 " {
Ok ( Some ( " 11 " . to_string ( ) ) )
} else if file_text = = " 11 " {
Ok ( None )
} else {
unreachable! ( ) ;
}
} ,
)
. unwrap ( ) ;
assert_eq! ( result , Some ( " 11 " . to_string ( ) ) ) ;
}
2022-03-29 14:57:42 -04:00
}