2024-01-01 19:58:21 +00:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2020-01-30 02:16:48 +00:00
2020-09-16 13:38:38 +00:00
//! This module provides file formatting utilities using
2020-10-23 01:21:02 +00:00
//! [`dprint-plugin-typescript`](https://github.com/dprint/dprint-plugin-typescript).
2020-01-30 02:16:48 +00: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-29 15:51:11 +00:00
use crate ::args ::CliOptions ;
2023-06-14 22:29:19 +00:00
use crate ::args ::Flags ;
use crate ::args ::FmtFlags ;
2023-01-07 20:22:09 +00:00
use crate ::args ::FmtOptions ;
2022-06-27 20:54:09 +00:00
use crate ::args ::FmtOptionsConfig ;
use crate ::args ::ProseWrap ;
2020-05-23 13:22:08 +00:00
use crate ::colors ;
2023-05-01 18:35:23 +00:00
use crate ::factory ::CliFactory ;
2022-11-28 22:28:54 +00:00
use crate ::util ::diff ::diff ;
use crate ::util ::file_watcher ;
2024-01-08 17:18:42 +00:00
use crate ::util ::fs ::canonicalize_path ;
2022-12-07 18:10:10 +00:00
use crate ::util ::fs ::FileCollector ;
2022-11-28 22:28:54 +00:00
use crate ::util ::path ::get_extension ;
2021-09-07 14:39:32 +00:00
use deno_ast ::ParsedSource ;
2024-01-16 00:15:39 +00:00
use deno_config ::glob ::FilePatterns ;
2023-01-16 00:30:52 +00:00
use deno_core ::anyhow ::anyhow ;
2022-01-04 22:02:56 +00:00
use deno_core ::anyhow ::bail ;
2021-12-10 01:24:37 +00:00
use deno_core ::anyhow ::Context ;
2020-09-14 16:48:57 +00:00
use deno_core ::error ::generic_error ;
use deno_core ::error ::AnyError ;
2020-09-21 16:36:37 +00:00
use deno_core ::futures ;
2022-03-29 17:33:00 +00:00
use deno_core ::parking_lot ::Mutex ;
2023-08-23 23:03:05 +00:00
use deno_core ::unsync ::spawn_blocking ;
2021-03-26 16:34:25 +00:00
use log ::debug ;
use log ::info ;
2022-12-09 15:54:24 +00:00
use log ::warn ;
2020-01-30 02:16:48 +00:00
use std ::fs ;
2020-02-09 10:19:05 +00:00
use std ::io ::stdin ;
use std ::io ::stdout ;
use std ::io ::Read ;
use std ::io ::Write ;
2020-01-30 02:16:48 +00:00
use std ::path ::Path ;
use std ::path ::PathBuf ;
2022-03-29 17:33:00 +00:00
use std ::sync ::atomic ::AtomicUsize ;
use std ::sync ::atomic ::Ordering ;
use std ::sync ::Arc ;
2020-01-30 02:16:48 +00:00
2022-07-12 22:58:39 +00:00
use crate ::cache ::IncrementalCache ;
2022-04-20 02:14:00 +00:00
2020-05-04 19:17:15 +00:00
/// Format JavaScript/TypeScript files.
2023-06-14 22:29:19 +00:00
pub async fn format ( flags : Flags , fmt_flags : FmtFlags ) -> Result < ( ) , AnyError > {
if fmt_flags . is_stdin ( ) {
let cli_options = CliOptions ::from_flags ( flags ) ? ;
let fmt_options = cli_options . resolve_fmt_options ( fmt_flags ) ? ;
2023-03-22 14:15:53 +00:00
return format_stdin (
fmt_options ,
cli_options
. ext_flag ( )
. as_ref ( )
. map ( | s | s . as_str ( ) )
. unwrap_or ( " ts " ) ,
) ;
2021-09-13 18:19:10 +00:00
}
2023-06-15 17:09:37 +00:00
if let Some ( watch_flags ) = & fmt_flags . watch {
2022-01-31 16:39:39 +00:00
file_watcher ::watch_func (
2023-06-14 22:29:19 +00:00
flags ,
2023-10-31 00:25:58 +00:00
file_watcher ::PrintConfig ::new ( " Fmt " , ! watch_flags . no_clear_screen ) ,
2023-10-19 05:05:00 +00:00
move | flags , watcher_communicator , changed_paths | {
2023-06-14 22:29:19 +00:00
let fmt_flags = fmt_flags . clone ( ) ;
Ok ( async move {
2024-03-12 03:48:00 +00:00
let factory = CliFactory ::from_flags ( flags ) ? ;
2023-06-14 22:29:19 +00:00
let cli_options = factory . cli_options ( ) ;
let fmt_options = cli_options . resolve_fmt_options ( fmt_flags ) ? ;
2024-03-27 18:25:39 +00:00
let files = collect_fmt_files ( cli_options , fmt_options . files . clone ( ) )
. and_then ( | files | {
2023-06-14 22:29:19 +00:00
if files . is_empty ( ) {
Err ( generic_error ( " No target files found. " ) )
} else {
Ok ( files )
}
} ) ? ;
2023-10-31 00:25:58 +00:00
let _ = watcher_communicator . watch_paths ( files . clone ( ) ) ;
2023-06-14 22:29:19 +00:00
let refmt_files = if let Some ( paths ) = changed_paths {
if fmt_options . check {
// check all files on any changed (https://github.com/denoland/deno/issues/12446)
files
. iter ( )
2024-01-08 17:18:42 +00:00
. any ( | path | {
canonicalize_path ( path )
. map ( | path | paths . contains ( & path ) )
. unwrap_or ( false )
} )
2023-06-14 22:29:19 +00:00
. then_some ( files )
. unwrap_or_else ( | | [ ] . to_vec ( ) )
} else {
files
. into_iter ( )
2024-01-08 17:18:42 +00:00
. filter ( | path | {
canonicalize_path ( path )
. map ( | path | paths . contains ( & path ) )
. unwrap_or ( false )
} )
2023-06-14 22:29:19 +00:00
. collect ::< Vec < _ > > ( )
}
} else {
files
} ;
format_files ( factory , fmt_options , refmt_files ) . await ? ;
Ok ( ( ) )
} )
2022-01-31 16:39:39 +00:00
} ,
)
. await ? ;
2020-05-04 19:17:15 +00:00
} else {
2024-03-12 03:48:00 +00:00
let factory = CliFactory ::from_flags ( flags ) ? ;
2023-06-14 22:29:19 +00:00
let cli_options = factory . cli_options ( ) ;
let fmt_options = cli_options . resolve_fmt_options ( fmt_flags ) ? ;
2024-03-27 18:25:39 +00:00
let files = collect_fmt_files ( cli_options , fmt_options . files . clone ( ) )
. and_then ( | files | {
2024-01-08 17:18:42 +00:00
if files . is_empty ( ) {
Err ( generic_error ( " No target files found. " ) )
} else {
Ok ( files )
}
} ) ? ;
2023-06-14 22:29:19 +00:00
format_files ( factory , fmt_options , files ) . await ? ;
2020-05-04 19:17:15 +00:00
}
2020-11-22 20:45:44 +00:00
Ok ( ( ) )
2020-03-25 21:24:26 +00:00
}
2023-06-14 22:29:19 +00:00
async fn format_files (
factory : CliFactory ,
fmt_options : FmtOptions ,
paths : Vec < PathBuf > ,
) -> Result < ( ) , AnyError > {
let caches = factory . caches ( ) ? ;
let check = fmt_options . check ;
let incremental_cache = Arc ::new ( IncrementalCache ::new (
caches . fmt_incremental_cache_db ( ) ,
& fmt_options . options ,
& paths ,
) ) ;
if check {
check_source_files ( paths , fmt_options . options , incremental_cache . clone ( ) )
. await ? ;
} else {
format_source_files ( paths , fmt_options . options , incremental_cache . clone ( ) )
. await ? ;
}
incremental_cache . wait_completion ( ) . await ;
Ok ( ( ) )
}
2024-03-27 18:25:39 +00:00
fn collect_fmt_files (
cli_options : & CliOptions ,
files : FilePatterns ,
) -> Result < Vec < PathBuf > , AnyError > {
2024-03-08 01:16:32 +00:00
FileCollector ::new ( | e | is_supported_ext_fmt ( e . path ) )
2022-12-07 18:10:10 +00:00
. ignore_git_folder ( )
. ignore_node_modules ( )
2024-03-27 18:25:39 +00:00
. set_vendor_folder ( cli_options . vendor_dir_path ( ) . map ( ToOwned ::to_owned ) )
2024-01-08 17:18:42 +00:00
. collect_file_patterns ( files )
2022-12-07 18:10:10 +00:00
}
2021-09-05 14:22:45 +00:00
/// Formats markdown (using <https://github.com/dprint/dprint-plugin-markdown>) and its code blocks
2021-01-19 17:39:35 +00:00
/// (ts/tsx, js/jsx).
2021-09-13 18:19:10 +00:00
fn format_markdown (
file_text : & str ,
fmt_options : & FmtOptionsConfig ,
2022-03-29 17:33:00 +00:00
) -> Result < Option < String > , AnyError > {
2021-09-13 18:19:10 +00:00
let markdown_config = get_resolved_markdown_config ( fmt_options ) ;
2021-01-19 17:39:35 +00:00
dprint_plugin_markdown ::format_text (
2021-07-30 13:03:41 +00:00
file_text ,
2021-09-13 18:19:10 +00:00
& markdown_config ,
2021-06-06 16:42:12 +00:00
move | tag , text , line_width | {
2021-01-19 17:39:35 +00:00
let tag = tag . to_lowercase ( ) ;
if matches! (
tag . as_str ( ) ,
2021-02-18 16:31:32 +00:00
" ts "
| " tsx "
| " js "
| " jsx "
2022-06-09 23:55:04 +00:00
| " cjs "
| " cts "
| " mjs "
| " mts "
2021-02-18 16:31:32 +00:00
| " javascript "
| " typescript "
| " json "
| " jsonc "
2021-01-19 17:39:35 +00: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 ,
} ;
2023-10-24 13:37:02 +00:00
let fake_filename =
PathBuf ::from ( format! ( " deno_fmt_stdin. {extension} " ) ) ;
2021-02-18 16:31:32 +00:00
if matches! ( extension , " json " | " jsonc " ) {
2021-09-13 18:19:10 +00:00
let mut json_config = get_resolved_json_config ( fmt_options ) ;
2021-02-18 16:31:32 +00:00
json_config . line_width = line_width ;
2023-10-24 13:37:02 +00:00
dprint_plugin_json ::format_text ( & fake_filename , text , & json_config )
2021-02-18 16:31:32 +00:00
} else {
2021-09-13 18:19:10 +00:00
let mut codeblock_config =
get_resolved_typescript_config ( fmt_options ) ;
2021-02-18 16:31:32 +00:00
codeblock_config . line_width = line_width ;
dprint_plugin_typescript ::format_text (
& fake_filename ,
2021-07-30 13:03:41 +00:00
text ,
2021-02-18 16:31:32 +00:00
& codeblock_config ,
)
}
2021-01-19 17:39:35 +00:00
} else {
2022-03-29 17:33:00 +00:00
Ok ( None )
2021-01-19 17:39:35 +00:00
}
2021-06-06 16:42:12 +00:00
} ,
2021-01-19 17:39:35 +00:00
)
}
2021-02-18 16:31:32 +00:00
/// Formats JSON and JSONC using the rules provided by .deno()
2021-09-05 14:22:45 +00:00
/// of configuration builder of <https://github.com/dprint/dprint-plugin-json>.
2022-04-26 22:39:47 +00:00
/// See <https://github.com/dprint/dprint-plugin-json/blob/cfa1052dbfa0b54eb3d814318034cdc514c813d7/src/configuration/builder.rs#L87> for configuration.
2021-12-08 00:21:04 +00:00
pub fn format_json (
2023-10-24 13:37:02 +00:00
file_path : & Path ,
2021-09-13 18:19:10 +00:00
file_text : & str ,
fmt_options : & FmtOptionsConfig ,
2022-03-29 17:33:00 +00:00
) -> Result < Option < String > , AnyError > {
2021-09-13 18:19:10 +00:00
let config = get_resolved_json_config ( fmt_options ) ;
2023-10-24 13:37:02 +00:00
dprint_plugin_json ::format_text ( file_path , file_text , & config )
2021-02-18 16:31:32 +00:00
}
2023-11-27 15:32:12 +00:00
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, or IPYNB file.
2021-05-18 06:35:46 +00:00
pub fn format_file (
file_path : & Path ,
file_text : & str ,
2022-04-20 02:14:00 +00:00
fmt_options : & FmtOptionsConfig ,
2022-03-29 17:33:00 +00:00
) -> Result < Option < String > , AnyError > {
2022-02-25 01:03:12 +00:00
let ext = get_extension ( file_path ) . unwrap_or_default ( ) ;
2023-11-27 15:32:12 +00:00
match ext . as_str ( ) {
" md " | " mkd " | " mkdn " | " mdwn " | " mdown " | " markdown " = > {
format_markdown ( file_text , fmt_options )
}
" json " | " jsonc " = > format_json ( file_path , file_text , fmt_options ) ,
" ipynb " = > dprint_plugin_jupyter ::format_text (
file_text ,
| file_path : & Path , file_text : String | {
format_file ( file_path , & file_text , fmt_options )
} ,
) ,
_ = > {
let config = get_resolved_typescript_config ( fmt_options ) ;
dprint_plugin_typescript ::format_text ( file_path , file_text , & config )
}
2021-05-18 06:35:46 +00:00
}
}
2021-10-21 14:18:18 +00:00
pub fn format_parsed_source (
2021-09-13 18:19:10 +00:00
parsed_source : & ParsedSource ,
2023-01-07 20:22:09 +00:00
fmt_options : & FmtOptionsConfig ,
2022-03-29 17:33:00 +00:00
) -> Result < Option < String > , AnyError > {
2021-10-21 14:18:18 +00:00
dprint_plugin_typescript ::format_parsed_source (
parsed_source ,
2023-01-07 20:22:09 +00:00
& get_resolved_typescript_config ( fmt_options ) ,
2021-09-07 14:39:32 +00:00
)
}
2021-09-13 18:19:10 +00:00
async fn check_source_files (
paths : Vec < PathBuf > ,
fmt_options : FmtOptionsConfig ,
2022-04-20 02:14:00 +00:00
incremental_cache : Arc < IncrementalCache > ,
2021-09-13 18:19:10 +00:00
) -> Result < ( ) , AnyError > {
2020-04-23 23:01:15 +00:00
let not_formatted_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-09-09 14:45:31 +00:00
let checked_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-05-23 13:22:08 +00:00
// prevent threads outputting at the same time
let output_lock = Arc ::new ( Mutex ::new ( 0 ) ) ;
2020-04-23 23:01:15 +00:00
run_parallelized ( paths , {
let not_formatted_files_count = not_formatted_files_count . clone ( ) ;
2020-09-09 14:45:31 +00:00
let checked_files_count = checked_files_count . clone ( ) ;
2020-04-23 23:01:15 +00:00
move | file_path | {
2020-09-09 14:45:31 +00:00
checked_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
2020-05-28 17:35:24 +00:00
let file_text = read_file_contents ( & file_path ) ? . text ;
2021-05-18 06:35:46 +00:00
2022-04-20 02:14:00 +00: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 17:33:00 +00: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 23:01:15 +00:00
}
2022-04-20 02:14:00 +00: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 23:01:15 +00:00
Err ( e ) = > {
2022-06-18 16:44:43 +00:00
not_formatted_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
2022-03-29 17:33:00 +00:00
let _g = output_lock . lock ( ) ;
2022-12-09 15:54:24 +00:00
warn! ( " Error checking: {} " , file_path . to_string_lossy ( ) ) ;
2023-01-04 23:54:54 +00:00
warn! (
" {} " ,
2023-01-27 15:43:16 +00:00
format! ( " {e} " )
2023-01-04 23:54:54 +00:00
. split ( '\n' )
. map ( | l | {
if l . trim ( ) . is_empty ( ) {
String ::new ( )
} else {
2023-01-27 15:43:16 +00:00
format! ( " {l} " )
2023-01-04 23:54:54 +00:00
}
} )
. collect ::< Vec < _ > > ( )
. join ( " \n " )
) ;
2020-01-30 02:16:48 +00:00
}
}
2020-04-23 23:01:15 +00:00
Ok ( ( ) )
2020-01-30 02:16:48 +00:00
}
2020-04-23 23:01:15 +00:00
} )
. await ? ;
2020-01-30 02:16:48 +00:00
2020-04-23 23:01:15 +00:00
let not_formatted_files_count =
2020-09-08 09:58:17 +00:00
not_formatted_files_count . load ( Ordering ::Relaxed ) ;
2020-09-09 14:45:31 +00: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 23:01:15 +00:00
if not_formatted_files_count = = 0 {
2020-09-20 11:49:22 +00:00
info! ( " Checked {} " , checked_files_str ) ;
2020-02-13 21:02:18 +00:00
Ok ( ( ) )
} else {
2020-09-09 14:45:31 +00:00
let not_formatted_files_str = files_str ( not_formatted_files_count ) ;
2020-09-14 16:48:57 +00:00
Err ( generic_error ( format! (
2023-01-27 15:43:16 +00:00
" Found {not_formatted_files_count} not formatted {not_formatted_files_str} in {checked_files_str} " ,
2020-08-25 22:22:15 +00:00
) ) )
2020-01-30 02:16:48 +00:00
}
}
2021-09-13 18:19:10 +00:00
async fn format_source_files (
paths : Vec < PathBuf > ,
fmt_options : FmtOptionsConfig ,
2022-04-20 02:14:00 +00:00
incremental_cache : Arc < IncrementalCache > ,
2021-09-13 18:19:10 +00:00
) -> Result < ( ) , AnyError > {
2020-04-23 23:01:15 +00:00
let formatted_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-09-09 14:45:31 +00:00
let checked_files_count = Arc ::new ( AtomicUsize ::new ( 0 ) ) ;
2020-04-23 23:01:15 +00: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 14:45:31 +00:00
let checked_files_count = checked_files_count . clone ( ) ;
2020-04-23 23:01:15 +00:00
move | file_path | {
2020-09-09 14:45:31 +00:00
checked_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
2020-05-28 17:35:24 +00:00
let file_contents = read_file_contents ( & file_path ) ? ;
2021-05-18 06:35:46 +00:00
2022-04-20 02:14:00 +00: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 17:33:00 +00:00
Ok ( Some ( formatted_text ) ) = > {
2022-04-20 02:14:00 +00:00
incremental_cache . update_file ( & file_path , & formatted_text ) ;
2022-03-29 17:33:00 +00: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 23:01:15 +00:00
}
2022-04-20 02:14:00 +00:00
Ok ( None ) = > {
incremental_cache . update_file ( & file_path , & file_contents . text ) ;
}
2020-04-23 23:01:15 +00:00
Err ( e ) = > {
2022-03-29 17:33:00 +00:00
let _g = output_lock . lock ( ) ;
2024-05-09 02:45:06 +00:00
log ::error! ( " Error formatting: {} " , file_path . to_string_lossy ( ) ) ;
log ::error! ( " {e} " ) ;
2020-01-30 02:16:48 +00:00
}
}
2020-04-23 23:01:15 +00:00
Ok ( ( ) )
2020-01-30 02:16:48 +00:00
}
2020-04-23 23:01:15 +00:00
} )
. await ? ;
2020-09-08 09:58:17 +00:00
let formatted_files_count = formatted_files_count . load ( Ordering ::Relaxed ) ;
2020-04-08 14:31:48 +00:00
debug! (
" Formatted {} {} " ,
2020-04-23 23:01:15 +00:00
formatted_files_count ,
files_str ( formatted_files_count ) ,
2020-04-08 14:31:48 +00:00
) ;
2020-09-09 14:45:31 +00:00
let checked_files_count = checked_files_count . load ( Ordering ::Relaxed ) ;
2020-09-20 11:49:22 +00:00
info! (
2020-09-09 14:45:31 +00:00
" Checked {} {} " ,
checked_files_count ,
files_str ( checked_files_count )
) ;
2020-02-26 10:50:53 +00:00
Ok ( ( ) )
2020-01-30 02:16:48 +00:00
}
2022-04-20 02:14:00 +00: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 " ,
2023-06-26 13:10:27 +00:00
" the text it produces is not syntactically correct. As a temporary " ,
" workaround you can ignore this file ({}). \n \n {:#} "
2022-04-20 02:14:00 +00:00
) ,
2022-08-30 18:46:03 +00:00
file_path . display ( ) ,
2022-04-20 02:14:00 +00:00
err ,
)
}
}
count + = 1 ;
if count = = 5 {
panic! (
concat! (
" Formatting not stable. Bailed after {} tries. This indicates a bug " ,
2022-08-30 18:46:03 +00:00
" in the formatter where it formats the file ({}) differently each time. As a " ,
2022-04-20 02:14:00 +00:00
" temporary workaround you can ignore this file. "
) ,
2022-08-30 18:46:03 +00:00
count ,
file_path . display ( ) ,
2022-04-20 02:14:00 +00:00
)
}
}
}
None = > Ok ( None ) ,
}
}
2020-02-09 10:19:05 +00:00
/// Format stdin and write result to stdout.
2023-03-22 14:15:53 +00:00
/// Treats input as set by `--ext` flag.
2020-02-09 10:19:05 +00:00
/// Compatible with `--check` flag.
2023-03-22 14:15:53 +00:00
fn format_stdin ( fmt_options : FmtOptions , ext : & str ) -> Result < ( ) , AnyError > {
2020-02-09 10:19:05 +00:00
let mut source = String ::new ( ) ;
if stdin ( ) . read_to_string ( & mut source ) . is_err ( ) {
2022-01-04 22:02:56 +00:00
bail! ( " Failed to read from stdin " ) ;
2020-02-09 10:19:05 +00:00
}
2023-03-22 14:15:53 +00:00
let file_path = PathBuf ::from ( format! ( " _stdin. {ext} " ) ) ;
2023-01-07 20:22:09 +00:00
let formatted_text = format_file ( & file_path , & source , & fmt_options . options ) ? ;
if fmt_options . check {
2024-05-09 02:45:06 +00:00
#[ allow(clippy::print_stdout) ]
2022-03-29 17:33:00 +00:00
if formatted_text . is_some ( ) {
2022-01-04 22:02:56 +00:00
println! ( " Not formatted stdin " ) ;
2020-02-09 10:19:05 +00:00
}
2022-01-04 22:02:56 +00:00
} else {
2022-03-29 17:33:00 +00:00
stdout ( ) . write_all ( formatted_text . unwrap_or ( source ) . as_bytes ( ) ) ? ;
2020-02-09 10:19:05 +00:00
}
2020-02-27 20:39:41 +00:00
Ok ( ( ) )
2020-01-30 02:16:48 +00:00
}
2020-02-13 21:02:18 +00:00
2020-05-04 19:17:15 +00:00
fn files_str ( len : usize ) -> & 'static str {
2020-09-20 11:49:22 +00:00
if len < = 1 {
2020-05-04 19:17:15 +00:00
" file "
} else {
" files "
}
}
2021-09-13 18:19:10 +00: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 17:39:35 +00:00
2021-09-13 18:19:10 +00: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 (
2023-12-06 03:06:19 +00:00
dprint_plugin_typescript ::configuration ::QuoteStyle ::PreferSingle ,
2021-09-13 18:19:10 +00:00
) ;
}
}
2023-01-24 20:07:00 +00:00
if let Some ( semi_colons ) = options . semi_colons {
builder . semi_colons ( match semi_colons {
2023-01-25 20:06:00 +00:00
true = > dprint_plugin_typescript ::configuration ::SemiColons ::Prefer ,
false = > dprint_plugin_typescript ::configuration ::SemiColons ::Asi ,
2023-01-24 20:07:00 +00:00
} ) ;
}
2021-09-13 18:19:10 +00:00
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 19:17:15 +00:00
2021-09-13 18:19:10 +00:00
builder . build ( )
2021-02-18 16:31:32 +00:00
}
2020-05-28 17:35:24 +00:00
struct FileContents {
text : String ,
had_bom : bool ,
}
2020-09-14 16:48:57 +00:00
fn read_file_contents ( file_path : & Path ) -> Result < FileContents , AnyError > {
2022-11-18 01:59:10 +00:00
let file_bytes = fs ::read ( file_path )
2021-12-10 01:24:37 +00:00
. with_context ( | | format! ( " Error reading {} " , file_path . display ( ) ) ) ? ;
2024-02-01 03:15:22 +00:00
let had_bom = file_bytes . starts_with ( & [ 0xEF , 0xBB , 0xBF ] ) ;
// will have the BOM stripped
let text = deno_graph ::source ::decode_owned_file_source ( file_bytes )
. with_context ( | | {
2023-01-16 00:30:52 +00:00
anyhow! ( " {} is not a valid UTF-8 file " , file_path . display ( ) )
} ) ? ;
2020-05-28 17:35:24 +00:00
Ok ( FileContents { text , had_bom } )
}
fn write_file_contents (
2020-09-08 09:58:17 +00:00
file_path : & Path ,
2024-02-01 03:15:22 +00:00
mut file_contents : FileContents ,
2020-09-14 16:48:57 +00:00
) -> Result < ( ) , AnyError > {
2020-05-28 17:35:24 +00:00
let file_text = if file_contents . had_bom {
// add back the BOM
2024-02-01 03:15:22 +00:00
file_contents . text . insert ( 0 , '\u{FEFF}' ) ;
file_contents . text
2020-05-28 17:35:24 +00:00
} else {
file_contents . text
} ;
Ok ( fs ::write ( file_path , file_text ) ? )
}
2020-06-11 23:44:17 +00:00
pub async fn run_parallelized < F > (
2020-04-23 23:01:15 +00:00
file_paths : Vec < PathBuf > ,
f : F ,
2020-09-14 16:48:57 +00:00
) -> Result < ( ) , AnyError >
2020-04-23 23:01:15 +00:00
where
2020-09-14 16:48:57 +00:00
F : FnOnce ( PathBuf ) -> Result < ( ) , AnyError > + Send + 'static + Clone ,
2020-04-23 23:01:15 +00:00
{
let handles = file_paths . iter ( ) . map ( | file_path | {
let f = f . clone ( ) ;
let file_path = file_path . clone ( ) ;
2023-05-14 21:40:01 +00:00
spawn_blocking ( move | | f ( file_path ) )
2020-04-23 23:01:15 +00:00
} ) ;
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-25 01:03:12 +00:00
. and_then ( | handle_result | handle_result . err ( ) )
2020-04-23 23:01:15 +00:00
} ) ;
if let Some ( e ) = errors . next ( ) {
Err ( e )
} else {
Ok ( ( ) )
}
}
2022-01-18 01:10:17 +00: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 {
2023-11-27 15:32:12 +00:00
get_extension ( path ) . is_some_and ( | ext | {
2022-01-18 01:10:17 +00:00
matches! (
ext . as_str ( ) ,
" ts "
| " tsx "
| " js "
| " jsx "
2022-06-09 23:55:04 +00:00
| " cjs "
| " cts "
2022-01-18 01:10:17 +00:00
| " mjs "
2022-06-09 23:55:04 +00:00
| " mts "
2022-01-18 01:10:17 +00:00
| " json "
| " jsonc "
| " md "
| " mkd "
| " mkdn "
| " mdwn "
| " mdown "
| " markdown "
2023-11-27 15:32:12 +00:00
| " ipynb "
2022-01-18 01:10:17 +00:00
)
2023-11-27 15:32:12 +00:00
} )
2022-01-18 01:10:17 +00:00
}
2022-04-20 02:14:00 +00: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 " ) ) ) ;
2022-09-19 14:32:21 +00:00
assert! ( is_supported_ext_fmt ( Path ::new ( " testdata/run/001_hello.js " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " testdata/run/002_hello.ts " ) ) ) ;
2022-04-20 02:14:00 +00:00
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 " ) ) ) ;
2023-11-27 15:32:12 +00:00
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.ipynb " ) ) ) ;
2022-04-20 02:14:00 +00: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 ( ) ,
2023-01-27 15:43:16 +00:00
| _ , file_text , _ | Ok ( Some ( format! ( " 1 {file_text} " ) ) ) ,
2022-04-20 02:14:00 +00:00
)
. 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 18:57:42 +00:00
2022-04-20 02:14:00 +00: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 ( ) ) ) ;
}
2023-12-06 03:06:19 +00:00
#[ test ]
fn test_single_quote_true_prefers_single_quote ( ) {
let file_text = format_file (
& PathBuf ::from ( " test.ts " ) ,
" console.log( \" there's \" ); \n console.log('hi'); \n console.log( \" bye \" ) \n " ,
& FmtOptionsConfig {
single_quote : Some ( true ) ,
.. Default ::default ( )
} ,
)
. unwrap ( )
. unwrap ( ) ;
assert_eq! (
file_text ,
// should use double quotes for the string with a single quote
" console.log( \" there's \" ); \n console.log('hi'); \n console.log('bye'); \n " ,
) ;
}
2022-03-29 18:57:42 +00:00
}