Skip to content

Commit 7bc4bcf

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

File tree

16 files changed

+583
-91
lines changed

16 files changed

+583
-91
lines changed

.aspell.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
mode: all
2+
ignore:
3+
- go.mod
4+
- go.sum
5+
- .aspell.yml
6+
- repo
7+
- yaml
8+
- config
9+
allowed:
10+
- aspell

.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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commited
124+
- `disabled`
125+
- check is disabled

aspell/aspell.go

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

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+
}

0 commit comments

Comments
 (0)