package sa1001 import ( "go/ast" htmltemplate "html/template" "strings" texttemplate "text/template" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/knowledge" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "SA1001", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `Invalid template`, Since: "2017.1", Severity: lint.SeverityError, MergeIf: lint.MergeIfAny, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { fn := func(node ast.Node) { call := node.(*ast.CallExpr) // OPT(dh): use integer for kind var kind string switch code.CallName(pass, call) { case "(*text/template.Template).Parse": kind = "text" case "(*html/template.Template).Parse": kind = "html" default: return } sel := call.Fun.(*ast.SelectorExpr) if !code.IsCallToAny(pass, sel.X, "text/template.New", "html/template.New") { // TODO(dh): this is a cheap workaround for templates with // different delims. A better solution with less false // negatives would use data flow analysis to see where the // template comes from and where it has been return } s, ok := code.ExprToString(pass, call.Args[knowledge.Arg("(*text/template.Template).Parse.text")]) if !ok { return } var err error switch kind { case "text": _, err = texttemplate.New("").Parse(s) case "html": _, err = htmltemplate.New("").Parse(s) } if err != nil { // TODO(dominikh): whitelist other parse errors, if any if strings.Contains(err.Error(), "unexpected") || strings.Contains(err.Error(), "bad character") { report.Report(pass, call.Args[knowledge.Arg("(*text/template.Template).Parse.text")], err.Error()) } } } code.Preorder(pass, fn, (*ast.CallExpr)(nil)) return nil, nil }