Skip to content

Commit fb0ebab

Browse files
cobbinmaSeigeC
authored andcommitted
output: generate HTML report (golangci#2043)
1 parent 67efa3f commit fb0ebab

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

pkg/commands/run.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,8 @@ func (e *Executor) createPrinter() (printers.Printer, error) {
432432
p = printers.NewCheckstyle()
433433
case config.OutFormatCodeClimate:
434434
p = printers.NewCodeClimate()
435+
case config.OutFormatHTML:
436+
p = printers.NewHTML()
435437
case config.OutFormatJunitXML:
436438
p = printers.NewJunitXML()
437439
case config.OutFormatGithubActions:

pkg/config/output.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const (
77
OutFormatTab = "tab"
88
OutFormatCheckstyle = "checkstyle"
99
OutFormatCodeClimate = "code-climate"
10+
OutFormatHTML = "html"
1011
OutFormatJunitXML = "junit-xml"
1112
OutFormatGithubActions = "github-actions"
1213
)
@@ -18,6 +19,7 @@ var OutFormats = []string{
1819
OutFormatTab,
1920
OutFormatCheckstyle,
2021
OutFormatCodeClimate,
22+
OutFormatHTML,
2123
OutFormatJunitXML,
2224
OutFormatGithubActions,
2325
}

pkg/printers/html.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package printers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"html/template"
7+
"strings"
8+
9+
"github.com/golangci/golangci-lint/pkg/logutils"
10+
"github.com/golangci/golangci-lint/pkg/result"
11+
)
12+
13+
const templateContent = `<!doctype html>
14+
<html lang="en">
15+
<head>
16+
<meta charset="utf-8">
17+
<title>golangci-lint</title>
18+
<link rel="shortcut icon" type="image/png" href="https://golangci-lint.run/favicon-32x32.png">
19+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.2/css/bulma.min.css"
20+
integrity="sha512-byErQdWdTqREz6DLAA9pCnLbdoGGhXfU6gm1c8bkf7F51JVmUBlayGe2A31VpXWQP+eiJ3ilTAZHCR3vmMyybA=="
21+
crossorigin="anonymous" referrerpolicy="no-referrer"/>
22+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css"
23+
integrity="sha512-kZqGbhf9JTB4bVJ0G8HCkqmaPcRgo88F0dneK30yku5Y/dep7CZfCnNml2Je/sY4lBoqoksXz4PtVXS4GHSUzQ=="
24+
crossorigin="anonymous" referrerpolicy="no-referrer"/>
25+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js"
26+
integrity="sha512-s+tOYYcC3Jybgr9mVsdAxsRYlGNq4mlAurOrfNuGMQ/SCofNPu92tjE7YRZCsdEtWL1yGkqk15fU/ark206YTg=="
27+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
28+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/languages/go.min.js"
29+
integrity="sha512-+UYV2NyyynWEQcZ4sMTKmeppyV331gqvMOGZ61/dqc89Tn1H40lF05ACd03RSD9EWwGutNwKj256mIR8waEJBQ=="
30+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
31+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"
32+
integrity="sha512-qlzIeUtTg7eBpmEaS12NZgxz52YYZVF5myj89mjJEesBd/oE9UPsYOX2QAXzvOAZYEvQohKdcY8zKE02ifXDmA=="
33+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
34+
<script type="text/javascript"
35+
src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"
36+
integrity="sha512-9jGNr5Piwe8nzLLYTk8QrEMPfjGU0px80GYzKZUxi7lmCfrBjtyCc1V5kkS5vxVwwIB7Qpzc7UxLiQxfAN30dw=="
37+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
38+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"
39+
integrity="sha512-kp7YHLxuJDJcOzStgd6vtpxr4ZU9kjn77e6dBsivSz+pUuAuMlE2UTdKB7jjsWT84qbS8kdCWHPETnP/ctrFsA=="
40+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
41+
</head>
42+
<body>
43+
<section class="section">
44+
<div class="container">
45+
<div id="content"></div>
46+
</div>
47+
</section>
48+
<script>
49+
const data = {{ . }};
50+
</script>
51+
<script type="text/babel">
52+
class Highlight extends React.Component {
53+
componentDidMount() {
54+
hljs.highlightElement(ReactDOM.findDOMNode(this));
55+
}
56+
57+
render() {
58+
return <pre className="go"><code>{this.props.code}</code></pre>;
59+
}
60+
}
61+
62+
class Issue extends React.Component {
63+
render() {
64+
return (
65+
<div className="issue box">
66+
<div>
67+
<div className="columns">
68+
<div className="column is-four-fifths">
69+
<h5 className="title is-5 has-text-danger-dark">{this.props.data.Title}</h5>
70+
</div>
71+
<div className="column is-one-fifth">
72+
<h6 className="title is-6">{this.props.data.Linter}</h6>
73+
</div>
74+
</div>
75+
<strong>{this.props.data.Pos}</strong>
76+
</div>
77+
<div className="highlight">
78+
<Highlight code={this.props.data.Code}/>
79+
</div>
80+
</div>
81+
);
82+
}
83+
}
84+
85+
class Issues extends React.Component {
86+
render() {
87+
if (!this.props.data.Issues || this.props.data.Issues.length === 0) {
88+
return (
89+
<div>
90+
<div className="notification">
91+
No issues found!
92+
</div>
93+
</div>
94+
);
95+
}
96+
97+
return (
98+
<div className="issues">
99+
{this.props.data.Issues.map(issue => (<Issue data={issue}/>))}
100+
</div>
101+
);
102+
}
103+
}
104+
105+
ReactDOM.render(
106+
<div className="content">
107+
<div className="columns is-centered">
108+
<div className="column is-three-quarters">
109+
<Issues data={data}/>
110+
</div>
111+
</div>
112+
</div>,
113+
document.getElementById("content")
114+
);
115+
</script>
116+
</body>
117+
</html>`
118+
119+
type htmlIssue struct {
120+
Title string
121+
Pos string
122+
Linter string
123+
Code string
124+
}
125+
126+
type HTML struct{}
127+
128+
func NewHTML() *HTML {
129+
return &HTML{}
130+
}
131+
132+
func (h HTML) Print(_ context.Context, issues []result.Issue) error {
133+
var htmlIssues []htmlIssue
134+
135+
for i := range issues {
136+
pos := fmt.Sprintf("%s:%d", issues[i].FilePath(), issues[i].Line())
137+
if issues[i].Pos.Column != 0 {
138+
pos += fmt.Sprintf(":%d", issues[i].Pos.Column)
139+
}
140+
141+
htmlIssues = append(htmlIssues, htmlIssue{
142+
Title: strings.TrimSpace(issues[i].Text),
143+
Pos: pos,
144+
Linter: issues[i].FromLinter,
145+
Code: strings.Join(issues[i].SourceLines, "\n"),
146+
})
147+
}
148+
149+
t, err := template.New("golangci-lint").Parse(templateContent)
150+
if err != nil {
151+
return err
152+
}
153+
154+
return t.Execute(logutils.StdOut, struct{ Issues []htmlIssue }{Issues: htmlIssues})
155+
}

0 commit comments

Comments
 (0)