Skip to content

Commit 1cf8b85

Browse files
committed
MAJOR: add aspell as additional check for spelling errors
1 parent f74106a commit 1cf8b85

17 files changed

+645
-94
lines changed

.aspell.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
mode: all
2+
ignore:
3+
- go.mod
4+
- go.sum
5+
- .aspell.yml
6+
- *test.go
7+
allowed:
8+
- aspell
9+
- repo
10+
- yaml
11+
- config
12+
- Github
13+
- env
14+
- failsafe
15+
- OPTIM
16+
- golang
17+
- mkdir
18+
- WORKDIR
19+
- apk
20+
- ENTRYPOINT
21+
- ubuntu
22+
- golangci

.github/workflows/actions.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ jobs:
66
runs-on: ubuntu-latest
77
steps:
88
- uses: actions/checkout@v4
9+
- name: Set up Go
10+
uses: actions/setup-go@v4
11+
with:
12+
go-version-file: 'go.mod'
13+
check-latest: true
914
- name: golangci-lint
10-
uses: golangci/golangci-lint-action@v3
15+
uses: golangci/golangci-lint-action@v6
1116
go_build:
1217
name: Go build
1318
runs-on: ubuntu-latest
@@ -32,6 +37,8 @@ jobs:
3237
runs-on: ubuntu-latest
3338
needs: ["go_build"]
3439
steps:
40+
- name: Install Aspell
41+
run: sudo apt-get update && sudo apt-get install -y aspell aspell-en
3542
- uses: actions/checkout@v4
3643
- name: Set up Go
3744
uses: actions/setup-go@v4
@@ -51,6 +58,8 @@ jobs:
5158
runs-on: ubuntu-latest
5259
needs: ["go_build"]
5360
steps:
61+
- name: Install Aspell
62+
run: sudo apt-get update && sudo apt-get install -y aspell aspell-en
5463
- uses: actions/checkout@v4
5564
with:
5665
fetch-depth: 0

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
FROM golang:alpine as builder
1+
FROM golang:alpine AS builder
22
RUN mkdir /build
33
ADD . /build/
44
WORKDIR /build
55
RUN go build -o check
66

77
FROM alpine:latest
8+
RUN apk --no-cache add aspell aspell-en
89
COPY --from=builder /build/check /check
910
WORKDIR /
1011
ENTRYPOINT ["/check"]

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ bug: fix set-var parsing bug in config-parser
3232
```
3333
BUG/MEDIUM: fix set-var
3434
```
35-
- Unkown severity
35+
- Unknown severity
3636
```
3737
BUG/MODERATE: fix set-var parsing bug in config-parser
3838
```
@@ -95,3 +95,31 @@ TagOrder:
9595
### Optional parameters
9696

9797
The program accepts an optional parameter to specify the location (path) of the base of the git repository. This can be useful in certain cases where the checked-out repo is in a non-standard location within the CI environment, compared to the running path from which the check-commit binary is being invoked.
98+
99+
### aspell
100+
101+
to check also spellcheck errors aspell was added. it can be configured with `.aspell.yml`
102+
103+
example
104+
```yaml
105+
mode: subject
106+
ignore:
107+
- go.mod
108+
- go.sum
109+
- *test.go
110+
allowed:
111+
- aspell
112+
- config
113+
```
114+
115+
mode can be set as
116+
117+
- `subject`
118+
- `default` option
119+
- only subject of commit message will be checked
120+
- `commit`
121+
- whole commit message will be checked
122+
- `all`
123+
- both commit message and all code committed
124+
- `disabled`
125+
- check is disabled

