@sun-asterisk/sunlint 1.3.40 → 1.3.42
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/dart/D001-recommended-lints.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D002-dispose-resources.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D003-prefer-widget-classes.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D004-avoid-shrinkwrap.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D005-widget-nesting.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D006-large-callbacks.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D007-lifecycle-order.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D008-long-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D009-function-parameters.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D010-cyclomatic-complexity.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D011-named-parameters.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D012-named-booleans.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D013-single-public-class.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D014-safe-collection-access.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D015-copywith-consistency.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D016-project-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D017-review-dependencies.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D018-no-commented-code.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D019-single-child-wrappers.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D020-if-else-limit.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D021-negated-booleans.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D022-setstate-usage.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D023-unnecessary-overrides.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D024-avoid-unnecessary-statefulwidget.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D025-nested-ternaries.md +35 -0
- 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
- package/skill-assets/sunlint-code-quality/rules/ruby/RB001-use-snake-case.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB002-use-camel-case.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB003-use-screaming-snake-case.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB004-predicate-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB005-dangerous-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB006-indentation.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB007-line-length.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB008-rescue-exception.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB009-save-bang.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB010-avoid-n-plus-one.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB011-use-find-each.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB012-sql-injection.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB013-prefer-has-many-through.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB014-dependent-associations.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB015-modern-validations.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB016-thin-controllers.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB017-avoid-fat-models.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB018-service-objects.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB019-avoid-metaprogramming.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB020-use-pluck.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB021-use-size.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB022-order-by-timestamps.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB023-where-missing.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB024-method-length.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB025-parameter-limits.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB026-avoid-deep-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB027-guard-clauses.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB028-class-length.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB029-meaningful-names.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB030-dry-principle.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB031-mvc-architecture.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB032-use-concerns.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB033-moderate-callbacks.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB034-use-decorators.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB035-comprehensive-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB036-frozen-string-literal.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB037-it-parameter.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB038-modern-enum-syntax.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB039-solid-adapters.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB040-rails-authentication.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB041-async-query-loading.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB042-hotwire-turbo.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB043-use-propshaft.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB044-structured-logging.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB045-prism-parser.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW001-block-based-kvo.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW002-class-delegate-protocol.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW003-compiler-protocol-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW004-contains-over-filter-count.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW005-convenience-type.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW006-discarded-notification-center-observer.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW007-discouraged-direct-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW008-discouraged-optional-boolean.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW009-empty-count.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW010-empty-string.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW011-explicit-init.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW012-fatal-error-message.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW013-for-where.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW014-force-cast.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW015-force-try.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW016-force-unwrapping.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW017-function-parameter-count.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW018-large-tuple.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW019-legacy-constructor.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW020-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW021-no-extension-access-modifier.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW022-overridden-super-call.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW023-override-in-extension.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW024-private-over-fileprivate.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW025-private-unit-test.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW026-prohibited-super-call.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW027-sorted-first-last.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW028-syntactic-sugar.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW029-unused-closure-parameter.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW030-unused-enumerated.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW031-unused-optional-binding.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW032-valid-ibinspectable.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW033-vertical-parameter-alignment.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW034-void-return.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW035-weak-delegate.md +28 -0
|
@@ -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
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Separate Processing And Data Access
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables testable business logic
|
|
5
|
+
tags: separation, repository, service, architecture, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Separate Processing And Data Access
|
|
9
|
+
|
|
10
|
+
Mixing business logic with database queries creates tight coupling and makes testing require real databases.
|
|
11
|
+
|
|
12
|
+
**Incorrect (mixed concerns):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
type OrderService struct {
|
|
16
|
+
db *sql.DB
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func (s *OrderService) CalculateDiscount(userId string) (int, error) {
|
|
20
|
+
// Business logic mixed with data access
|
|
21
|
+
var isPremium bool
|
|
22
|
+
db.QueryRow("SELECT is_premium FROM users WHERE id = ?", userId).Scan(&isPremium)
|
|
23
|
+
|
|
24
|
+
var orderCount int
|
|
25
|
+
db.QueryRow("SELECT count(*) FROM orders WHERE user_id = ?", userId).Scan(&orderCount)
|
|
26
|
+
|
|
27
|
+
discount := 0
|
|
28
|
+
if orderCount > 10 { discount += 5 }
|
|
29
|
+
if isPremium { discount += 10 }
|
|
30
|
+
|
|
31
|
+
return discount, nil
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Correct (separated layers):**
|
|
36
|
+
|
|
37
|
+
```go
|
|
38
|
+
// Repository - data access only
|
|
39
|
+
type UserRepository interface {
|
|
40
|
+
FindByID(id string) (*User, error)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type OrderRepository interface {
|
|
44
|
+
CountByUserID(id string) (int, error)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Service - business logic only
|
|
48
|
+
type DiscountService struct {
|
|
49
|
+
userRepo UserRepository
|
|
50
|
+
orderRepo OrderRepository
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func (s *DiscountService) CalculateDiscount(userId string) (int, error) {
|
|
54
|
+
user, _ := s.userRepo.FindByID(userId)
|
|
55
|
+
count, _ := s.orderRepo.CountByUserID(userId)
|
|
56
|
+
|
|
57
|
+
return s.computeDiscount(user, count), nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func (s *DiscountService) computeDiscount(user *User, orderCount int) int {
|
|
61
|
+
discount := 0
|
|
62
|
+
if orderCount > 10 { discount += 5 }
|
|
63
|
+
if user != nil && user.IsPremium { discount += 10 }
|
|
64
|
+
return discount
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Tools:** Architectural review, Code Review
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Log All Relevant Context On Errors
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables quick debugging and incident response
|
|
5
|
+
tags: error-handling, logging, context, debugging, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Log All Relevant Context On Errors
|
|
9
|
+
|
|
10
|
+
Context-rich logs enable quick debugging. Without proper context, finding root causes is difficult.
|
|
11
|
+
|
|
12
|
+
**Incorrect (minimal context):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
slog.Error("error occurred")
|
|
16
|
+
slog.Error(err.Error())
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (comprehensive context using slog):**
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
slog.Error("failed to process order",
|
|
23
|
+
// What happened
|
|
24
|
+
"error", err,
|
|
25
|
+
"stack", string(debug.Stack()), // Optional: but useful for critical errors
|
|
26
|
+
|
|
27
|
+
// Context
|
|
28
|
+
"order_id", order.ID,
|
|
29
|
+
"user_id", user.ID,
|
|
30
|
+
"request_id", ctx.Value("request_id"),
|
|
31
|
+
|
|
32
|
+
// Input that caused the issue
|
|
33
|
+
"item_count", len(order.Items),
|
|
34
|
+
"total_amount", order.Total,
|
|
35
|
+
|
|
36
|
+
// Timing
|
|
37
|
+
"processing_time_ms", time.Since(startTime).Milliseconds(),
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Essential context:**
|
|
42
|
+
- Error details
|
|
43
|
+
- Entity identifiers
|
|
44
|
+
- Request/correlation IDs
|
|
45
|
+
- Relevant input summary
|
|
46
|
+
- Timing information
|
|
47
|
+
|
|
48
|
+
**Tools:** `slog`, `zap`, `logback` equivalent
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Hardcoded Secrets In Repo
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents credential exposure
|
|
5
|
+
tags: secrets, credentials, security, git, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Hardcoded Secrets In Repo
|
|
9
|
+
|
|
10
|
+
Secrets in code are exposed to everyone with repo access and can be scraped by attackers.
|
|
11
|
+
|
|
12
|
+
**Incorrect (secrets in code):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
const APIKey = "sk-abc123xyz789"
|
|
16
|
+
const DBPassword = "admin123"
|
|
17
|
+
|
|
18
|
+
func init() {
|
|
19
|
+
client := stripe.NewClient("sk_live_xxx")
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (environment/secrets manager):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
// From environment
|
|
27
|
+
apiKey := os.Getenv("API_KEY")
|
|
28
|
+
|
|
29
|
+
// From secrets manager
|
|
30
|
+
apiKey, err := secretManager.GetSecret(ctx, "stripe-api-key")
|
|
31
|
+
|
|
32
|
+
// Validation at startup
|
|
33
|
+
if os.Getenv("API_KEY") == "" {
|
|
34
|
+
log.Fatal("API_KEY environment variable is required")
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```gitignore
|
|
39
|
+
# .gitignore
|
|
40
|
+
.env
|
|
41
|
+
*.pem
|
|
42
|
+
*.key
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Tools:** GitLeaks, TruffleHog, pre-commit hooks
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Boolean Names Is/Has/Should
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: makes conditions instantly readable
|
|
5
|
+
tags: naming, booleans, readability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Boolean Names Is/Has/Should
|
|
9
|
+
|
|
10
|
+
Boolean prefixes make conditions instantly readable.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unclear boolean names):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
active := user.Status == "active"
|
|
16
|
+
admin := checkAdminRole(user)
|
|
17
|
+
items := len(cart) > 0
|
|
18
|
+
update := needsRefresh()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (boolean prefixes):**
|
|
22
|
+
|
|
23
|
+
```go
|
|
24
|
+
isActive := user.Status == "active"
|
|
25
|
+
isAdmin := checkAdminRole(user)
|
|
26
|
+
hasItems := len(cart) > 0
|
|
27
|
+
shouldUpdate := needsRefresh()
|
|
28
|
+
canEdit := hasPermission(user, "edit")
|
|
29
|
+
willExpire := expirationDate.Before(time.Now())
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Boolean prefixes:**
|
|
33
|
+
|
|
34
|
+
| Prefix | Use For |
|
|
35
|
+
|--------|---------|
|
|
36
|
+
| `Is` | State (IsActive, IsEnabled) |
|
|
37
|
+
| `Has` | Ownership (HasPermission, HasError) |
|
|
38
|
+
| `Should` | Decision (ShouldUpdate, ShouldRetry) |
|
|
39
|
+
| `Can` | Capability (CanEdit, CanDelete) |
|
|
40
|
+
| `Will` | Future (WillExpire, WillRetry) |
|
|
41
|
+
|
|
42
|
+
**Tools:** Linter, Code Review
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Separate Parsing From Handlers
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: keeps handlers thin and focused
|
|
5
|
+
tags: handler, parsing, transformation, patterns, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Separate Parsing From Handlers
|
|
9
|
+
|
|
10
|
+
Handlers (controllers) should be thin - only handling HTTP concerns. Transformation logic should be extracted.
|
|
11
|
+
|
|
12
|
+
**Incorrect (transformation in handler):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
user, _ := userService.FindByID(r.URL.Query().Get("id"))
|
|
17
|
+
|
|
18
|
+
// Transformation logic in handler
|
|
19
|
+
response := map[string]any{
|
|
20
|
+
"id": user.ID,
|
|
21
|
+
"full_name": user.FirstName + " " + user.LastName,
|
|
22
|
+
"email": strings.ToLower(user.Email),
|
|
23
|
+
"created_at": user.CreatedAt.Format("2006-01-02"),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
json.NewEncoder(w).Encode(response)
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (separate mapper/DTO):**
|
|
31
|
+
|
|
32
|
+
```go
|
|
33
|
+
type UserResponse struct {
|
|
34
|
+
ID string `json:"id"`
|
|
35
|
+
FullName string `json:"full_name"`
|
|
36
|
+
Email string `json:"email"`
|
|
37
|
+
CreatedAt string `json:"created_at"`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func ToUserResponse(user *User) UserResponse {
|
|
41
|
+
return UserResponse{
|
|
42
|
+
ID: user.ID,
|
|
43
|
+
FullName: user.FirstName + " " + user.LastName,
|
|
44
|
+
Email: strings.ToLower(user.Email),
|
|
45
|
+
CreatedAt: user.CreatedAt.Format("2006-01-02"),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Clean handler
|
|
50
|
+
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
51
|
+
user, _ := userService.FindByID(r.URL.Query().Get("id"))
|
|
52
|
+
json.NewEncoder(w).Encode(ToUserResponse(user))
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Benefits:**
|
|
57
|
+
- Reusable transformation logic
|
|
58
|
+
- Testable mappers
|
|
59
|
+
- Clean handlers
|
|
60
|
+
- Consistent response format
|
|
61
|
+
|
|
62
|
+
**Tools:** Code review, Architecture rules
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Ignore Embedded Struct Logic
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: ensures proper composition behavior
|
|
5
|
+
tags: composition, embedding, override, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Ignore Embedded Struct Logic
|
|
9
|
+
|
|
10
|
+
When "overriding" methods of an embedded struct, ensure the embedded behavior is preserved unless explicitly intended otherwise.
|
|
11
|
+
|
|
12
|
+
**Incorrect (ignoring embedded logic):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
type BaseService struct{}
|
|
16
|
+
|
|
17
|
+
func (s *BaseService) Save(entity any) error {
|
|
18
|
+
s.Validate(entity)
|
|
19
|
+
s.BeforeSave(entity)
|
|
20
|
+
// ... actual save logic ...
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type UserService struct {
|
|
25
|
+
BaseService
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func (s *UserService) Save(user any) error {
|
|
29
|
+
// Completely ignores BaseService.Save logic (validation, hooks, etc.)
|
|
30
|
+
return s.repo.Save(user)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Correct (explicitly calling embedded method):**
|
|
35
|
+
|
|
36
|
+
```go
|
|
37
|
+
type UserService struct {
|
|
38
|
+
BaseService
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func (s *UserService) Save(user *User) error {
|
|
42
|
+
// Add user-specific preprocessing
|
|
43
|
+
user.UpdatedAt = time.Now()
|
|
44
|
+
|
|
45
|
+
// Call embedded struct implementation
|
|
46
|
+
if err := s.BaseService.Save(user); err != nil {
|
|
47
|
+
return err
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add user-specific postprocessing
|
|
51
|
+
return s.updateSearchIndex(user)
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**When to skip:**
|
|
56
|
+
- Complete replacement is intentional
|
|
57
|
+
- Base implementation doesn't apply
|
|
58
|
+
- Document the reason clearly
|
|
59
|
+
|
|
60
|
+
**Tools:** Static Analysis, Code Review
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Hardcode Configuration
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables environment-specific deployments
|
|
5
|
+
tags: configuration, environment, deployment, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Hardcode Configuration
|
|
9
|
+
|
|
10
|
+
Hardcoded configuration requires code changes to deploy to different environments.
|
|
11
|
+
|
|
12
|
+
**Incorrect (hardcoded config):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
const APIUrl = "https://api.production.example.com"
|
|
16
|
+
const Timeout = 5000
|
|
17
|
+
const MaxFileSize = 10485760
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (externalized config):**
|
|
21
|
+
|
|
22
|
+
```go
|
|
23
|
+
type Config struct {
|
|
24
|
+
API struct {
|
|
25
|
+
URL string
|
|
26
|
+
Timeout time.Duration
|
|
27
|
+
}
|
|
28
|
+
Upload struct {
|
|
29
|
+
MaxFileSize int64
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func LoadConfig() *Config {
|
|
34
|
+
return &Config{
|
|
35
|
+
API: struct {
|
|
36
|
+
URL string
|
|
37
|
+
Timeout time.Duration
|
|
38
|
+
}{
|
|
39
|
+
URL: getEnv("API_URL", "http://localhost:3000"),
|
|
40
|
+
Timeout: time.Duration(getEnvInt("API_TIMEOUT", 5000)) * time.Millisecond,
|
|
41
|
+
},
|
|
42
|
+
// ...
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Usage
|
|
47
|
+
cfg := LoadConfig()
|
|
48
|
+
client := &http.Client{Timeout: cfg.API.Timeout}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Tools:** `os.Getenv`, `viper`, `clever-env`, Manual review
|