2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 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-29 11:51:11 -04:00
use crate ::args ::CliOptions ;
2023-06-14 18:29:19 -04:00
use crate ::args ::Flags ;
use crate ::args ::FmtFlags ;
2023-01-07 15:22:09 -05:00
use crate ::args ::FmtOptions ;
2022-06-27 16:54:09 -04:00
use crate ::args ::FmtOptionsConfig ;
use crate ::args ::ProseWrap ;
2024-08-02 09:52:48 -04:00
use crate ::args ::UnstableFmtOptions ;
2024-07-03 20:54:33 -04:00
use crate ::cache ::Caches ;
2020-05-23 09:22:08 -04:00
use crate ::colors ;
2023-05-01 14:35:23 -04:00
use crate ::factory ::CliFactory ;
2022-11-28 17:28:54 -05:00
use crate ::util ::diff ::diff ;
use crate ::util ::file_watcher ;
2024-01-08 12:18:42 -05:00
use crate ::util ::fs ::canonicalize_path ;
2022-11-28 17:28:54 -05:00
use crate ::util ::path ::get_extension ;
2024-07-03 20:54:33 -04:00
use async_trait ::async_trait ;
2021-09-07 10:39:32 -04:00
use deno_ast ::ParsedSource ;
2024-07-05 17:53:09 -04:00
use deno_config ::glob ::FileCollector ;
2024-01-15 19:15:39 -05:00
use deno_config ::glob ::FilePatterns ;
2023-01-15 19:30:52 -05:00
use deno_core ::anyhow ::anyhow ;
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 ;
2023-08-23 19:03:05 -04:00
use deno_core ::unsync ::spawn_blocking ;
2021-03-26 12:34:25 -04:00
use log ::debug ;
use log ::info ;
2022-12-09 10:54:24 -05:00
use log ::warn ;
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-07-12 18:58:39 -04:00
use crate ::cache ::IncrementalCache ;
2022-04-19 22:14:00 -04:00
2020-05-04 15:17:15 -04:00
/// Format JavaScript/TypeScript files.
2024-07-23 19:00:48 -04:00
pub async fn format (
flags : Arc < Flags > ,
fmt_flags : FmtFlags ,
) -> Result < ( ) , AnyError > {
2023-06-14 18:29:19 -04:00
if fmt_flags . is_stdin ( ) {
let cli_options = CliOptions ::from_flags ( flags ) ? ;
2024-07-19 15:56:07 -04:00
let start_dir = & cli_options . start_dir ;
let fmt_config = start_dir
. to_fmt_config ( FilePatterns ::new_with_base ( start_dir . dir_path ( ) ) ) ? ;
2024-08-02 09:52:48 -04:00
let fmt_options = FmtOptions ::resolve (
fmt_config ,
cli_options . resolve_config_unstable_fmt_options ( ) ,
& fmt_flags ,
) ;
2023-03-22 10:15:53 -04:00
return format_stdin (
2024-07-03 20:54:33 -04:00
& fmt_flags ,
2023-03-22 10:15:53 -04:00
fmt_options ,
cli_options
. ext_flag ( )
. as_ref ( )
. map ( | s | s . as_str ( ) )
. unwrap_or ( " ts " ) ,
) ;
2021-09-13 14:19:10 -04:00
}
2023-06-15 13:09:37 -04:00
if let Some ( watch_flags ) = & fmt_flags . watch {
2022-01-31 11:39:39 -05:00
file_watcher ::watch_func (
2023-06-14 18:29:19 -04:00
flags ,
2023-10-30 20:25:58 -04:00
file_watcher ::PrintConfig ::new ( " Fmt " , ! watch_flags . no_clear_screen ) ,
2023-10-19 01:05:00 -04:00
move | flags , watcher_communicator , changed_paths | {
2023-06-14 18:29:19 -04:00
let fmt_flags = fmt_flags . clone ( ) ;
Ok ( async move {
2024-07-23 19:00:48 -04:00
let factory = CliFactory ::from_flags ( flags ) ;
let cli_options = factory . cli_options ( ) ? ;
2024-07-03 20:54:33 -04:00
let caches = factory . caches ( ) ? ;
let mut paths_with_options_batches =
resolve_paths_with_options_batches ( cli_options , & fmt_flags ) ? ;
for paths_with_options in & mut paths_with_options_batches {
let _ = watcher_communicator
. watch_paths ( paths_with_options . paths . clone ( ) ) ;
let files = std ::mem ::take ( & mut paths_with_options . paths ) ;
paths_with_options . paths = if let Some ( paths ) = & changed_paths {
if fmt_flags . check {
// check all files on any changed (https://github.com/denoland/deno/issues/12446)
files
. iter ( )
. any ( | path | {
canonicalize_path ( path )
. map ( | path | paths . contains ( & path ) )
. unwrap_or ( false )
} )
. then_some ( files )
. unwrap_or_else ( | | [ ] . to_vec ( ) )
2023-06-14 18:29:19 -04:00
} else {
2024-07-03 20:54:33 -04:00
files
. into_iter ( )
. filter ( | path | {
canonicalize_path ( path )
. map ( | path | paths . contains ( & path ) )
. unwrap_or ( false )
} )
. collect ::< Vec < _ > > ( )
2023-06-14 18:29:19 -04:00
}
} else {
files
2024-07-03 20:54:33 -04:00
} ;
}
format_files ( caches , & fmt_flags , paths_with_options_batches ) . await ? ;
2023-06-14 18:29:19 -04:00
Ok ( ( ) )
} )
2022-01-31 11:39:39 -05:00
} ,
)
. await ? ;
2020-05-04 15:17:15 -04:00
} else {
2024-07-23 19:00:48 -04:00
let factory = CliFactory ::from_flags ( flags ) ;
let cli_options = factory . cli_options ( ) ? ;
2024-07-03 20:54:33 -04:00
let caches = factory . caches ( ) ? ;
let paths_with_options_batches =
resolve_paths_with_options_batches ( cli_options , & fmt_flags ) ? ;
format_files ( caches , & fmt_flags , paths_with_options_batches ) . 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
}
2024-07-03 20:54:33 -04:00
struct PathsWithOptions {
base : PathBuf ,
2023-06-14 18:29:19 -04:00
paths : Vec < PathBuf > ,
2024-07-03 20:54:33 -04:00
options : FmtOptions ,
}
fn resolve_paths_with_options_batches (
cli_options : & CliOptions ,
fmt_flags : & FmtFlags ,
) -> Result < Vec < PathsWithOptions > , AnyError > {
let members_fmt_options =
cli_options . resolve_fmt_options_for_members ( fmt_flags ) ? ;
let mut paths_with_options_batches =
Vec ::with_capacity ( members_fmt_options . len ( ) ) ;
2024-07-08 10:12:10 -04:00
for ( _ctx , member_fmt_options ) in members_fmt_options {
2024-07-03 20:54:33 -04:00
let files =
collect_fmt_files ( cli_options , member_fmt_options . files . clone ( ) ) ? ;
if ! files . is_empty ( ) {
paths_with_options_batches . push ( PathsWithOptions {
base : member_fmt_options . files . base . clone ( ) ,
paths : files ,
options : member_fmt_options ,
} ) ;
}
}
if paths_with_options_batches . is_empty ( ) {
return Err ( generic_error ( " No target files found. " ) ) ;
}
Ok ( paths_with_options_batches )
}
async fn format_files (
caches : & Arc < Caches > ,
fmt_flags : & FmtFlags ,
paths_with_options_batches : Vec < PathsWithOptions > ,
2023-06-14 18:29:19 -04:00
) -> Result < ( ) , AnyError > {
2024-07-03 20:54:33 -04:00
let formatter : Box < dyn Formatter > = if fmt_flags . check {
Box ::new ( CheckFormatter ::default ( ) )
2023-06-14 18:29:19 -04:00
} else {
2024-07-03 20:54:33 -04:00
Box ::new ( RealFormatter ::default ( ) )
} ;
for paths_with_options in paths_with_options_batches {
log ::debug! (
" Formatting {} file(s) in {} " ,
paths_with_options . paths . len ( ) ,
paths_with_options . base . display ( )
) ;
let fmt_options = paths_with_options . options ;
let paths = paths_with_options . paths ;
let incremental_cache = Arc ::new ( IncrementalCache ::new (
caches . fmt_incremental_cache_db ( ) ,
2024-08-02 09:52:48 -04:00
& ( & fmt_options . options , & fmt_options . unstable ) , // cache key
2024-07-03 20:54:33 -04:00
& paths ,
) ) ;
formatter
2024-08-02 09:52:48 -04:00
. handle_files (
paths ,
fmt_options . options ,
fmt_options . unstable ,
incremental_cache . clone ( ) ,
)
2023-06-14 18:29:19 -04:00
. await ? ;
2024-07-03 20:54:33 -04:00
incremental_cache . wait_completion ( ) . await ;
2023-06-14 18:29:19 -04:00
}
2024-07-03 20:54:33 -04:00
formatter . finish ( )
2023-06-14 18:29:19 -04:00
}
2024-03-27 14:25:39 -04:00
fn collect_fmt_files (
cli_options : & CliOptions ,
files : FilePatterns ,
) -> Result < Vec < PathBuf > , AnyError > {
2024-03-07 20:16:32 -05:00
FileCollector ::new ( | e | is_supported_ext_fmt ( e . path ) )
2022-12-07 13:10:10 -05:00
. ignore_git_folder ( )
. ignore_node_modules ( )
2024-03-27 14:25:39 -04:00
. set_vendor_folder ( cli_options . vendor_dir_path ( ) . map ( ToOwned ::to_owned ) )
2024-07-05 17:53:09 -04:00
. collect_file_patterns ( & deno_config ::fs ::RealDenoConfigFs , files )
2022-12-07 13:10:10 -05: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 ,
2024-08-02 09:52:48 -04:00
unstable_options : & UnstableFmtOptions ,
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 "
2024-08-02 08:12:51 -04:00
| " yml "
| " yaml "
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 ,
} ;
2023-10-24 09:37:02 -04:00
let fake_filename =
PathBuf ::from ( format! ( " deno_fmt_stdin. {extension} " ) ) ;
2024-08-02 08:12:51 -04:00
match extension {
" json " | " jsonc " = > {
let mut json_config = get_resolved_json_config ( fmt_options ) ;
json_config . line_width = line_width ;
dprint_plugin_json ::format_text ( & fake_filename , text , & json_config )
}
2024-08-02 09:52:48 -04:00
" yml " | " yaml " = > {
if unstable_options . yaml {
pretty_yaml ::format_text (
text ,
& get_resolved_yaml_config ( fmt_options ) ,
)
. map ( Some )
. map_err ( AnyError ::from )
} else {
Ok ( None )
}
}
2024-08-02 08:12:51 -04:00
_ = > {
let mut codeblock_config =
get_resolved_typescript_config ( fmt_options ) ;
codeblock_config . line_width = line_width ;
dprint_plugin_typescript ::format_text (
& fake_filename ,
text . to_string ( ) ,
& codeblock_config ,
)
}
2021-02-18 11:31:32 -05:00
}
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 (
2023-10-24 09:37:02 -04:00
file_path : & Path ,
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 ) ;
2023-10-24 09:37:02 -04:00
dprint_plugin_json ::format_text ( file_path , file_text , & config )
2021-02-18 11:31:32 -05:00
}
2023-11-27 10:32:12 -05:00
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, or IPYNB file.
2021-05-18 02:35:46 -04:00
pub fn format_file (
file_path : & Path ,
file_text : & str ,
2022-04-19 22:14:00 -04:00
fmt_options : & FmtOptionsConfig ,
2024-08-02 09:52:48 -04:00
unstable_options : & UnstableFmtOptions ,
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 ( ) ;
2023-11-27 10:32:12 -05:00
match ext . as_str ( ) {
" md " | " mkd " | " mkdn " | " mdwn " | " mdown " | " markdown " = > {
2024-08-02 09:52:48 -04:00
format_markdown ( file_text , fmt_options , unstable_options )
2023-11-27 10:32:12 -05:00
}
" json " | " jsonc " = > format_json ( file_path , file_text , fmt_options ) ,
2024-08-02 09:52:48 -04:00
" yml " | " yaml " = > {
if unstable_options . yaml {
pretty_yaml ::format_text (
file_text ,
& get_resolved_yaml_config ( fmt_options ) ,
)
. map ( Some )
. map_err ( AnyError ::from )
} else {
Ok ( None )
}
}
2023-11-27 10:32:12 -05:00
" ipynb " = > dprint_plugin_jupyter ::format_text (
file_text ,
| file_path : & Path , file_text : String | {
2024-08-02 09:52:48 -04:00
format_file ( file_path , & file_text , fmt_options , unstable_options )
2023-11-27 10:32:12 -05:00
} ,
) ,
_ = > {
let config = get_resolved_typescript_config ( fmt_options ) ;
2024-06-05 11:04:16 -04:00
dprint_plugin_typescript ::format_text (
file_path ,
file_text . to_string ( ) ,
& config ,
)
2023-11-27 10:32:12 -05:00
}
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 ,
2023-01-07 15:22:09 -05:00
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 ,
2023-01-07 15:22:09 -05:00
& get_resolved_typescript_config ( fmt_options ) ,
2021-09-07 10:39:32 -04:00
)
}
2024-07-03 20:54:33 -04:00
#[ async_trait ]
trait Formatter {
async fn handle_files (
& self ,
paths : Vec < PathBuf > ,
fmt_options : FmtOptionsConfig ,
2024-08-02 09:52:48 -04:00
unstable_options : UnstableFmtOptions ,
2024-07-03 20:54:33 -04:00
incremental_cache : Arc < IncrementalCache > ,
) -> Result < ( ) , AnyError > ;
2022-04-19 22:14:00 -04:00
2024-07-03 20:54:33 -04:00
fn finish ( & self ) -> Result < ( ) , AnyError > ;
}
#[ derive(Default) ]
struct CheckFormatter {
not_formatted_files_count : Arc < AtomicUsize > ,
checked_files_count : Arc < AtomicUsize > ,
}
#[ async_trait ]
impl Formatter for CheckFormatter {
async fn handle_files (
& self ,
paths : Vec < PathBuf > ,
fmt_options : FmtOptionsConfig ,
2024-08-02 09:52:48 -04:00
unstable_options : UnstableFmtOptions ,
2024-07-03 20:54:33 -04:00
incremental_cache : Arc < IncrementalCache > ,
) -> Result < ( ) , AnyError > {
// prevent threads outputting at the same time
let output_lock = Arc ::new ( Mutex ::new ( 0 ) ) ;
run_parallelized ( paths , {
let not_formatted_files_count = self . not_formatted_files_count . clone ( ) ;
let checked_files_count = self . checked_files_count . clone ( ) ;
move | file_path | {
checked_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
let file_text = read_file_contents ( & file_path ) ? . text ;
// skip checking the file if we know it's formatted
if incremental_cache . is_file_same ( & file_path , & file_text ) {
return Ok ( ( ) ) ;
2022-04-19 22:14:00 -04:00
}
2024-07-03 20:54:33 -04:00
2024-08-02 09:52:48 -04:00
match format_file (
& file_path ,
& file_text ,
& fmt_options ,
& unstable_options ,
) {
2024-07-03 20:54:33 -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 ) ;
}
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 ) ;
}
Err ( e ) = > {
not_formatted_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
let _g = output_lock . lock ( ) ;
warn! ( " Error checking: {} " , file_path . to_string_lossy ( ) ) ;
warn! (
" {} " ,
format! ( " {e} " )
. split ( '\n' )
. map ( | l | {
if l . trim ( ) . is_empty ( ) {
String ::new ( )
} else {
format! ( " {l} " )
}
} )
. collect ::< Vec < _ > > ( )
. join ( " \n " )
) ;
}
2020-01-29 21:16:48 -05:00
}
2024-07-03 20:54:33 -04:00
Ok ( ( ) )
2020-01-29 21:16:48 -05:00
}
2024-07-03 20:54:33 -04:00
} )
. await ? ;
Ok ( ( ) )
}
fn finish ( & self ) -> Result < ( ) , AnyError > {
let not_formatted_files_count =
self . not_formatted_files_count . load ( Ordering ::Relaxed ) ;
let checked_files_count = self . checked_files_count . load ( Ordering ::Relaxed ) ;
let checked_files_str =
format! ( " {} {} " , checked_files_count , files_str ( checked_files_count ) ) ;
if not_formatted_files_count = = 0 {
info! ( " Checked {} " , checked_files_str ) ;
2020-04-23 19:01:15 -04:00
Ok ( ( ) )
2024-07-03 20:54:33 -04:00
} else {
let not_formatted_files_str = files_str ( not_formatted_files_count ) ;
Err ( generic_error ( format! (
" Found {not_formatted_files_count} not formatted {not_formatted_files_str} in {checked_files_str} " ,
) ) )
2020-01-29 21:16:48 -05:00
}
}
}
2024-07-03 20:54:33 -04:00
#[ derive(Default) ]
struct RealFormatter {
formatted_files_count : Arc < AtomicUsize > ,
checked_files_count : Arc < AtomicUsize > ,
}
2022-04-19 22:14:00 -04:00
2024-07-03 20:54:33 -04:00
#[ async_trait ]
impl Formatter for RealFormatter {
async fn handle_files (
& self ,
paths : Vec < PathBuf > ,
fmt_options : FmtOptionsConfig ,
2024-08-02 09:52:48 -04:00
unstable_options : UnstableFmtOptions ,
2024-07-03 20:54:33 -04:00
incremental_cache : Arc < IncrementalCache > ,
) -> Result < ( ) , AnyError > {
let output_lock = Arc ::new ( Mutex ::new ( 0 ) ) ; // prevent threads outputting at the same time
run_parallelized ( paths , {
let formatted_files_count = self . formatted_files_count . clone ( ) ;
let checked_files_count = self . checked_files_count . clone ( ) ;
move | file_path | {
checked_files_count . fetch_add ( 1 , Ordering ::Relaxed ) ;
let file_contents = read_file_contents ( & file_path ) ? ;
// skip formatting the file if we know it's formatted
if incremental_cache . is_file_same ( & file_path , & file_contents . text ) {
return Ok ( ( ) ) ;
2020-04-23 19:01:15 -04:00
}
2024-07-03 20:54:33 -04:00
match format_ensure_stable (
& file_path ,
& file_contents . text ,
2024-08-02 09:52:48 -04:00
| file_path , file_text | {
format_file ( file_path , file_text , & fmt_options , & unstable_options )
} ,
2024-07-03 20:54:33 -04:00
) {
Ok ( Some ( formatted_text ) ) = > {
incremental_cache . update_file ( & file_path , & formatted_text ) ;
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 ( ) ) ;
}
Ok ( None ) = > {
incremental_cache . update_file ( & file_path , & file_contents . text ) ;
}
Err ( e ) = > {
let _g = output_lock . lock ( ) ;
log ::error! ( " Error formatting: {} " , file_path . to_string_lossy ( ) ) ;
log ::error! ( " {e} " ) ;
}
2020-01-29 21:16:48 -05:00
}
2024-07-03 20:54:33 -04:00
Ok ( ( ) )
2020-01-29 21:16:48 -05:00
}
2024-07-03 20:54:33 -04:00
} )
. await ? ;
Ok ( ( ) )
}
2020-09-09 10:45:31 -04:00
2024-07-03 20:54:33 -04:00
fn finish ( & self ) -> Result < ( ) , AnyError > {
let formatted_files_count =
self . formatted_files_count . load ( Ordering ::Relaxed ) ;
debug! (
" Formatted {} {} " ,
formatted_files_count ,
files_str ( formatted_files_count ) ,
) ;
let checked_files_count = self . checked_files_count . load ( Ordering ::Relaxed ) ;
info! (
" Checked {} {} " ,
checked_files_count ,
files_str ( checked_files_count )
) ;
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 ,
2024-08-02 09:52:48 -04:00
fmt_func : impl Fn ( & Path , & str ) -> Result < Option < String > , AnyError > ,
2022-04-19 22:14:00 -04:00
) -> Result < Option < String > , AnyError > {
2024-08-02 09:52:48 -04:00
let formatted_text = fmt_func ( file_path , file_text ) ? ;
2022-04-19 22:14:00 -04:00
match formatted_text {
Some ( mut current_text ) = > {
let mut count = 0 ;
loop {
2024-08-02 09:52:48 -04:00
match fmt_func ( file_path , & current_text ) {
2022-04-19 22:14:00 -04:00
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 09:10:27 -04:00
" the text it produces is not syntactically correct. As a temporary " ,
" workaround you can ignore this file ({}). \n \n {:#} "
2022-04-19 22:14:00 -04:00
) ,
2022-08-30 14:46:03 -04:00
file_path . display ( ) ,
2022-04-19 22:14:00 -04:00
err ,
)
}
}
count + = 1 ;
if count = = 5 {
panic! (
concat! (
" Formatting not stable. Bailed after {} tries. This indicates a bug " ,
2022-08-30 14:46:03 -04:00
" in the formatter where it formats the file ({}) differently each time. As a " ,
2022-04-19 22:14:00 -04:00
" temporary workaround you can ignore this file. "
) ,
2022-08-30 14:46:03 -04:00
count ,
file_path . display ( ) ,
2022-04-19 22:14:00 -04:00
)
}
}
}
None = > Ok ( None ) ,
}
}
2020-02-09 05:19:05 -05:00
/// Format stdin and write result to stdout.
2023-03-22 10:15:53 -04:00
/// Treats input as set by `--ext` flag.
2020-02-09 05:19:05 -05:00
/// Compatible with `--check` flag.
2024-07-03 20:54:33 -04:00
fn format_stdin (
fmt_flags : & FmtFlags ,
fmt_options : FmtOptions ,
ext : & str ,
) -> 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
}
2023-03-22 10:15:53 -04:00
let file_path = PathBuf ::from ( format! ( " _stdin. {ext} " ) ) ;
2024-08-02 09:52:48 -04:00
let formatted_text = format_file (
& file_path ,
& source ,
& fmt_options . options ,
& fmt_options . unstable ,
) ? ;
2024-07-03 20:54:33 -04:00
if fmt_flags . check {
2024-05-08 22:45:06 -04:00
#[ allow(clippy::print_stdout) ]
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 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 (
2023-12-05 22:06:19 -05:00
dprint_plugin_typescript ::configuration ::QuoteStyle ::PreferSingle ,
2021-09-13 14:19:10 -04:00
) ;
}
}
2023-01-24 15:07:00 -05:00
if let Some ( semi_colons ) = options . semi_colons {
builder . semi_colons ( match semi_colons {
2023-01-25 15:06:00 -05:00
true = > dprint_plugin_typescript ::configuration ::SemiColons ::Prefer ,
false = > dprint_plugin_typescript ::configuration ::SemiColons ::Asi ,
2023-01-24 15:07:00 -05:00
} ) ;
}
2021-09-13 14:19:10 -04: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 15:17:15 -04:00
2021-09-13 14:19:10 -04:00
builder . build ( )
2021-02-18 11:31:32 -05:00
}
2024-08-02 08:12:51 -04:00
fn get_resolved_yaml_config (
options : & FmtOptionsConfig ,
) -> pretty_yaml ::config ::FormatOptions {
use pretty_yaml ::config ::* ;
let layout_options = LayoutOptions {
print_width : options . line_width . unwrap_or ( 80 ) as usize ,
use_tabs : options . use_tabs . unwrap_or_default ( ) ,
indent_width : options . indent_width . unwrap_or ( 2 ) as usize ,
line_break : LineBreak ::Lf ,
} ;
let language_options = LanguageOptions {
quotes : if let Some ( true ) = options . single_quote {
Quotes ::PreferSingle
} else {
Quotes ::PreferDouble
} ,
trailing_comma : true ,
format_comments : false ,
indent_block_sequence_in_map : true ,
brace_spacing : true ,
bracket_spacing : false ,
dash_spacing : DashSpacing ::OneSpace ,
trim_trailing_whitespaces : true ,
trim_trailing_zero : false ,
ignore_comment_directive : " deno-fmt-ignore " . into ( ) ,
} ;
FormatOptions {
layout : layout_options ,
language : language_options ,
}
}
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 > {
2022-11-17 20:59:10 -05:00
let file_bytes = fs ::read ( file_path )
2021-12-09 20:24:37 -05:00
. with_context ( | | format! ( " Error reading {} " , file_path . display ( ) ) ) ? ;
2024-01-31 22:15:22 -05: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-15 19:30:52 -05:00
anyhow! ( " {} is not a valid UTF-8 file " , file_path . display ( ) )
} ) ? ;
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 ,
2024-01-31 22:15:22 -05:00
mut 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
2024-01-31 22:15:22 -05:00
file_contents . text . insert ( 0 , '\u{FEFF}' ) ;
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 ( ) ;
2023-05-14 17:40:01 -04:00
spawn_blocking ( move | | f ( file_path ) )
2020-04-23 19:01:15 -04: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-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 {
2023-11-27 10:32:12 -05:00
get_extension ( path ) . is_some_and ( | ext | {
2022-01-17 20:10:17 -05:00
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 "
2024-08-02 08:12:51 -04:00
| " yml "
| " yaml "
2023-11-27 10:32:12 -05:00
| " ipynb "
2022-01-17 20:10:17 -05:00
)
2023-11-27 10:32:12 -05:00
} )
2022-01-17 20:10:17 -05:00
}
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 " ) ) ) ;
2022-09-19 10:32:21 -04: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-19 22:14:00 -04: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 " ) ) ) ;
2024-08-02 08:12:51 -04:00
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.yml " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.Yml " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.yaml " ) ) ) ;
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.YaML " ) ) ) ;
2023-11-27 10:32:12 -05:00
assert! ( is_supported_ext_fmt ( Path ::new ( " foo.ipynb " ) ) ) ;
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 ( ) {
2024-08-02 09:52:48 -04:00
format_ensure_stable ( & PathBuf ::from ( " mod.ts " ) , " 1 " , | _ , file_text | {
Ok ( Some ( format! ( " 1 {file_text} " ) ) )
} )
2022-04-19 22:14:00 -04:00
. unwrap ( ) ;
}
#[ test ]
fn test_format_ensure_stable_error_first ( ) {
2024-08-02 09:52:48 -04:00
let err = format_ensure_stable ( & PathBuf ::from ( " mod.ts " ) , " 1 " , | _ , _ | {
bail! ( " Error formatting. " )
} )
2022-04-19 22:14:00 -04:00
. 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 ( ) {
2024-08-02 09:52:48 -04:00
format_ensure_stable ( & PathBuf ::from ( " mod.ts " ) , " 1 " , | _ , file_text | {
if file_text = = " 1 " {
Ok ( Some ( " 11 " . to_string ( ) ) )
} else {
bail! ( " Error formatting. " )
}
} )
2022-04-19 22:14:00 -04:00
. unwrap ( ) ;
}
#[ test ]
fn test_format_stable_after_two ( ) {
2024-08-02 09:52:48 -04:00
let result =
format_ensure_stable ( & PathBuf ::from ( " mod.ts " ) , " 1 " , | _ , file_text | {
2022-04-19 22:14:00 -04:00
if file_text = = " 1 " {
Ok ( Some ( " 11 " . to_string ( ) ) )
} else if file_text = = " 11 " {
Ok ( None )
} else {
unreachable! ( ) ;
}
2024-08-02 09:52:48 -04:00
} )
. unwrap ( ) ;
2022-04-19 22:14:00 -04:00
assert_eq! ( result , Some ( " 11 " . to_string ( ) ) ) ;
}
2023-12-05 22:06:19 -05: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 ( )
} ,
2024-08-02 09:52:48 -04:00
& UnstableFmtOptions ::default ( ) ,
2023-12-05 22:06:19 -05:00
)
. 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 14:57:42 -04:00
}