aspell/aspell.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package aspell
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"log"
7+
"os/exec"
8+
"slices"
9+
"strings"
10+
11+
"check-commit/match"
12+
13+
"github.com/fatih/camelcase"
14+
)
15+
16+
type Aspell struct {
17+
Mode mode `yaml:"mode"`
18+
Ignore []string `yaml:"ignore"`
19+
AllowedWords []string `yaml:"allowed"`
20+
HelpText string `yaml:"-"`
21+
}
22+
23+
var (
24+
camelCaseOK = map[string]struct{}{
25+
"HAProxy": {},
26+
"golang": {},
27+
}
28+
camelCaseNotOK = map[string]struct{}{}
29+
)
30+
31+
func (a Aspell) checkSingle(data string, allowedWords []string) error {
32+
var words []string
33+
var badWords []string
34+
35+
checkRes, err := checkWithAspellExec(data)
36+
if checkRes != "" {
37+
words = strings.Split(checkRes, "\n")
38+
}
39+
if err != nil {
40+
return err
41+
}
42+
43+
for _, word := range words {
44+
if len(word) < 1 {
45+
continue
46+
}
47+
if _, ok := camelCaseNotOK[word]; ok {
48+
badWords = append(badWords, word)
49+
continue
50+
}
51+
if _, ok := camelCaseOK[word]; ok {
52+
continue
53+
}
54+
if slices.Contains(a.AllowedWords, word) || slices.Contains(allowedWords, word) {
55+
continue
56+
}
57+
splitted := camelcase.Split(word)
58+
if len(splitted) > 1 {
59+
for _, s := range splitted {
60+
er := a.checkSingle(s, allowedWords)
61+
if er != nil {
62+
camelCaseNotOK[word] = struct{}{}
63+
badWords = append(badWords, word+":"+s)
64+
break
65+
}
66+
}
67+
} else {
68+
camelCaseNotOK[word] = struct{}{}
69+
badWords = append(badWords, word)
70+
}
71+
}
72+
73+
if len(badWords) > 0 {
74+
return fmt.Errorf("aspell: %s", badWords)
75+
}
76+
return nil
77+
}
78+
79+
func (a Aspell) Check(subjects []string, commitsFull []string, content []map[string]string) error {
80+
var response string
81+
var checks []string
82+
switch a.Mode {
83+
case modeDisabled:
84+
return nil
85+
case modeSubject:
86+
checks = subjects
87+
case modeCommit:
88+
checks = commitsFull
89+
case modeAll:
90+
for _, file := range content {
91+
for name, v := range file {
92+
nextFile := false
93+
for _, filter := range a.Ignore {
94+
if match.MatchFilter(name, filter) {
95+
// log.Println("File", name, "in ignore list")
96+
nextFile = true
97+
continue
98+
}
99+
}
100+
if nextFile {
101+
continue
102+
}
103+
var imports []string
104+
if strings.HasSuffix(name, ".go") {
105+
imports = match.GetImportWordsFromGoFile(name)
106+
}
107+
if err := a.checkSingle(v, imports); err != nil {
108+
log.Println("File", name, err.Error())
109+
response += fmt.Sprintf("%s\n", err)
110+
}
111+
}
112+
}
113+
checks = []string{}
114+
default:
115+
checks = subjects
116+
}
117+
118+
for _, subject := range checks {
119+
if err := a.checkSingle(subject, []string{}); err != nil {
120+
response += fmt.Sprintf("%s\n", err)
121+
}
122+
}
123+
124+
if len(response) > 0 {
125+
return fmt.Errorf("%s", response)
126+
}
127+
return nil
128+
}
129+
130+
func checkWithAspellExec(subject string) (string, error) {
131+
cmd := exec.Command("aspell", "--list")
132+
cmd.Stdin = strings.NewReader(subject)
133+
134+
var stdout, stderr bytes.Buffer
135+
cmd.Stdout = &stdout
136+
cmd.Stderr = &stderr
137+
err := cmd.Run()
138+
if err != nil {
139+
log.Printf("aspell error: %s, stderr: %s", err, stderr.String())
140+
return "", err
141+
}
142+
143+
return stdout.String() + stderr.String(), nil
144+
}

