@sun-asterisk/sunlint 1.3.40 → 1.3.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/rule-selection-service.js +11 -0
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/go/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C013-no-dead-code.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C014-dependency-injection.md +85 -0
- package/skill-assets/sunlint-code-quality/rules/go/C017-no-constructor-logic.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/go/C018-generic-errors.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/C020-no-unused-imports.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C022-no-unused-variables.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/C023-no-duplicate-names.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/C024-centralize-constants.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/C029-catch-log-root-cause.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/C030-custom-error-classes.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/go/C033-separate-data-access.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go/C035-error-context-logging.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C041-no-hardcoded-secrets.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C042-boolean-naming.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/C052-controller-parsing.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/go/C060-superclass-logic.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/go/C067-no-hardcoded-config.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S003-open-redirect.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/go/S004-no-log-credentials.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/go/S005-server-authorization.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S006-default-credentials.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S007-output-encoding.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S009-approved-crypto.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/S010-csprng.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S011-encrypted-client-hello.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/S012-secrets-management.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S013-tls-connections.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/go/S016-no-sensitive-query-string.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S017-parameterized-queries.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/go/S019-email-input-sanitization.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S020-eval-code-execution.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S022-context-escaping.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S023-dynamic-js-encoding.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S025-server-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/S026-tls-encryption.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S027-mtls-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S028-upload-limits.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S029-csrf-protection.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S030-directory-browsing.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S031-secure-cookie-flag.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S032-httponly-cookie.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S033-samesite-cookie.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S034-host-prefix-cookie.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S035-app-hostnames.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S036-internal-file-paths.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/S037-anti-cache-headers.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S039-tls-certificate-validation.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/S041-logout-invalidation.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S042-long-lived-sessions.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S044-critical-changes-reauth.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S045-brute-force-protection.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S047-oauth-csrf-protection.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S048-oauth-redirect-validation.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S049-auth-code-expiry.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S050-token-entropy.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S051-password-length.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S052-otp-entropy.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S053-generic-error-messages.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S054-no-default-admin.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S055-content-type-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S056-log-injection.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S057-synchronized-time.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S058-ssrf-protection.md +70 -0
|
@@ -126,6 +126,17 @@ class RuleSelectionService {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// Filter out disabled rules from config (rules set to 'off', false, or 0)
|
|
130
|
+
const disabledRules = new Set(
|
|
131
|
+
Object.entries(allRules)
|
|
132
|
+
.filter(([, value]) => value === 'off' || value === false || value === 0)
|
|
133
|
+
.map(([ruleId]) => ruleId)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (disabledRules.size > 0) {
|
|
137
|
+
selectedRules = selectedRules.filter(ruleId => !disabledRules.has(ruleId));
|
|
138
|
+
}
|
|
139
|
+
|
|
129
140
|
// Convert to rule objects
|
|
130
141
|
return selectedRules.map(ruleId => {
|
|
131
142
|
const adapterRule = this.ruleAdapter.getRuleById(ruleId);
|
package/package.json
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Function Names Verb-Noun
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: makes code self-documenting
|
|
5
|
+
tags: naming, functions, readability, conventions, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Function Names Verb-Noun
|
|
9
|
+
|
|
10
|
+
Functions do things. Action verbs make purpose clear.
|
|
11
|
+
|
|
12
|
+
**Incorrect (vague names):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func user() { } // Noun only
|
|
16
|
+
func userData() { } // Noun only
|
|
17
|
+
func doSomething() { } // Vague
|
|
18
|
+
func handleStuff() { } // Vague
|
|
19
|
+
func manager() { } // Noun only
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (action verbs):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
func GetUser() { }
|
|
26
|
+
func CreateUserAccount() { }
|
|
27
|
+
func ValidateEmailFormat() { }
|
|
28
|
+
func CalculateTotalPrice() { }
|
|
29
|
+
func SendConfirmationEmail() { }
|
|
30
|
+
func ConvertCurrencyToUSD() { }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Verb categories:**
|
|
34
|
+
|
|
35
|
+
| Category | Verbs |
|
|
36
|
+
|----------|-------|
|
|
37
|
+
| Retrieval | `Get`, `Fetch`, `Find`, `Load`, `Query` |
|
|
38
|
+
| Creation | `Create`, `Build`, `Make`, `Generate` |
|
|
39
|
+
| Modification | `Set`, `Update`, `Modify`, `Change` |
|
|
40
|
+
| Deletion | `Delete`, `Remove`, `Destroy`, `Clear` |
|
|
41
|
+
| Validation | `Validate`, `Verify`, `Check`, `Ensure` |
|
|
42
|
+
| Computation | `Calculate`, `Compute`, `Parse`, `Format` |
|
|
43
|
+
| Boolean | `Is`, `Has`, `Can`, `Should`, `Will` |
|
|
44
|
+
|
|
45
|
+
**Tools:** PR review, GolangCI-Lint
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Use Dead Code
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces codebase noise and maintenance burden
|
|
5
|
+
tags: dead-code, cleanup, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Use Dead Code
|
|
9
|
+
|
|
10
|
+
Dead code confuses readers and increases cognitive load. Git history preserves deleted code.
|
|
11
|
+
|
|
12
|
+
**Incorrect (keeping dead code):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func ProcessOrder(order *Order) float64 {
|
|
16
|
+
// Old implementation - keeping for reference
|
|
17
|
+
// total := 0.0
|
|
18
|
+
// for _, item := range order.Items {
|
|
19
|
+
// total += item.Price * float64(item.Quantity)
|
|
20
|
+
// }
|
|
21
|
+
// return total
|
|
22
|
+
|
|
23
|
+
total := calculateTotal(order)
|
|
24
|
+
return total
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Unused function - someone might need it later
|
|
28
|
+
func legacyCalculation() { }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct (clean code):**
|
|
32
|
+
|
|
33
|
+
```go
|
|
34
|
+
func ProcessOrder(order *Order) float64 {
|
|
35
|
+
return calculateTotal(order)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Delete unused functions - git history preserves them
|
|
39
|
+
// Delete commented code - git history preserves it
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Types of dead code:**
|
|
43
|
+
- Commented-out code
|
|
44
|
+
- Unused functions/structs/methods
|
|
45
|
+
- Unreachable code
|
|
46
|
+
- Unused variables (Go compiler will error on these locally, but they may exist in long-lived branches)
|
|
47
|
+
|
|
48
|
+
**Tools:** gofmt, govet, staticcheck, GolangCI-Lint
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Dependency Injection
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables testability and loose coupling
|
|
5
|
+
tags: dependency-injection, testing, coupling, architecture, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Dependency Injection
|
|
9
|
+
|
|
10
|
+
Direct instantiation creates tight coupling, making testing difficult and changes risky. DI enables mockability, replaceability, and testability.
|
|
11
|
+
|
|
12
|
+
**Incorrect (hardcoded dependencies):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
type OrderService struct {
|
|
16
|
+
db *PostgresDatabase
|
|
17
|
+
mailer *SendGridMailer
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func NewOrderService() *OrderService {
|
|
21
|
+
return &OrderService{
|
|
22
|
+
db: NewPostgresDatabase(), // Hardcoded dependency
|
|
23
|
+
mailer: NewSendGridMailer(), // Hardcoded dependency
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func (s *OrderService) CreateOrder(data OrderData) error {
|
|
28
|
+
order, err := s.db.Insert("orders", data)
|
|
29
|
+
if err != nil {
|
|
30
|
+
return err
|
|
31
|
+
}
|
|
32
|
+
return s.mailer.Send(data.Email, "Order created")
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Correct (injected dependencies via interfaces):**
|
|
37
|
+
|
|
38
|
+
```go
|
|
39
|
+
type Database interface {
|
|
40
|
+
Insert(table string, data any) (any, error)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type Mailer interface {
|
|
44
|
+
Send(to string, message string) error
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type OrderService struct {
|
|
48
|
+
db Database
|
|
49
|
+
mailer Mailer
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func NewOrderService(db Database, mailer Mailer) *OrderService {
|
|
53
|
+
return &OrderService{
|
|
54
|
+
db: db,
|
|
55
|
+
mailer: mailer,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func (s *OrderService) CreateOrder(data OrderData) error {
|
|
60
|
+
order, err := s.db.Insert("orders", data)
|
|
61
|
+
if err != nil {
|
|
62
|
+
return err
|
|
63
|
+
}
|
|
64
|
+
return s.mailer.Send(data.Email, "Order created")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Usage
|
|
68
|
+
service := NewOrderService(
|
|
69
|
+
NewPostgresDatabase(connString),
|
|
70
|
+
NewSendGridMailer(apiKey),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// Testing
|
|
74
|
+
mockDb := new(MockDatabase)
|
|
75
|
+
mockMailer := new(MockMailer)
|
|
76
|
+
testService := NewOrderService(mockDb, mockMailer)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Benefits:**
|
|
80
|
+
- Easy mocking for unit tests
|
|
81
|
+
- Swappable implementations
|
|
82
|
+
- Clear dependencies visible in constructor/factory
|
|
83
|
+
- Supports interface-based design (accept interfaces, return structs)
|
|
84
|
+
|
|
85
|
+
**Tools:** GolangCI-Lint, Manual review
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Business Logic In Factory Functions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: ensures predictable object initialization
|
|
5
|
+
tags: factory, initialization, side-effects, patterns, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Business Logic In Factory Functions
|
|
9
|
+
|
|
10
|
+
Factory functions (e.g., `NewService`) should only initialize state. Side effects in factory functions are unexpected and make testing difficult.
|
|
11
|
+
|
|
12
|
+
**Incorrect (logic in factory function):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
type UserService struct {
|
|
16
|
+
config Config
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func NewUserService(configPath string) (*UserService, error) {
|
|
20
|
+
// BAD: Reading files in factory function
|
|
21
|
+
rawConfig, err := os.ReadFile(configPath)
|
|
22
|
+
if err != nil {
|
|
23
|
+
return nil, err
|
|
24
|
+
}
|
|
25
|
+
var config Config
|
|
26
|
+
json.Unmarshal(rawConfig, &config)
|
|
27
|
+
|
|
28
|
+
// BAD: Network calls/API initialization here
|
|
29
|
+
resp, _ := http.Get("https://api.example.com/init")
|
|
30
|
+
|
|
31
|
+
// BAD: Logging/side effects
|
|
32
|
+
log.Println("UserService initialized")
|
|
33
|
+
|
|
34
|
+
return &UserService{config: config}, nil
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Correct (clear separation):**
|
|
39
|
+
|
|
40
|
+
```go
|
|
41
|
+
type UserService struct {
|
|
42
|
+
config Config
|
|
43
|
+
httpClient *http.Client
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func NewUserService(config Config, httpClient *http.Client) *UserService {
|
|
47
|
+
// Only assignment - no side effects
|
|
48
|
+
return &UserService{
|
|
49
|
+
config: config,
|
|
50
|
+
httpClient: httpClient,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Service initialization in main or a dedicated bootstrapper
|
|
55
|
+
func InitializeApp() {
|
|
56
|
+
rawConfig, _ := os.ReadFile("./config.json")
|
|
57
|
+
var config Config
|
|
58
|
+
json.Unmarshal(rawConfig, &config)
|
|
59
|
+
|
|
60
|
+
httpClient := &http.Client{Timeout: 10 * time.Second}
|
|
61
|
+
|
|
62
|
+
service := NewUserService(config, httpClient)
|
|
63
|
+
log.Println("UserService ready")
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Tools:** PR review, Manual review
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Return Generic Errors
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables proper error handling and monitoring
|
|
5
|
+
tags: error-handling, custom-errors, debugging, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Return Generic Errors
|
|
9
|
+
|
|
10
|
+
Generic errors (like `errors.New("error")`) lack context needed for debugging. They make it impossible to distinguish between error types for proper handling.
|
|
11
|
+
|
|
12
|
+
**Incorrect (generic errors):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
if user == nil {
|
|
16
|
+
return errors.New("error")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if !isValid {
|
|
20
|
+
return fmt.Errorf("invalid")
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (specific custom errors or sentinel errors):**
|
|
25
|
+
|
|
26
|
+
```go
|
|
27
|
+
// Sentinel errors for simple checks
|
|
28
|
+
var ErrUserNotFound = errors.New("user not found")
|
|
29
|
+
|
|
30
|
+
func (s *Service) GetUser(id string) (*User, error) {
|
|
31
|
+
if user == nil {
|
|
32
|
+
return nil, fmt.Errorf("%w: user with ID %s not found", ErrUserNotFound, id)
|
|
33
|
+
}
|
|
34
|
+
return user, nil
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Custom error types for complex context
|
|
38
|
+
type ValidationError struct {
|
|
39
|
+
Field string
|
|
40
|
+
Message string
|
|
41
|
+
Value any
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func (e *ValidationError) Error() string {
|
|
45
|
+
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if !isValid {
|
|
49
|
+
return &ValidationError{
|
|
50
|
+
Field: "email",
|
|
51
|
+
Message: "invalid format",
|
|
52
|
+
Value: email,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Custom errors should include:
|
|
58
|
+
- Descriptive message with context (use `%w` for wrapping)
|
|
59
|
+
- Error codes or types for programmatic handling (using `errors.Is` or `errors.As`)
|
|
60
|
+
- Relevant data for debugging
|
|
61
|
+
- Appropriate mapping to status codes in the transport layer (e.g., HTTP 404)
|
|
62
|
+
|
|
63
|
+
**Tools:** GolangCI-Lint (errname, goerr113), Manual review
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Use Error Log For Non-critical
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents alert fatigue and log noise
|
|
5
|
+
tags: logging, log-levels, error, observability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Use Error Log For Non-critical
|
|
9
|
+
|
|
10
|
+
Incorrect log levels cause alert fatigue and hide real issues. When everything is an "error", nothing is.
|
|
11
|
+
|
|
12
|
+
**Incorrect (overusing error level):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// NOT an error - expected business case
|
|
16
|
+
slog.Error("User entered wrong password")
|
|
17
|
+
|
|
18
|
+
// NOT an error - validation failure
|
|
19
|
+
slog.Error("Email format invalid")
|
|
20
|
+
|
|
21
|
+
// NOT an error - temporary network issue
|
|
22
|
+
slog.Error("Retry attempt 2 of 5")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (appropriate log levels using slog):**
|
|
26
|
+
|
|
27
|
+
```go
|
|
28
|
+
// WARN - recoverable, may need attention
|
|
29
|
+
slog.Warn("Payment retry attempt", "attempt", 2, "max_attempts", 5)
|
|
30
|
+
|
|
31
|
+
// INFO - normal business events
|
|
32
|
+
slog.Info("Login failed - invalid password", "user_id", userID, "attempts", 3)
|
|
33
|
+
|
|
34
|
+
// DEBUG - detailed troubleshooting
|
|
35
|
+
slog.Debug("Validation failed", "field", "email", "value", maskedEmail)
|
|
36
|
+
|
|
37
|
+
// ERROR - only for actual system failures
|
|
38
|
+
slog.Error("Database connection lost", "host", dbHost, "error", err)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Log Level Guide:**
|
|
42
|
+
|
|
43
|
+
| Level | Use For |
|
|
44
|
+
|-------|---------|
|
|
45
|
+
| ERROR | System failures, crashes, unrecoverable |
|
|
46
|
+
| WARN | Potential issues, degraded performance |
|
|
47
|
+
| INFO | Business events, state changes |
|
|
48
|
+
| DEBUG | Detailed troubleshooting |
|
|
49
|
+
|
|
50
|
+
**Tools:** Log linter, Custom rule, `slog` (standard library)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Unused Imports
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces codebase noise and compilation time
|
|
5
|
+
tags: imports, cleanup, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Unused Imports
|
|
9
|
+
|
|
10
|
+
Unused imports increase binary size and slow down compilation. Go compiler strictly enforces this at compile time.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unused imports):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
package main
|
|
16
|
+
|
|
17
|
+
import (
|
|
18
|
+
"fmt"
|
|
19
|
+
"os" // UNUSED
|
|
20
|
+
"time" // UNUSED
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
func main() {
|
|
24
|
+
fmt.Println("Hello")
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Correct (clean imports):**
|
|
29
|
+
|
|
30
|
+
```go
|
|
31
|
+
package main
|
|
32
|
+
|
|
33
|
+
import (
|
|
34
|
+
"fmt"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
func main() {
|
|
38
|
+
fmt.Println("Hello")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Side-effect only imports use the blank identifier
|
|
42
|
+
import _ "net/http/pprof"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Tools:** `goimports`, `golines`, Go Compiler (enforced)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Unused Variables
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces codebase noise and potential bugs
|
|
5
|
+
tags: variables, cleanup, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Unused Variables
|
|
9
|
+
|
|
10
|
+
Unused variables clutter the code and often indicate a bug (missing logic). Go compiler strictly enforces this for local variables.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unused variables):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Calculate(a, b int) int {
|
|
16
|
+
result := a + b
|
|
17
|
+
temp := result * 2 // UNUSED
|
|
18
|
+
|
|
19
|
+
return result
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (clean code):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
func Calculate(a, b int) int {
|
|
27
|
+
return a + b
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// If a variable is needed for its side effect but the value is not, use _
|
|
31
|
+
_, err := os.Open("file.txt")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Tools:** Go Compiler, `go vet`, GolangCI-Lint (unused)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Variable Shadowing
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents subtle bugs where inner variables hide outer ones
|
|
5
|
+
tags: variables, shadowing, scope, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Variable Shadowing
|
|
9
|
+
|
|
10
|
+
Variable shadowing occurs when a variable declared within a certain scope has the same name as a variable in an outer scope. This can lead to subtle and hard-to-debug logic errors.
|
|
11
|
+
|
|
12
|
+
**Incorrect (shadowed variables):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func processItems(items []string) {
|
|
16
|
+
user := "admin"
|
|
17
|
+
|
|
18
|
+
for _, item := range items {
|
|
19
|
+
user := findUser(item) // Shadowing outer 'user' with :=
|
|
20
|
+
fmt.Println("Processing for user:", user)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Outer 'user' is still "admin", which might be unexpected if the loop
|
|
24
|
+
// was intended to update it.
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Correct (unique names or explicit assignment):**
|
|
29
|
+
|
|
30
|
+
```go
|
|
31
|
+
func processItems(items []string) {
|
|
32
|
+
globalUser := "admin"
|
|
33
|
+
|
|
34
|
+
for _, item := range items {
|
|
35
|
+
currentUser := findUser(item)
|
|
36
|
+
fmt.Println("Processing for user:", currentUser)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** `go vet -shadow`, GolangCI-Lint (shadow)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Centralize Constants
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: makes values easy to find and update
|
|
5
|
+
tags: constants, magic-numbers, configuration, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Centralize Constants
|
|
9
|
+
|
|
10
|
+
Magic numbers or strings scattered throughout the code are hard to find and update. Centralizing them improves maintainability.
|
|
11
|
+
|
|
12
|
+
**Incorrect (magic numbers/strings):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func ValidateUser(password string) bool {
|
|
16
|
+
if len(password) < 8 {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func (s *Service) Retry() {
|
|
23
|
+
for i := 0; i < 3; i++ {
|
|
24
|
+
// retry logic
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Correct (centralized constants):**
|
|
30
|
+
|
|
31
|
+
```go
|
|
32
|
+
package constants
|
|
33
|
+
|
|
34
|
+
const (
|
|
35
|
+
MaxRetryAttempts = 3
|
|
36
|
+
MinPasswordLength = 8
|
|
37
|
+
DefaultTimeoutSecs = 30
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const (
|
|
41
|
+
StatusPending = iota
|
|
42
|
+
StatusApproved
|
|
43
|
+
StatusCancelled
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// Usage
|
|
47
|
+
if len(password) < constants.MinPasswordLength { ... }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Benefits:**
|
|
51
|
+
- Single source of truth
|
|
52
|
+
- Self-documenting
|
|
53
|
+
- Easy to update across the entire codebase
|
|
54
|
+
|
|
55
|
+
**Tools:** GolangCI-Lint (gocritic, gomnd), Code Review
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: All Error Handling Must Log Root Cause
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables debugging and incident response
|
|
5
|
+
tags: error-handling, logging, debugging, observability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## All Error Handling Must Log Root Cause
|
|
9
|
+
|
|
10
|
+
Silent failures make debugging impossible. Without proper logging, you cannot trace issues in production.
|
|
11
|
+
|
|
12
|
+
**Incorrect (silent or minimal logging):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func processPayment(order order.Order) {
|
|
16
|
+
err := paymentGateway.Charge(order)
|
|
17
|
+
if err != nil {
|
|
18
|
+
// Silent failure!
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func saveUser(user *User) error {
|
|
24
|
+
if err := db.Save(user); err != nil {
|
|
25
|
+
return nil // No logging, no context
|
|
26
|
+
}
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct (comprehensive error logging/wrapping):**
|
|
32
|
+
|
|
33
|
+
```go
|
|
34
|
+
func processPayment(ctx context.Context, order order.Order) error {
|
|
35
|
+
if err := paymentGateway.Charge(order); err != nil {
|
|
36
|
+
slog.Error("payment processing failed",
|
|
37
|
+
"order_id", order.ID,
|
|
38
|
+
"user_id", order.UserID,
|
|
39
|
+
"amount", order.Amount,
|
|
40
|
+
"error", err,
|
|
41
|
+
"request_id", ctx.Value("request_id"),
|
|
42
|
+
)
|
|
43
|
+
return fmt.Errorf("charging order %s: %w", order.ID, err)
|
|
44
|
+
}
|
|
45
|
+
return nil
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Log context should include:**
|
|
50
|
+
- Error message (and stack trace if available)
|
|
51
|
+
- Relevant entity IDs (order, user, etc.)
|
|
52
|
+
- Request/correlation ID
|
|
53
|
+
- Input that caused the error
|
|
54
|
+
- Timing information
|
|
55
|
+
|
|
56
|
+
**Tools:** GolangCI-Lint, PR review, `slog`
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Custom Error Types
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables proper error categorization and handling
|
|
5
|
+
tags: error-handling, custom-errors, patterns, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Custom Error Types
|
|
9
|
+
|
|
10
|
+
Custom error types enable proper error handling, categorization, and monitoring. They provide clear contracts for API consumers.
|
|
11
|
+
|
|
12
|
+
**Incorrect (generic errors):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
return errors.New("user not found")
|
|
16
|
+
return errors.New("invalid input")
|
|
17
|
+
return errors.New("database connection failed")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (custom error structs):**
|
|
21
|
+
|
|
22
|
+
```go
|
|
23
|
+
// Base application error type can be useful but Go often uses simple structs
|
|
24
|
+
type AppError struct {
|
|
25
|
+
Code string
|
|
26
|
+
Message string
|
|
27
|
+
StatusCode int
|
|
28
|
+
Context map[string]any
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func (e *AppError) Error() string {
|
|
32
|
+
return e.Message
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Specific errors
|
|
36
|
+
type UserNotFoundError struct {
|
|
37
|
+
UserID string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func (e UserNotFoundError) Error() string {
|
|
41
|
+
return fmt.Sprintf("user %s not found", e.UserID)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ValidationError struct {
|
|
45
|
+
Field string
|
|
46
|
+
Message string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func (e ValidationError) Error() string {
|
|
50
|
+
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Usage
|
|
54
|
+
return UserNotFoundError{UserID: "123"}
|
|
55
|
+
return ValidationError{Field: "email", Message: "invalid format"}
|
|
56
|
+
|
|
57
|
+
// Handling
|
|
58
|
+
if errors.As(err, &UserNotFoundError{}) {
|
|
59
|
+
// handle 404
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Benefits:**
|
|
64
|
+
- Type-safe error handling using `errors.As`
|
|
65
|
+
- Automatic HTTP status mapping
|
|
66
|
+
- Structured error context
|
|
67
|
+
- Consistent error format
|
|
68
|
+
|
|
69
|
+
**Tools:** GolangCI-Lint, Code Convention
|