Skip to content

Commit 2540752

Browse files
committed
Tooling: Add Go checks
1 parent c4e1381 commit 2540752

13 files changed

Lines changed: 572 additions & 3 deletions

scripts/check/checks/common.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
AppDesktop App = "desktop"
1515
AppWebsite App = "website"
1616
AppLicenseServer App = "license-server"
17+
AppScripts App = "scripts"
1718
AppOther App = "other"
1819
)
1920

@@ -116,3 +117,28 @@ func Pluralize(count int, singular, plural string) string {
116117
}
117118
return plural
118119
}
120+
121+
// FindGoModules finds all go.mod files in the given directory and returns
122+
// the directories containing them.
123+
func FindGoModules(rootDir string) ([]string, error) {
124+
findCmd := exec.Command("find", ".", "-name", "go.mod", "-type", "f")
125+
findCmd.Dir = rootDir
126+
output, err := RunCommand(findCmd, true)
127+
if err != nil {
128+
return nil, err
129+
}
130+
131+
var modules []string
132+
for _, line := range strings.Split(strings.TrimSpace(output), "\n") {
133+
if line != "" {
134+
// Get directory containing go.mod
135+
dir := strings.TrimSuffix(line, "/go.mod")
136+
dir = strings.TrimPrefix(dir, "./")
137+
if dir == "go.mod" {
138+
dir = "."
139+
}
140+
modules = append(modules, dir)
141+
}
142+
}
143+
return modules, nil
144+
}

scripts/check/checks/registry.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,80 @@ var AllChecks = []CheckDefinition{
211211
DependsOn: []string{"license-server-typecheck"},
212212
Run: RunLicenseServerTests,
213213
},
214+
215+
// Scripts - Go checks
216+
{
217+
ID: "scripts-go-gofmt",
218+
DisplayName: "gofmt",
219+
App: AppScripts,
220+
Tech: "🐹 Go",
221+
DependsOn: nil,
222+
Run: RunGoFmt,
223+
},
224+
{
225+
ID: "scripts-go-vet",
226+
DisplayName: "go vet",
227+
App: AppScripts,
228+
Tech: "🐹 Go",
229+
DependsOn: []string{"scripts-go-gofmt"},
230+
Run: RunGoVet,
231+
},
232+
{
233+
ID: "scripts-go-staticcheck",
234+
DisplayName: "staticcheck",
235+
App: AppScripts,
236+
Tech: "🐹 Go",
237+
DependsOn: []string{"scripts-go-gofmt"},
238+
Run: RunStaticcheck,
239+
},
240+
{
241+
ID: "scripts-go-ineffassign",
242+
DisplayName: "ineffassign",
243+
App: AppScripts,
244+
Tech: "🐹 Go",
245+
DependsOn: []string{"scripts-go-gofmt"},
246+
Run: RunIneffassign,
247+
},
248+
{
249+
ID: "scripts-go-misspell",
250+
DisplayName: "misspell",
251+
App: AppScripts,
252+
Tech: "🐹 Go",
253+
DependsOn: nil,
254+
Run: RunMisspell,
255+
},
256+
{
257+
ID: "scripts-go-gocyclo",
258+
DisplayName: "gocyclo",
259+
App: AppScripts,
260+
Tech: "🐹 Go",
261+
DependsOn: []string{"scripts-go-gofmt"},
262+
Run: RunGocyclo,
263+
},
264+
{
265+
ID: "scripts-go-nilaway",
266+
DisplayName: "nilaway",
267+
App: AppScripts,
268+
Tech: "🐹 Go",
269+
DependsOn: []string{"scripts-go-vet"},
270+
Run: RunNilaway,
271+
},
272+
{
273+
ID: "scripts-go-govulncheck",
274+
DisplayName: "govulncheck",
275+
App: AppScripts,
276+
Tech: "🐹 Go",
277+
DependsOn: nil,
278+
Run: RunGovulncheck,
279+
},
280+
{
281+
ID: "scripts-go-tests",
282+
DisplayName: "tests",
283+
App: AppScripts,
284+
Tech: "🐹 Go",
285+
DependsOn: []string{"scripts-go-vet"},
286+
Run: RunGoTests,
287+
},
214288
}
215289

