@sun-asterisk/sunlint 1.3.48 → 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/core/file-targeting-service.js +148 -15
- package/core/init-command.js +118 -70
- package/core/project-detector.js +517 -0
- 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/package.json +1 -1
- 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-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
|
@@ -50,6 +50,7 @@ const rule = (0, base_rule_1.createRule)('PR006', 'Router/Wireframe Layer', 'Xá
|
|
|
50
50
|
folderPatterns: ['routers', 'wireframes', 'navigation', 'routing', 'Routers', 'coordinators'],
|
|
51
51
|
filePatterns: ['*Router.*', '*Wireframe.*', '*Coordinator.*', '*Navigator.*'],
|
|
52
52
|
codePatterns: [
|
|
53
|
+
// Swift
|
|
53
54
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /protocol\s+\w+Router/ },
|
|
54
55
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
|
|
55
56
|
{
|
|
@@ -59,12 +60,31 @@ const rule = (0, base_rule_1.createRule)('PR006', 'Router/Wireframe Layer', 'Xá
|
|
|
59
60
|
},
|
|
60
61
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.METHOD, pattern: /func\s+navigate/ },
|
|
61
62
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.METHOD, pattern: /func\s+present/ },
|
|
63
|
+
// Kotlin
|
|
62
64
|
{
|
|
63
65
|
language: enums_1.Language.KOTLIN,
|
|
64
66
|
type: enums_1.CodePatternType.PATTERN,
|
|
65
67
|
pattern: /interface\s+\w+Router/,
|
|
66
68
|
},
|
|
67
69
|
{ language: enums_1.Language.KOTLIN, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
|
|
70
|
+
// Java
|
|
71
|
+
{ language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
|
|
72
|
+
// PHP
|
|
73
|
+
{
|
|
74
|
+
language: enums_1.Language.PHP,
|
|
75
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
76
|
+
pattern: /Route::(get|post|put|delete|group)\s*\(/,
|
|
77
|
+
},
|
|
78
|
+
// Python
|
|
79
|
+
{ language: enums_1.Language.PYTHON, type: enums_1.CodePatternType.PATTERN, pattern: /urlpatterns\s*=/ },
|
|
80
|
+
{
|
|
81
|
+
language: enums_1.Language.PYTHON,
|
|
82
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
83
|
+
pattern: /router\s*=\s*APIRouter\s*\(/,
|
|
84
|
+
},
|
|
85
|
+
// Go
|
|
86
|
+
{ language: enums_1.Language.GO, type: enums_1.CodePatternType.PATTERN, pattern: /func\s+\w+Router\b/ },
|
|
87
|
+
{ language: enums_1.Language.GO, type: enums_1.CodePatternType.PATTERN, pattern: /r\.Group\s*\(/ },
|
|
68
88
|
],
|
|
69
89
|
}, {
|
|
70
90
|
isKeyIndicator: true,
|
|
@@ -110,10 +130,10 @@ let PR006RouterLayer = (() => {
|
|
|
110
130
|
}
|
|
111
131
|
}
|
|
112
132
|
// Check for router files - match any file containing Router/Wireframe/WireFrame/Coordinator
|
|
113
|
-
const routerFiles = context.files.filter((f) => /Router\.(swift|kt|java|ts|js)$/i.test(f.fileName) ||
|
|
133
|
+
const routerFiles = context.files.filter((f) => /Router\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName) ||
|
|
114
134
|
/WireFrame\.(swift|kt|java)$/i.test(f.fileName) ||
|
|
115
135
|
/Wireframe\.(swift|kt|java)$/i.test(f.fileName) ||
|
|
116
|
-
/Coordinator\.(swift|kt|java|ts)$/i.test(f.fileName));
|
|
136
|
+
/Coordinator\.(swift|kt|java|ts|php|py|go)$/i.test(f.fileName));
|
|
117
137
|
if (routerFiles.length > 0) {
|
|
118
138
|
for (const file of routerFiles.slice(0, 5)) {
|
|
119
139
|
matches.push({
|
|
@@ -129,12 +149,17 @@ let PR006RouterLayer = (() => {
|
|
|
129
149
|
.filter((f) => !f.isTest && !f.isConfig && f.language)
|
|
130
150
|
.slice(0, 50);
|
|
131
151
|
const codePatterns = [
|
|
132
|
-
/protocol\s+\w+Router/,
|
|
133
|
-
/class\s+\w+Router/,
|
|
134
|
-
/class\s+\w+Coordinator/,
|
|
135
|
-
/func\s+navigateTo/,
|
|
136
|
-
/func\s+presentModule/,
|
|
137
|
-
/interface\s+\w+Router/,
|
|
152
|
+
/protocol\s+\w+Router/, // Swift
|
|
153
|
+
/class\s+\w+Router/, // Swift/Kotlin/Java
|
|
154
|
+
/class\s+\w+Coordinator/, // Swift
|
|
155
|
+
/func\s+navigateTo/, // Swift/Kotlin
|
|
156
|
+
/func\s+presentModule/, // Swift
|
|
157
|
+
/interface\s+\w+Router/, // Kotlin/Java
|
|
158
|
+
/Route::(get|post|put|delete|group)\s*\(/, // PHP Laravel
|
|
159
|
+
/urlpatterns\s*=/, // Python Django
|
|
160
|
+
/router\s*=\s*APIRouter\s*\(/, // Python FastAPI
|
|
161
|
+
/func\s+\w+Router\b/, // Go
|
|
162
|
+
/r\.Group\s*\(/, // Go Gin
|
|
138
163
|
];
|
|
139
164
|
for (const file of sourceFiles) {
|
|
140
165
|
try {
|
|
@@ -50,12 +50,14 @@ const rule = (0, base_rule_1.createRule)('PR007', 'Interactor Layer', 'Xác nh
|
|
|
50
50
|
folderPatterns: ['interactors', 'usecases', 'Interactors', 'UseCases'],
|
|
51
51
|
filePatterns: ['*Interactor.*', '*UseCase.*', '*BusinessLogic.*'],
|
|
52
52
|
codePatterns: [
|
|
53
|
+
// Swift
|
|
53
54
|
{
|
|
54
55
|
language: enums_1.Language.SWIFT,
|
|
55
56
|
type: enums_1.CodePatternType.PATTERN,
|
|
56
57
|
pattern: /protocol\s+\w+Interactor/,
|
|
57
58
|
},
|
|
58
59
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
|
|
60
|
+
// Kotlin
|
|
59
61
|
{
|
|
60
62
|
language: enums_1.Language.KOTLIN,
|
|
61
63
|
type: enums_1.CodePatternType.PATTERN,
|
|
@@ -67,6 +69,31 @@ const rule = (0, base_rule_1.createRule)('PR007', 'Interactor Layer', 'Xác nh
|
|
|
67
69
|
pattern: /class\s+\w+Interactor/,
|
|
68
70
|
},
|
|
69
71
|
{ language: enums_1.Language.KOTLIN, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
72
|
+
// Java
|
|
73
|
+
{ language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
|
|
74
|
+
{ language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
75
|
+
// TypeScript
|
|
76
|
+
{
|
|
77
|
+
language: enums_1.Language.TYPESCRIPT,
|
|
78
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
79
|
+
pattern: /class\s+\w+UseCase/,
|
|
80
|
+
},
|
|
81
|
+
// PHP
|
|
82
|
+
{ language: enums_1.Language.PHP, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
83
|
+
{ language: enums_1.Language.PHP, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
|
|
84
|
+
// Python
|
|
85
|
+
{ language: enums_1.Language.PYTHON, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
86
|
+
// Go
|
|
87
|
+
{
|
|
88
|
+
language: enums_1.Language.GO,
|
|
89
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
90
|
+
pattern: /type\s+\w+UseCase\s+struct/,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
language: enums_1.Language.GO,
|
|
94
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
95
|
+
pattern: /type\s+\w+Interactor\s+struct/,
|
|
96
|
+
},
|
|
70
97
|
],
|
|
71
98
|
}, {
|
|
72
99
|
isKeyIndicator: true,
|
|
@@ -110,8 +137,8 @@ let PR007InteractorLayer = (() => {
|
|
|
110
137
|
}
|
|
111
138
|
}
|
|
112
139
|
// Check for interactor files
|
|
113
|
-
const interactorFiles = context.files.filter((f) => /Interactor\.(swift|kt|java|ts|js)$/i.test(f.fileName) ||
|
|
114
|
-
/UseCase\.(swift|kt|java|ts|js)$/i.test(f.fileName));
|
|
140
|
+
const interactorFiles = context.files.filter((f) => /Interactor\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName) ||
|
|
141
|
+
/UseCase\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName));
|
|
115
142
|
if (interactorFiles.length > 0) {
|
|
116
143
|
for (const file of interactorFiles.slice(0, 5)) {
|
|
117
144
|
matches.push({
|
|
@@ -127,10 +154,12 @@ let PR007InteractorLayer = (() => {
|
|
|
127
154
|
.filter((f) => !f.isTest && !f.isConfig && f.language)
|
|
128
155
|
.slice(0, 50);
|
|
129
156
|
const codePatterns = [
|
|
130
|
-
/protocol\s+\w+Interactor/,
|
|
131
|
-
/class\s+\w+Interactor/,
|
|
132
|
-
/interface\s+\w+Interactor/,
|
|
133
|
-
/class\s+\w+UseCase/,
|
|
157
|
+
/protocol\s+\w+Interactor/, // Swift
|
|
158
|
+
/class\s+\w+Interactor/, // Swift/Kotlin/Java/PHP
|
|
159
|
+
/interface\s+\w+Interactor/, // Kotlin/Java
|
|
160
|
+
/class\s+\w+UseCase/, // Kotlin/Java/TS/PHP/Python
|
|
161
|
+
/type\s+\w+UseCase\s+struct/, // Go
|
|
162
|
+
/type\s+\w+Interactor\s+struct/, // Go
|
|
134
163
|
];
|
|
135
164
|
for (const file of sourceFiles) {
|
|
136
165
|
try {
|
|
@@ -86,9 +86,20 @@ const FRAMEWORK_DETECTORS = [
|
|
|
86
86
|
dependencies: [
|
|
87
87
|
{ file: 'pom.xml', packages: ['spring-boot-starter'] },
|
|
88
88
|
{ file: 'build.gradle', packages: ['spring-boot'] },
|
|
89
|
+
{ file: 'build.gradle.kts', packages: ['spring-boot'] },
|
|
89
90
|
],
|
|
90
91
|
codePatterns: [/@SpringBootApplication/],
|
|
91
92
|
},
|
|
93
|
+
// Ktor
|
|
94
|
+
{
|
|
95
|
+
framework: enums_1.Framework.KTOR,
|
|
96
|
+
configFiles: [],
|
|
97
|
+
dependencies: [
|
|
98
|
+
{ file: 'build.gradle.kts', packages: ['io.ktor'] },
|
|
99
|
+
{ file: 'build.gradle', packages: ['io.ktor'] },
|
|
100
|
+
],
|
|
101
|
+
codePatterns: [/routing\s*\{/, /embeddedServer\s*\(/],
|
|
102
|
+
},
|
|
92
103
|
// Angular
|
|
93
104
|
{
|
|
94
105
|
framework: enums_1.Framework.ANGULAR,
|
|
@@ -133,15 +144,26 @@ const FRAMEWORK_DETECTORS = [
|
|
|
133
144
|
// Laravel
|
|
134
145
|
{
|
|
135
146
|
framework: enums_1.Framework.LARAVEL,
|
|
136
|
-
configFiles: ['artisan'],
|
|
147
|
+
configFiles: ['artisan', 'config/app.php'],
|
|
137
148
|
dependencies: [{ file: 'composer.json', packages: ['laravel/framework'] }],
|
|
138
|
-
codePatterns: [/extends\s+Controller/],
|
|
149
|
+
codePatterns: [/extends\s+Controller/, /use\s+Illuminate\\/],
|
|
150
|
+
},
|
|
151
|
+
// Symfony
|
|
152
|
+
{
|
|
153
|
+
framework: enums_1.Framework.SYMFONY,
|
|
154
|
+
configFiles: ['config/bundles.php', 'symfony.lock'],
|
|
155
|
+
dependencies: [{ file: 'composer.json', packages: ['symfony/framework-bundle'] }],
|
|
156
|
+
codePatterns: [/#\[Route\s*\(/, /extends\s+AbstractController/],
|
|
139
157
|
},
|
|
140
158
|
// Django
|
|
141
159
|
{
|
|
142
160
|
framework: enums_1.Framework.DJANGO,
|
|
143
161
|
configFiles: ['manage.py'],
|
|
144
|
-
dependencies: [
|
|
162
|
+
dependencies: [
|
|
163
|
+
{ file: 'requirements.txt', packages: ['django', 'Django'] },
|
|
164
|
+
{ file: 'pyproject.toml', packages: ['django', 'Django'] },
|
|
165
|
+
{ file: 'Pipfile', packages: ['django', 'Django'] },
|
|
166
|
+
],
|
|
145
167
|
codePatterns: [/from\s+django/, /INSTALLED_APPS/],
|
|
146
168
|
},
|
|
147
169
|
// Rails
|
|
@@ -186,15 +208,23 @@ const FRAMEWORK_DETECTORS = [
|
|
|
186
208
|
{
|
|
187
209
|
framework: enums_1.Framework.FASTAPI,
|
|
188
210
|
configFiles: [],
|
|
189
|
-
dependencies: [
|
|
211
|
+
dependencies: [
|
|
212
|
+
{ file: 'requirements.txt', packages: ['fastapi'] },
|
|
213
|
+
{ file: 'pyproject.toml', packages: ['fastapi'] },
|
|
214
|
+
{ file: 'Pipfile', packages: ['fastapi'] },
|
|
215
|
+
],
|
|
190
216
|
codePatterns: [/from\s+fastapi/, /@app\.(get|post|put|delete)\s*\(/],
|
|
191
217
|
},
|
|
192
218
|
// Flask
|
|
193
219
|
{
|
|
194
220
|
framework: enums_1.Framework.FLASK,
|
|
195
221
|
configFiles: [],
|
|
196
|
-
dependencies: [
|
|
197
|
-
|
|
222
|
+
dependencies: [
|
|
223
|
+
{ file: 'requirements.txt', packages: ['flask', 'Flask'] },
|
|
224
|
+
{ file: 'pyproject.toml', packages: ['flask', 'Flask'] },
|
|
225
|
+
{ file: 'Pipfile', packages: ['flask', 'Flask'] },
|
|
226
|
+
],
|
|
227
|
+
codePatterns: [/from\s+flask/, /Flask\s*\(\s*__name__\s*\)/],
|
|
198
228
|
},
|
|
199
229
|
// Gin (Go)
|
|
200
230
|
{
|
|
@@ -247,7 +277,7 @@ let PS003FrameworkDetection = (() => {
|
|
|
247
277
|
// Check dependencies
|
|
248
278
|
if (detector.dependencies) {
|
|
249
279
|
for (const dep of detector.dependencies) {
|
|
250
|
-
const depDetected = await this.checkDependency(projectRoot, dep.file, dep.packages);
|
|
280
|
+
const depDetected = await this.checkDependency(projectRoot, dep.file, dep.packages, context.files);
|
|
251
281
|
if (depDetected) {
|
|
252
282
|
matches.push({
|
|
253
283
|
type: enums_1.MatchType.CONFIG,
|
|
@@ -289,10 +319,26 @@ let PS003FrameworkDetection = (() => {
|
|
|
289
319
|
score,
|
|
290
320
|
};
|
|
291
321
|
}
|
|
292
|
-
async checkDependency(projectRoot, depFile, packages) {
|
|
293
|
-
// Handle glob patterns
|
|
322
|
+
async checkDependency(projectRoot, depFile, packages, files) {
|
|
323
|
+
// Handle glob patterns by searching matching files
|
|
294
324
|
if (depFile.includes('*')) {
|
|
295
|
-
|
|
325
|
+
if (!files)
|
|
326
|
+
return false;
|
|
327
|
+
const globRegex = new RegExp(`${depFile.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`, 'i');
|
|
328
|
+
const matchingFiles = files.filter((f) => globRegex.test(f.fileName));
|
|
329
|
+
for (const file of matchingFiles) {
|
|
330
|
+
try {
|
|
331
|
+
const content = await file_scanner_1.FileScanner.readFileContent(file.absolutePath);
|
|
332
|
+
for (const pkg of packages) {
|
|
333
|
+
if (content.includes(pkg)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Skip unreadable files
|
|
340
|
+
}
|
|
341
|
+
}
|
|
296
342
|
return false;
|
|
297
343
|
}
|
|
298
344
|
const filePath = `${projectRoot}/${depFile}`;
|
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
|
+
```dart
|
|
15
|
+
User user() { } // Noun only
|
|
16
|
+
void userData() { } // Noun only
|
|
17
|
+
void doSomething() { } // Vague
|
|
18
|
+
void handleStuff() { } // Vague
|
|
19
|
+
void manager() { } // Noun only
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (action verbs):**
|
|
23
|
+
|
|
24
|
+
```dart
|
|
25
|
+
Future<User> getUser() async { }
|
|
26
|
+
void createUserAccount() { }
|
|
27
|
+
bool validateEmailFormat(String email) { }
|
|
28
|
+
double calculateTotalPrice(List<Item> items) { }
|
|
29
|
+
Future<void> sendConfirmationEmail(String email) async { }
|
|
30
|
+
String convertCurrencyToVnd(double amount) { }
|
|
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, dart_analyzer
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Use Dead Code
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces codebase noise and app size
|
|
5
|
+
tags: dead-code, cleanup, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Use Dead Code
|
|
9
|
+
|
|
10
|
+
Dead code confuses readers and increases app size. Git history preserves deleted code.
|
|
11
|
+
|
|
12
|
+
**Incorrect (keeping dead code):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
void processOrder(Order order) {
|
|
16
|
+
// Old implementation - keeping for reference
|
|
17
|
+
// final total = order.items.fold(0.0, (sum, item) {
|
|
18
|
+
// return sum + item.price * item.quantity;
|
|
19
|
+
// });
|
|
20
|
+
|
|
21
|
+
final total = calculateTotal(order);
|
|
22
|
+
return total;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Unused function - someone might need it later
|
|
26
|
+
double legacyCalculation(List<Item> items) {
|
|
27
|
+
return items.length * 10.0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
import 'package:app/utils/unused_helper.dart'; // Never used
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Correct (clean code):**
|
|
34
|
+
|
|
35
|
+
```dart
|
|
36
|
+
void processOrder(Order order) {
|
|
37
|
+
final total = calculateTotal(order);
|
|
38
|
+
return total;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Delete unused functions - git history preserves them
|
|
42
|
+
// Delete commented code - git history preserves it
|
|
43
|
+
// Remove unused imports
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Types of dead code:**
|
|
47
|
+
- Commented-out code blocks
|
|
48
|
+
- Unused functions/classes/mixins
|
|
49
|
+
- Unused imports
|
|
50
|
+
- Unreachable code (after `return`/`throw`)
|
|
51
|
+
- Unused variables and parameters
|
|
52
|
+
|
|
53
|
+
**Tools:** dart analyze, `unused_import` lint rule, Code Review
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
```dart
|
|
15
|
+
class OrderService {
|
|
16
|
+
final _db = DatabaseHelper(); // Hardcoded dependency
|
|
17
|
+
final _mailer = EmailService(); // Hardcoded dependency
|
|
18
|
+
|
|
19
|
+
Future<Order> createOrder(OrderData data) async {
|
|
20
|
+
final order = await _db.insert('orders', data.toMap());
|
|
21
|
+
await _mailer.send(data.email, 'Order created');
|
|
22
|
+
return order;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Correct (injected dependencies):**
|
|
28
|
+
|
|
29
|
+
```dart
|
|
30
|
+
abstract class IDatabase {
|
|
31
|
+
Future<Map<String, dynamic>> insert(String table, Map<String, dynamic> data);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
abstract class IMailer {
|
|
35
|
+
Future<void> send(String to, String message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class OrderService {
|
|
39
|
+
OrderService({
|
|
40
|
+
required IDatabase db,
|
|
41
|
+
required IMailer mailer,
|
|
42
|
+
}) : _db = db,
|
|
43
|
+
_mailer = mailer;
|
|
44
|
+
|
|
45
|
+
final IDatabase _db;
|
|
46
|
+
final IMailer _mailer;
|
|
47
|
+
|
|
48
|
+
Future<Order> createOrder(OrderData data) async {
|
|
49
|
+
final order = await _db.insert('orders', data.toMap());
|
|
50
|
+
await _mailer.send(data.email, 'Order created');
|
|
51
|
+
return order;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Usage - production
|
|
56
|
+
final service = OrderService(
|
|
57
|
+
db: SqliteDatabase(path: dbPath),
|
|
58
|
+
mailer: SmtpMailer(host: smtpHost),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Usage - testing
|
|
62
|
+
final service = OrderService(
|
|
63
|
+
db: MockDatabase(),
|
|
64
|
+
mailer: MockMailer(),
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**With get_it or injectable:**
|
|
69
|
+
|
|
70
|
+
```dart
|
|
71
|
+
// Using get_it
|
|
72
|
+
@injectable
|
|
73
|
+
class OrderService {
|
|
74
|
+
OrderService(this._db, this._mailer);
|
|
75
|
+
|
|
76
|
+
final IDatabase _db;
|
|
77
|
+
final IMailer _mailer;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Register
|
|
81
|
+
GetIt.instance.registerFactory<OrderService>(
|
|
82
|
+
() => OrderService(GetIt.instance(), GetIt.instance()),
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Benefits:**
|
|
87
|
+
- Easy mocking for unit tests
|
|
88
|
+
- Swappable implementations
|
|
89
|
+
- Clear dependencies visible in constructor
|
|
90
|
+
- Supports interface-based design
|
|
91
|
+
|
|
92
|
+
**Tools:** get_it, injectable, mockito, mocktail
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Business Logic In Constructors
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: ensures predictable object initialization
|
|
5
|
+
tags: constructor, initialization, side-effects, patterns, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Business Logic In Constructors
|
|
9
|
+
|
|
10
|
+
Constructors should only initialize state. Side effects in constructors are unexpected and make testing difficult.
|
|
11
|
+
|
|
12
|
+
**Incorrect (logic in constructor):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class UserRepository {
|
|
16
|
+
UserRepository(String configPath) {
|
|
17
|
+
// BAD: File I/O in constructor
|
|
18
|
+
final file = File(configPath).readAsStringSync();
|
|
19
|
+
final config = jsonDecode(file) as Map<String, dynamic>;
|
|
20
|
+
|
|
21
|
+
// BAD: Network call in constructor
|
|
22
|
+
_client = HttpClient()..open('GET', config['apiUrl'] as String, 80, '/init');
|
|
23
|
+
|
|
24
|
+
// BAD: Mutation side-effects
|
|
25
|
+
_cache.clear();
|
|
26
|
+
|
|
27
|
+
// BAD: Logging with side effects
|
|
28
|
+
print('UserRepository initialized');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Correct (factory method for async / complex init):**
|
|
34
|
+
|
|
35
|
+
```dart
|
|
36
|
+
class UserRepository {
|
|
37
|
+
UserRepository({
|
|
38
|
+
required HttpClient client,
|
|
39
|
+
required AppConfig config,
|
|
40
|
+
}) : _client = client, // Only assignments
|
|
41
|
+
_config = config;
|
|
42
|
+
|
|
43
|
+
final HttpClient _client;
|
|
44
|
+
final AppConfig _config;
|
|
45
|
+
|
|
46
|
+
// Factory method for complex initialization
|
|
47
|
+
static Future<UserRepository> create(String configPath) async {
|
|
48
|
+
final file = await File(configPath).readAsString();
|
|
49
|
+
final config = AppConfig.fromJson(jsonDecode(file) as Map<String, dynamic>);
|
|
50
|
+
|
|
51
|
+
final client = HttpClient();
|
|
52
|
+
await client.initialize(config.apiUrl);
|
|
53
|
+
|
|
54
|
+
return UserRepository(client: client, config: config);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Usage
|
|
59
|
+
final repo = await UserRepository.create('./config.json');
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Tools:** Code Review, dart_analyzer
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Throw Generic Errors
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables proper error handling and monitoring
|
|
5
|
+
tags: error-handling, exceptions, custom-errors, debugging, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Throw Generic Errors
|
|
9
|
+
|
|
10
|
+
Generic errors lack context needed for debugging. They make it impossible to distinguish between error types for proper handling.
|
|
11
|
+
|
|
12
|
+
**Incorrect (generic errors):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
if (user == null) {
|
|
16
|
+
throw Exception('error');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!isValid) {
|
|
20
|
+
throw Exception();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (response.statusCode != 200) {
|
|
24
|
+
throw Exception('Failed');
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Correct (specific custom exceptions):**
|
|
29
|
+
|
|
30
|
+
```dart
|
|
31
|
+
if (user == null) {
|
|
32
|
+
throw UserNotFoundException('User with ID $userId not found');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!isValid) {
|
|
36
|
+
throw ValidationException(
|
|
37
|
+
field: 'email',
|
|
38
|
+
message: 'Email format is invalid',
|
|
39
|
+
value: email,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (response.statusCode != 200) {
|
|
44
|
+
throw ApiException(
|
|
45
|
+
statusCode: response.statusCode,
|
|
46
|
+
message: 'API request failed',
|
|
47
|
+
endpoint: '/users/$userId',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Custom exceptions should include:
|
|
53
|
+
- Descriptive message with context
|
|
54
|
+
- Relevant data for debugging
|
|
55
|
+
- Error code for programmatic handling
|
|
56
|
+
|
|
57
|
+
**Tools:** Code Review, dart_analyzer
|
|
@@ -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
|
+
```dart
|
|
15
|
+
// NOT an error - expected business case
|
|
16
|
+
logger.e('User entered wrong password');
|
|
17
|
+
|
|
18
|
+
// NOT an error - validation failure
|
|
19
|
+
logger.e('Email format invalid');
|
|
20
|
+
|
|
21
|
+
// NOT an error - retry in progress
|
|
22
|
+
logger.e('Retry attempt 2 of 5');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (appropriate log levels):**
|
|
26
|
+
|
|
27
|
+
```dart
|
|
28
|
+
// WARN - recoverable, may need attention
|
|
29
|
+
logger.w('Payment retry attempt', error: {'attempt': 2, 'maxAttempts': 5});
|
|
30
|
+
|
|
31
|
+
// INFO - normal business events
|
|
32
|
+
logger.i('Login failed - invalid password', {'userId': userId, 'attempts': 3});
|
|
33
|
+
|
|
34
|
+
// DEBUG - detailed troubleshooting
|
|
35
|
+
logger.d('Validation failed', error: {'field': 'email'});
|
|
36
|
+
|
|
37
|
+
// ERROR - only for actual system / unrecoverable failures
|
|
38
|
+
logger.e('Database connection lost', error: e, stackTrace: st);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Log Level Guide (using `logger` package):**
|
|
42
|
+
|
|
43
|
+
| Method | Level | Use For |
|
|
44
|
+
|--------|-------|---------|
|
|
45
|
+
| `logger.e()` | ERROR | System failures, crashes, unrecoverable |
|
|
46
|
+
| `logger.w()` | WARN | Potential issues, degraded performance |
|
|
47
|
+
| `logger.i()` | INFO | Business events, state changes |
|
|
48
|
+
| `logger.d()` | DEBUG | Detailed troubleshooting |
|
|
49
|
+
|
|
50
|
+
**Tools:** logger package, Code Review
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Import Unused Modules
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces app size and improves clarity
|
|
5
|
+
tags: imports, cleanup, bundle-size, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Import Unused Modules
|
|
9
|
+
|
|
10
|
+
Unused imports add noise and may increase app size.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unused imports):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
import 'package:app/models/user.dart';
|
|
16
|
+
import 'package:app/models/order.dart'; // Never used
|
|
17
|
+
import 'package:app/utils/date_utils.dart'; // Never used
|
|
18
|
+
import 'package:intl/intl.dart'; // Never used
|
|
19
|
+
|
|
20
|
+
// Only User is actually used
|
|
21
|
+
User getUser(String id) {
|
|
22
|
+
return userRepository.findById(id);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct (only needed imports):**
|
|
27
|
+
|
|
28
|
+
```dart
|
|
29
|
+
import 'package:app/models/user.dart';
|
|
30
|
+
|
|
31
|
+
User getUser(String id) {
|
|
32
|
+
return userRepository.findById(id);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Enable via analysis_options.yaml:**
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
# analysis_options.yaml
|
|
40
|
+
linter:
|
|
41
|
+
rules:
|
|
42
|
+
- unused_import
|
|
43
|
+
- avoid_unused_constructor_parameters
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Tools:** dart analyze, IDE auto-organize imports (Cmd/Ctrl+Shift+O)
|