Skip to content

Commit da7eb9e

Browse files
committed
ci: implement most changes requested by golangci-lint
1 parent 9e5769a commit da7eb9e

File tree

11 files changed

+102
-76
lines changed

11 files changed

+102
-76
lines changed

.golangci.toml

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,62 @@
88

99
[linters] # Find the whole list here https://golangci-lint.run/usage/linters/
1010
default = "standard"
11+
disabled = [
12+
"forbidigo", # forbids identifiers matched by reg exps # PLANNED: restore when fmt.Print is not necessary
13+
]
1114
enable = [
15+
"asciicheck", # simple linter to check that your code does not contain non-ASCII identifiers
16+
"bodyclose", # checks whether HTTP response body is closed successfully
17+
"copyloopvar", # A linter detects places where loop variables are copied
18+
"cyclop", # Checks function and package cyclomatic complexity
19+
"durationcheck", # check for two durations multiplied together
1220
"errcheck", # checking for unchecked errors in go programs
1321
"errorlint", # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
14-
"forbidigo", # forbids identifiers matched by reg exps
22+
"fatcontext", # Detects nested contexts in loops and function literals
23+
"gocritic", # Provides diagnostics that check for bugs, performance and style issues
24+
"goimports", # Goimports does everything that gofmt does. Additionally it checks unused imports
25+
"gomoddirectives", # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod
26+
"gomodguard", # check for blocked imports from go.mod
27+
"gosec", # inspects source code for security problems
1528
"gosimple", # linter for Go source code that specializes in simplifying a code
29+
"govet", # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
30+
"iface", # Detect the incorrect use of interfaces, helping developers avoid interface pollution
31+
"ineffassign", # detects when assignments to existing variables are not used
32+
"intrange", # Intrange is a linter to find places where for loops could make use of an integer range
1633
"misspell", # finds commonly misspelled English words in comments
1734
"nakedret", # finds naked returns in functions greater than a specified function length
35+
"nilerr", # finds the code that returns nil even if it checks that the error is not nil.
36+
"noctx", # noctx finds sending http request without context.Context
1837
"nolintlint", # reports ill-formed or insufficient nolint directives
38+
"perfsprint", # Checks that fmt.Sprintf can be replaced with a faster alternative
39+
"prealloc", # Finds slice declarations that could potentially be pre-allocated
40+
"predeclared", # Find code that shadows one of Go's predeclared identifiers
41+
"revive", # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint
42+
"sqlclosecheck", # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed
1943
"staticcheck", # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
2044
"stylecheck", # a replacement for golint
21-
"unused", # checks Go code for unused constants, variables, functions and types
22-
"govet", # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
23-
"ineffassign", # detects when assignments to existing variables are not used
45+
"testifylint", # Checks usage of github.com/stretchr/testify
46+
"testpackage", # makes you use a separate _test package
2447
"typecheck", # Like the front-end of a Go compiler, parses and type-checks Go code
25-
"asciicheck", # simple linter to check that your code does not contain non-ASCII identifiers
26-
"bodyclose", # checks whether HTTP response body is closed successfully
27-
"durationcheck", # check for two durations multiplied together
28-
"goimports", # Goimports does everything that gofmt does. Additionally it checks unused imports
29-
"gosec", # inspects source code for security problems
30-
"importas", # enforces consistent import aliases
31-
"nilerr", # finds the code that returns nil even if it checks that the error is not nil.
32-
"noctx", # noctx finds sending http request without context.Context
3348
"unconvert", # Remove unnecessary type conversions
49+
"unused", # checks Go code for unused constants, variables, functions and types
3450
"wastedassign", # wastedassign finds wasted assignment statements.
35-
"depguard", # check for blocked imports from Go files
36-
"gomodguard", # check for blocked imports from go.mod
37-
"gomoddirectives", # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod
51+
"wrapcheck", # Checks that errors returned from external packages are wrapped.
3852
]
3953

4054
[linters.settings] # See all configuration here: https://golangci-lint.run/usage/linters
4155

56+
[linters.settings.revive]
57+
enable-all-rules = true
58+
4259
[linters.settings.rowserrcheck]
4360
packages = ["github.com/jmoiron/sqlx"]
4461

4562
[linters.settings.staticcheck]
4663
checks = ["all"]
4764

65+
[linters.settings.testifylint]
66+
enable-all = true
67+
4868
[run]
4969
timeout = "1m" # Default is 1 min

