Skip to content

Commit c377fa6

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

17 files changed

+734
-96
lines changed

.aspell.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
- Gitlab
14+
- env
15+
- failsafe
16+
- golang
17+
- mkdir
18+
- WORKDIR
19+
- apk
20+
- ENTRYPOINT
21+
- ubuntu
22+
- golangci
23+
- splitted
24+
- sudo
25+
- mri
26+
- IID
27+
- repo
28+
- OPTIM
29+
- Submatch
30+
- Lshortfile
31+
- MAXSUBJECTLEN
32+
- MAXSUBJECTPARTS
33+
- MINSUBJECTLEN
34+
- MINSUBJECTPARTS
35+
- malformatted
36+
- cmdline
37+
- tc
38+
- mr

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

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

0 commit comments

Comments
 (0)