@sun-asterisk/sunlint 1.3.47 → 1.3.49
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/config/rules/rules-registry-generated.json +1717 -282
- package/core/architecture-integration.js +57 -15
- package/core/cli-action-handler.js +51 -36
- package/core/config-manager.js +6 -0
- package/core/config-merger.js +33 -0
- package/core/config-validator.js +37 -2
- package/core/file-targeting-service.js +148 -15
- package/core/init-command.js +118 -70
- package/core/output-service.js +12 -3
- package/core/project-detector.js +517 -0
- package/core/scoring-service.js +12 -6
- package/core/summary-report-service.js +9 -4
- package/core/tui-select.js +245 -0
- package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
- package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
- package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
- package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
- package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
- package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
- package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
- package/engines/impact/cli.js +54 -39
- package/engines/impact/config/default-config.js +105 -5
- package/engines/impact/core/impact-analyzer.js +12 -15
- package/engines/impact/core/utils/gitignore-parser.js +123 -0
- package/engines/impact/core/utils/method-call-graph.js +272 -87
- package/origin-rules/dart-en.md +1 -1
- package/origin-rules/go-en.md +231 -0
- package/origin-rules/php-en.md +107 -0
- package/origin-rules/python-en.md +113 -0
- package/origin-rules/ruby-en.md +607 -0
- package/package.json +1 -1
- package/scripts/copy-arch-detect.js +5 -1
- package/scripts/copy-impact-analyzer.js +5 -1
- package/scripts/generate-rules-registry.js +30 -14
- package/skill-assets/sunlint-code-quality/SKILL.md +3 -2
- package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/go/G001-explicit-error-handling.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/G002-context-first-argument.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/G003-receiver-consistency.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/go/G004-avoid-panic.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/G005-goroutine-leak-prevention.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/G006-interface-consumer-definition.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN001-gin-binding-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN002-gin-error-response.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN003-graceful-shutdown.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN004-gin-route-logical-grouping.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
- package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
- package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
|
@@ -0,0 +1,108 @@
|
|
|
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 config requires code changes to deploy to different environments.
|
|
11
|
+
|
|
12
|
+
**Incorrect (hardcoded config):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
const apiUrl = 'https://api.production.example.com';
|
|
16
|
+
const timeout = 5000;
|
|
17
|
+
const maxFileSize = 10485760;
|
|
18
|
+
const enableAnalytics = true;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (externalized config via dart-define):**
|
|
22
|
+
|
|
23
|
+
```dart
|
|
24
|
+
// lib/config/app_config.dart
|
|
25
|
+
class AppConfig {
|
|
26
|
+
AppConfig._();
|
|
27
|
+
|
|
28
|
+
static const String apiUrl = String.fromEnvironment(
|
|
29
|
+
'API_URL',
|
|
30
|
+
defaultValue: 'https://api.dev.example.com',
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
static const int timeoutMs = int.fromEnvironment(
|
|
34
|
+
'TIMEOUT_MS',
|
|
35
|
+
defaultValue: 5000,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
static const int maxFileSizeBytes = int.fromEnvironment(
|
|
39
|
+
'MAX_FILE_SIZE',
|
|
40
|
+
defaultValue: 10 * 1024 * 1024, // 10MB
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
static const bool enableAnalytics = bool.fromEnvironment(
|
|
44
|
+
'ENABLE_ANALYTICS',
|
|
45
|
+
defaultValue: false,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Usage
|
|
50
|
+
final client = HttpClient(baseUrl: AppConfig.apiUrl, timeout: Duration(milliseconds: AppConfig.timeoutMs));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Build with environment config:**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Development
|
|
57
|
+
flutter run --dart-define=API_URL=https://api.dev.example.com --dart-define=ENABLE_ANALYTICS=false
|
|
58
|
+
|
|
59
|
+
# Production
|
|
60
|
+
flutter build apk --dart-define=API_URL=https://api.production.example.com --dart-define=ENABLE_ANALYTICS=true
|
|
61
|
+
|
|
62
|
+
# Using a JSON config file (flutter_launcher_icons approach)
|
|
63
|
+
flutter run --dart-define-from-file=config/dev.json
|
|
64
|
+
flutter build apk --dart-define-from-file=config/prod.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Example config files:**
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
// config/dev.json
|
|
71
|
+
{
|
|
72
|
+
"API_URL": "https://api.dev.example.com",
|
|
73
|
+
"ENABLE_ANALYTICS": "false",
|
|
74
|
+
"TIMEOUT_MS": "10000"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
// config/prod.json
|
|
80
|
+
{
|
|
81
|
+
"API_URL": "https://api.production.example.com",
|
|
82
|
+
"ENABLE_ANALYTICS": "true",
|
|
83
|
+
"TIMEOUT_MS": "5000"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**For flavor-based config:**
|
|
88
|
+
|
|
89
|
+
```dart
|
|
90
|
+
// lib/config/flavors.dart
|
|
91
|
+
enum Flavor { dev, staging, production }
|
|
92
|
+
|
|
93
|
+
class FlavorConfig {
|
|
94
|
+
static Flavor? _flavor;
|
|
95
|
+
|
|
96
|
+
static void initialize(Flavor flavor) {
|
|
97
|
+
_flavor = flavor;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static String get apiUrl => switch (_flavor!) {
|
|
101
|
+
Flavor.dev => 'https://api.dev.example.com',
|
|
102
|
+
Flavor.staging => 'https://api.staging.example.com',
|
|
103
|
+
Flavor.production => 'https://api.example.com',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Tools:** dart-define, envied, flutter_flavor, Code Review
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Explicit Error Handling
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: prevents silent failures and ensures robust error recovery
|
|
5
|
+
tags: go, error-handling, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Explicit Error Handling
|
|
9
|
+
|
|
10
|
+
In Go, errors are values and must be handled explicitly. Ignoring errors using `_` or failing to check them leads to unpredictable state and silent failures.
|
|
11
|
+
|
|
12
|
+
**Incorrect (ignoring errors):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func processData(data string) {
|
|
16
|
+
file, _ := os.Open("config.json") // Error ignored
|
|
17
|
+
defer file.Close()
|
|
18
|
+
|
|
19
|
+
bytes, _ := io.ReadAll(file) // Error ignored
|
|
20
|
+
json.Unmarshal(bytes, &config) // Error ignored
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (explicit checking):**
|
|
25
|
+
|
|
26
|
+
```go
|
|
27
|
+
func processData(data string) error {
|
|
28
|
+
file, err := os.Open("config.json")
|
|
29
|
+
if err != nil {
|
|
30
|
+
return fmt.Errorf("failed to open config: %w", err)
|
|
31
|
+
}
|
|
32
|
+
defer file.Close()
|
|
33
|
+
|
|
34
|
+
bytes, err := io.ReadAll(file)
|
|
35
|
+
if err != nil {
|
|
36
|
+
return fmt.Errorf("failed to read config: %w", err)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if err := json.Unmarshal(bytes, &config); err != nil {
|
|
40
|
+
return fmt.Errorf("failed to parse config: %w", err)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Benefits:**
|
|
48
|
+
- Prevents silent crashes and data corruption
|
|
49
|
+
- Provides clear trace of what went wrong
|
|
50
|
+
- Forces developers to think about failure paths
|
|
51
|
+
- Makes code easier to debug
|
|
52
|
+
|
|
53
|
+
**Tools:** errcheck, golangci-lint, staticcheck
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Context as First Argument
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: follows Go idiomatic patterns for cancellation and timeouts
|
|
5
|
+
tags: go, context, patterns, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Context as First Argument
|
|
9
|
+
|
|
10
|
+
`context.Context` should always be the first parameter of a function, typically named `ctx`. This consistency makes it easy to propagate cancellation, deadlines, and markers across call stacks.
|
|
11
|
+
|
|
12
|
+
**Incorrect (wrong order or missing):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func (s *Service) FetchUser(userID string, ctx context.Context) (*User, error) {
|
|
16
|
+
// ctx is second argument
|
|
17
|
+
return s.repo.Get(ctx, userID)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func ProcessTask(taskID string) {
|
|
21
|
+
// Missing context for potentially long-running operation
|
|
22
|
+
db.Exec("UPDATE tasks SET status = 'done' WHERE id = ?", taskID)
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct (context first):**
|
|
27
|
+
|
|
28
|
+
```go
|
|
29
|
+
func (s *Service) FetchUser(ctx context.Context, userID string) (*User, error) {
|
|
30
|
+
return s.repo.Get(ctx, userID)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func ProcessTask(ctx context.Context, taskID string) error {
|
|
34
|
+
return db.ExecContext(ctx, "UPDATE tasks SET status = 'done' WHERE id = ?", taskID)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Benefits:**
|
|
39
|
+
- Standardizes API signatures across the codebase
|
|
40
|
+
- Simplifies cancellation propagation
|
|
41
|
+
- Enables proper timeout handling for I/O and external calls
|
|
42
|
+
- Improves observability with trace IDs in context
|
|
43
|
+
|
|
44
|
+
**Tools:** golangci-lint, contextcheck
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Consistent Receiver Naming
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: improves readability and consistency
|
|
5
|
+
tags: go, style, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Consistent Receiver Naming
|
|
9
|
+
|
|
10
|
+
Receiver names should be short (1-2 letters) and consistent across all methods of a type. Do not use generic names like `this`, `self`, or `me`.
|
|
11
|
+
|
|
12
|
+
**Incorrect (inconsistent or verbose):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func (this *UserRepository) Get(id string) (*User, error) { ... }
|
|
16
|
+
|
|
17
|
+
func (repo *UserRepository) List() ([]*User, error) { ... }
|
|
18
|
+
|
|
19
|
+
func (self *UserRepository) Update(u *User) error { ... }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (short and consistent):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// Use 'r' consistently for UserRepository
|
|
26
|
+
func (r *UserRepository) Get(id string) (*User, error) { ... }
|
|
27
|
+
|
|
28
|
+
func (r *UserRepository) List() ([]*User, error) { ... }
|
|
29
|
+
|
|
30
|
+
func (r *UserRepository) Update(u *User) error { ... }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Benefits:**
|
|
34
|
+
- Reduces visual noise in method definitions
|
|
35
|
+
- Follows Go community standards
|
|
36
|
+
- Makes it easier to search and scan methods of the same type
|
|
37
|
+
|
|
38
|
+
**Tools:** golangci-lint, stylecheck
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Panic in Production
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents application crashes and enables graceful degradation
|
|
5
|
+
tags: go, error-handling, stability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Panic in Production
|
|
9
|
+
|
|
10
|
+
Panic should be reserved for truly unrecoverable programmer errors (e.g., initialization failure where the app cannot start). Business logic and normal I/O should always use `error` returns.
|
|
11
|
+
|
|
12
|
+
**Incorrect (panic for normal errors):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func GetUser(id string) *User {
|
|
16
|
+
user, err := db.Find(id)
|
|
17
|
+
if err != nil {
|
|
18
|
+
panic(err) // Crash!
|
|
19
|
+
}
|
|
20
|
+
return user
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (return error):**
|
|
25
|
+
|
|
26
|
+
```go
|
|
27
|
+
func GetUser(id string) (*User, error) {
|
|
28
|
+
user, err := db.Find(id)
|
|
29
|
+
if err != nil {
|
|
30
|
+
return nil, fmt.Errorf("failed to find user: %w", err)
|
|
31
|
+
}
|
|
32
|
+
return user, nil
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// In main or init, panic is acceptable if the app MUST die
|
|
36
|
+
func main() {
|
|
37
|
+
config, err := LoadConfig()
|
|
38
|
+
if err != nil {
|
|
39
|
+
panic("critical config missing: " + err.Error())
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Benefits:**
|
|
45
|
+
- Improves application uptime and reliability
|
|
46
|
+
- Allows handlers to recover and return HTTP 500 instead of crashing the process
|
|
47
|
+
- Makes the code more predictable and testable
|
|
48
|
+
|
|
49
|
+
**Tools:** golangci-lint, staticcheck
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Goroutine Leak Prevention
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents memory exhaustion and zombie processes
|
|
5
|
+
tags: go, concurrency, stability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Goroutine Leak Prevention
|
|
9
|
+
|
|
10
|
+
Always ensure that a goroutine has a clear termination path, typically via a `context.Context` or a close channel. Goroutines that block indefinitely on channel operations or I/O without a timeout cause memory leaks.
|
|
11
|
+
|
|
12
|
+
**Incorrect (leaky goroutine):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func StartWatcher(ch chan string) {
|
|
16
|
+
go func() {
|
|
17
|
+
for msg := range ch { // Blocks forever if ch is never closed
|
|
18
|
+
fmt.Println(msg)
|
|
19
|
+
}
|
|
20
|
+
}()
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (context-aware goroutine):**
|
|
25
|
+
|
|
26
|
+
```go
|
|
27
|
+
func StartWatcher(ctx context.Context, ch chan string) {
|
|
28
|
+
go func() {
|
|
29
|
+
for {
|
|
30
|
+
select {
|
|
31
|
+
case <-ctx.Done():
|
|
32
|
+
return // Exit cleanly when context is cancelled
|
|
33
|
+
case msg, ok := <-ch:
|
|
34
|
+
if !ok {
|
|
35
|
+
return // Exit when channel is closed
|
|
36
|
+
}
|
|
37
|
+
fmt.Println(msg)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}()
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Benefits:**
|
|
45
|
+
- Prevents memory leaks and resource exhaustion
|
|
46
|
+
- Ensures clean application shutdown
|
|
47
|
+
- Makes concurrent code more predictable and testable
|
|
48
|
+
|
|
49
|
+
**Tools:** goleak, golangci-lint
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Define Interfaces at Consumer Side
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: promotes decoupling and simplifies testing
|
|
5
|
+
tags: go, architecture, interfaces, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Define Interfaces at Consumer Side
|
|
9
|
+
|
|
10
|
+
In Go, interfaces are satisfied implicitly. Do not define interfaces in the same package where the implementation is defined (producer side). Instead, define the interface in the package that requires the dependency (consumer side).
|
|
11
|
+
|
|
12
|
+
**Incorrect (interface in producer package):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// package auth
|
|
16
|
+
type Service interface {
|
|
17
|
+
Login(user, pass string) (string, error)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type serviceImpl struct { ... }
|
|
21
|
+
func (s *serviceImpl) Login(u, p string) (string, error) { ... }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (interface in consumer package):**
|
|
25
|
+
|
|
26
|
+
```go
|
|
27
|
+
// package auth (implementation only)
|
|
28
|
+
type Service struct { ... }
|
|
29
|
+
func (s *Service) Login(u, p string) (string, error) { ... }
|
|
30
|
+
|
|
31
|
+
// package handler (consumer)
|
|
32
|
+
type AuthProvider interface {
|
|
33
|
+
Login(user, pass string) (string, error)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func LoginHandler(auth AuthProvider) { ... }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Benefits:**
|
|
40
|
+
- Prevents package circular dependencies
|
|
41
|
+
- Allows packages to define exactly what they need from a dependency
|
|
42
|
+
- Makes it easier to mock dependencies for unit testing
|
|
43
|
+
- Keeps the system more loosely coupled
|
|
44
|
+
|
|
45
|
+
**Tools:** Code Review, Architecture rules
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Gin Binding for Validation
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: simplifies input handling and ensures consistent validation
|
|
5
|
+
tags: gin, go, validation, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Gin Binding for Validation
|
|
9
|
+
|
|
10
|
+
Leverage Gin's built-in binding mechanism (`ShouldBindJSON`, `ShouldBindQuery`, etc.) and the `validator` library tags. This centralizes validation logic and keeps handlers clean.
|
|
11
|
+
|
|
12
|
+
**Incorrect (manual parsing and validation):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func CreateUser(c *gin.Context) {
|
|
16
|
+
var raw map[string]interface{}
|
|
17
|
+
if err := c.BindJSON(&raw); err != nil {
|
|
18
|
+
c.JSON(400, gin.H{"error": "invalid json"})
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
email, ok := raw["email"].(string)
|
|
23
|
+
if !ok || email == "" {
|
|
24
|
+
c.JSON(400, gin.H{"error": "email is required"})
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
// ... more manual checks
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct (struct binding and tags):**
|
|
32
|
+
|
|
33
|
+
```go
|
|
34
|
+
type CreateUserRequest struct {
|
|
35
|
+
Email string `json:"email" binding:"required,email"`
|
|
36
|
+
Password string `json:"password" binding:"required,min=8"`
|
|
37
|
+
Age int `json:"age" binding:"gte=18"`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func CreateUser(c *gin.Context) {
|
|
41
|
+
var req CreateUserRequest
|
|
42
|
+
if err := c.ShouldBindJSON(&req); err != nil {
|
|
43
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Process validated request
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Benefits:**
|
|
52
|
+
- Reduces boilerplate code in handlers
|
|
53
|
+
- Declarative validation is easier to read and maintain
|
|
54
|
+
- Automatically handles type conversion (e.g., string to int)
|
|
55
|
+
- Provides standard error messages
|
|
56
|
+
|
|
57
|
+
**Tools:** Gin, go-playground/validator
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Abort with Status for Errors
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: ensures middleware chain is interrupted and consistent response is sent
|
|
5
|
+
tags: gin, go, error-handling, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Abort with Status for Errors
|
|
9
|
+
|
|
10
|
+
When a fatal error occurs in a Gin handler or middleware (e.g., auth failure, validation error), use `c.AbortWithStatusJSON` or `c.Abort()` to stop the execution of subsequent handlers in the chain.
|
|
11
|
+
|
|
12
|
+
**Incorrect (not aborting or only partial return):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func AuthMiddleware(c *gin.Context) {
|
|
16
|
+
token := c.GetHeader("Authorization")
|
|
17
|
+
if token == "" {
|
|
18
|
+
c.JSON(401, gin.H{"error": "unauthorized"})
|
|
19
|
+
// MISSING c.Abort()! Subsequent handlers WILL execute.
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (using AbortWithStatusJSON):**
|
|
26
|
+
|
|
27
|
+
```go
|
|
28
|
+
func AuthMiddleware(c *gin.Context) {
|
|
29
|
+
if c.GetHeader("Authorization") == "" {
|
|
30
|
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func Handler(c *gin.Context) {
|
|
36
|
+
if err := someFunc(); err != nil {
|
|
37
|
+
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "internal error"})
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Benefits:**
|
|
44
|
+
- Prevents security bypasses in middleware
|
|
45
|
+
- Consistent error response pattern
|
|
46
|
+
- Guarantees that only one response is sent to the client
|
|
47
|
+
|
|
48
|
+
**Tools:** Gin
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Implement Graceful Shutdown
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents data loss and ensures clean connection handling
|
|
5
|
+
tags: gin, go, stability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Implement Graceful Shutdown
|
|
9
|
+
|
|
10
|
+
Web servers should handle termination signals (`SIGINT`, `SIGTERM`) to allow inflight requests to finish and to close database connections cleanly. Gin's `Run()` method blocks and doesn't support this out of the box; use `http.Server` instead.
|
|
11
|
+
|
|
12
|
+
**Incorrect (standard Run):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func main() {
|
|
16
|
+
r := gin.Default()
|
|
17
|
+
r.GET("/", handler)
|
|
18
|
+
r.Run(":8080") // Blocks and dies immediately on CTRL+C
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (graceful shutdown):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
func main() {
|
|
26
|
+
router := gin.Default()
|
|
27
|
+
srv := &http.Server{
|
|
28
|
+
Addr: ":8080",
|
|
29
|
+
Handler: router,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
go func() {
|
|
33
|
+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
34
|
+
log.Fatalf("listen: %s\n", err)
|
|
35
|
+
}
|
|
36
|
+
}()
|
|
37
|
+
|
|
38
|
+
// Wait for interrupt signal
|
|
39
|
+
quit := make(chan os.Signal, 1)
|
|
40
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
41
|
+
<-quit
|
|
42
|
+
|
|
43
|
+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
44
|
+
defer cancel()
|
|
45
|
+
|
|
46
|
+
if err := srv.Shutdown(ctx); err != nil {
|
|
47
|
+
log.Fatal("Server Shutdown:", err)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Benefits:**
|
|
53
|
+
- Prevents dropping active user connections
|
|
54
|
+
- Allows finishing long-running database transactions
|
|
55
|
+
- Ensures monitoring tools report clean shutdown instead of "crash"
|
|
56
|
+
|
|
57
|
+
**Tools:** Go Standard Library, Gin
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Logical Route Grouping
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: improves code organization and shared middleware management
|
|
5
|
+
tags: gin, go, routing, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Logical Route Grouping
|
|
9
|
+
|
|
10
|
+
Use `RouterGroup` to organize routes logically (e.g., by version, resource, or access level). This allows applying middleware (like auth or logging) to a specific group of routes without repetition.
|
|
11
|
+
|
|
12
|
+
**Incorrect (flat and repetitive):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func RegisterRoutes(r *gin.Engine) {
|
|
16
|
+
r.GET("/api/v1/users", AuthMiddleware(), GetUsers)
|
|
17
|
+
r.POST("/api/v1/users", AuthMiddleware(), CreateUser)
|
|
18
|
+
r.GET("/api/v1/users/:id", AuthMiddleware(), GetUser)
|
|
19
|
+
r.GET("/health", HealthCheck)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (logical grouping):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
func RegisterRoutes(r *gin.Engine) {
|
|
27
|
+
// Public routes
|
|
28
|
+
r.GET("/health", HealthCheck)
|
|
29
|
+
|
|
30
|
+
// API v1 group
|
|
31
|
+
v1 := r.Group("/api/v1")
|
|
32
|
+
{
|
|
33
|
+
// Auth required group
|
|
34
|
+
auth := v1.Group("/")
|
|
35
|
+
auth.Use(AuthMiddleware())
|
|
36
|
+
{
|
|
37
|
+
users := auth.Group("/users")
|
|
38
|
+
{
|
|
39
|
+
users.GET("/", GetUsers)
|
|
40
|
+
users.POST("/", CreateUser)
|
|
41
|
+
users.GET("/:id", GetUser)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Benefits:**
|
|
49
|
+
- Reduces code duplication for middleware application
|
|
50
|
+
- Provides clear structure of the API URL space
|
|
51
|
+
- Simplifies refactoring and versioning (e.g., adding `/v2` group)
|
|
52
|
+
- Improves readability of the main routing configuration
|
|
53
|
+
|
|
54
|
+
**Tools:** Gin
|