Add --printUnusedTemplates

Fixes #9502
This commit is contained in:
Bjørn Erik Pedersen 2022-02-15 15:26:18 +01:00
parent 923419d7fd
commit f2e7b49acf
12 changed files with 167 additions and 9 deletions

View 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("noChmod", "", false, "don't sync permission mode of files")
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations") cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.") 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.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
cmd.Flags().StringVarP(&cc.memprofile, "profile-mem", "", "", "write memory 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") cmd.Flags().BoolVarP(&cc.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals")

View file

@ -197,6 +197,7 @@ func TestFlags(t *testing.T) {
"--renderToDisk", "--renderToDisk",
"--source=mysource", "--source=mysource",
"--printPathWarnings", "--printPathWarnings",
"--printUnusedTemplates",
}, },
check: func(c *qt.C, sc *serverCmd) { check: func(c *qt.C, sc *serverCmd) {
c.Assert(sc, qt.Not(qt.IsNil)) c.Assert(sc, qt.Not(qt.IsNil))

View file

@ -31,6 +31,7 @@ import (
"time" "time"
"github.com/gohugoio/hugo/hugofs/files" "github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/common/types"
@ -217,6 +218,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
"force", "force",
"gc", "gc",
"printI18nWarnings", "printI18nWarnings",
"printUnusedTemplates",
"invalidateCDN", "invalidateCDN",
"layoutDir", "layoutDir",
"logFile", "logFile",
@ -501,7 +503,6 @@ func (c *commandeer) build() error {
return err return err
} }
// TODO(bep) Feedback?
if !c.h.quiet { if !c.h.quiet {
fmt.Println() fmt.Println()
c.hugo().PrintProcessingStats(os.Stdout) c.hugo().PrintProcessingStats(os.Stdout)
@ -513,6 +514,11 @@ func (c *commandeer) build() error {
c.logger.Warnln("Duplicate target paths:", dupes) 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 { if c.h.buildWatch {

View file

@ -53,6 +53,7 @@ hugo [flags]
--printI18nWarnings print missing translations --printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals --printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc. --printPathWarnings print warnings on duplicate target paths etc.
--printUnusedTemplates print warnings on unused templates.
--quiet build in quiet mode --quiet build in quiet mode
--renderToMemory render to memory (only useful for benchmark testing) --renderToMemory render to memory (only useful for benchmark testing)
-s, --source string filesystem path to read files relative from -s, --source string filesystem path to read files relative from

View file

@ -49,6 +49,7 @@ See https://gohugo.io/hugo-modules/ for more information.
--printI18nWarnings print missing translations --printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals --printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc. --printPathWarnings print warnings on duplicate target paths etc.
--printUnusedTemplates print warnings on unused templates.
--templateMetrics display metrics about template executions --templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics --templateMetricsHints calculate some improvement hints when combined with --templateMetrics
-t, --theme strings themes to use (located in /themes/THEMENAME/) -t, --theme strings themes to use (located in /themes/THEMENAME/)

View file

@ -50,6 +50,7 @@ hugo new [path] [flags]
--printI18nWarnings print missing translations --printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals --printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc. --printPathWarnings print warnings on duplicate target paths etc.
--printUnusedTemplates print warnings on unused templates.
--templateMetrics display metrics about template executions --templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics --templateMetricsHints calculate some improvement hints when combined with --templateMetrics
-t, --theme strings themes to use (located in /themes/THEMENAME/) -t, --theme strings themes to use (located in /themes/THEMENAME/)

View file

@ -63,6 +63,7 @@ hugo server [flags]
--printI18nWarnings print missing translations --printI18nWarnings print missing translations
--printMemoryUsage print memory usage to screen at intervals --printMemoryUsage print memory usage to screen at intervals
--printPathWarnings print warnings on duplicate target paths etc. --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) --renderToDisk render to Destination path (default is render to memory & serve from there)
--templateMetrics display metrics about template executions --templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics --templateMetricsHints calculate some improvement hints when combined with --templateMetrics

View file

@ -44,6 +44,11 @@ type TemplateFinder interface {
TemplateLookupVariant TemplateLookupVariant
} }
// UnusedTemplatesProvider lists unused templates if the build is configured to track those.
type UnusedTemplatesProvider interface {
UnusedTemplates() []FileInfo
}
// TemplateHandler finds and executes templates. // TemplateHandler finds and executes templates.
type TemplateHandler interface { type TemplateHandler interface {
TemplateFinder TemplateFinder

View file

@ -27,6 +27,11 @@ type Info interface {
identity.Provider identity.Provider
} }
type FileInfo interface {
Name() string
Filename() string
}
type InfoManager interface { type InfoManager interface {
ParseInfo() ParseInfo ParseInfo() ParseInfo
@ -65,10 +70,6 @@ func (info ParseInfo) IsZero() bool {
return info.Config.Version == 0 return info.Config.Version == 0
} }
// Info holds some info extracted from a parsed template.
type Info1 struct {
}
type ParseConfig struct { type ParseConfig struct {
Version int Version int
} }

View file

@ -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"))
}

View file

@ -67,10 +67,11 @@ var embeddedTemplatesAliases = map[string][]string{
} }
var ( var (
_ tpl.TemplateManager = (*templateExec)(nil) _ tpl.TemplateManager = (*templateExec)(nil)
_ tpl.TemplateHandler = (*templateExec)(nil) _ tpl.TemplateHandler = (*templateExec)(nil)
_ tpl.TemplateFuncGetter = (*templateExec)(nil) _ tpl.TemplateFuncGetter = (*templateExec)(nil)
_ tpl.TemplateFinder = (*templateExec)(nil) _ tpl.TemplateFinder = (*templateExec)(nil)
_ tpl.UnusedTemplatesProvider = (*templateExec)(nil)
_ tpl.Template = (*templateState)(nil) _ tpl.Template = (*templateState)(nil)
_ tpl.Info = (*templateState)(nil) _ tpl.Info = (*templateState)(nil)
@ -130,6 +131,11 @@ func newTemplateExec(d *deps.Deps) (*templateExec, error) {
funcMap[k] = v.Interface() funcMap[k] = v.Interface()
} }
var templateUsageTracker map[string]templateInfo
if d.Cfg.GetBool("printUnusedTemplates") {
templateUsageTracker = make(map[string]templateInfo)
}
h := &templateHandler{ h := &templateHandler{
nameBaseTemplateName: make(map[string]string), nameBaseTemplateName: make(map[string]string),
transformNotFound: make(map[string]*templateState), transformNotFound: make(map[string]*templateState),
@ -146,6 +152,8 @@ func newTemplateExec(d *deps.Deps) (*templateExec, error) {
layoutHandler: output.NewLayoutHandler(), layoutHandler: output.NewLayoutHandler(),
layoutsFs: d.BaseFs.Layouts.Fs, layoutsFs: d.BaseFs.Layouts.Fs,
layoutTemplateCache: make(map[layoutCacheKey]tpl.Template), layoutTemplateCache: make(map[layoutCacheKey]tpl.Template),
templateUsageTracker: templateUsageTracker,
} }
if err := h.loadEmbedded(); err != nil { 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()) 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) execErr := t.executor.Execute(templ, wr, data)
if execErr != nil { if execErr != nil {
execErr = t.addFileContext(templ, execErr) execErr = t.addFileContext(templ, execErr)
} }
return 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) { func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
v, found := t.funcs[name] v, found := t.funcs[name]
return v, found return v, found
@ -285,6 +352,10 @@ type templateHandler struct {
// Note that for shortcodes that same information is embedded in the // Note that for shortcodes that same information is embedded in the
// shortcodeTemplates type. // shortcodeTemplates type.
templateInfo map[string]tpl.Info templateInfo map[string]tpl.Info
// May be nil.
templateUsageTracker map[string]templateInfo
templateUsageTrackerMu sync.Mutex
} }
// AddTemplate parses and adds a template to the collection. // AddTemplate parses and adds a template to the collection.

View file

@ -34,6 +34,14 @@ type templateInfo struct {
realFilename string realFilename string
} }
func (t templateInfo) Name() string {
return t.name
}
func (t templateInfo) Filename() string {
return t.realFilename
}
func (t templateInfo) IsZero() bool { func (t templateInfo) IsZero() bool {
return t.name == "" return t.name == ""
} }