216290
// GetCheckByID returns a check definition by its ID.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package checks
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
// GocycloThreshold is the maximum cyclomatic complexity allowed.
11+
const GocycloThreshold = 15
12+
13+
// RunGocyclo checks cyclomatic complexity of Go functions.
14+
func RunGocyclo(ctx *CheckContext) (CheckResult, error) {
15+
scriptsDir := filepath.Join(ctx.RootDir, "scripts")
16+
17+
// Ensure gocyclo is installed
18+
if !CommandExists("gocyclo") {
19+
installCmd := exec.Command("go", "install", "github.com/fzipp/gocyclo/cmd/gocyclo@latest")
20+
if _, err := RunCommand(installCmd, true); err != nil {
21+
return CheckResult{}, fmt.Errorf("failed to install gocyclo: %w", err)
22+
}
23+
}
24+
25+
// Count Go files
26+
findCmd := exec.Command("find", ".", "-name", "*.go", "-type", "f")
27+
findCmd.Dir = scriptsDir
28+
findOutput, _ := RunCommand(findCmd, true)
29+
fileCount := 0
30+
if strings.TrimSpace(findOutput) != "" {
31+
fileCount = len(strings.Split(strings.TrimSpace(findOutput), "\n"))
32+
}
33+
34+
// Run gocyclo with threshold
35+
cmd := exec.Command("gocyclo", "-over", fmt.Sprintf("%d", GocycloThreshold), ".")
36+
cmd.Dir = scriptsDir
37+
output, err := RunCommand(cmd, true)
38+
39+
// gocyclo returns exit code 1 if it finds functions over the threshold
40+
if err != nil || strings.TrimSpace(output) != "" {
41+
if strings.TrimSpace(output) != "" {
42+
return CheckResult{}, fmt.Errorf("functions exceed complexity threshold of %d\n%s", GocycloThreshold, indentOutput(output))
43+
}
44+
return CheckResult{}, fmt.Errorf("gocyclo failed\n%s", indentOutput(output))
45+
}
46+
47+
if fileCount > 0 {
48+
return Success(fmt.Sprintf("%d %s checked, complexity OK", fileCount, Pluralize(fileCount, "file", "files"))), nil
49+
}
50+
return Success("Complexity OK"), nil
51+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package checks
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
// RunGoFmt formats Go code with gofmt.
11+
func RunGoFmt(ctx *CheckContext) (CheckResult, error) {
12+
scriptsDir := filepath.Join(ctx.RootDir, "scripts")
13+
14+
// Count Go files
15+
findCmd := exec.Command("find", ".", "-name", "*.go", "-type", "f")
16+
findCmd.Dir = scriptsDir
17+
findOutput, _ := RunCommand(findCmd, true)
18+
fileCount := 0
19+
if strings.TrimSpace(findOutput) != "" {
20+
fileCount = len(strings.Split(strings.TrimSpace(findOutput), "\n"))
21+
}
22+
23+
if ctx.CI {
24+
// Check mode - list files that need formatting
25+
cmd := exec.Command("gofmt", "-s", "-l", ".")
26+
cmd.Dir = scriptsDir
27+
output, err := RunCommand(cmd, true)
28+
if err != nil {
29+
return CheckResult{}, fmt.Errorf("gofmt check failed\n%s", indentOutput(output))
30+
}
31+
if strings.TrimSpace(output) != "" {
32+
return CheckResult{}, fmt.Errorf("files need formatting, run gofmt -s -w . locally\n%s", indentOutput(output))
33+
}
34+
} else {
35+
// Fix mode - format files in place
36+
cmd := exec.Command("gofmt", "-s", "-w", ".")
37+
cmd.Dir = scriptsDir
38+
output, err := RunCommand(cmd, true)
39+
if err != nil {
40+
return CheckResult{}, fmt.Errorf("gofmt failed\n%s", indentOutput(output))
41+
}
42+
}
43+
44+
if fileCount > 0 {
45+
return Success(fmt.Sprintf("%d %s already formatted", fileCount, Pluralize(fileCount, "file", "files"))), nil
46+
}
47+
return Success("All files already formatted"), nil
48+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package checks
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
// RunGovulncheck checks for known vulnerabilities in Go dependencies.
11+
func RunGovulncheck(ctx *CheckContext) (CheckResult, error) {
12+
scriptsDir := filepath.Join(ctx.RootDir, "scripts")
13+
14+
// Ensure govulncheck is installed
15+
if !CommandExists("govulncheck") {
16+
installCmd := exec.Command("go", "install", "golang.org/x/vuln/cmd/govulncheck@latest")
17+
if _, err := RunCommand(installCmd, true); err != nil {
18+
return CheckResult{}, fmt.Errorf("failed to install govulncheck: %w", err)
19+
}
20+
}
21+
22+
modules, err := FindGoModules(scriptsDir)
23+
if err != nil {
24+
return CheckResult{}, fmt.Errorf("failed to find Go modules: %w", err)
25+
}
26+
27+
var allIssues []string
28+
29+
for _, mod := range modules {
30+
modDir := filepath.Join(scriptsDir, mod)
31+
32+
cmd := exec.Command("govulncheck", "./...")
33+
cmd.Dir = modDir
34+
output, err := RunCommand(cmd, true)
35+
if err != nil {
36+
allIssues = append(allIssues, fmt.Sprintf("[%s]\n%s", mod, output))
37+
}
38+
}
39+
40+
if len(allIssues) > 0 {
41+
return CheckResult{}, fmt.Errorf("vulnerabilities found\n%s", indentOutput(strings.Join(allIssues, "\n")))
42+
}
43+
44+
modCount := len(modules)
45+
return Success(fmt.Sprintf("Scanned %d %s, no vulnerabilities", modCount, Pluralize(modCount, "module", "modules"))), nil
46+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package checks
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
// RunIneffassign detects ineffectual assignments.
11+
func RunIneffassign(ctx *CheckContext) (CheckResult, error) {
12+
scriptsDir := filepath.Join(ctx.RootDir, "scripts")
13+
14+
// Ensure ineffassign is installed
15+
if !CommandExists("ineffassign") {
16+
installCmd := exec.Command("go", "install", "github.com/gordonklaus/ineffassign@latest")
17+
if _, err := RunCommand(installCmd, true); err != nil {
18+
return CheckResult{}, fmt.Errorf("failed to install ineffassign: %w", err)
19+
}
20+
}
21+
22+
modules, err := FindGoModules(scriptsDir)
23+
if err != nil {
24+
return CheckResult{}, fmt.Errorf("failed to find Go modules: %w", err)
25+
}
26+
27+
var allIssues []string
28+
fileCount := 0
29+
30+
for _, mod := range modules {
31+
modDir := filepath.Join(scriptsDir, mod)
32+
33+
// Count Go files in this module
34+
findCmd := exec.Command("find", ".", "-name", "*.go", "-type", "f")
35+
findCmd.Dir = modDir
36+
findOutput, _ := RunCommand(findCmd, true)
37+
if strings.TrimSpace(findOutput) != "" {
38+
fileCount += len(strings.Split(strings.TrimSpace(findOutput), "\n"))
39+
}
40+
41+
cmd := exec.Command("ineffassign", "./...")
42+
cmd.Dir = modDir
43+
output, err := RunCommand(cmd, true)
44+
if err != nil {
45+
allIssues = append(allIssues, fmt.Sprintf("[%s]\n%s", mod, output))
46+
}
47+
}
48+
49+
if len(allIssues) > 0 {
50+
return CheckResult{}, fmt.Errorf("ineffectual assignments found\n%s", indentOutput(strings.Join(allIssues, "\n")))
51+
}
52+
53+
if fileCount > 0 {
54+
return Success(fmt.Sprintf("%d %s checked, no ineffectual assignments", fileCount, Pluralize(fileCount, "file", "files"))), nil
55+
}
56+
return Success("No ineffectual assignments"), nil
57+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package checks
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
// RunMisspell checks for spelling mistakes.
11+
func RunMisspell(ctx *CheckContext) (CheckResult, error) {
12+
scriptsDir := filepath.Join(ctx.RootDir, "scripts")
13+
14+
// Ensure misspell is installed
15+
if !CommandExists("misspell") {
16+
installCmd := exec.Command("go", "install", "github.com/client9/misspell/cmd/misspell@latest")
17+
if _, err := RunCommand(installCmd, true); err != nil {
18+
return CheckResult{}, fmt.Errorf("failed to install misspell: %w", err)
19+
}
20+
}
21+
22+
// Count Go files
23+
findCmd := exec.Command("find", ".", "-name", "*.go", "-type", "f")
24+
findCmd.Dir = scriptsDir
25+
findOutput, _ := RunCommand(findCmd, true)
26+
fileCount := 0
27+
if strings.TrimSpace(findOutput) != "" {
28+
fileCount = len(strings.Split(strings.TrimSpace(findOutput), "\n"))
29+
}
30+
31+
cmd := exec.Command("misspell", "-error", ".")
32+
cmd.Dir = scriptsDir
33+
output, err := RunCommand(cmd, true)
34+
if err != nil {
35+
return CheckResult{}, fmt.Errorf("spelling mistakes found\n%s", indentOutput(output))
36+
}
37+
38+
if fileCount > 0 {
39+
return Success(fmt.Sprintf("%d %s checked, no misspellings", fileCount, Pluralize(fileCount, "file", "files"))), nil
40+
}
41+
return Success("No misspellings"), nil
42+
}

0 commit comments

Comments
 (0)