From f2e7b49acfaeab4e1a28cb1096f6461b555900fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 15 Feb 2022 15:26:18 +0100 Subject: [PATCH] Add --printUnusedTemplates Fixes #9502 --- commands/commands.go | 1 + commands/commands_test.go | 1 + commands/hugo.go | 8 ++- docs/content/en/commands/hugo.md | 1 + docs/content/en/commands/hugo_mod.md | 1 + docs/content/en/commands/hugo_new.md | 1 + docs/content/en/commands/hugo_server.md | 1 + tpl/template.go | 5 ++ tpl/template_info.go | 9 +-- tpl/tplimpl/integration_test.go | 61 +++++++++++++++++++ tpl/tplimpl/template.go | 79 +++++++++++++++++++++++-- tpl/tplimpl/template_errors.go | 8 +++ 12 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 tpl/tplimpl/integration_test.go 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 == "" }