Skip to content

Commit 3f43384

Browse files
Merge branch 'main' into dimitri/lockfile-states
2 parents 31fc656 + 348756f commit 3f43384

File tree

74 files changed

+4695
-850
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+4695
-850
lines changed

Cargo.lock

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

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ turborepo-fs = { path = "crates/turborepo-fs" }
6161
turborepo-lib = { path = "crates/turborepo-lib", default-features = false }
6262
turborepo-lockfiles = { path = "crates/turborepo-lockfiles" }
6363
turborepo-microfrontends = { path = "crates/turborepo-microfrontends" }
64-
turborepo-process = { path = "crates/turborepo-process" }
6564
turborepo-repository = { path = "crates/turborepo-repository" }
6665
turborepo-ui = { path = "crates/turborepo-ui" }
6766
turborepo-unescape = { path = "crates/turborepo-unescape" }
6867
turborepo-scm = { path = "crates/turborepo-scm" }
68+
turborepo-signals = { path = "crates/turborepo-signals" }
6969
wax = { path = "crates/turborepo-wax" }
7070
turborepo-vercel-api = { path = "crates/turborepo-vercel-api" }
7171
turborepo-vercel-api-mock = { path = "crates/turborepo-vercel-api-mock" }

crates/turborepo-lib/Cargo.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,15 @@ lazy_static = { workspace = true }
7575
libc = "0.2.140"
7676
merge = { workspace = true }
7777
miette = { workspace = true, features = ["fancy"] }
78+
nix = "0.26.2"
7879
notify = { workspace = true }
7980
num_cpus = { workspace = true }
8081
owo-colors = { workspace = true }
8182
oxc_resolver = { workspace = true }
8283
path-clean = "1.0.1"
8384
petgraph = { workspace = true }
8485
pidlock = { path = "../turborepo-pidlock" }
86+
portable-pty = "0.8.1"
8587
pprof = { version = "0.12.1", features = [
8688
"prost-codec",
8789
"frame-pointer",
@@ -135,9 +137,9 @@ turborepo-fs = { path = "../turborepo-fs" }
135137
turborepo-graph-utils = { path = "../turborepo-graph-utils" }
136138
turborepo-lockfiles = { workspace = true }
137139
turborepo-microfrontends = { workspace = true }
138-
turborepo-process = { workspace = true }
139140
turborepo-repository = { path = "../turborepo-repository" }
140141
turborepo-scm = { workspace = true }
142+
turborepo-signals = { workspace = true }
141143
turborepo-telemetry = { path = "../turborepo-telemetry" }
142144
turborepo-ui = { workspace = true }
143145
turborepo-unescape = { workspace = true }
@@ -153,6 +155,9 @@ which = { workspace = true }
153155
uds_windows = "1.0.2"
154156
async-io = "1.12.0"
155157

158+
[target.'cfg(target_os = "windows")'.dev-dependencies]
159+
windows-sys = { version = "0.59", features = ["Win32_System_Threading"] }
160+
156161
[build-dependencies]
157162
capnpc = "0.18.0"
158163
tonic-build = "0.8.4"

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

+167-11
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,184 @@
1-
use std::collections::{BTreeMap, HashSet};
1+
use std::{
2+
collections::{BTreeMap, HashSet},
3+
sync::Arc,
4+
};
25

6+
use camino::Utf8Path;
37
use itertools::Itertools;
48
use miette::{NamedSource, SourceSpan};
5-
use oxc_resolver::{ResolveError, Resolver};
9+
use oxc_resolver::{ResolveError, Resolver, TsConfig};
10+
use swc_common::{comments::SingleThreadedComments, SourceFile, Span};
611
use turbo_trace::ImportType;
7-
use turbopath::{AbsoluteSystemPath, PathRelation, RelativeUnixPath};
12+
use turbopath::{AbsoluteSystemPath, AnchoredSystemPathBuf, PathRelation, RelativeUnixPath};
813
use turborepo_repository::{
9-
package_graph::{PackageName, PackageNode},
14+
package_graph::{PackageInfo, PackageName, PackageNode},
1015
package_json::PackageJson,
1116
};
1217

1318
use crate::{
14-
boundaries::{BoundariesDiagnostic, Error},
19+
boundaries::{tsconfig::TsConfigLoader, BoundariesDiagnostic, BoundariesResult, Error},
1520
run::Run,
1621
};
1722

1823
impl Run {
24+
/// Checks if the given import can be resolved as a tsconfig path alias,
25+
/// e.g. `@/types/foo` -> `./src/foo`, and if so, checks the resolved paths.
26+
///
27+
/// Returns true if the import was resolved as a tsconfig path alias.
28+
#[allow(clippy::too_many_arguments)]
29+
fn check_import_as_tsconfig_path_alias(
30+
&self,
31+
tsconfig_loader: &mut TsConfigLoader,
32+
package_name: &PackageName,
33+
package_root: &AbsoluteSystemPath,
34+
span: SourceSpan,
35+
file_path: &AbsoluteSystemPath,
36+
file_content: &str,
37+
import: &str,
38+
result: &mut BoundariesResult,
39+
) -> Result<bool, Error> {
40+
let dir = file_path.parent().expect("file_path must have a parent");
41+
let Some(tsconfig) = tsconfig_loader.load(dir, result) else {
42+
return Ok(false);
43+
};
44+
45+
let resolved_paths = tsconfig.resolve(dir.as_std_path(), import);
46+
for path in &resolved_paths {
47+
let Some(utf8_path) = Utf8Path::from_path(path) else {
48+
result.diagnostics.push(BoundariesDiagnostic::InvalidPath {
49+
path: path.to_string_lossy().to_string(),
50+
});
51+
continue;
52+
};
53+
let resolved_import_path = AbsoluteSystemPath::new(utf8_path)?;
54+
result.diagnostics.extend(self.check_file_import(
55+
file_path,
56+
package_root,
57+
package_name,
58+
import,
59+
resolved_import_path,
60+
span,
61+
file_content,
62+
)?);
63+
}
64+
65+
Ok(!resolved_paths.is_empty())
66+
}
67+
68+
#[allow(clippy::too_many_arguments)]
69+
pub(crate) fn check_import(
70+
&self,
71+
comments: &SingleThreadedComments,
72+
tsconfig_loader: &mut TsConfigLoader,
73+
result: &mut BoundariesResult,
74+
source_file: &Arc<SourceFile>,
75+
package_name: &PackageName,
76+
package_root: &AbsoluteSystemPath,
77+
import: &str,
78+
import_type: &ImportType,
79+
span: &Span,
80+
file_path: &AbsoluteSystemPath,
81+
file_content: &str,
82+
package_info: &PackageInfo,
83+
internal_dependencies: &HashSet<&PackageNode>,
84+
unresolved_external_dependencies: Option<&BTreeMap<String, String>>,
85+
resolver: &Resolver,
86+
) -> Result<(), Error> {
87+
// If the import is prefixed with `@boundaries-ignore`, we ignore it, but print
88+
// a warning
89+
match Self::get_ignored_comment(comments, *span) {
90+
Some(reason) if reason.is_empty() => {
91+
result.warnings.push(
92+
"@boundaries-ignore requires a reason, e.g. `// @boundaries-ignore implicit \
93+
dependency`"
94+
.to_string(),
95+
);
96+
}
97+
Some(_) => {
98+
// Try to get the line number for warning
99+
let line = result.source_map.lookup_line(span.lo()).map(|l| l.line);
100+
if let Ok(line) = line {
101+
result
102+
.warnings
103+
.push(format!("ignoring import on line {} in {}", line, file_path));
104+
} else {
105+
result
106+
.warnings
107+
.push(format!("ignoring import in {}", file_path));
108+
}
109+
110+
return Ok(());
111+
}
112+
None => {}
113+
}
114+
115+
let (start, end) = result.source_map.span_to_char_offset(source_file, *span);
116+
let start = start as usize;
117+
let end = end as usize;
118+
119+
let span = SourceSpan::new(start.into(), end - start);
120+
121+
if self.check_import_as_tsconfig_path_alias(
122+
tsconfig_loader,
123+
package_name,
124+
package_root,
125+
span,
126+
file_path,
127+
file_content,
128+
import,
129+
result,
130+
)? {
131+
return Ok(());
132+
}
133+
134+
// We have a file import
135+
let check_result = if import.starts_with(".") {
136+
let import_path = RelativeUnixPath::new(import)?;
137+
let dir_path = file_path
138+
.parent()
139+
.ok_or_else(|| Error::NoParentDir(file_path.to_owned()))?;
140+
let resolved_import_path = dir_path.join_unix_path(import_path).clean()?;
141+
self.check_file_import(
142+
file_path,
143+
package_root,
144+
package_name,
145+
import,
146+
&resolved_import_path,
147+
span,
148+
file_content,
149+
)?
150+
} else if Self::is_potential_package_name(import) {
151+
self.check_package_import(
152+
import,
153+
*import_type,
154+
span,
155+
file_path,
156+
file_content,
157+
&package_info.package_json,
158+
internal_dependencies,
159+
unresolved_external_dependencies,
160+
resolver,
161+
)
162+
} else {
163+
None
164+
};
165+
166+
result.diagnostics.extend(check_result);
167+
168+
Ok(())
169+
}
170+
171+
#[allow(clippy::too_many_arguments)]
19172
pub(crate) fn check_file_import(
20173
&self,
21174
file_path: &AbsoluteSystemPath,
22175
package_path: &AbsoluteSystemPath,
176+
package_name: &PackageName,
23177
import: &str,
178+
resolved_import_path: &AbsoluteSystemPath,
24179
source_span: SourceSpan,
25180
file_content: &str,
26181
) -> Result<Option<BoundariesDiagnostic>, Error> {
27-
let import_path = RelativeUnixPath::new(import)?;
28-
let dir_path = file_path
29-
.parent()
30-
.ok_or_else(|| Error::NoParentDir(file_path.to_owned()))?;
31-
let resolved_import_path = dir_path.join_unix_path(import_path).clean()?;
32182
// We have to check for this case because `relation_to_path` returns `Parent` if
33183
// the paths are equal and there's nothing wrong with importing the
34184
// package you're in.
@@ -38,11 +188,17 @@ impl Run {
38188
// We use `relation_to_path` and not `contains` because `contains`
39189
// panics on invalid paths with too many `..` components
40190
if !matches!(
41-
package_path.relation_to_path(&resolved_import_path),
191+
package_path.relation_to_path(resolved_import_path),
42192
PathRelation::Parent
43193
) {
194+
let resolved_import_path =
195+
AnchoredSystemPathBuf::relative_path_between(package_path, resolved_import_path)
196+
.to_string();
197+
44198
Ok(Some(BoundariesDiagnostic::ImportLeavesPackage {
45199
import: import.to_string(),
200+
resolved_import_path,
201+
package_name: package_name.to_owned(),
46202
span: source_span,
47203
text: NamedSource::new(file_path.as_str(), file_content.to_string()),
48204
}))

0 commit comments

Comments
 (0)