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..28287ee3 --- /dev/null +++ b/cmd/linter/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "os" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/linter" +) + +func main() { + rules := linter.LinterRules() + + 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..997de0b2 --- /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..f5f1fff2 --- /dev/null +++ b/internal/linter/linterRules.go @@ -0,0 +1,57 @@ +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 +}