Skip to content

Commit b1482b0

Browse files
committed
Clean up patch code, add nice output
1 parent adf42d5 commit b1482b0

File tree

4 files changed

+59
-38
lines changed

4 files changed

+59
-38
lines changed

Cargo.lock

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turborepo-lib/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ human-panic = "1.2.1"
6969
human_format = "1.1.0"
7070
humantime = "2.1.0"
7171
ignore = "0.4.22"
72+
indicatif = { workspace = true }
7273
itertools = { workspace = true }
7374
jsonc-parser = { version = "0.21.0" }
7475
lazy_static = { workspace = true }

crates/turborepo-lib/src/boundaries/mod.rs

+34-19
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod tags;
44
mod tsconfig;
55

66
use std::{
7-
collections::{HashMap, HashSet},
7+
collections::{BTreeMap, HashMap, HashSet},
88
fs::OpenOptions,
99
io::Write,
1010
sync::{Arc, LazyLock, Mutex},
@@ -13,6 +13,7 @@ use std::{
1313
pub use config::{BoundariesConfig, Permissions, Rule};
1414
use git2::Repository;
1515
use globwalk::Settings;
16+
use indicatif::ProgressIterator;
1617
use miette::{Diagnostic, NamedSource, Report, SourceSpan};
1718
use regex::Regex;
1819
use swc_common::{
@@ -235,7 +236,8 @@ impl Run {
235236
let repo = Repository::discover(self.repo_root()).ok().map(Mutex::new);
236237
let mut result = BoundariesResult::default();
237238
let global_implicit_dependencies = self.get_implicit_dependencies(&PackageName::Root);
238-
for (package_name, package_info) in packages {
239+
println!("Checking packages...");
240+
for (package_name, package_info) in packages.into_iter().progress() {
239241
if !self.filtered_pkgs().contains(package_name)
240242
|| matches!(package_name, PackageName::Root)
241243
{
@@ -475,43 +477,56 @@ impl Run {
475477
Ok(())
476478
}
477479

478-
pub fn add_ignore(
480+
pub fn patch_file(
479481
&self,
480482
file_path: &AbsoluteSystemPath,
481-
span: SourceSpan,
482-
reason: String,
483+
file_patches: Vec<(SourceSpan, String)>,
483484
) -> Result<(), Error> {
485+
// Deduplicate and sort by offset
486+
let file_patches = file_patches
487+
.into_iter()
488+
.map(|(span, patch)| (span.offset(), patch.into()))
489+
.collect::<BTreeMap<usize, String>>();
490+
484491
let contents = file_path
485492
.read_to_string()
486493
.map_err(|_| Error::FileNotFound(file_path.to_owned()))?;
487-
let contents_before_span = &contents[..span.offset()];
488494

489-
// Find the last newline before the span
490-
let newline_idx = contents_before_span.rfind('\n');
491495
let mut options = OpenOptions::new();
492496
options.read(true).write(true).truncate(true);
493497
let mut file = file_path
494498
.open_with_options(options)
495499
.map_err(|_| Error::FileNotFound(file_path.to_owned()))?;
496500

497-
if let Some(newline_idx) = newline_idx {
498-
file.write_all(contents[..newline_idx].as_bytes())
499-
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
500-
file.write_all(b"\n")
501-
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
501+
let mut last_idx = 0;
502+
for (idx, reason) in file_patches {
503+
let contents_before_span = &contents[last_idx..idx];
504+
505+
// Find the last newline before the span (note this is the index into the slice,
506+
// not the full file)
507+
let newline_idx = contents_before_span.rfind('\n');
508+
509+
// If newline exists, we write all the contents before newline
510+
if let Some(newline_idx) = newline_idx {
511+
file.write_all(contents[last_idx..(last_idx + newline_idx)].as_bytes())
512+
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
513+
file.write_all(b"\n")
514+
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
515+
}
516+
502517
file.write_all(b"// @boundaries-ignore ")
503518
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
504519
file.write_all(reason.as_bytes())
505520
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
506-
file.write_all(contents[newline_idx..].as_bytes())
507-
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
508-
} else {
509-
file.write_all(b"// @boundaries-ignore\n")
510-
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
511-
file.write_all(contents.as_bytes())
521+
file.write_all(b"\n")
512522
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
523+
524+
last_idx = idx;
513525
}
514526

527+
file.write_all(contents[last_idx..].as_bytes())
528+
.map_err(|_| Error::FileWriteError(file_path.to_owned()))?;
529+
515530
Ok(())
516531
}
517532
}

crates/turborepo-lib/src/commands/boundaries.rs

+22-18
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::collections::HashSet;
1+
use std::collections::HashMap;
22

33
use dialoguer::{Confirm, Input};
4-
use miette::Report;
4+
use miette::{Report, SourceSpan};
5+
use turbopath::AbsoluteSystemPath;
56
use turborepo_signals::{listeners::get_signal, SignalHandler};
67
use turborepo_telemetry::events::command::CommandEventBuilder;
78
use turborepo_ui::{color, BOLD_GREEN};
@@ -25,20 +26,11 @@ pub async fn run(
2526
let result = run.check_boundaries().await?;
2627

2728
if let Some(ignore) = ignore {
28-
let mut seen_locations = HashSet::new();
29+
let mut patches: HashMap<&AbsoluteSystemPath, Vec<(SourceSpan, String)>> = HashMap::new();
2930
for diagnostic in &result.diagnostics {
3031
let Some((path, span)) = diagnostic.path_and_span() else {
3132
continue;
3233
};
33-
if seen_locations.contains(&(path, span)) {
34-
continue;
35-
}
36-
seen_locations.insert((path, span));
37-
38-
let short_path = match run.repo_root().anchor(path) {
39-
Ok(path) => path.to_string(),
40-
Err(_) => path.to_string(),
41-
};
4234

4335
let reason = match ignore {
4436
BoundariesIgnore::All => Some(reason.clone().unwrap_or_else(|| {
@@ -47,6 +39,10 @@ pub async fn run(
4739
BoundariesIgnore::Prompt => {
4840
print!("\x1B[2J\x1B[1;1H");
4941
println!("{:?}", Report::new(diagnostic.clone()));
42+
let short_path = match run.repo_root().anchor(path) {
43+
Ok(path) => path.to_string(),
44+
Err(_) => path.to_string(),
45+
};
5046
let prompt = format!("Add @boundaries-ignore to {}?", short_path);
5147
if Confirm::new()
5248
.with_prompt(prompt)
@@ -69,14 +65,22 @@ pub async fn run(
6965
};
7066

7167
if let Some(reason) = reason {
72-
println!(
73-
"{} {}",
74-
color!(run.color_config(), BOLD_GREEN, "patching"),
75-
short_path
76-
);
77-
run.add_ignore(path, span, reason)?;
68+
patches.entry(path).or_default().push((span, reason));
7869
}
7970
}
71+
72+
for (path, file_patches) in patches {
73+
let short_path = match run.repo_root().anchor(path) {
74+
Ok(path) => path.to_string(),
75+
Err(_) => path.to_string(),
76+
};
77+
println!(
78+
"{} {}",
79+
color!(run.color_config(), BOLD_GREEN, "patching"),
80+
short_path
81+
);
82+
run.patch_file(path, file_patches)?;
83+
}
8084
} else {
8185
result.emit(run.color_config());
8286
}

0 commit comments

Comments
 (0)