package analyzers import ( "context" "fmt" "os" "path/filepath" "strings" "sync" ) type BestPractice struct { ID string Category string // security, architecture, performance, quality Title string Description string Pattern string Language string Framework string Severity string Reference string CodeExample string } type PracticesFetcher struct { cache map[string][]BestPractice cacheMu sync.RWMutex language string frameworks []string } func NewPracticesFetcher() *PracticesFetcher { return &PracticesFetcher{ cache: make(map[string][]BestPractice), } } func (f *PracticesFetcher) DetectLanguage(path string) string { markers := map[string]string{ "go.mod": "go", "go.sum": "go", "package.json": "javascript", "tsconfig.json": "typescript", "requirements.txt": "python", "pyproject.toml": "python", "setup.py": "python", "Cargo.toml": "rust", "pom.xml": "java", "build.gradle": "java", "composer.json": "php", "Gemfile": "ruby", } for file, lang := range markers { if _, err := os.Stat(filepath.Join(path, file)); err == nil { f.language = lang return lang } } return "go" } func (f *PracticesFetcher) DetectFrameworks(path, language string) []string { frameworks := []string{} switch language { case "go": if f.hasImport(path, "github.com/gin-gonic") { frameworks = append(frameworks, "gin") } if f.hasImport(path, "github.com/labstack/echo") { frameworks = append(frameworks, "echo") } if f.hasImport(path, "github.com/gofiber/fiber") { frameworks = append(frameworks, "fiber") } if f.hasImport(path, "gorm.io") { frameworks = append(frameworks, "gorm") } if f.hasImport(path, "github.com/spf13/cobra") { frameworks = append(frameworks, "cobra") } if f.hasImport(path, "k8s.io/client-go") { frameworks = append(frameworks, "kubernetes") } case "typescript", "javascript": pkgPath := filepath.Join(path, "package.json") if data, err := os.ReadFile(pkgPath); err == nil { content := string(data) if strings.Contains(content, `"react"`) || strings.Contains(content, `"next"`) { frameworks = append(frameworks, "react") } if strings.Contains(content, `"vue"`) { frameworks = append(frameworks, "vue") } if strings.Contains(content, `"express"`) { frameworks = append(frameworks, "express") } if strings.Contains(content, `"nestjs"`) || strings.Contains(content, `"@nestjs"`) { frameworks = append(frameworks, "nestjs") } } case "python": reqPath := filepath.Join(path, "requirements.txt") if data, err := os.ReadFile(reqPath); err == nil { content := strings.ToLower(string(data)) if strings.Contains(content, "django") { frameworks = append(frameworks, "django") } if strings.Contains(content, "flask") { frameworks = append(frameworks, "flask") } if strings.Contains(content, "fastapi") { frameworks = append(frameworks, "fastapi") } } } f.frameworks = frameworks return frameworks } func (f *PracticesFetcher) hasImport(path, importPath string) bool { err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || !strings.HasSuffix(filePath, ".go") { return nil } data, err := os.ReadFile(filePath) if err != nil { return nil } if strings.Contains(string(data), importPath) { return fmt.Errorf("found") } return nil }) return err != nil } func (f *PracticesFetcher) FetchPractices(ctx context.Context, language string, frameworks []string) ([]BestPractice, error) { cacheKey := language + ":" + strings.Join(frameworks, ",") f.cacheMu.RLock() if practices, ok := f.cache[cacheKey]; ok { f.cacheMu.RUnlock() return practices, nil } f.cacheMu.RUnlock() practices := f.getBuiltInPractices(language, frameworks) f.cacheMu.Lock() f.cache[cacheKey] = practices f.cacheMu.Unlock() return practices, nil } func (f *PracticesFetcher) getBuiltInPractices(language string, frameworks []string) []BestPractice { var practices []BestPractice practices = append(practices, f.getLanguagePractices(language)...) for _, fw := range frameworks { practices = append(practices, f.getFrameworkPractices(fw)...) } practices = append(practices, f.getSecurityPractices(language)...) practices = append(practices, f.getArchitecturePractices()...) practices = append(practices, f.getPerformancePractices(language)...) return practices } func (f *PracticesFetcher) getLanguagePractices(lang string) []BestPractice { var practices []BestPractice switch lang { case "go": practices = append(practices, []BestPractice{ { ID: "go:error-handling", Category: "quality", Title: "Always handle errors explicitly", Description: "Never ignore errors. Each error should be handled, wrapped with context, or explicitly logged.", Pattern: `if err != nil`, Language: "go", Severity: "high", Reference: "https://go.dev/blog/error-handling-and-go", }, { ID: "go:defer-in-loop", Category: "performance", Title: "Avoid defer in loops", Description: "defer in loops causes resources to be held until function returns. Move loop body to a separate function.", Pattern: `for.*\{[\s\S]*defer`, Language: "go", Severity: "medium", }, { ID: "go:context-first", Category: "architecture", Title: "context.Context should be first parameter", Description: "Functions that use context should accept it as the first parameter.", Pattern: `func\s+\w+\([^)]*context\.Context`, Language: "go", Severity: "low", }, { ID: "go:interface-location", Category: "architecture", Title: "Define interfaces where they are used", Description: "Interfaces should be defined by the consumer, not the implementer. This promotes loose coupling.", Language: "go", Severity: "medium", }, { ID: "go:exported-comments", Category: "quality", Title: "Exported symbols must have documentation comments", Description: "All exported functions, types, and variables should have doc comments starting with their name.", Language: "go", Severity: "low", Reference: "https://go.dev/doc/comment", }, { ID: "go:receiver-type", Category: "architecture", Title: "Use pointer receivers consistently", Description: "If any method has a pointer receiver, all methods should have pointer receivers. Use value receivers for small immutable types.", Language: "go", Severity: "low", }, { ID: "go:goroutine-leak", Category: "performance", Title: "Goroutines must have a termination path", Description: "Every goroutine should have a clear termination condition, typically via context cancellation or a done channel.", Language: "go", Severity: "high", }, }...) case "typescript", "javascript": practices = append(practices, []BestPractice{ { ID: "ts:async-await", Category: "quality", Title: "Prefer async/await over raw Promises", Description: "async/await provides better readability and error handling than .then() chains.", Language: "typescript", Severity: "low", }, { ID: "ts:any-type", Category: "quality", Title: "Avoid the any type", Description: "Use specific types or unknown instead of any to maintain type safety.", Pattern: `:\s*any\b`, Language: "typescript", Severity: "medium", }, { ID: "ts:null-check", Category: "quality", Title: "Use strict null checks", Description: "Enable strictNullChecks in tsconfig.json and handle null/undefined explicitly.", Language: "typescript", Severity: "medium", }, }...) case "python": practices = append(practices, []BestPractice{ { ID: "py:type-hints", Category: "quality", Title: "Use type hints for function signatures", Description: "Add type annotations to function parameters and return values for better documentation and tooling.", Language: "python", Severity: "low", }, { ID: "py:context-manager", Category: "quality", Title: "Use context managers for resource handling", Description: "Always use 'with' statements for files, connections, and other resources.", Pattern: `with\s+\w+`, Language: "python", Severity: "medium", }, }...) } return practices } func (f *PracticesFetcher) getFrameworkPractices(framework string) []BestPractice { var practices []BestPractice switch framework { case "gin", "echo", "fiber", "express": practices = append(practices, []BestPractice{ { ID: "web:input-validation", Category: "security", Title: "Validate all user input", Description: "Never trust user input. Validate and sanitize all request parameters, body, and headers.", Severity: "critical", Framework: framework, }, { ID: "web:error-exposure", Category: "security", Title: "Don't expose internal errors to users", Description: "Log detailed errors internally but return generic error messages to users.", Severity: "high", Framework: framework, }, { ID: "web:rate-limiting", Category: "security", Title: "Implement rate limiting", Description: "Protect endpoints with rate limiting to prevent abuse and DoS attacks.", Severity: "high", Framework: framework, }, { ID: "web:security-headers", Category: "security", Title: "Set security headers", Description: "Include X-Content-Type-Options, X-Frame-Options, Content-Security-Policy headers.", Severity: "medium", Framework: framework, }, }...) case "react", "vue": practices = append(practices, []BestPractice{ { ID: "frontend:xss-prevention", Category: "security", Title: "Prevent XSS vulnerabilities", Description: "Never use dangerouslySetInnerHTML/v-html with user content. Sanitize all user input.", Severity: "critical", Framework: framework, }, { ID: "frontend:dependency-audit", Category: "security", Title: "Audit dependencies regularly", Description: "Run npm audit or yarn audit regularly and update vulnerable packages.", Severity: "high", Framework: framework, }, }...) case "django", "fastapi", "flask": practices = append(practices, []BestPractice{ { ID: "django:sql-injection", Category: "security", Title: "Use ORM to prevent SQL injection", Description: "Never use raw string formatting in SQL queries. Always use parameterized queries or ORM methods.", Severity: "critical", Framework: framework, }, { ID: "django:csrf-protection", Category: "security", Title: "Enable CSRF protection", Description: "Ensure CSRF middleware is enabled for all state-changing operations.", Severity: "high", Framework: framework, }, }...) } return practices } func (f *PracticesFetcher) getSecurityPractices(lang string) []BestPractice { return []BestPractice{ { ID: "sec:hardcoded-secrets", Category: "security", Title: "No hardcoded secrets", Description: "Never commit secrets, API keys, passwords, or tokens in source code. Use environment variables or secret management.", Pattern: `(password|secret|api_key|apikey|token)\s*[=:]\s*['"][^'"]+['"]`, Severity: "critical", Reference: "https://owasp.org/www-project-web-security-testing-guide/", }, { ID: "sec:sql-injection", Category: "security", Title: "Prevent SQL injection", Description: "Use parameterized queries or prepared statements. Never concatenate user input into SQL strings.", Severity: "critical", Reference: "https://owasp.org/www-community/attacks/SQL_Injection", }, { ID: "sec:xss-prevention", Category: "security", Title: "Prevent Cross-Site Scripting (XSS)", Description: "Encode output, validate input, use Content-Security-Policy headers.", Severity: "critical", Reference: "https://owasp.org/www-community/attacks/xss/", }, { ID: "sec:insecure-deserialization", Category: "security", Title: "Avoid insecure deserialization", Description: "Don't deserialize untrusted data. Validate and sanitize all serialized input.", Severity: "critical", Reference: "https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data", }, { ID: "sec:weak-crypto", Category: "security", Title: "Use strong cryptography", Description: "Use modern algorithms (AES-256-GCM, SHA-256+, RSA-2048+). Never use MD5, SHA1 for security purposes.", Pattern: `(md5|sha1)\s*\(`, Severity: "high", }, { ID: "sec:logging-sensitive", Category: "security", Title: "Don't log sensitive data", Description: "Never log passwords, tokens, credit cards, or PII. Mask or redact sensitive fields.", Severity: "high", }, { ID: "sec:auth-checks", Category: "security", Title: "Implement proper authentication checks", Description: "Verify authentication on every protected endpoint. Don't rely on client-side checks.", Severity: "critical", }, { ID: "sec:input-validation", Category: "security", Title: "Validate all input on the server", Description: "Client-side validation is for UX. Server-side validation is for security.", Severity: "critical", }, } } func (f *PracticesFetcher) getArchitecturePractices() []BestPractice { return []BestPractice{ { ID: "arch:single-responsibility", Category: "architecture", Title: "Single Responsibility Principle", Description: "Each module/class should have one reason to change. Split large modules into focused ones.", Severity: "medium", }, { ID: "arch:dependency-injection", Category: "architecture", Title: "Use dependency injection", Description: "Inject dependencies rather than creating them internally. This improves testability and flexibility.", Severity: "medium", }, { ID: "arch:layer-separation", Category: "architecture", Title: "Separate concerns by layer", Description: "Keep presentation, business logic, and data access layers separate.", Severity: "medium", }, { ID: "arch:interface-segregation", Category: "architecture", Title: "Prefer small, focused interfaces", Description: "Clients shouldn't depend on methods they don't use. Split large interfaces.", Severity: "low", }, { ID: "arch:avoid-god-classes", Category: "architecture", Title: "Avoid god classes/modules", Description: "Classes with too many responsibilities should be split. Watch for high method/field counts.", Severity: "medium", }, { ID: "arch:circular-dependencies", Category: "architecture", Title: "Eliminate circular dependencies", Description: "Circular dependencies indicate tight coupling. Refactor to use dependency inversion.", Severity: "high", }, } } func (f *PracticesFetcher) getPerformancePractices(lang string) []BestPractice { practices := []BestPractice{ { ID: "perf:n-plus-one", Category: "performance", Title: "Avoid N+1 query patterns", Description: "When iterating over results, avoid making separate queries for each item. Use JOINs or batch loading.", Severity: "high", }, { ID: "perf:unbounded-results", Category: "performance", Title: "Limit query results", Description: "Always paginate or limit query results to prevent memory exhaustion.", Severity: "medium", }, { ID: "perf:connection-pooling", Category: "performance", Title: "Use connection pooling", Description: "Don't create new connections per request. Use connection pools for databases and HTTP clients.", Severity: "high", }, { ID: "perf:caching", Category: "performance", Title: "Cache expensive operations", Description: "Cache frequently accessed, rarely changing data. Consider memoization for expensive computations.", Severity: "medium", }, { ID: "perf:blocking-in-hot-path", Category: "performance", Title: "Avoid blocking operations in hot paths", Description: "Move I/O, network calls, and heavy computations out of request handlers when possible.", Severity: "medium", }, } if lang == "go" { practices = append(practices, []BestPractice{ { ID: "go:perf:string-concat", Category: "performance", Title: "Use strings.Builder for string concatenation", Description: "In loops, use strings.Builder instead of += for efficient string concatenation.", Pattern: `for[\s\S]*\+=.*["` + "`" + `]`, Language: "go", Severity: "medium", }, { ID: "go:perf:slice-prealloc", Category: "performance", Title: "Pre-allocate slices when size is known", Description: "Use make([]T, 0, capacity) when you know the final size to avoid reallocations.", Language: "go", Severity: "low", }, { ID: "go:perf:json-marshal", Category: "performance", Title: "Consider streaming JSON for large payloads", Description: "For large JSON, use json.Encoder/Decoder instead of Marshal/Unmarshal to reduce allocations.", Language: "go", Severity: "low", }, }...) } return practices } func (f *PracticesFetcher) GetPracticesByCategory(category string) []BestPractice { f.cacheMu.RLock() defer f.cacheMu.RUnlock() var result []BestPractice for _, practices := range f.cache { for _, p := range practices { if p.Category == category { result = append(result, p) } } } return result } func (f *PracticesFetcher) GetAllPractices() []BestPractice { f.cacheMu.RLock() defer f.cacheMu.RUnlock() var result []BestPractice seen := make(map[string]bool) for _, practices := range f.cache { for _, p := range practices { if !seen[p.ID] { result = append(result, p) seen[p.ID] = true } } } return result }