Last active
July 24, 2025 16:47
-
-
Save NSMyself/e632850b223a6f84ccbdf3b80b08ee10 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package storecheck defines an analyzer that enforces Store struct visibility | |
// in business/core/<packageName>/stores/db/db.go files | |
package storecheck | |
import ( | |
"fmt" | |
"go/ast" | |
"go/token" | |
"path/filepath" | |
"regexp" | |
"golang.org/x/tools/go/analysis" | |
"golang.org/x/tools/go/analysis/passes/inspect" | |
"golang.org/x/tools/go/ast/inspector" | |
) | |
var Analyzer = &analysis.Analyzer{ | |
Name: "xm_store_check", | |
Doc: "enforces that Store structs are public in db.go files", | |
Run: run, | |
Requires: []*analysis.Analyzer{inspect.Analyzer}, | |
} | |
// pathPattern matches business/core/<packageName>/stores/db/db.go | |
// where packageName is any valid Go identifier | |
var pathPattern = regexp.MustCompile(`business/core/[a-zA-Z_][a-zA-Z0-9_]*/stores/db/db\.go$`) | |
func run(pass *analysis.Pass) (interface{}, error) { | |
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | |
// Filter to only check files that match our target pattern | |
targetFiles := make(map[*ast.File]bool) | |
for _, file := range pass.Files { | |
pos := pass.Fset.Position(file.Pos()) | |
filename := pos.Filename | |
// Normalize path separators for cross-platform compatibility | |
normalizedPath := filepath.ToSlash(filename) | |
// DEBUG: Print all files being processed | |
fmt.Printf("DEBUG: Processing file: %s\n", normalizedPath) | |
if pathPattern.MatchString(normalizedPath) { | |
fmt.Printf("DEBUG: File matches pattern: %s\n", normalizedPath) | |
targetFiles[file] = true | |
} | |
} | |
// If no target files found, nothing to check | |
if len(targetFiles) == 0 { | |
return nil, nil | |
} | |
// Look for type declarations | |
nodeFilter := []ast.Node{ | |
(*ast.GenDecl)(nil), | |
} | |
inspect.Preorder(nodeFilter, func(n ast.Node) { | |
genDecl := n.(*ast.GenDecl) | |
// Only interested in type declarations | |
if genDecl.Tok != token.TYPE { | |
return | |
} | |
// Check if this declaration is in one of our target files | |
filePos := pass.Fset.Position(genDecl.Pos()) | |
var isTargetFile bool | |
for file := range targetFiles { | |
if pass.Fset.Position(file.Pos()).Filename == filePos.Filename { | |
isTargetFile = true | |
break | |
} | |
} | |
if !isTargetFile { | |
return | |
} | |
// Check each type spec in the declaration | |
for _, spec := range genDecl.Specs { | |
typeSpec, ok := spec.(*ast.TypeSpec) | |
if !ok { | |
continue | |
} | |
// DEBUG: Print all struct names found | |
if _, ok := typeSpec.Type.(*ast.StructType); ok { | |
fmt.Printf("DEBUG: Found struct: %s (exported: %v)\n", typeSpec.Name.Name, typeSpec.Name.IsExported()) | |
} | |
// Check if this is a struct that should be named "Store" | |
// We want to catch both "store" (incorrect) and "Store" (correct) | |
structName := typeSpec.Name.Name | |
if structName != "Store" && structName != "store" { | |
continue | |
} | |
// Verify it's actually a struct type | |
if _, ok := typeSpec.Type.(*ast.StructType); !ok { | |
continue | |
} | |
fmt.Printf("DEBUG: Found Store struct, exported: %v\n", typeSpec.Name.IsExported()) | |
// Check if Store struct is not exported (starts with lowercase) | |
if !typeSpec.Name.IsExported() { | |
pass.Reportf(typeSpec.Pos(), "Store struct must be public in db.go files") | |
} | |
} | |
}) | |
return nil, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment