Skip to content

Commit

Permalink
Add more tests and fixing issues
Browse files Browse the repository at this point in the history
  • Loading branch information
NicholasLYang committed Oct 25, 2024
1 parent f86b92f commit cef6e1a
Show file tree
Hide file tree
Showing 23 changed files with 293 additions and 48 deletions.
6 changes: 3 additions & 3 deletions crates/turbo-trace/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ async fn main() -> Result<(), PathError> {
};

if !result.errors.is_empty() {
for error in &result.errors {
eprintln!("error: {}", error);
for error in result.errors {
println!("{:?}", Report::new(error))
}
std::process::exit(1);
} else {
for file in &result.files {
for file in result.files.keys() {
println!("{}", file);
}
}
Expand Down
114 changes: 71 additions & 43 deletions crates/turbo-trace/src/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use swc_ecma_ast::EsVersion;
use swc_ecma_parser::{lexer::Lexer, Capturing, EsSyntax, Parser, Syntax, TsSyntax};
use swc_ecma_visit::VisitWith;
use thiserror::Error;
use tokio::{sync::Mutex, task::JoinSet};
use tokio::task::JoinSet;
use tracing::debug;
use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf, PathError};

use crate::import_finder::ImportFinder;
Expand All @@ -26,6 +27,7 @@ pub struct Tracer {
ts_config: Option<AbsoluteSystemPathBuf>,
source_map: Arc<SourceMap>,
cwd: AbsoluteSystemPathBuf,
errors: Vec<TraceError>,
}

#[derive(Debug, Error, Diagnostic)]
Expand Down Expand Up @@ -70,23 +72,26 @@ impl Tracer {
files,
ts_config,
cwd,
errors: Vec::new(),
source_map: Arc::new(SourceMap::default()),
}
}

