Fix mixin expansion in compiled templates and adjust include resolution

This commit is contained in:
2026-01-30 22:05:00 +05:30
parent 2c98dab144
commit e337a28202
4 changed files with 16 additions and 19 deletions

View File

@@ -207,14 +207,17 @@ fn compileSingleFile(
// Parse template with full resolution (handles includes, extends, mixins)
const final_ast = try engine.parseTemplate(arena_allocator, template_name, registry);
// Expand mixin calls into concrete AST nodes for codegen
const expanded_ast = try mixin.expandMixins(arena_allocator, final_ast, registry);
// Extract field names
const fields = try zig_codegen.extractFieldNames(arena_allocator, final_ast);
const fields = try zig_codegen.extractFieldNames(arena_allocator, expanded_ast);
// Generate Zig code
var codegen = zig_codegen.Codegen.init(arena_allocator);
defer codegen.deinit();
const zig_code = try codegen.generate(final_ast, "render", fields);
const zig_code = try codegen.generate(expanded_ast, "render", fields);
// Create flat filename from views-relative path to avoid collisions
// e.g., "pages/404.pug" → "pages_404.zig"

View File

@@ -114,12 +114,15 @@ fn compileSingleFile(allocator: mem.Allocator, input_path: []const u8, output_pa
// Parse template with full resolution (handles includes, extends, mixins)
const final_ast = try engine.parseTemplate(allocator, template_name, &registry);
// Expand mixin calls into concrete AST nodes for codegen
const expanded_ast = try mixin.expandMixins(allocator, final_ast, &registry);
// Note: Don't free final_ast as it's managed by the ViewEngine
// The normalized_source is intentionally leaked as AST strings point into it
// Both will be cleaned up by the allocator when the CLI exits
// Extract field names from final resolved AST
const fields = try zig_codegen.extractFieldNames(allocator, final_ast);
const fields = try zig_codegen.extractFieldNames(allocator, expanded_ast);
defer {
for (fields) |field| allocator.free(field);
allocator.free(fields);
@@ -139,7 +142,7 @@ fn compileSingleFile(allocator: mem.Allocator, input_path: []const u8, output_pa
var codegen = Codegen.init(allocator);
defer codegen.deinit();
const zig_code = try codegen.generate(final_ast, function_name, fields);
const zig_code = try codegen.generate(expanded_ast, function_name, fields);
defer allocator.free(zig_code);
// Write output file

View File

@@ -280,8 +280,7 @@ pub const ViewEngine = struct {
/// Resolves a path relative to the current file's directory.
/// - Paths starting with "/" are absolute from views_dir root
/// - Paths starting with "./" or "../" are relative to current file's directory
/// - Other paths are relative to views_dir root (Pug convention)
/// - Other paths are relative to current file's directory (if provided)
/// Returns a path relative to views_dir.
fn resolveRelativePath(self: *const ViewEngine, allocator: std.mem.Allocator, path: []const u8, current_dir: ?[]const u8) ![]const u8 {
_ = self;
@@ -291,14 +290,6 @@ pub const ViewEngine = struct {
return allocator.dupe(u8, path[1..]);
}
// Check if path is explicitly relative (starts with "./" or "../")
const is_explicit_relative = std.mem.startsWith(u8, path, "./") or std.mem.startsWith(u8, path, "../");
// If not explicitly relative, treat as relative to views_dir root (Pug convention)
if (!is_explicit_relative) {
return allocator.dupe(u8, path);
}
// If no current directory (top-level call), path is already relative to views_dir
const dir = current_dir orelse {
return allocator.dupe(u8, path);
@@ -455,15 +446,15 @@ test "resolveRelativePath - relative paths from subdirectory" {
defer allocator.free(result3);
try std.testing.expectEqualStrings("pages/utils", result3);
// From pages/, include header (no ./) -> header (relative to views root, Pug convention)
// From pages/, include header (no ./) -> pages/header (relative to current dir)
const result4 = try engine.resolveRelativePath(allocator, "header", "pages");
defer allocator.free(result4);
try std.testing.expectEqualStrings("header", result4);
try std.testing.expectEqualStrings("pages/header", result4);
// From pages/, include includes/partial -> includes/partial (relative to views root)
// From pages/, include includes/partial -> pages/includes/partial (relative to current dir)
const result5 = try engine.resolveRelativePath(allocator, "includes/partial", "pages");
defer allocator.free(result5);
try std.testing.expectEqualStrings("includes/partial", result5);
try std.testing.expectEqualStrings("pages/includes/partial", result5);
}
test "resolveRelativePath - absolute paths from views root" {