From 246f818052a02ac8a585b22480f8792ba47c6fb2 Mon Sep 17 00:00:00 2001 From: Gaballa Date: Fri, 22 May 2026 04:52:32 -0700 Subject: [PATCH 1/3] basic custom linter --- .github/workflows/ci.yaml | 4 +++ Makefile | 5 +++- cmd/linter/main.go | 62 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 cmd/linter/main.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ac43748f..6810ffb7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,6 +33,10 @@ jobs: if: steps.filter.outputs.source_code == 'true' run: nix develop .#ci -c bash -c "make fmt && git diff --exit-code" + - name: Linter check + if: steps.filter.outputs.source_code == 'true' + run: nix develop .#ci -c make linter + - name: Check if: steps.filter.outputs.source_code == 'true' run: nix develop .#ci -c make check diff --git a/Makefile b/Makefile index e8fcedae..5762081b 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,9 @@ check-sql: ## Lint all sql files fix-sql: ## Fix all sql files sqlfluff fix --dialect sqlite +linter: + go run cmd/linter/main.go + release: ## Create a new release tag @echo "Current version: $(VERSION)" @read -p "Enter new version (e.g., v0.2.0): " version; \ @@ -69,4 +72,4 @@ clean: ## Clean up binaries and build artifacts help: ## Display this help screen @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -.PHONY: help fmt run build all api cli check test check-sql fix-sql clean release generate +.PHONY: help fmt run build all api cli check test check-sql fix-sql clean release generate linter diff --git a/cmd/linter/main.go b/cmd/linter/main.go new file mode 100644 index 00000000..102312cd --- /dev/null +++ b/cmd/linter/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "errors" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "log" + "os" + "path/filepath" + "slices" + "strconv" +) + +var filesToSkip []string = []string{".", "swagger.go"} + +func main() { + + // Go to api/handlers + // This should be run in the project root + handlersPath := "internal/api/handlers/" + os.Chdir(handlersPath) + + err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error { + if slices.Contains(filesToSkip, path) { + return nil + } + + // Creating an AST tree by parsing + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + log.Println(err) + os.Exit(1) + } + + // Now we can search for specific things like functions and imports + ast.Inspect(file, func(n ast.Node) bool { + // looking for an import, we shouldn't have dbmodels in handler + switch x := n.(type) { + case *ast.ImportSpec: + + importedModule, _ := strconv.Unquote(x.Path.Value) + + // yah messy, but for the sake of proposal + check := (importedModule == "github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels") + if check { + // This error is going to make me throw up lol + log.Println(errors.New("Bad import found: " + importedModule + " in " + handlersPath + path)) + os.Exit(1) + } + } + return true + }) + return nil + }) + if err != nil { + log.Println(err) + os.Exit(1) + } +} From 301ee11d7864990db09cd6bffd8253c6c91645ff Mon Sep 17 00:00:00 2001 From: Gaballa Date: Fri, 5 Jun 2026 15:26:36 -0700 Subject: [PATCH 2/3] refactor proposal --- cmd/linter/main.go | 59 ++++---------------------------- internal/linter/linter.go | 55 ++++++++++++++++++++++++++++++ internal/linter/linterRules.go | 61 ++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 52 deletions(-) create mode 100644 internal/linter/linter.go create mode 100644 internal/linter/linterRules.go diff --git a/cmd/linter/main.go b/cmd/linter/main.go index 102312cd..28287ee3 100644 --- a/cmd/linter/main.go +++ b/cmd/linter/main.go @@ -1,62 +1,17 @@ package main import ( - "errors" - "go/ast" - "go/parser" - "go/token" - "io/fs" - "log" "os" - "path/filepath" - "slices" - "strconv" -) -var filesToSkip []string = []string{".", "swagger.go"} + "github.com/acmcsufoss/api.acmcsuf.com/internal/linter" +) func main() { + rules := linter.LinterRules() - // Go to api/handlers - // This should be run in the project root - handlersPath := "internal/api/handlers/" - os.Chdir(handlersPath) - - err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error { - if slices.Contains(filesToSkip, path) { - return nil - } - - // Creating an AST tree by parsing - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) - if err != nil { - log.Println(err) - os.Exit(1) - } - - // Now we can search for specific things like functions and imports - ast.Inspect(file, func(n ast.Node) bool { - // looking for an import, we shouldn't have dbmodels in handler - switch x := n.(type) { - case *ast.ImportSpec: - - importedModule, _ := strconv.Unquote(x.Path.Value) - - // yah messy, but for the sake of proposal - check := (importedModule == "github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels") - if check { - // This error is going to make me throw up lol - log.Println(errors.New("Bad import found: " + importedModule + " in " + handlersPath + path)) - os.Exit(1) - } - } - return true - }) - return nil - }) - if err != nil { - log.Println(err) - os.Exit(1) + for _, rule := range rules { + linter.Lint(rule) } + + os.Exit(0) } diff --git a/internal/linter/linter.go b/internal/linter/linter.go new file mode 100644 index 00000000..0d0db57d --- /dev/null +++ b/internal/linter/linter.go @@ -0,0 +1,55 @@ +package linter + +import ( + "errors" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "log" + "os" + "path/filepath" + "slices" + "strconv" +) + +func Lint(rule lintRule) { + + os.Chdir(rule.path) + + err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error { + if slices.Contains(rule.skipFiles, path) { + return nil + } + + // Creating an AST tree by parsing + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + log.Println(err) + os.Exit(1) + } + + // Now we can search for specific things like functions and imports + ast.Inspect(file, func(n ast.Node) bool { + switch x := n.(type) { + + // Import check + case *ast.ImportSpec: + + importedModule, _ := strconv.Unquote(x.Path.Value) + + if slices.Contains(rule.badImports, importedModule) { + log.Println(errors.New("Bad import found: " + importedModule + " in " + rule.path + path)) + os.Exit(1) + } + } + return true + }) + return nil + }) + if err != nil { + log.Println(err) + os.Exit(1) + } +} diff --git a/internal/linter/linterRules.go b/internal/linter/linterRules.go new file mode 100644 index 00000000..171423fd --- /dev/null +++ b/internal/linter/linterRules.go @@ -0,0 +1,61 @@ +package linter + +// +// Rules for linting +// Right now just deals with bad imports but can be added onto later +// +type lintRule struct { + + // Path to check + path string + + // Files to skip in a path + skipFiles []string + + // Imports that should not be in a file + badImports []string + +} + +func LinterRules() []lintRule { + var rules []lintRule + + // ====================== API RULES ====================== + handlerRule := lintRule{ + path: "internal/api/handlers/", + skipFiles: []string{".", "swagger.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + } + rules = append(rules, handlerRule) + + // CLI and API linter rules could probably be split in the future for faster test times + + // ====================== CLI RULES ====================== + cliPath := "internal/cli" + announcementsCLIRule := lintRule{ + path: cliPath + "/announcements/", + skipFiles: []string{".", "root.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + } + + rules = append(rules, announcementsCLIRule) + + eventsCLIRule := lintRule{ + path: cliPath + "/events/", + skipFiles: []string{".", "root.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + } + + rules = append(rules, eventsCLIRule) + + officersCLIRule := lintRule{ + path: cliPath + "/officers/", + skipFiles: []string{".", "root.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + } + + rules = append(rules, officersCLIRule) + + return rules +} + From 6605709e21033e5bf542222b5b28e9f9f1ad1436 Mon Sep 17 00:00:00 2001 From: Gaballa Date: Fri, 5 Jun 2026 15:31:24 -0700 Subject: [PATCH 3/3] fmt --- internal/linter/linter.go | 6 +++--- internal/linter/linterRules.go | 36 +++++++++++++++------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/internal/linter/linter.go b/internal/linter/linter.go index 0d0db57d..997de0b2 100644 --- a/internal/linter/linter.go +++ b/internal/linter/linter.go @@ -1,4 +1,4 @@ -package linter +package linter import ( "errors" @@ -14,7 +14,7 @@ import ( ) func Lint(rule lintRule) { - + os.Chdir(rule.path) err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error { @@ -39,7 +39,7 @@ func Lint(rule lintRule) { importedModule, _ := strconv.Unquote(x.Path.Value) - if slices.Contains(rule.badImports, importedModule) { + if slices.Contains(rule.badImports, importedModule) { log.Println(errors.New("Bad import found: " + importedModule + " in " + rule.path + path)) os.Exit(1) } diff --git a/internal/linter/linterRules.go b/internal/linter/linterRules.go index 171423fd..f5f1fff2 100644 --- a/internal/linter/linterRules.go +++ b/internal/linter/linterRules.go @@ -1,9 +1,7 @@ package linter -// // Rules for linting // Right now just deals with bad imports but can be added onto later -// type lintRule struct { // Path to check @@ -14,48 +12,46 @@ type lintRule struct { // Imports that should not be in a file badImports []string - } func LinterRules() []lintRule { var rules []lintRule - // ====================== API RULES ====================== + // ====================== API RULES ====================== handlerRule := lintRule{ - path: "internal/api/handlers/", - skipFiles: []string{".", "swagger.go"}, - badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + path: "internal/api/handlers/", + skipFiles: []string{".", "swagger.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, } rules = append(rules, handlerRule) // CLI and API linter rules could probably be split in the future for faster test times - // ====================== CLI RULES ====================== - cliPath := "internal/cli" + // ====================== CLI RULES ====================== + cliPath := "internal/cli" announcementsCLIRule := lintRule{ - path: cliPath + "/announcements/", - skipFiles: []string{".", "root.go"}, - badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + path: cliPath + "/announcements/", + skipFiles: []string{".", "root.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, } rules = append(rules, announcementsCLIRule) - + eventsCLIRule := lintRule{ - path: cliPath + "/events/", - skipFiles: []string{".", "root.go"}, - badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + path: cliPath + "/events/", + skipFiles: []string{".", "root.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, } rules = append(rules, eventsCLIRule) officersCLIRule := lintRule{ - path: cliPath + "/officers/", - skipFiles: []string{".", "root.go"}, - badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, + path: cliPath + "/officers/", + skipFiles: []string{".", "root.go"}, + badImports: []string{"github.com/acmcsufoss/api.acmcsuf.com/internal/api/store/dbmodels"}, } rules = append(rules, officersCLIRule) return rules } -