cmd/subcommands/list.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,18 @@ type FileSummary struct {
6565
type OutputFormat func([]FileSummary) string
6666

6767
func summarize(summaries []FileSummary) string {
68-
mod_time_col := "Modified"
68+
modTimeCol := "Modified"
6969

7070
t := table.NewWriter()
71-
t.AppendHeader(table.Row{"subDir", "File Name", mod_time_col, "Header"})
71+
t.AppendHeader(table.Row{"subDir", "File Name", modTimeCol, "Header"})
7272
for _, summary := range summaries {
7373
stat := summary.stat
7474
t.AppendRow([]interface{}{
7575
stat.subDir, stat.file.Name(), stat.fileInfo.ModTime(), summary.header,
7676
})
7777
}
7878
t.SetColumnConfigs([]table.ColumnConfig{{
79-
Name: mod_time_col,
79+
Name: modTimeCol,
8080
Transformer: text.NewTimeTransformer(time.RFC822, nil), // "02 Jan 06 15:04 MST"
8181
}})
8282
return t.Render()
@@ -125,7 +125,7 @@ func calculateStats(syncDir, subDir string) (stats []FileStat, err error) {
125125
if !file.IsDir() && strings.HasSuffix(file.Name(), ".dj") {
126126
fi, err := file.Info()
127127
if err != nil {
128-
return stats, fmt.Errorf("Error with specified file (`%v`): %w", file, err)
128+
return stats, fmt.Errorf("error with specified file (`%v`): %w", file, err)
129129
}
130130
stat := FileStat{file: file, fileInfo: fi, subDir: subDir, path: filepath.Join(dir, file.Name())}
131131
stats = append(stats, stat)

cmd/subcommands/new.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package subcommands
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67
"os/exec"
@@ -21,11 +22,20 @@ func toTimeName(t time.Time) string {
2122
}
2223

2324
func fromTimeName(name string) (time.Time, error) {
24-
return time.Parse(time.RFC3339, strings.Replace(name, "_", ":", 2))
25+
parsedName := strings.Replace(name, "_", ":", 2)
26+
time, err := time.Parse(time.RFC3339, parsedName)
27+
if err != nil {
28+
return time, fmt.Errorf("failed to parse time %s (%s): %w", name, parsedName, err)
29+
}
30+
return time, nil
31+
2532
}
2633

2734
func createFile(path string) (err error) {
2835
file, err := os.Create(path)
36+
if err != nil {
37+
return fmt.Errorf("failed to create file: %w", err)
38+
}
2939
defer file.Close()
3040
return
3141
}
@@ -51,20 +61,20 @@ func AttachNew(cli *clir.Cli) {
5161
return fmt.Errorf("'%s' is not one of %v subDirs in '%s'. Create the folder if intended", subDir, folderNames, syncDir)
5262
}
5363

54-
name := fmt.Sprintf("%s.dj", toTimeName(time.Now()))
64+
name := toTimeName(time.Now()) + ".dj"
5565
path := filepath.Join(syncDir, subDir, name)
5666
if err := createFile(path); err != nil {
5767
return err
5868
}
5969
if open {
6070
visual := os.Getenv("VISUAL")
6171
if visual == "" {
62-
return fmt.Errorf("No value is set for Visual")
72+
return errors.New("no value is set for shell environment 'VISUAL'")
6373
}
6474
cmd := exec.Command("open", "-a", visual, path)
6575
out, err := cmd.Output()
6676
if err != nil {
67-
return fmt.Errorf("Failed to run '%v' with error '%w' and output '%v'", cmd, err, out)
77+
return fmt.Errorf("failed to run '%v' with error '%w' and output '%v'", cmd, err, out)
6878
}
6979
}
7080
fmt.Println(path)

cmd/subcommands/rename.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
func readCreationTime(path string) (string, error) {
1414
t, err := times.Stat(path)
1515
if err != nil {
16-
return "", fmt.Errorf("Error with specified file (`%s`): %w", path, err)
16+
return "", fmt.Errorf("error with specified file (`%s`): %w", path, err)
1717
}
1818
return toTimeName(t.BirthTime()), nil
1919
}
@@ -33,7 +33,10 @@ func renameAction(flags *RenameFlags) (err error) {
3333
if err != nil {
3434
return
3535
}
36-
renameFile(flags.Path, cTime)
36+
err = renameFile(flags.Path, cTime)
37+
if err != nil {
38+
return fmt.Errorf("failed to rename file: %w", err)
39+
}
3740
fmt.Printf("Renamed %s with time %v\n", flags.Path, cTime)
3841
return
3942
}

cmd/subcommands/search.go

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/jmoiron/sqlx"
13-
_ "github.com/marcboeker/go-duckdb"
13+
_ "github.com/marcboeker/go-duckdb" // Configure DuckDB driver
1414

1515
"github.com/KyleKing/yak-shears/cmd/config"
1616
"github.com/leaanthony/clir"
@@ -20,17 +20,16 @@ import (
2020
// https://gobyexample.com/http-client
2121
// https://www.digitalocean.com/community/tutorials/how-to-make-http-requests-in-go
2222

23-
//go:embed sql/init.sql
24-
var SQL_INIT string
25-
26-
//go:embed sql/insertNote.sql
27-
var INSERT_NOTE string
28-
29-
//go:embed sql/insertEmbedding.sql
30-
var INSERT_EMBEDDING string
31-
32-
//go:embed sql/searchQuery.sql
33-
var SEARCH_QUERY string
23+
var (
24+
//go:embed sql/init.sql
25+
sqlInitStmt string
26+
//go:embed sql/insertNote.sql
27+
insertNoteStmt string
28+
//go:embed sql/insertEmbedding.sql
29+
insertEmbeddingStmt string
30+
//go:embed sql/searchQuery.sql
31+
searchQueryStmt string
32+
)
3433

3534
type Note struct {
3635
SubDir string `db:"sub_dir"`
@@ -43,19 +42,19 @@ type Note struct {
4342
func storeNotes(db *sqlx.DB, notes []Note) (err error) {
4443
// PLANNED: submit multiple notes in single statement
4544
for _, note := range notes {
46-
_, err := db.NamedExec(INSERT_NOTE, note)
45+
_, err := db.NamedExec(insertNoteStmt, note)
4746
if err != nil {
48-
return fmt.Errorf("failed to execute INSERT_NOTE for note %s: %w", note.Filename, err)
47+
return fmt.Errorf("failed to execute insertNote for note %s: %w", note.Filename, err)
4948
}
5049

5150
// TODO: Consider alternative chunking techniques
5251
for _, chunk := range strings.Split(note.Content, `\n`) {
53-
_, err := db.NamedExec(INSERT_EMBEDDING, map[string]interface{}{
52+
_, err := db.NamedExec(insertEmbeddingStmt, map[string]interface{}{
5453
"filename": note.Filename,
5554
"embedding": chunk,
5655
})
5756
if err != nil {
58-
return fmt.Errorf("failed to execute INSERT_EMBEDDING for chunk in note %s: %w", note.Filename, err)
57+
return fmt.Errorf("failed to execute insertEmbeddingStmt for chunk in note %s: %w", note.Filename, err)
5958
}
6059
}
6160
}
@@ -91,8 +90,21 @@ func ingestSubdir(db *sqlx.DB, syncDir, subDir string) (err error) {
9190
return nil
9291
}
9392

94-
// Ingest ALL Notes (HACK: Should be replaced by incremental ingestion)
93+
// Purge data
94+
func dropDataHack(db *sqlx.DB) (err error) {
95+
_, err = db.Exec("DROP TABLE IF EXISTS note CASCADE")
96+
if err != nil {
97+
return fmt.Errorf("failed to remove tables: %w", err)
98+
}
99+
return nil
100+
}
101+
102+
// Ingest ALL Notes
95103
func ingestAllNotes(db *sqlx.DB, syncDir string) (err error) {
104+
if err = dropDataHack(db); err != nil {
105+
return err
106+
}
107+
96108
folderNames, err := ListsubDirs(syncDir)
97109
if err != nil {
98110
return fmt.Errorf("failed to list subdirectories in %s: %w", syncDir, err)
@@ -106,23 +118,15 @@ func ingestAllNotes(db *sqlx.DB, syncDir string) (err error) {
106118
return nil
107119
}
108120

109-
// HACK: Remove ALL notes (only for testing)
110-
func removeAllNotes(db *sqlx.DB) {
111-
// PLANNED: CASCADE from table note didn't work
112-
_, err := db.Exec("DELETE FROM embedding")
113-
check(err)
114-
_, err = db.Exec("DELETE FROM note")
115-
check(err)
116-
}
117-
118-
// Search for note in database
121+
// Search for note in database (TODO: currently only a PoC without WHERE/'query')
119122
func search(db *sqlx.DB, query string) (err error) {
123+
fmt.Printf("Warning: does not yet use query='%s'", query)
120124
notes := []Note{}
121-
nstmt, err := db.PrepareNamed(SEARCH_QUERY)
122-
// TODO: currently only a PoC without WHERE/'query'
125+
nstmt, err := db.PrepareNamed(searchQueryStmt)
123126
if err != nil {
124127
return fmt.Errorf("failed to prepare search query: %w", err)
125128
}
129+
defer nstmt.Close()
126130
err = nstmt.Select(&notes, map[string]interface{}{
127131
"limit_": 2,
128132
"offset_": 0,
@@ -140,7 +144,7 @@ func search(db *sqlx.DB, query string) (err error) {
140144
}
141145

142146
// Connect to the database and non-destructively initialize the schema, if not already found
143-
func connectDb(dir string) (db *sqlx.DB, err error) {
147+
func connectDB(dir string) (db *sqlx.DB, err error) {
144148
path := filepath.Join(dir, "yak-shears.db?access_mode=READ_WRITE")
145149
db, err = sqlx.Open("duckdb", path)
146150
if err != nil {
@@ -149,11 +153,10 @@ func connectDb(dir string) (db *sqlx.DB, err error) {
149153

150154
// PLANNED: use a connection for threading
151155
// conn, err := db.Conn(context.Background())
152-
// check(err)
153156
// defer conn.Close()
154157
// return conn, nil
155158

156-
_, err = db.Exec(SQL_INIT)
159+
_, err = db.Exec(sqlInitStmt)
157160
if err != nil {
158161
return nil, fmt.Errorf("failed to initialize database schema: %w", err)
159162
}
@@ -181,13 +184,13 @@ func AttachSearch(cli *clir.Cli) {
181184
return err
182185
}
183186

184-
db, err := connectDb(syncDir)
187+
db, err := connectDB(syncDir)
185188
if err != nil {
186189
return err
187190
}
188191
defer db.Close()
189-
// // HACK: re-ingestion on search is only for testing
190-
// removeAllNotes(db)
192+
193+
// HACK: replace with conditional and incremental ingestion
191194
if err := ingestAllNotes(db, syncDir); err != nil {
192195
return err
193196
}

cmd/subcommands/search_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func TestAttachSearch(t *testing.T) {
1313
tmpTestSubDir := resetTmpTestDir(t, "search")
1414

1515
// PLANNED: Consider testing against a seeded database
16-
// db, err := connectDb(filepath.Dir(tmpTestSubDir))
16+
// db, err := connectDB(filepath.Dir(tmpTestSubDir))
1717
// require.NoError(t, err)
1818
// defer db.Close()
1919
//

cmd/subcommands/testUtils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func resetTmpTestDir(t *testing.T, subDir string) string {
3434
// Look for exactly one file that matches the prefix
3535
func matchCreatedFile(testDir string, prefix string, t *testing.T) {
3636
matchedPaths := []string{}
37-
validateFiles := func(path string, fileInfo os.FileInfo, inpErr error) error {
37+
validateFiles := func(path string, _ os.FileInfo, _ error) error {
3838
stat, err := os.Stat(path)
3939
if err != nil {
4040
return err
@@ -43,7 +43,7 @@ func matchCreatedFile(testDir string, prefix string, t *testing.T) {
4343
if strings.HasPrefix(filepath.Base(path), prefix) {
4444
matchedPaths = append(matchedPaths, path)
4545
} else if stat.Mode().IsRegular() {
46-
return fmt.Errorf("At least one unrecognized file when attempting to match (%s): %s (matchedPaths=%v)", prefix, path, matchedPaths)
46+
return fmt.Errorf("at least one unrecognized file when attempting to match (%s): %s (matchedPaths=%v)", prefix, path, matchedPaths)
4747
}
4848

4949
return nil

cmd/subcommands/utils.go

Lines changed: 0 additions & 10 deletions
This file was deleted.

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func main() {
1111
cli := cmd.InitCli()
1212

1313
if err := cli.Run(); err != nil {
14-
fmt.Println("Error encountered: ", err)
14+
fmt.Fprintf(os.Stderr, "Error encountered: %v\n", err)
1515
os.Exit(1)
1616
}
1717
}

mise.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description = "Use in GitHub Actions"
77

88
[tasks.format]
99
description = "Format code"
10-
run = ["hk fix --quiet", "testifylint --enable-all --fix ./...", "golangci-lint run --fix ./..."]
10+
run = ["golangci-lint run --fix ./...", "hk fix --quiet"]
1111
sources = ['**/*.go']
1212

1313
[tasks.install-system]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ param_style = "colon"
3333

3434
[tool.tomlsort]
3535
in_place = true
36+
sort_inline_arrays = true
3637
sort_inline_tables = true
3738
sort_table_keys = true
38-
# sort_inline_arrays = true
3939
trailing_comma_inline_array = true

0 commit comments

Comments
 (0)