Skip to content

Commit 197bc94

Browse files
branchseerclaude
andcommitted
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 31fd15f commit 197bc94

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
@@ -205,37 +205,52 @@ impl UserRunConfig {
205205
#[expect(clippy::disallowed_types, reason = "test code uses std types for convenience")]
206206
pub fn generate_ts_definition() -> String {
207207
use std::{
208+
any::TypeId,
209+
collections::HashSet,
208210
io::Write,
209211
process::{Command, Stdio},
210212
};
211213

212214
use ts_rs::TypeVisitor;
213215

214-
struct DeclCollector(Vec<String>);
216+
struct DeclCollector {
217+
decls: Vec<String>,
218+
visited: HashSet<TypeId>,
219+
}
215220

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

226-
let mut collector = DeclCollector(Vec::new());
236+
let mut collector = DeclCollector { decls: Vec::new(), visited: HashSet::new() };
227237
Self::visit_dependencies(&mut collector);
228238

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

232246
// Export all types
233-
let mut types: String = collector
234-
.0
247+
let dep_types: String = collector
248+
.decls
235249
.iter()
236250
.map(|decl| vite_str::format!("export {decl}"))
237251
.collect::<Vec<_>>()
238252
.join("\n\n");
253+
types.push_str(&dep_types);
239254

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

0 commit comments

Comments
 (0)