Add --printUnusedTemplates
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 15 Feb 2022 14:26:18 +0000 (15:26 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 15 Feb 2022 19:01:57 +0000 (20:01 +0100)
Fixes #9502

12 files changed:
commands/commands.go
commands/commands_test.go
commands/hugo.go
docs/content/en/commands/hugo.md
docs/content/en/commands/hugo_mod.md
docs/content/en/commands/hugo_new.md
docs/content/en/commands/hugo_server.md
tpl/template.go
tpl/template_info.go
tpl/tplimpl/integration_test.go [new file with mode: 0644]
tpl/tplimpl/template.go
tpl/tplimpl/template_errors.go

index 63707e368bee3b310a44f0ea2170642369c03434..10b7a6649c58afdd2e0f5674d8b7fcdff671af3a 100644 (file)
@@ -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")
index 9e623e9a27cf9575feeea3bb76a5d52179942744..b4fb8962134c5de88d71ca7c67265407e20ccc4d 100644 (file)
@@ -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))
index ce3c4ab7ba353a29f172bc77107eee5f9ba26051..8c5294f006daffce37887977f87afaec5f716a4d 100644 (file)
@@ -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 {
index 088a8102f374bf0ddd4d64e4aff31b4c5cb2b5ae..0aa81719a7e9776283636514e57bdd54a146eb8e 100644 (file)
@@ -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
index b0860f2999092147b844a54d19e1827a97b605be..afcda98237816cc55562415d7191bede7407b483 100644 (file)
@@ -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/)
index e8a7d3b08025cc4f192d89dfc1d256908cc7fc9b..a8b084963387fec9cc01579346628706d2d61215 100644 (file)
@@ -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/)
index abdb64a5eed2aef18f58efaef9e7c06e60447c19..c224c4a52c4e1427c2ac98c94e3b6b36a7906cea 100644 (file)
@@ -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
index 0375b4a1776f5dfedd57f4d7233f05097e5f940e..c5a6a44c0c41332950286162fda437f74b18f1aa 100644 (file)
@@ -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
index d9b438138bfc723995fc0665c621b67c3ba4a050..c21c0ae7d5f797bc185ab0761b50416065b999b2 100644 (file)
@@ -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 (file)
index 0000000..f71e28b
--- /dev/null
@@ -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"))
+}
index 66ef7fc21b13badd466f568b9802c680f1e5dccd..9d2911ee95b16ccf1f6de82ea2c487ce2b8c562c 100644 (file)
@@ -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.
index df80726f5d6f4c1e8001e7947ab8e2765bcce9bf..06d895536930e7409300a819c88b904f9ea0b9fc 100644 (file)
@@ -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 == ""
 }