Skip to content

Commit 41d53b0

Browse files
committed
add index signature to $$Exports for mount() compatibility
The Svelte 5 mount() function returns Exports which must extend Record<string, any>. Without an index signature, our generated $$Exports interface wasn't assignable to Record<string, unknown>, causing false positives like: 'Type $$Exports is not assignable to Record<string, unknown>' Adding [key: string]: any to $$Exports makes it compatible with the mount() return type.
1 parent d662e21 commit 41d53b0

File tree

1 file changed

+30
-0
lines changed

1 file changed

+30
-0
lines changed

src/transformer.zig

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,9 @@ pub fn transform(allocator: std.mem.Allocator, ast: Ast) !VirtualFile {
342342
try output.appendSlice(allocator, ";\n\n");
343343

344344
// Generate $$Exports interface for instance exports (methods accessible on component)
345+
// Include index signature for compatibility with Record<string, any> (required by mount())
345346
try output.appendSlice(allocator, "export interface $$Exports {\n");
347+
try output.appendSlice(allocator, " [key: string]: any;\n");
346348
for (instance_exports.items) |exp| {
347349
try output.appendSlice(allocator, " ");
348350
try output.appendSlice(allocator, exp.name);
@@ -3335,10 +3337,38 @@ test "instance exports in generated component type" {
33353337
try std.testing.expect(std.mem.indexOf(u8, virtual.content, "handleKey:") != null);
33363338
try std.testing.expect(std.mem.indexOf(u8, virtual.content, "focus: typeof focus") != null);
33373339

3340+
// $$Exports must have index signature for Record<string, any> compatibility (required by mount())
3341+
try std.testing.expect(std.mem.indexOf(u8, virtual.content, "[key: string]: any;") != null);
3342+
33383343
// Component type should use $$Exports
33393344
try std.testing.expect(std.mem.indexOf(u8, virtual.content, "__SvelteComponentType__<$$Props, $$Exports, $$Bindings>") != null);
33403345
}
33413346

3347+
test "$$Exports is compatible with Record<string, any> for mount()" {
3348+
// Regression test: mount() returns Exports which must extend Record<string, any>.
3349+
// Without an index signature, empty $$Exports {} is not assignable to Record<string, unknown>.
3350+
// This caused false positives like: "Type '$$Exports' is not assignable to 'Record<string, unknown>'"
3351+
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
3352+
defer arena.deinit();
3353+
const allocator = arena.allocator();
3354+
3355+
// Component with no exports - the common case that triggered the bug
3356+
const source =
3357+
\\<script lang="ts">
3358+
\\ let { name }: { name: string } = $props();
3359+
\\</script>
3360+
\\<p>{name}</p>
3361+
;
3362+
3363+
var parser: @import("svelte_parser.zig").Parser = .init(allocator, source, "test.svelte");
3364+
const ast = try parser.parse();
3365+
const virtual = try transform(allocator, ast);
3366+
3367+
// Even with no exports, $$Exports must have index signature
3368+
try std.testing.expect(std.mem.indexOf(u8, virtual.content, "export interface $$Exports") != null);
3369+
try std.testing.expect(std.mem.indexOf(u8, virtual.content, "[key: string]: any;") != null);
3370+
}
3371+
33423372
test "transform typescript component" {
33433373
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
33443374
defer arena.deinit();

0 commit comments

Comments
 (0)