Skip to content

Commit 72f99e8

Browse files
committed
refactor: replace task_call_stack with recursive Error::NestPlan
1 parent e52b185 commit 72f99e8

File tree

6 files changed

+83
-222
lines changed

6 files changed

+83
-222
lines changed

crates/vite_task/src/session/mod.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use vite_task_graph::{
2121
loader::UserConfigLoader,
2222
};
2323
use vite_task_plan::{
24-
ExecutionPlan, TaskGraphLoader, TaskPlanErrorKind,
24+
ExecutionPlan, TaskGraphLoader,
2525
plan_request::{PlanRequest, ScriptCommand, SyntheticPlanRequest},
2626
prepend_path_env,
2727
};
@@ -484,16 +484,15 @@ impl<'a> Session<'a> {
484484
let plan_request = match command.into_plan_request(&cwd) {
485485
Ok(plan_request) => plan_request,
486486
Err(crate::cli::CLITaskQueryError::MissingTaskSpecifier) => {
487-
return Err(TaskPlanErrorKind::MissingTaskSpecifier.with_empty_call_stack());
487+
return Err(vite_task_plan::Error::MissingTaskSpecifier);
488488
}
489489
Err(error) => {
490-
return Err(TaskPlanErrorKind::ParsePlanRequestError {
490+
return Err(vite_task_plan::Error::ParsePlanRequest {
491491
error: error.into(),
492492
program: Str::from("vp"),
493493
args: Arc::default(),
494494
cwd: Arc::clone(&cwd),
495-
}
496-
.with_empty_call_stack());
495+
});
497496
}
498497
};
499498
let plan = ExecutionPlan::plan(

crates/vite_task_bin/tests/e2e_snapshots/fixtures/task-select/snapshots/typo in task script fails without list.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ source: crates/vite_task_bin/tests/e2e_snapshots/main.rs
33
expression: e2e_outputs
44
---
55
[1]> vp run run-typo-task
6-
Error: Failed to plan execution, task call stack: task-select-test#run-typo-task
6+
Error: Failed to plan tasks from `vp run nonexistent-xyz` in task task-select-test#run-typo-task
77

88
Caused by:
99
0: Failed to query tasks from task graph

crates/vite_task_plan/src/context.rs

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use std::{env::JoinPathsError, ffi::OsStr, fmt::Display, ops::Range, sync::Arc};
1+
use std::{env::JoinPathsError, ffi::OsStr, ops::Range, sync::Arc};
22

33
use rustc_hash::FxHashMap;
44
use vite_path::AbsolutePath;
55
use vite_str::Str;
6-
use vite_task_graph::{IndexedTaskGraph, TaskNodeIndex, display::TaskDisplay};
6+
use vite_task_graph::{IndexedTaskGraph, TaskNodeIndex};
77

88
use crate::{PlanRequestParser, path_env::prepend_path_env};
99

@@ -41,46 +41,6 @@ pub struct PlanContext<'a> {
4141
indexed_task_graph: &'a IndexedTaskGraph,
4242
}
4343

44-
/// A human-readable frame in the task call stack.
45-
#[derive(Debug, Clone)]
46-
pub struct TaskCallStackFrameDisplay {
47-
pub task_display: TaskDisplay,
48-
49-
#[expect(dead_code, reason = "to be used in terminal error display")]
50-
pub command_span: Range<usize>,
51-
}
52-
53-
impl Display for TaskCallStackFrameDisplay {
54-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55-
// TODO: display command_span
56-
write!(f, "{}", self.task_display)
57-
}
58-
}
59-
60-
/// A human-readable display of the task call stack.
61-
#[derive(Default, Debug, Clone)]
62-
pub struct TaskCallStackDisplay {
63-
frames: Arc<[TaskCallStackFrameDisplay]>,
64-
}
65-
66-
impl TaskCallStackDisplay {
67-
pub fn is_empty(&self) -> bool {
68-
self.frames.is_empty()
69-
}
70-
}
71-
72-
impl Display for TaskCallStackDisplay {
73-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74-
for (i, frame) in self.frames.iter().enumerate() {
75-
if i > 0 {
76-
write!(f, " -> ")?;
77-
}
78-
write!(f, "{frame}")?;
79-
}
80-
Ok(())
81-
}
82-
}
83-
8444
impl<'a> PlanContext<'a> {
8545
pub fn new(
8646
workspace_path: &'a Arc<AbsolutePath>,
@@ -104,20 +64,6 @@ impl<'a> PlanContext<'a> {
10464
&self.envs
10565
}
10666

107-
/// Get a human-readable display of the current task call stack.
108-
pub fn display_call_stack(&self) -> TaskCallStackDisplay {
109-
TaskCallStackDisplay {
110-
frames: self
111-
.task_call_stack
112-
.iter()
113-
.map(|(idx, span)| TaskCallStackFrameDisplay {
114-
task_display: self.indexed_task_graph.display_task(*idx),
115-
command_span: span.clone(),
116-
})
117-
.collect(),
118-
}
119-
}
120-
12167
/// Check if adding the given task node index would create a recursion in the call stack.
12268
pub fn check_recursion(
12369
&self,

crates/vite_task_plan/src/error.rs

Lines changed: 29 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
reason = "Arc<Path> is used for non-UTF-8 path data in error types"
44
)]
55
use std::path::Path;
6-
use std::{env::JoinPathsError, ffi::OsStr, fmt::Display, sync::Arc};
6+
use std::{env::JoinPathsError, ffi::OsStr, sync::Arc};
77

88
use vite_path::{AbsolutePath, relative::InvalidPathDataError};
99
use vite_str::Str;
10+
use vite_task_graph::display::TaskDisplay;
1011

11-
use crate::{
12-
context::{PlanContext, TaskCallStackDisplay, TaskRecursionError},
13-
envs::ResolveEnvError,
14-
};
12+
use crate::{context::TaskRecursionError, envs::ResolveEnvError};
1513

1614
#[derive(Debug, thiserror::Error)]
1715
pub enum CdCommandError {
@@ -30,7 +28,7 @@ pub struct WhichError {
3028
#[source]
3129
pub error: which::Error,
3230
}
33-
impl Display for WhichError {
31+
impl std::fmt::Display for WhichError {
3432
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3533
write!(
3634
f,
@@ -66,7 +64,7 @@ pub enum PathType {
6664
Program,
6765
PackagePath,
6866
}
69-
impl Display for PathType {
67+
impl std::fmt::Display for PathType {
7068
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7169
match self {
7270
Self::Cwd => write!(f, "current working directory"),
@@ -84,18 +82,26 @@ pub struct PathFingerprintError {
8482
pub kind: PathFingerprintErrorKind,
8583
}
8684

87-
/// Errors that can occur when planning a specific execution from a task .
85+
/// Errors that can occur when planning a specific execution from a task.
8886
#[derive(Debug, thiserror::Error)]
89-
pub enum TaskPlanErrorKind {
87+
pub enum Error {
88+
#[error("Failed to plan tasks from `{command}` in task {task_display}")]
89+
NestPlan {
90+
task_display: TaskDisplay,
91+
command: Str,
92+
#[source]
93+
error: Box<Self>,
94+
},
95+
9096
#[error("Failed to load task graph")]
91-
TaskGraphLoadError(
97+
TaskGraphLoad(
9298
#[source]
9399
#[from]
94100
vite_task_graph::TaskGraphLoadError,
95101
),
96102

97103
#[error("Failed to execute 'cd' command")]
98-
CdCommandError(
104+
CdCommand(
99105
#[source]
100106
#[from]
101107
CdCommandError,
@@ -105,10 +111,10 @@ pub enum TaskPlanErrorKind {
105111
ProgramNotFound(#[from] WhichError),
106112

107113
#[error(transparent)]
108-
PathFingerprintError(#[from] PathFingerprintError),
114+
PathFingerprint(#[from] PathFingerprintError),
109115

110116
#[error("Failed to query tasks from task graph")]
111-
TaskQueryError(
117+
TaskQuery(
112118
#[source]
113119
#[from]
114120
vite_task_graph::query::TaskQueryError,
@@ -118,7 +124,7 @@ pub enum TaskPlanErrorKind {
118124
TaskRecursionDetected(#[from] TaskRecursionError),
119125

120126
#[error("Invalid vite task command: {program} with args {args:?} under cwd {cwd:?}")]
121-
ParsePlanRequestError {
127+
ParsePlanRequest {
122128
program: Str,
123129
args: Arc<[Str]>,
124130
cwd: Arc<AbsolutePath>,
@@ -127,100 +133,38 @@ pub enum TaskPlanErrorKind {
127133
},
128134

129135
#[error("Failed to add node_modules/.bin to PATH environment variable")]
130-
AddNodeModulesBinPathError {
136+
AddNodeModulesBinPath {
131137
#[source]
132138
join_paths_error: JoinPathsError,
133139
},
134140

135141
#[error("Failed to resolve environment variables")]
136-
ResolveEnvError(#[source] ResolveEnvError),
142+
ResolveEnv(#[source] ResolveEnvError),
137143

138144
#[error("No task specifier provided for 'run' command")]
139145
MissingTaskSpecifier,
140146
}
141147

142-
#[derive(Debug, thiserror::Error)]
143-
pub struct Error {
144-
task_call_stack: TaskCallStackDisplay,
145-
146-
#[source]
147-
kind: TaskPlanErrorKind,
148-
}
149-
150-
impl Display for Error {
151-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152-
write!(f, "Failed to plan execution")?;
153-
if !self.task_call_stack.is_empty() {
154-
write!(f, ", task call stack: {}", self.task_call_stack)?;
155-
}
156-
Ok(())
157-
}
158-
}
159-
160-
impl TaskPlanErrorKind {
161-
#[must_use]
162-
pub fn with_empty_call_stack(self) -> Error {
163-
Error { task_call_stack: TaskCallStackDisplay::default(), kind: self }
164-
}
165-
}
166-
167148
impl Error {
168149
#[must_use]
169150
pub const fn is_missing_task_specifier(&self) -> bool {
170-
matches!(self.kind, TaskPlanErrorKind::MissingTaskSpecifier)
151+
matches!(self, Self::MissingTaskSpecifier)
171152
}
172153

173154
/// If this error represents a top-level task-not-found lookup failure,
174155
/// returns the task name that the user typed.
175156
///
176-
/// Returns `None` if the error occurred in a nested task (non-empty call stack),
157+
/// Returns `None` if the error occurred in a nested task (wrapped in `NestPlan`),
177158
/// since nested task errors should propagate as-is rather than triggering
178159
/// interactive task selection.
179160
#[must_use]
180161
pub fn task_not_found_name(&self) -> Option<&str> {
181-
if !self.task_call_stack.is_empty() {
182-
return None;
183-
}
184-
match &self.kind {
185-
TaskPlanErrorKind::TaskQueryError(
186-
vite_task_graph::query::TaskQueryError::SpecifierLookupError { specifier, .. },
187-
) => Some(specifier.task_name.as_str()),
188-
_ => None,
189-
}
190-
}
191-
}
192-
193-
#[expect(
194-
clippy::result_large_err,
195-
reason = "Error wraps TaskPlanErrorKind with call stack for diagnostics"
196-
)]
197-
pub trait TaskPlanErrorKindResultExt {
198-
type Ok;
199-
/// Attach the current task call stack from the planning context to the error.
200-
fn with_plan_context(self, context: &PlanContext<'_>) -> Result<Self::Ok, Error>;
201-
202-
/// Attach an empty task call stack to the error.
203-
fn with_empty_call_stack(self) -> Result<Self::Ok, Error>;
204-
}
205-
206-
impl<T> TaskPlanErrorKindResultExt for Result<T, TaskPlanErrorKind> {
207-
type Ok = T;
208-
209-
/// Attach the current task call stack from the planning context to the error.
210-
fn with_plan_context(self, context: &PlanContext<'_>) -> Result<T, Error> {
211-
match self {
212-
Ok(value) => Ok(value),
213-
Err(kind) => {
214-
let task_call_stack = context.display_call_stack();
215-
Err(Error { task_call_stack, kind })
216-
}
217-
}
218-
}
219-
220-
fn with_empty_call_stack(self) -> Result<T, Error> {
221162
match self {
222-
Ok(value) => Ok(value),
223-
Err(kind) => Err(kind.with_empty_call_stack()),
163+
Self::TaskQuery(vite_task_graph::query::TaskQueryError::SpecifierLookupError {
164+
specifier,
165+
..
166+
}) => Some(specifier.task_name.as_str()),
167+
_ => None,
224168
}
225169
}
226170
}

crates/vite_task_plan/src/lib.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ pub mod plan_request;
1111
use std::{collections::BTreeMap, ffi::OsStr, fmt::Debug, sync::Arc};
1212

1313
use context::PlanContext;
14-
use error::TaskPlanErrorKindResultExt;
15-
pub use error::{Error, TaskPlanErrorKind};
14+
pub use error::Error;
1615
use execution_graph::ExecutionGraph;
1716
use in_process::InProcessExecution;
1817
pub use path_env::{get_path_env, prepend_path_env};
@@ -216,11 +215,7 @@ impl ExecutionPlan {
216215
) -> Result<Self, Error> {
217216
let root_node = match plan_request {
218217
PlanRequest::Query(query_plan_request) => {
219-
let indexed_task_graph = task_graph_loader
220-
.load_task_graph()
221-
.await
222-
.map_err(TaskPlanErrorKind::TaskGraphLoadError)
223-
.with_empty_call_stack()?;
218+
let indexed_task_graph = task_graph_loader.load_task_graph().await?;
224219

225220
let context = PlanContext::new(
226221
workspace_path,
@@ -240,8 +235,7 @@ impl ExecutionPlan {
240235
None,
241236
cwd,
242237
ParentCacheConfig::None,
243-
)
244-
.with_empty_call_stack()?;
238+
)?;
245239
ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(execution))
246240
}
247241
};
@@ -252,7 +246,7 @@ impl ExecutionPlan {
252246
///
253247
/// # Errors
254248
/// Returns an error if the program is not found or path fingerprinting fails.
255-
#[expect(clippy::result_large_err, reason = "Error contains task call stack for diagnostics")]
249+
#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")]
256250
pub fn plan_synthetic(
257251
workspace_path: &Arc<AbsolutePath>,
258252
cwd: &Arc<AbsolutePath>,
@@ -267,8 +261,7 @@ impl ExecutionPlan {
267261
Some(execution_cache_key),
268262
cwd,
269263
ParentCacheConfig::None,
270-
)
271-
.with_empty_call_stack()?;
264+
)?;
272265
Ok(Self { root_node: ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(execution)) })
273266
}
274267
}

0 commit comments

Comments
 (0)