Repository: gohugoio/hugo (language: go)

Issue:
regression: Support old layout construct layouts/section/section.html
https://discourse.gohugo.io/t/template-lookup-problem-with-v0-146-1/54309/6


Discussion / hints:

Fix patch:
diff --git a/tpl/tplimpl/templatedescriptor.go b/tpl/tplimpl/templatedescriptor.go
index 21649b0327b..fc757126f86 100644
--- a/tpl/tplimpl/templatedescriptor.go
+++ b/tpl/tplimpl/templatedescriptor.go
@@ -92,6 +92,7 @@ func (this TemplateDescriptor) doCompare(category Category, other TemplateDescri
 	if other.Kind != "" && other.Kind != this.Kind {
 		return w
 	}
+
 	if other.Layout != "" && other.Layout != layoutAll && other.Layout != this.Layout {
 		if isLayoutCustom(this.Layout) {
 			if this.Kind == "" {
@@ -108,6 +109,7 @@ func (this TemplateDescriptor) doCompare(category Category, other TemplateDescri
 			return w
 		}
 	}
+
 	if other.Lang != "" && other.Lang != this.Lang {
 		return w
 	}
@@ -120,7 +122,7 @@ func (this TemplateDescriptor) doCompare(category Category, other TemplateDescri
 		// We want e.g. home page in amp output format (media type text/html) to
 		// find a template even if one isn't specified for that output format,
 		// when one exist for the html output format (same media type).
-		if category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && this.Layout != other.Layout)) {
+		if category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && (this.Layout != other.Layout && other.Layout != layoutAll))) {
 			return w
 		}
 
diff --git a/tpl/tplimpl/templates.go b/tpl/tplimpl/templates.go
index 1f911b9a58d..4c3ad3be1ab 100644
--- a/tpl/tplimpl/templates.go
+++ b/tpl/tplimpl/templates.go
@@ -3,7 +3,9 @@ package tplimpl
 import (
 	"io"
 	"regexp"
+	"strconv"
 	"strings"
+	"sync/atomic"
 	"unicode"
 	"unicode/utf8"
 
@@ -49,11 +51,6 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
 	}
 	pi := ti.PathInfo
 	name := pi.PathNoLeadingSlash()
-	if ti.isLegacyMapped {
-		// When mapping the old taxonomy structure to the new one, we may map the same path to multiple templates per kind.
-		// Append the kind here to make the name unique.
-		name += ("-" + ti.D.Kind)
-	}
 
 	var (
 		templ tpl.Template
@@ -62,12 +59,18 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
 
 	if ti.D.IsPlainText {
 		prototype := t.parseText
+		if prototype.Lookup(name) != nil {
+			name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
+		}
 		templ, err = prototype.New(name).Parse(ti.content)
 		if err != nil {
 			return err
 		}
 	} else {
 		prototype := t.parseHTML
+		if prototype.Lookup(name) != nil {
+			name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
+		}
 		templ, err = prototype.New(name).Parse(ti.content)
 		if err != nil {
 			return err
@@ -296,6 +299,8 @@ type templateNamespace struct {
 	prototypeText *texttemplate.Template
 	prototypeHTML *htmltemplate.Template
 
+	nameCounter atomic.Uint64
+
 	standaloneText *texttemplate.Template
 
 	baseofTextClones []*texttemplate.Template


Test patch:
diff --git a/tpl/templates/templates_integration_test.go b/tpl/templates/templates_integration_test.go
index a0dcf03488a..93922b4c42b 100644
--- a/tpl/templates/templates_integration_test.go
+++ b/tpl/templates/templates_integration_test.go
@@ -166,3 +166,66 @@ p3.current.Ancestors.Reverse: {{ with templates.Current }}{{ range .Ancestors.Re
 		"p2.current.Ancestors: _partials/p1.html|all.html",
 	)
 }
+
+func TestBaseOfIssue13583(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- content/_index.md --
+---
+title: "Home"
+outputs: ["html", "amp"]
+---
+title: "Home"
+-- layouts/baseof.html --
+layouts/baseof.html
+{{ block "main" . }}{{ end }}
+-- layouts/baseof.amp.html --
+layouts/baseof.amp.html
+{{ block "main" . }}{{ end }}
+-- layouts/home.html --
+{{ define "main" }}
+Home.
+{{ end }}
+
+`
+	b := hugolib.Test(t, files)
+	b.AssertFileContent("public/index.html", "layouts/baseof.html")
+	b.AssertFileContent("public/amp/index.html", "layouts/baseof.amp.html")
+}
+
+func TestAllVsAmp(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- content/_index.md --
+---
+title: "Home"
+outputs: ["html", "amp"]
+---
+title: "Home"
+-- layouts/all.html --
+All.
+
+`
+	b := hugolib.Test(t, files)
+	b.AssertFileContent("public/index.html", "All.")
+	b.AssertFileContent("public/amp/index.html", "All.")
+}
+
+// Issue #13584.
+func TestLegacySectionSection(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- content/mysection/_index.md --
+-- layouts/section/section.html --
+layouts/section/section.html
+
+`
+	b := hugolib.Test(t, files)
+	b.AssertFileContent("public/mysection/index.html", "layouts/section/section.html")
+}
diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
index eee96205395..8fe89586d02 100644
--- a/tpl/tplimpl/templatestore.go
+++ b/tpl/tplimpl/templatestore.go
@@ -301,7 +301,7 @@ func (ti *TemplInfo) String() string {
 	return ti.PathInfo.String()
 }
 
-func (ti *TemplInfo) findBestMatchBaseof(s *TemplateStore, k1 string, slashCountK1 int, best *bestMatch) {
+func (ti *TemplInfo) findBestMatchBaseof(s *TemplateStore, d1 TemplateDescriptor, k1 string, slashCountK1 int, best *bestMatch) {
 	if ti.baseVariants == nil {
 		return
 	}
@@ -310,11 +310,11 @@ func (ti *TemplInfo) findBestMatchBaseof(s *TemplateStore, k1 string, slashCount
 		slashCountK2 := strings.Count(k2, "/")
 		distance := slashCountK1 - slashCountK2
 
-		for d, vv := range v {
-			weight := s.dh.compareDescriptors(CategoryBaseof, ti.D, d)
+		for d2, vv := range v {
+			weight := s.dh.compareDescriptors(CategoryBaseof, d1, d2)
 			weight.distance = distance
 			if best.isBetter(weight, vv.Template) {
-				best.updateValues(weight, k2, d, vv.Template)
+				best.updateValues(weight, k2, d2, vv.Template)
 			}
 		}
 		return false, nil
@@ -538,7 +538,7 @@ func (s *TemplateStore) LookupPagesLayout(q TemplateQuery) *TemplInfo {
 		return m
 	}
 	best1.reset()
-	m.findBestMatchBaseof(s, key, slashCountKey, best1)
+	m.findBestMatchBaseof(s, q.Desc, key, slashCountKey, best1)
 	if best1.w.w1 <= 0 {
 		return nil
 	}
@@ -690,7 +690,7 @@ func (t *TemplateStore) UnusedTemplates() []*TemplInfo {
 	var unused []*TemplInfo
 
 	for vv := range t.templates() {
-		if vv.subCategory != SubCategoryMain {
+		if vv.subCategory != SubCategoryMain || vv.isLegacyMapped {
 			// Skip inline partials and internal templates.
 			continue
 		}
@@ -1169,8 +1169,8 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
 		case containerPartials, containerShortcodes, containerMarkup:
 			// OK.
 		default:
-			applyLegacyMapping = true
 			pi = fromLegacyPath(pi)
+			applyLegacyMapping = strings.Count(pi.Path(), "/") <= 2
 		}
 
 		if applyLegacyMapping {
@@ -1183,6 +1183,7 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
 					ext:            pi.Ext(),
 					outputFormat:   pi.OutputFormat(),
 				}
+
 				if m2, ok := legacyOrdinalMappings[key]; ok {
 					if m1.ordinal < m2.m.ordinal {
 						// Higher up == better match.
@@ -1208,26 +1209,74 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
 
 			base := piOrig.PathBeforeLangAndOutputFormatAndExt()
 			identifiers := pi.IdentifiersUnknown()
+			if pi.Kind() != "" {
+				identifiers = append(identifiers, pi.Kind())
+			}
+
+			shouldIncludeSection := func(section string) bool {
+				switch section {
+				case containerShortcodes, containerPartials, containerMarkup:
+					return false
+				case "taxonomy", "":
+					return false
+				default:
+					for k, v := range s.opts.TaxonomySingularPlural {
+						if k == section || v == section {
+							return false
+						}
+					}
+					return true
+				}
+			}
+			if shouldIncludeSection(pi.Section()) {
+				identifiers = append(identifiers, pi.Section())
+			}
+
+			identifiers = helpers.UniqueStrings(identifiers)
 
 			// Tokens on e.g. form /SECTIONKIND/THESECTION
-			insertSectionTokens := func(section string, kindOnly bool) string {
-				s := base
+			insertSectionTokens := func(section string) []string {
+				kindOnly := isLayoutStandard(section)
+				var ss []string
+				s1 := base
+				if !kindOnly {
+					s1 = strings.ReplaceAll(s1, section, sectionToken)
+				}
+				s1 = strings.ReplaceAll(s1, kinds.KindSection, sectionKindToken)
+				if s1 != base {
+					ss = append(ss, s1)
+				}
+				s1 = strings.ReplaceAll(base, kinds.KindSection, sectionKindToken)
 				if !kindOnly {
-					s = strings.Replace(s, section, sectionToken, 1)
+					s1 = strings.ReplaceAll(s1, section, sectionToken)
+				}
+				if s1 != base {
+					ss = append(ss, s1)
 				}
-				s = strings.Replace(s, kinds.KindSection, sectionKindToken, 1)
-				return s
+
+				helpers.UniqueStringsReuse(ss)
+
+				return ss
 			}
 
-			for _, section := range identifiers {
-				if section == baseNameBaseof {
+			for _, id := range identifiers {
+				if id == "" {
 					continue
 				}
-				kindOnly := isLayoutStandard(section)
-				p := insertSectionTokens(section, kindOnly)
-				if m1, ok := s.opts.legacyMappingSection[p]; ok {
-					m1.mapping.targetPath = strings.Replace(m1.mapping.targetPath, sectionToken, section, 1)
-					handleMapping(m1)
+
+				p := insertSectionTokens(id)
+				for _, ss := range p {
+					if m1, ok := s.opts.legacyMappingSection[ss]; ok {
+						targetPath := m1.mapping.targetPath
+
+						if targetPath != "" {
+							targetPath = strings.ReplaceAll(targetPath, sectionToken, id)
+							targetPath = strings.ReplaceAll(targetPath, sectionKindToken, id)
+							targetPath = strings.ReplaceAll(targetPath, "//", "/")
+						}
+						m1.mapping.targetPath = targetPath
+						handleMapping(m1)
+					}
 				}
 			}
 
diff --git a/tpl/tplimpl/templatestore_integration_test.go b/tpl/tplimpl/templatestore_integration_test.go
index 4f76626ad77..74abe58d0fd 100644
--- a/tpl/tplimpl/templatestore_integration_test.go
+++ b/tpl/tplimpl/templatestore_integration_test.go
@@ -441,7 +441,7 @@ title: "P1"
 {{ define "main" }}FOO{{ end }}
 -- layouts/_default/single.json --
 -- layouts/_default/single.html --
-{{ define "main" }}MAIN{{ end }}
+{{ define "main" }}MAIN /_default/single.html{{ end }}
 -- layouts/post/single.html --
 {{ define "main" }}MAIN{{ end }}
 -- layouts/_partials/usedpartial.html --
@@ -461,6 +461,8 @@ title: "P1"
 	)
 	b.Build()
 
+	b.AssertFileContent("public/p1/index.html", "MAIN /_default/single.html")
+
 	unused := b.H.GetTemplateStore().UnusedTemplates()
 	var names []string
 	for _, tmpl := range unused {
@@ -468,8 +470,9 @@ title: "P1"
 			names = append(names, fi.Meta().PathInfo.PathNoLeadingSlash())
 		}
 	}
-	b.Assert(len(unused), qt.Equals, 5, qt.Commentf("%#v", names))
+
 	b.Assert(names, qt.DeepEquals, []string{"_partials/unusedpartial.html", "shortcodes/unusedshortcode.html", "baseof.json", "post/single.html", "_default/single.json"})
+	b.Assert(len(unused), qt.Equals, 5, qt.Commentf("%#v", names))
 }
 
 func TestCreateManyTemplateStores(t *testing.T) {