Skip to content

Commit

Permalink
feat(query): recover from trace errors (#9244)
Browse files Browse the repository at this point in the history
### Description

Even if we get trace errors, return the files we have successfully
traced

### Testing Instructions

Added a test for a file with an invalid import
  • Loading branch information
NicholasLYang authored Oct 11, 2024
1 parent a99773e commit 961ce00
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 39 deletions.
2 changes: 1 addition & 1 deletion crates/turbo-trace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
mod import_finder;
mod tracer;

pub use tracer::{TraceError, Tracer};
pub use tracer::{TraceError, TraceResult, Tracer};
6 changes: 3 additions & 3 deletions crates/turbo-trace/src/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,18 @@ impl Tracer {
cwd: AbsoluteSystemPathBuf,
files: Vec<AbsoluteSystemPathBuf>,
ts_config: Option<Utf8PathBuf>,
) -> Result<Self, PathError> {
) -> Self {
let ts_config =
ts_config.map(|ts_config| AbsoluteSystemPathBuf::from_unknown(&cwd, ts_config));

let seen = HashSet::new();

Ok(Self {
Self {
files,
seen,
ts_config,
source_map: Rc::new(SourceMap::default()),
})
}
}

pub fn trace(mut self) -> TraceResult {
Expand Down
88 changes: 71 additions & 17 deletions crates/turborepo-lib/src/query/file.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::sync::Arc;

use async_graphql::Object;
use async_graphql::{Object, SimpleObject};
use itertools::Itertools;
use turbo_trace::Tracer;
use turbopath::AbsoluteSystemPathBuf;

use crate::{query::Error, run::Run};
use crate::{
query::{Array, Error},
run::Run,
};

pub struct File {
run: Arc<Run>,
Expand All @@ -18,6 +21,66 @@ impl File {
}
}

#[derive(SimpleObject, Debug)]
pub struct TraceError {
message: String,
path: Option<String>,
start: Option<usize>,
end: Option<usize>,
}

impl From<turbo_trace::TraceError> for TraceError {
fn from(error: turbo_trace::TraceError) -> Self {
let message = error.to_string();
match error {
turbo_trace::TraceError::FileNotFound(file) => TraceError {
message,
path: Some(file.to_string()),
start: None,
end: None,
},
turbo_trace::TraceError::PathEncoding(_) => TraceError {
message,
path: None,
start: None,
end: None,
},
turbo_trace::TraceError::RootFile(path) => TraceError {
message,
path: Some(path.to_string()),
start: None,
end: None,
},
turbo_trace::TraceError::Resolve { span, text } => TraceError {
message,
path: Some(text.name().to_string()),
start: Some(span.offset()),
end: Some(span.offset() + span.len()),
},
}
}
}

#[derive(SimpleObject)]
struct TraceResult {
files: Array<File>,
errors: Array<TraceError>,
}

impl TraceResult {
fn new(result: turbo_trace::TraceResult, run: Arc<Run>) -> Self {
Self {
files: result
.files
.into_iter()
.sorted()
.map(|path| File::new(run.clone(), path))
.collect(),
errors: result.errors.into_iter().map(|e| e.into()).collect(),
}
}
}

#[Object]
impl File {
async fn contents(&self) -> Result<String, Error> {
Expand All @@ -37,25 +100,16 @@ impl File {
Ok(self.path.to_string())
}

async fn dependencies(&self) -> Result<Vec<File>, Error> {
async fn dependencies(&self) -> TraceResult {
let tracer = Tracer::new(
self.run.repo_root().to_owned(),
vec![self.path.clone()],
None,
)?;

let result = tracer.trace();
if !result.errors.is_empty() {
return Err(Error::Trace(result.errors));
}
);

Ok(result
.files
.into_iter()
// Filter out the file we're looking at
.filter(|file| file != &self.path)
.map(|path| File::new(self.run.clone(), path))
.sorted_by(|a, b| a.path.cmp(&b.path))
.collect())
let mut result = tracer.trace();
// Remove the file itself from the result
result.files.remove(&self.path);
TraceResult::new(result, self.run.clone())
}
}
2 changes: 2 additions & 0 deletions crates/turborepo-lib/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ impl RepositoryQuery {
#[graphql(concrete(name = "RepositoryTasks", params(RepositoryTask)))]
#[graphql(concrete(name = "Packages", params(Package)))]
#[graphql(concrete(name = "ChangedPackages", params(ChangedPackage)))]
#[graphql(concrete(name = "Files", params(File)))]
#[graphql(concrete(name = "TraceErrors", params(file::TraceError)))]
pub struct Array<T: OutputType> {
items: Vec<T>,
length: usize,
Expand Down
2 changes: 2 additions & 0 deletions turborepo-tests/integration/fixtures/turbo_trace/invalid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import foo from "./non-existent-file.js";
import { Button } from "./button.tsx";
75 changes: 57 additions & 18 deletions turborepo-tests/integration/tests/turbo-trace.t
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,88 @@ Setup
}
}
$ ${TURBO} query "query { file(path: \"main.ts\") { path, dependencies { path } } }"
$ ${TURBO} query "query { file(path: \"main.ts\") { path, dependencies { files { items { path } } } } }"
WARNING query command is experimental and may change in the future
{
"data": {
"file": {
"path": "main.ts",
"dependencies": [
{
"path": "button.tsx"
},
{
"path": "foo.js"
},
{
"path": "node_modules(\/|\\\\)repeat-string(\/|\\\\)index.js" (re)
"dependencies": {
"files": {
"items": [
{
"path": "button.tsx"
},
{
"path": "foo.js"
},
{
"path": "node_modules(\/|\\\\)repeat-string(\/|\\\\)index.js" (re)
}
]
}
]
}
}
}
}
$ ${TURBO} query "query { file(path: \"button.tsx\") { path, dependencies { path } } }"
$ ${TURBO} query "query { file(path: \"button.tsx\") { path, dependencies { files { items { path } } } } }"
WARNING query command is experimental and may change in the future
{
"data": {
"file": {
"path": "button.tsx",
"dependencies": []
"dependencies": {
"files": {
"items": []
}
}
}
}
}
$ ${TURBO} query "query { file(path: \"circular.ts\") { path, dependencies { path } } }"
$ ${TURBO} query "query { file(path: \"circular.ts\") { path dependencies { files { items { path } } } } }"
WARNING query command is experimental and may change in the future
{
"data": {
"file": {
"path": "circular.ts",
"dependencies": [
{
"path": "circular2.ts"
"dependencies": {
"files": {
"items": [
{
"path": "circular2.ts"
}
]
}
}
}
}
}
Trace file with invalid import
$ ${TURBO} query "query { file(path: \"invalid.ts\") { path dependencies { files { items { path } } errors { items { message } } } } }"
WARNING query command is experimental and may change in the future
{
"data": {
"file": {
"path": "invalid.ts",
"dependencies": {
"files": {
"items": [
{
"path": "button.tsx"
}
]
},
"errors": {
"items": [
{
"message": "failed to resolve import"
}
]
}
]
}
}
}
}
Expand Down

0 comments on commit 961ce00

Please sign in to comment.