aspell/aspell_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package aspell
2+
3+
import "testing"
4+
5+
func Test_checkWithAspell(t *testing.T) {
6+
aspell := Aspell{
7+
Mode: modeSubject,
8+
AllowedWords: []string{"config"},
9+
}
10+
tests := []struct {
11+
name string
12+
subject string
13+
wantErr bool
14+
}{
15+
{"OK 1", "BUG/MEDIUM: config: add default location of path to the configuration file", false},
16+
{"OK 2", "BUG/MEDIUM: config: add default location of path to the configuration file xtra", false},
17+
{"error - flie", "BUG/MEDIUM: config: add default location of path to the configuration flie", true},
18+
{"error - locatoin", "CLEANUP/MEDIUM: config: add default locatoin of path to the configuration file", true},
19+
{"error - locatoin+flie", "CLEANUP/MEDIUM: config: add default locatoin of path to the configuration flie", true},
20+
}
21+
for _, tt := range tests {
22+
t.Run(tt.name, func(t *testing.T) {
23+
err := aspell.checkSingle(tt.subject, []string{"xtra"})
24+
if tt.wantErr && err == nil {
25+
t.Errorf("checkWithAspell() error = %v, wantErr %v", err, tt.wantErr)
26+
}
27+
if !tt.wantErr && err != nil {
28+
t.Errorf("checkWithAspell() error = %v, wantErr %v", err, tt.wantErr)
29+
}
30+
})
31+
}
32+
}

aspell/mode.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package aspell
2+
3+
type mode string
4+
5+
const (
6+
modeDisabled mode = "disabled"
7+
modeSubject mode = "subject"
8+
modeCommit mode = "commit"
9+
modeAll mode = "all"
10+
)

aspell/new.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package aspell
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
func New(filename string) (Aspell, error) {
12+
var data []byte
13+
var err error
14+
if data, err = os.ReadFile(filename); err != nil {
15+
log.Printf("warning: aspell exceptions file not found (%s)", err)
16+
}
17+
18+
var aspell Aspell
19+
err = yaml.Unmarshal(data, &aspell)
20+
if err != nil {
21+
return Aspell{}, err
22+
}
23+
24+
switch aspell.Mode {
25+
case modeDisabled:
26+
case modeSubject:
27+
case modeCommit:
28+
case modeAll:
29+
case "":
30+
aspell.Mode = modeSubject
31+
default:
32+
return Aspell{}, fmt.Errorf("invalid mode: %s", aspell.Mode)
33+
}
34+
35+
log.Printf("aspell mode set to %s", aspell.Mode)
36+
aspell.HelpText = `aspell can be configured with .aspell.yml file.
37+
content example:
38+
mode: subject
39+
ignore:
40+
- go.mod
41+
- go.sum
42+
- .aspell.yml
43+
allowed:
44+
- aspell
45+
- config
46+
`
47+
return aspell, nil
48+
}

aspell_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"os"
6+
"testing"
7+
8+
"check-commit/aspell"
9+
)
10+
11+
func Test_checkWithAspell(t *testing.T) {
12+
aspell, err := aspell.New(".aspell.yml")
13+
if err != nil {
14+
t.Errorf("checkWithAspell() error = %v", err)
15+
}
16+
17+
readmeFile, err := os.Open("README.md")
18+
if err != nil {
19+
t.Errorf("could not open README.md file: %v", err)
20+
}
21+
defer readmeFile.Close()
22+
23+
scanner := bufio.NewScanner(readmeFile)
24+
readme := ""
25+
for scanner.Scan() {
26+
readme += scanner.Text() + "\n"
27+
}
28+
if err := scanner.Err(); err != nil {
29+
t.Errorf("could not read README.md file: %v", err)
30+
}
31+
err = aspell.Check([]string{"subject"}, []string{"body"}, []map[string]string{
32+
{"README.md": readme},
33+
})
34+
if err != nil {
35+
t.Errorf("checkWithAspell() error = %v", err)
36+
}
37+
}

0 commit comments

Comments
 (0)