diff --git a/node.go b/node.go index d2e1edb..14d9c11 100644 --- a/node.go +++ b/node.go @@ -2,6 +2,7 @@ package twig import ( "bytes" + "errors" "fmt" "io" "path/filepath" @@ -684,13 +685,14 @@ func (n *ExtendsNode) Render(w io.Writer, ctx *RenderContext) error { // Load the parent template with resolved path parentTemplate, err := ctx.engine.Load(resolvedName) if err != nil { - // If template not found with resolved path, try original name - if resolvedName != templateName { + // Only try the fallback if the template was not found AND the paths are different + if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName { parentTemplate, err = ctx.engine.Load(templateName) if err != nil { return err } } else { + // For any other error (including syntax errors), return immediately return err } } @@ -701,7 +703,7 @@ func (n *ExtendsNode) Render(w io.Writer, ctx *RenderContext) error { // This ensures the parent template knows it's being extended and preserves our blocks parentCtx := NewRenderContext(ctx.env, ctx.context, ctx.engine) parentCtx.extending = true // Flag that the parent is being extended - + // Pass along the parent template as lastLoadedTemplate for relative path resolution parentCtx.lastLoadedTemplate = parentTemplate @@ -793,19 +795,20 @@ func (n *IncludeNode) Render(w io.Writer, ctx *RenderContext) error { // Load the template with resolved path template, err := ctx.engine.Load(resolvedName) if err != nil { - // If template not found with resolved path, try original name - if resolvedName != templateName { + // Only try the fallback if the template was not found AND the paths are different + if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName { template, err = ctx.engine.Load(templateName) if err != nil { - if n.ignoreMissing { + if n.ignoreMissing && errors.Is(err, ErrTemplateNotFound) { return nil } return err } } else { - if n.ignoreMissing { + if n.ignoreMissing && errors.Is(err, ErrTemplateNotFound) { return nil } + // For any other error (including syntax errors), return immediately return err } } @@ -818,7 +821,7 @@ func (n *IncludeNode) Render(w io.Writer, ctx *RenderContext) error { includeCtx := ctx.Clone() includeCtx.lastLoadedTemplate = template defer includeCtx.Release() - + return template.nodes.Render(w, includeCtx) } @@ -840,7 +843,7 @@ func (n *IncludeNode) Render(w io.Writer, ctx *RenderContext) error { // Create a new context includeCtx = NewRenderContext(ctx.env, contextVars, ctx.engine) - // Set the template as the lastLoadedTemplate for relative path resolutionn includeCtx.lastLoadedTemplate = template + // Set the template as the lastLoadedTemplate for relative path resolutionn includeCtx.lastLoadedTemplate = template defer includeCtx.Release() // If sandboxed, enable sandbox mode @@ -1206,13 +1209,14 @@ func (n *ImportNode) Render(w io.Writer, ctx *RenderContext) error { // Load the template with resolved path template, err := ctx.engine.Load(resolvedName) if err != nil { - // If template not found with resolved path, try original name - if resolvedName != templateName { + // Only try the fallback if the template was not found AND the paths are different + if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName { template, err = ctx.engine.Load(templateName) if err != nil { return err } } else { + // For any other error (including syntax errors), return immediately return err } } @@ -1296,13 +1300,14 @@ func (n *FromImportNode) Render(w io.Writer, ctx *RenderContext) error { // Load the template with resolved path template, err := ctx.engine.Load(resolvedName) if err != nil { - // If template not found with resolved path, try original name - if resolvedName != templateName { + // Only try the fallback if the template was not found AND the paths are different + if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName { template, err = ctx.engine.Load(templateName) if err != nil { return err } } else { + // For any other error (including syntax errors), return immediately return err } } diff --git a/relative_path_test.go b/relative_path_test.go index cc439b7..6fde808 100644 --- a/relative_path_test.go +++ b/relative_path_test.go @@ -80,10 +80,10 @@ func TestRelativePathsWithFromImport(t *testing.T) { // Create simple macro templates macrosTemplate := `{% macro simple() %}Macro output{% endmacro %}` - + // Print the template content for debugging t.Logf("Simple template content: %s", macrosTemplate) - + // Note: The template needs to be in the format: {% from "template" import macro %} useTemplate := `{% from "./simple.twig" import simple %}{{ simple() }}` t.Logf("Use template content: %s", useTemplate) @@ -129,3 +129,145 @@ func normalizeWhitespace(s string) string { result := strings.Join(strings.Fields(s), " ") return result } + +// TestRelativePathsWithExtendsInSubfolder tests a specific scenario where a template +// in a child folder extends another template in the same folder using a relative path +func TestRelativePathsWithExtendsInSubfolder(t *testing.T) { + // Create temporary directories for testing + tempDir, err := os.MkdirTemp("", "twig-test-") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tempDir) + + // Create a main templates directory and a child subdirectory + templatesDir := filepath.Join(tempDir, "templates") + err = os.Mkdir(templatesDir, 0755) + if err != nil { + t.Fatalf("Failed to create templates dir: %v", err) + } + + t.Logf("Template dir: %s", templatesDir) + + childDir := filepath.Join(templatesDir, "child") + err = os.Mkdir(childDir, 0755) + if err != nil { + t.Fatalf("Failed to create child dir: %v", err) + } + + // Create the layout template in the templates directory + layoutTemplate := ` + +
+