pub async fn get_imports_from_file(
&self,
source_map: &SourceMap,
errors: &mut Vec<TraceError>,
resolver: &Resolver,
file_path: &AbsoluteSystemPath,
) -> Result<(Vec<AbsoluteSystemPathBuf>, SeenFile), TraceError> {
) -> Option<(Vec<AbsoluteSystemPathBuf>, SeenFile)> {
// Read the file content
let Ok(file_content) = tokio::fs::read_to_string(&file_path).await else {
return Err(TraceError::FileNotFound(file_path.to_owned()));
errors.push(TraceError::FileNotFound(file_path.to_owned()));
return None;
};

let comments = SingleThreadedComments::default();

let source_file = self.source_map.new_source_file(
let source_file = source_map.new_source_file(
FileName::Custom(file_path.to_string()).into(),
file_content.clone(),
);
Expand Down Expand Up @@ -116,7 +121,8 @@ impl Tracer {

// Parse the file as a module
let Ok(module) = parser.parse_module() else {
return Err(TraceError::FileNotFound(file_path.to_owned()));
errors.push(TraceError::FileNotFound(file_path.to_owned()));
return None;
};

// Visit the AST and find imports
Expand All @@ -126,30 +132,40 @@ impl Tracer {
// visit
let mut files = Vec::new();
for (import, span) in finder.imports() {
debug!("processing {} in {}", import, file_path);
let Some(file_dir) = file_path.parent() else {
return Err(TraceError::RootFile(file_path.to_owned()));
errors.push(TraceError::RootFile(file_path.to_owned()));
continue;
};
match resolver.resolve(file_dir, import) {
Ok(resolved) => match resolved.into_path_buf().try_into() {
Ok(path) => files.push(path),
Err(err) => {
return Err(TraceError::PathEncoding(err));
Ok(resolved) => {
debug!("resolved {:?}", resolved);
match resolved.into_path_buf().try_into() {
Ok(path) => files.push(path),
Err(err) => {
errors.push(TraceError::PathEncoding(err));
continue;
}
}
},
Err(ResolveError::Builtin { .. }) => {}
Err(_) => {
let (start, end) = self.source_map.span_to_char_offset(&source_file, *span);
}
Err(err @ ResolveError::Builtin { .. }) => {
debug!("built in: {:?}", err);
}
Err(err) => {
debug!("failed to resolve: {:?}", err);
let (start, end) = source_map.span_to_char_offset(&source_file, *span);

return Err(TraceError::Resolve {
errors.push(TraceError::Resolve {
path: import.to_string(),
span: (start as usize, end as usize).into(),
text: NamedSource::new(file_path.to_string(), file_content.clone()),
});
continue;
}
}
}

Ok((files, SeenFile { ast: Some(module) }))
Some((files, SeenFile { ast: Some(module) }))
}

pub async fn trace_file(
Expand All @@ -158,21 +174,27 @@ impl Tracer {
file_path: AbsoluteSystemPathBuf,
depth: usize,
seen: &mut HashMap<AbsoluteSystemPathBuf, SeenFile>,
) -> Result<(), TraceError> {
) {
if matches!(file_path.extension(), Some("css") | Some("json")) {
return Ok(());
return;
}
if seen.contains_key(&file_path) {
return Ok(());
return;
}

let (imports, seen_file) = self.get_imports_from_file(resolver, &file_path).await?;
self.files
.extend(imports.into_iter().map(|import| (import, depth + 1)));
let entry = seen.entry(file_path.clone()).or_default();

seen.insert(file_path, seen_file);
let Some((imports, seen_file)) =
Self::get_imports_from_file(&self.source_map, &mut self.errors, resolver, &file_path)
.await
else {
return;
};

Ok(())
*entry = seen_file;

self.files
.extend(imports.into_iter().map(|import| (import, depth + 1)));
}

pub fn create_resolver(&mut self) -> Resolver {
Expand All @@ -193,7 +215,6 @@ impl Tracer {
}

pub async fn trace(mut self, max_depth: Option<usize>) -> TraceResult {
let mut errors = vec![];
let mut seen: HashMap<AbsoluteSystemPathBuf, SeenFile> = HashMap::new();
let resolver = self.create_resolver();

Expand All @@ -203,24 +224,22 @@ impl Tracer {
continue;
}
}
if let Err(err) = self
.trace_file(&resolver, file_path, file_depth, &mut seen)
.await
{
errors.push(err);
}
self.trace_file(&resolver, file_path, file_depth, &mut seen)
.await;
}

TraceResult {
files: seen,
errors,
errors: self.errors,
}
}

pub async fn reverse_trace(mut self) -> TraceResult {
let files = match globwalk::globwalk(
&self.cwd,
&[
"**/*.js".parse().expect("valid glob"),
"**/*.jsx".parse().expect("valid glob"),
"**/*.ts".parse().expect("valid glob"),
"**/*.tsx".parse().expect("valid glob"),
],
Expand Down Expand Up @@ -248,32 +267,41 @@ impl Tracer {
let shared_self = shared_self.clone();
let resolver = resolver.clone();
futures.spawn(async move {
let (imported_files, seen_file) =
shared_self.get_imports_from_file(&resolver, &file).await?;
let mut errors = Vec::new();
let Some((imported_files, seen_file)) = Self::get_imports_from_file(
&shared_self.source_map,
&mut errors,
&resolver,
&file,
)
.await
else {
return (errors, None);
};

for import in imported_files {
if shared_self
.files
.iter()
.any(|(source, _)| import.as_path() == source.as_path())
{
return Ok(Some((file, seen_file)));
return (errors, Some((file, seen_file)));
}
}

Ok(None)
(errors, None)
});
}

let mut usages = HashMap::new();
let mut errors = Vec::new();

while let Some(result) = futures.join_next().await {
match result.unwrap() {
Ok(Some((path, file))) => {
usages.insert(path, file);
}
Ok(None) => {}
Err(err) => errors.push(err),
let (errs, file) = result.unwrap();
errors.extend(errs);

if let Some((path, seen_file)) = file {
usages.insert(path, seen_file);
}
}

Expand Down
35 changes: 34 additions & 1 deletion crates/turborepo/tests/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn test_double_symlink() -> Result<(), anyhow::Error> {
}

#[test]
fn test_trace() -> Result<(), anyhow::Error> {
fn test_ast() -> Result<(), anyhow::Error> {
// Separate because the `\\` -> `/` filter isn't compatible with ast
check_json!(
"turbo_trace",
Expand All @@ -30,6 +30,11 @@ fn test_trace() -> Result<(), anyhow::Error> {
"get `main.ts` with ast" => "query { file(path: \"main.ts\") { path ast } }",
);

Ok(())
}

#[test]
fn test_trace() -> Result<(), anyhow::Error> {
insta::with_settings!({ filters => vec![(r"\\\\", "/")]}, {
check_json!(
"turbo_trace",
Expand All @@ -41,8 +46,36 @@ fn test_trace() -> Result<(), anyhow::Error> {
"get `circular.ts` with dependencies" => "query { file(path: \"circular.ts\") { path dependencies { files { items { path } } } } }",
"get `invalid.ts` with dependencies" => "query { file(path: \"invalid.ts\") { path dependencies { files { items { path } } errors { items { message } } } } }",
"get `main.ts` with depth = 0" => "query { file(path: \"main.ts\") { path dependencies(depth: 1) { files { items { path } } } } }",
"get `with_prefix.ts` with dependencies" => "query { file(path: \"with_prefix.ts\") { path dependencies { files { items { path } } } } }",
);

Ok(())
})
}

#[test]
fn test_trace_on_monorepo() -> Result<(), anyhow::Error> {
insta::with_settings!({ filters => vec![(r"\\\\", "/")]}, {
check_json!(
"turbo_trace_monorepo",
"[email protected]",
"query",
"get `apps/my-app/index.ts` with dependencies" => "query { file(path: \"apps/my-app/index.ts\") { path dependencies { files { items { path } } errors { items { message } } } } }",
"get `packages/utils/index` with dependents" => "query { file(path: \"packages/utils/index.ts\") { path dependents { files { items { path } } errors { items { message } } } } }",
);

Ok(())
})
}

#[test]
fn test_reverse_trace() -> Result<(), anyhow::Error> {
check_json!(
"turbo_trace",
"[email protected]",
"query",
"get `button.tsx` with dependents" => "query { file(path: \"button.tsx\") { path dependents { files { items { path } } } } }",
);

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/turborepo/tests/query.rs
expression: query_output
---
{
"data": {
"file": {
"path": "button.tsx",
"dependents": {
"files": {
"items": [
{
"path": "invalid.ts"
},
{
"path": "main.ts"
}
]
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ expression: query_output
"errors": {
"items": [
{
"message": "failed to resolve import"
"message": "failed to resolve import to `./non-existent-file.js`"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/turborepo/tests/query.rs
expression: query_output
---
{
"data": {
"file": {
"path": "with_prefix.ts",
"dependencies": {
"files": {
"items": [
{
"path": "bar.js"
},
{
"path": "foo.js"
}
]
}
}
}
}
}
Loading

0 comments on commit cef6e1a

Please sign in to comment.