@@ -4,13 +4,16 @@ mod tags;
4
4
mod tsconfig;
5
5
6
6
use std:: {
7
- collections:: { HashMap , HashSet } ,
7
+ collections:: { BTreeMap , HashMap , HashSet } ,
8
+ fs:: OpenOptions ,
9
+ io:: Write ,
8
10
sync:: { Arc , LazyLock , Mutex } ,
9
11
} ;
10
12
11
13
pub use config:: { BoundariesConfig , Permissions , Rule } ;
12
14
use git2:: Repository ;
13
15
use globwalk:: Settings ;
16
+ use indicatif:: { ProgressBar , ProgressIterator } ;
14
17
use miette:: { Diagnostic , NamedSource , Report , SourceSpan } ;
15
18
use regex:: Regex ;
16
19
use swc_common:: {
@@ -25,7 +28,7 @@ use swc_ecma_visit::VisitWith;
25
28
use thiserror:: Error ;
26
29
use tracing:: log:: warn;
27
30
use turbo_trace:: { ImportFinder , Tracer } ;
28
- use turbopath:: AbsoluteSystemPathBuf ;
31
+ use turbopath:: { AbsoluteSystemPath , AbsoluteSystemPathBuf } ;
29
32
use turborepo_errors:: Spanned ;
30
33
use turborepo_repository:: package_graph:: { PackageInfo , PackageName , PackageNode } ;
31
34
use turborepo_ui:: { color, ColorConfig , BOLD_GREEN , BOLD_RED } ;
@@ -97,6 +100,7 @@ pub enum BoundariesDiagnostic {
97
100
) ]
98
101
#[ help( "add `type` to the import declaration" ) ]
99
102
NotTypeOnlyImport {
103
+ path : AbsoluteSystemPathBuf ,
100
104
import : String ,
101
105
#[ label( "package imported here" ) ]
102
106
span : SourceSpan ,
@@ -105,6 +109,7 @@ pub enum BoundariesDiagnostic {
105
109
} ,
106
110
#[ error( "cannot import package `{name}` because it is not a dependency" ) ]
107
111
PackageNotFound {
112
+ path : AbsoluteSystemPathBuf ,
108
113
name : String ,
109
114
#[ label( "package imported here" ) ]
110
115
span : SourceSpan ,
@@ -116,6 +121,7 @@ pub enum BoundariesDiagnostic {
116
121
"`{import}` resolves to path `{resolved_import_path}` which is outside of `{package_name}`"
117
122
) ) ]
118
123
ImportLeavesPackage {
124
+ path : AbsoluteSystemPathBuf ,
119
125
import : String ,
120
126
resolved_import_path : String ,
121
127
package_name : PackageName ,
@@ -142,6 +148,19 @@ pub enum Error {
142
148
GlobWalk ( #[ from] globwalk:: WalkError ) ,
143
149
#[ error( "failed to read file: {0}" ) ]
144
150
FileNotFound ( AbsoluteSystemPathBuf ) ,
151
+ #[ error( "failed to write to file: {0}" ) ]
152
+ FileWrite ( AbsoluteSystemPathBuf ) ,
153
+ }
154
+
155
+ impl BoundariesDiagnostic {
156
+ pub fn path_and_span ( & self ) -> Option < ( & AbsoluteSystemPath , SourceSpan ) > {
157
+ match self {
158
+ Self :: ImportLeavesPackage { path, span, .. } => Some ( ( path, * span) ) ,
159
+ Self :: PackageNotFound { path, span, .. } => Some ( ( path, * span) ) ,
160
+ Self :: NotTypeOnlyImport { path, span, .. } => Some ( ( path, * span) ) ,
161
+ _ => None ,
162
+ }
163
+ }
145
164
}
146
165
147
166
static PACKAGE_NAME_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
@@ -211,13 +230,21 @@ impl BoundariesResult {
211
230
}
212
231
213
232
impl Run {
214
- pub async fn check_boundaries ( & self ) -> Result < BoundariesResult , Error > {
233
+ pub async fn check_boundaries ( & self , show_progress : bool ) -> Result < BoundariesResult , Error > {
215
234
let rules_map = self . get_processed_rules_map ( ) ;
216
235
let packages: Vec < _ > = self . pkg_dep_graph ( ) . packages ( ) . collect ( ) ;
217
236
let repo = Repository :: discover ( self . repo_root ( ) ) . ok ( ) . map ( Mutex :: new) ;
218
237
let mut result = BoundariesResult :: default ( ) ;
219
238
let global_implicit_dependencies = self . get_implicit_dependencies ( & PackageName :: Root ) ;
220
- for ( package_name, package_info) in packages {
239
+
240
+ let progress = if show_progress {
241
+ println ! ( "Checking packages..." ) ;
242
+ ProgressBar :: new ( packages. len ( ) as u64 )
243
+ } else {
244
+ ProgressBar :: hidden ( )
245
+ } ;
246
+
247
+ for ( package_name, package_info) in packages. into_iter ( ) . progress_with ( progress) {
221
248
if !self . filtered_pkgs ( ) . contains ( package_name)
222
249
|| matches ! ( package_name, PackageName :: Root )
223
250
{
@@ -456,4 +483,57 @@ impl Run {
456
483
457
484
Ok ( ( ) )
458
485
}
486
+
487
+ pub fn patch_file (
488
+ & self ,
489
+ file_path : & AbsoluteSystemPath ,
490
+ file_patches : Vec < ( SourceSpan , String ) > ,
491
+ ) -> Result < ( ) , Error > {
492
+ // Deduplicate and sort by offset
493
+ let file_patches = file_patches
494
+ . into_iter ( )
495
+ . map ( |( span, patch) | ( span. offset ( ) , patch) )
496
+ . collect :: < BTreeMap < usize , String > > ( ) ;
497
+
498
+ let contents = file_path
499
+ . read_to_string ( )
500
+ . map_err ( |_| Error :: FileNotFound ( file_path. to_owned ( ) ) ) ?;
501
+
502
+ let mut options = OpenOptions :: new ( ) ;
503
+ options. read ( true ) . write ( true ) . truncate ( true ) ;
504
+ let mut file = file_path
505
+ . open_with_options ( options)
506
+ . map_err ( |_| Error :: FileNotFound ( file_path. to_owned ( ) ) ) ?;
507
+
508
+ let mut last_idx = 0 ;
509
+ for ( idx, reason) in file_patches {
510
+ let contents_before_span = & contents[ last_idx..idx] ;
511
+
512
+ // Find the last newline before the span (note this is the index into the slice,
513
+ // not the full file)
514
+ let newline_idx = contents_before_span. rfind ( '\n' ) ;
515
+
516
+ // If newline exists, we write all the contents before newline
517
+ if let Some ( newline_idx) = newline_idx {
518
+ file. write_all ( contents[ last_idx..( last_idx + newline_idx) ] . as_bytes ( ) )
519
+ . map_err ( |_| Error :: FileWrite ( file_path. to_owned ( ) ) ) ?;
520
+ file. write_all ( b"\n " )
521
+ . map_err ( |_| Error :: FileWrite ( file_path. to_owned ( ) ) ) ?;
522
+ }
523
+
524
+ file. write_all ( b"// @boundaries-ignore " )
525
+ . map_err ( |_| Error :: FileWrite ( file_path. to_owned ( ) ) ) ?;
526
+ file. write_all ( reason. as_bytes ( ) )
527
+ . map_err ( |_| Error :: FileWrite ( file_path. to_owned ( ) ) ) ?;
528
+ file. write_all ( b"\n " )
529
+ . map_err ( |_| Error :: FileWrite ( file_path. to_owned ( ) ) ) ?;
530
+
531
+ last_idx = idx;
532
+ }
533
+
534
+ file. write_all ( contents[ last_idx..] . as_bytes ( ) )
535
+ . map_err ( |_| Error :: FileWrite ( file_path. to_owned ( ) ) ) ?;
536
+
537
+ Ok ( ( ) )
538
+ }
459
539
}
0 commit comments