summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commands/commands.go1
-rw-r--r--commands/commands_test.go1
-rw-r--r--commands/hugo.go8
-rw-r--r--docs/content/en/commands/hugo.md1
-rw-r--r--docs/content/en/commands/hugo_mod.md1
-rw-r--r--docs/content/en/commands/hugo_new.md1
-rw-r--r--docs/content/en/commands/hugo_server.md1
-rw-r--r--tpl/template.go5
-rw-r--r--tpl/template_info.go9
-rw-r--r--tpl/tplimpl/integration_test.go61
-rw-r--r--tpl/tplimpl/template.go79
-rw-r--r--tpl/tplimpl/template_errors.go8
12 files changed, 167 insertions, 9 deletions
diff --git a/commands/commands.go b/commands/commands.go
index 63707e368..10b7a6649 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -306,6 +306,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
+ cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
cmd.Flags().StringVarP(&cc.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
cmd.Flags().StringVarP(&cc.memprofile, "profile-mem", "", "", "write memory profile to `file`")
cmd.Flags().BoolVarP(&cc.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals")
diff --git a/commands/commands_test.go b/commands/commands_test.go
index 9e623e9a2..b4fb89621 100644
--- a/commands/commands_test.go
+++ b/commands/commands_test.go
@@ -197,6 +197,7 @@ func TestFlags(t *testing.T) {
"--renderToDisk",
"--source=mysource",
"--printPathWarnings",
+ "--printUnusedTemplates",
},
check: func(c *qt.C, sc *serverCmd) {
c.Assert(sc, qt.Not(qt.IsNil))
diff --git a/commands/hugo.go b/commands/hugo.go
index ce3c4ab7b..8c5294f00 100644
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -31,6 +31,7 @@ import (
"time"
"github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/common/types"
@@ -217,6 +218,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
"force",
"gc",
"printI18nWarnings",
+ "printUnusedTemplates",
"invalidateCDN",
"layoutDir",
"logFile",
@@ -501,7 +503,6 @@ func (c *commandeer) build() error {
return err
}
- // TODO(bep) Feedback?
if !c.h.quiet {
fmt.Println()
c.hugo().PrintProcessingStats(os.Stdout)
@@ -513,6 +514,11 @@ func (c *commandeer) build() error {
c.logger.Warnln("Duplicate target paths:", dupes)
}
}
+
+ unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
+ for _, unusedTemplate := range unusedTemplates {
+ c.logger.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
+ }
}
if c.h.buildWatch {
diff --git a/docs/content/en/commands/hugo.md b/docs/content/en/commands/hugo.md
index 088a8102f..0aa81719a 100644
--- a/docs/content/en/commands/hugo.md
+++ b/docs/content/en/commands/hugo.md
@@ -53,6 +53,7 @@ hugo [flags]
--printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc.
+ --printUnusedTemplates print warnings on unused templates.
--quiet build in quiet mode
--renderToMemory render to memory (only useful for benchmark testing)
-s, --source string filesystem path to read files relative from
diff --git a/docs/content/en/commands/hugo_mod.md b/docs/content/en/commands/hugo_mod.md
index b0860f299..afcda9823 100644
--- a/docs/content/en/commands/hugo_mod.md
+++ b/docs/content/en/commands/hugo_mod.md
@@ -49,6 +49,7 @@ See https://gohugo.io/hugo-modules/ for more information.
--printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc.
+ --printUnusedTemplates print warnings on unused templates.
--templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
-t, --theme strings themes to use (located in /themes/THEMENAME/)
diff --git a/docs/content/en/commands/hugo_new.md b/docs/content/en/commands/hugo_new.md
index e8a7d3b08..a8b084963 100644
--- a/docs/content/en/commands/hugo_new.md
+++ b/docs/content/en/commands/hugo_new.md
@@ -50,6 +50,7 @@ hugo new [path] [flags]
--printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc.
+ --printUnusedTemplates print warnings on unused templates.
--templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
-t, --theme strings themes to use (located in /themes/THEMENAME/)
diff --git a/docs/content/en/commands/hugo_server.md b/docs/content/en/commands/hugo_server.md
index abdb64a5e..c224c4a52 100644
--- a/docs/content/en/commands/hugo_server.md
+++ b/docs/content/en/commands/hugo_server.md
@@ -63,6 +63,7 @@ hugo server [flags]
--printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc.
+ --printUnusedTemplates print warnings on unused templates.
--renderToDisk render to Destination path (default is render to memory & serve from there)
--templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
diff --git a/tpl/template.go b/tpl/template.go
index 0375b4a17..c5a6a44c0 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -44,6 +44,11 @@ type TemplateFinder interface {
TemplateLookupVariant
}
+// UnusedTemplatesProvider lists unused templates if the build is configured to track those.
+type UnusedTemplatesProvider interface {
+ UnusedTemplates() []FileInfo
+}
+
// TemplateHandler finds and executes templates.
type TemplateHandler interface {
TemplateFinder
diff --git a/tpl/template_info.go b/tpl/template_info.go
index d9b438138..c21c0ae7d 100644
--- a/tpl/template_info.go
+++ b/tpl/template_info.go
@@ -27,6 +27,11 @@ type Info interface {
identity.Provider
}
+type FileInfo interface {
+ Name() string
+ Filename() string
+}
+
type InfoManager interface {
ParseInfo() ParseInfo
@@ -65,10 +70,6 @@ func (info ParseInfo) IsZero() bool {
return info.Config.Version == 0
}
-// Info holds some info extracted from a parsed template.
-type Info1 struct {
-}
-
type ParseConfig struct {
Version int
}
diff --git a/tpl/tplimpl/integration_test.go b/tpl/tplimpl/integration_test.go
new file mode 100644
index 000000000..f71e28bc1
--- /dev/null
+++ b/tpl/tplimpl/integration_test.go
@@ -0,0 +1,61 @@
+package tplimpl_test
+
+import (
+ "path/filepath"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/tpl"
+)
+
+func TestPrintUnusedTemplates(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- config.toml --
+baseURL = 'http://example.com/'
+printUnusedTemplates=true
+-- content/p1.md --
+---
+title: "P1"
+---
+{{< usedshortcode >}}
+-- layouts/baseof.html --
+{{ block "main" . }}{{ end }}
+-- layouts/baseof.json --
+{{ block "main" . }}{{ end }}
+-- layouts/index.html --
+{{ define "main" }}FOO{{ end }}
+-- layouts/_default/single.json --
+-- layouts/_default/single.html --
+{{ define "main" }}MAIN{{ end }}
+-- layouts/post/single.html --
+{{ define "main" }}MAIN{{ end }}
+-- layouts/partials/usedpartial.html --
+-- layouts/partials/unusedpartial.html --
+-- layouts/shortcodes/usedshortcode.html --
+{{ partial "usedpartial.html" }}
+-- layouts/shortcodes/unusedshortcode.html --
+
+ `
+
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ NeedsOsFS: true,
+ },
+ )
+ b.Build()
+
+ unused := b.H.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
+
+ var names []string
+ for _, tmpl := range unused {
+ names = append(names, tmpl.Name())
+ }
+
+ b.Assert(names, qt.DeepEquals, []string{"_default/single.json", "baseof.json", "partials/unusedpartial.html", "post/single.html", "shortcodes/unusedshortcode.html"})
+ b.Assert(unused[0].Filename(), qt.Equals, filepath.Join(b.Cfg.WorkingDir, "layouts/_default/single.json"))
+}
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index 66ef7fc21..9d2911ee9 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -67,10 +67,11 @@ var embeddedTemplatesAliases = map[string][]string{
}
var (
- _ tpl.TemplateManager = (*templateExec)(nil)
- _ tpl.TemplateHandler = (*templateExec)(nil)
- _ tpl.TemplateFuncGetter = (*templateExec)(nil)
- _ tpl.TemplateFinder = (*templateExec)(nil)
+ _ tpl.TemplateManager = (*templateExec)(nil)
+ _ tpl.TemplateHandler = (*templateExec)(nil)
+ _ tpl.TemplateFuncGetter = (*templateExec)(nil)
+ _ tpl.TemplateFinder = (*templateExec)(nil)
+ _ tpl.UnusedTemplatesProvider = (*templateExec)(nil)
_ tpl.Template = (*templateState)(nil)
_ tpl.Info = (*templateState)(nil)
@@ -130,6 +131,11 @@ func newTemplateExec(d *deps.Deps) (*templateExec, error) {
funcMap[k] = v.Interface()
}
+ var templateUsageTracker map[string]templateInfo
+ if d.Cfg.GetBool("printUnusedTemplates") {
+ templateUsageTracker = make(map[string]templateInfo)
+ }
+
h := &templateHandler{
nameBaseTemplateName: make(map[string]string),
transformNotFound: make(map[string]*templateState),
@@ -146,6 +152,8 @@ func newTemplateExec(d *deps.Deps) (*templateExec, error) {
layoutHandler: output.NewLayoutHandler(),
layoutsFs: d.BaseFs.Layouts.Fs,
layoutTemplateCache: make(map[layoutCacheKey]tpl.Template),
+
+ templateUsageTracker: templateUsageTracker,
}
if err := h.loadEmbedded(); err != nil {
@@ -225,13 +233,72 @@ func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data interface{
defer t.Metrics.MeasureSince(templ.Name(), time.Now())
}
+ if t.templateUsageTracker != nil {
+ if ts, ok := templ.(*templateState); ok {
+ t.templateUsageTrackerMu.Lock()
+ if _, found := t.templateUsageTracker[ts.Name()]; !found {
+ t.templateUsageTracker[ts.Name()] = ts.info
+ }
+
+ if !ts.baseInfo.IsZero() {
+ if _, found := t.templateUsageTracker[ts.baseInfo.name]; !found {
+ t.templateUsageTracker[ts.baseInfo.name] = ts.baseInfo
+ }
+ }
+ t.templateUsageTrackerMu.Unlock()
+ }
+ }
+
execErr := t.executor.Execute(templ, wr, data)
if execErr != nil {
execErr = t.addFileContext(templ, execErr)
}
+
return execErr
}
+// TODO1
+func (t *templateExec) UnusedTemplates() []tpl.FileInfo {
+ if t.templateUsageTracker == nil {
+ return nil
+ }
+ var unused []tpl.FileInfo
+
+ for _, ti := range t.needsBaseof {
+ if _, found := t.templateUsageTracker[ti.name]; !found {
+ unused = append(unused, ti)
+ }
+ }
+
+ for _, ti := range t.baseof {
+ if _, found := t.templateUsageTracker[ti.name]; !found {
+ unused = append(unused, ti)
+ }
+ }
+
+ for _, ts := range t.main.templates {
+ ti := ts.info
+ if strings.HasPrefix(ti.name, "_internal/") {
+ continue
+ }
+ if strings.HasPrefix(ti.name, "partials/inline/pagination") {
+ // TODO(bep) we need to fix this. These are internal partials, but
+ // they may also be defined in the project, which currently could
+ // lead to some false negatives.
+ continue
+ }
+ if _, found := t.templateUsageTracker[ti.name]; !found {
+ unused = append(unused, ti)
+ }
+ }
+
+ sort.Slice(unused, func(i, j int) bool {
+ return unused[i].Name() < unused[j].Name()
+ })
+
+ return unused
+}
+
func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
v, found := t.funcs[name]
return v, found
@@ -285,6 +352,10 @@ type templateHandler struct {
// Note that for shortcodes that same information is embedded in the
// shortcodeTemplates type.
templateInfo map[string]tpl.Info
+
+ // May be nil.
+ templateUsageTracker map[string]templateInfo
+ templateUsageTrackerMu sync.Mutex
}
// AddTemplate parses and adds a template to the collection.
diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go
index df80726f5..06d895536 100644
--- a/tpl/tplimpl/template_errors.go
+++ b/tpl/tplimpl/template_errors.go
@@ -34,6 +34,14 @@ type templateInfo struct {
realFilename string
}
+func (t templateInfo) Name() string {
+ return t.name
+}
+
+func (t templateInfo) Filename() string {
+ return t.realFilename
+}
+
func (t templateInfo) IsZero() bool {
return t.name == ""
}