Skip to content

Commit d2b8f83

Browse files
branchseerclaude
andauthored
Fix recursive type dependency collection issue (#195)
## Summary - The `DeclCollector` in `generate_ts_definition` only called `visit_dependencies` on `UserRunConfig`, which per the `ts_rs` API only visits **direct** type dependencies. Any type referenced indirectly through a chain of dependencies (e.g. a type nested inside a flattened struct) would appear in the generated TypeScript output but never be declared as its own type. - Fix the collector to recursively call `T::visit_dependencies(self)` for each visited type, using a `HashSet<TypeId>` to prevent infinite loops from circular references. - Add an auto-generated header comment to `run-config.ts` to make it clear the file should not be edited manually. ### Why this matters The bug is latent today because all indirect types happen to be `#[serde(flatten)]`-ed, which causes `ts_rs` to inline their fields rather than emit separate type declarations. But as soon as a non-flattened custom type is added at depth > 1 (e.g. a struct referenced by `EnabledCacheConfig`), the generated TypeScript would reference that type by name without ever declaring it — producing invalid TypeScript. The recursive fix future-proofs the collector so any new nested types are automatically included. The new `UserGlobalCacheConfig` type (added on `main`) is now correctly emitted thanks to this fix. ## Test plan - [x] `cargo test -p vite_task_graph` — all 14 tests pass - [x] `cargo check` — full project compiles cleanly - [x] Generated `run-config.ts` includes header and all types https://claude.ai/code/session_01VQvSgXNnYL8tNWydvtbCvg --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 077d3af commit d2b8f83

File tree

2 files changed

+23
-6
lines changed

2 files changed

+23
-6
lines changed

crates/vite_task_graph/run-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// This file is auto-generated by `cargo test`. Do not edit manually.
2+
13
export type Task = {
24
/**
35
* The command to run for the task.

crates/vite_task_graph/src/config/user.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,37 +204,52 @@ impl UserRunConfig {
204204
#[expect(clippy::disallowed_types, reason = "test code uses std types for convenience")]
205205
pub fn generate_ts_definition() -> String {
206206
use std::{
207+
any::TypeId,
208+
collections::HashSet,
207209
io::Write,
208210
process::{Command, Stdio},
209211
};
210212

211213
use ts_rs::TypeVisitor;
212214

213-
struct DeclCollector(Vec<String>);
215+
struct DeclCollector {
216+
decls: Vec<String>,
217+
visited: HashSet<TypeId>,
218+
}
214219

215220
impl TypeVisitor for DeclCollector {
216221
fn visit<T: TS + 'static + ?Sized>(&mut self) {
222+
if !self.visited.insert(TypeId::of::<T>()) {
223+
return;
224+
}
217225
// Only collect declarations from types that are exportable
218226
// (i.e., have an output path - built-in types like HashMap don't)
219227
if T::output_path().is_some() {
220-
self.0.push(T::decl());
228+
self.decls.push(T::decl());
221229
}
230+
// Recursively visit dependencies of T
231+
T::visit_dependencies(self);
222232
}
223233
}
224234

225-
let mut collector = DeclCollector(Vec::new());
235+
let mut collector = DeclCollector { decls: Vec::new(), visited: HashSet::new() };
226236
Self::visit_dependencies(&mut collector);
227237

228238
// Sort declarations for deterministic output order
229-
collector.0.sort();
239+
collector.decls.sort();
240+
241+
// Header
242+
let mut types: String =
243+
"// This file is auto-generated by `cargo test`. Do not edit manually.\n\n".into();
230244

231245
// Export all types
232-
let mut types: String = collector
233-
.0
246+
let dep_types: String = collector
247+
.decls
234248
.iter()
235249
.map(|decl| vite_str::format!("export {decl}"))
236250
.collect::<Vec<_>>()
237251
.join("\n\n");
252+
types.push_str(&dep_types);
238253

239254
// Export the main type
240255
types.push_str("\n\nexport ");

0 commit comments

Comments
 (0)