@sun-asterisk/sunlint 1.3.46 → 1.3.47
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/adapters/sunlint-rule-adapter.js +16 -0
- package/core/rule-selection-service.js +24 -3
- package/engines/heuristic-engine.js +6 -1
- package/package.json +2 -2
- package/skill-assets/sunlint-code-quality/rules/ruby/C006-verb-noun-functions.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C013-no-dead-code.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C014-dependency-injection.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C017-no-constructor-logic.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C018-generic-errors.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C019-error-log-level.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C020-no-unused-imports.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C022-no-unused-variables.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C023-no-duplicate-names.md +39 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C024-centralize-constants.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C029-catch-log-root-cause.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C030-custom-error-classes.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C033-separate-data-access.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C035-error-context-logging.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C041-no-hardcoded-secrets.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C042-boolean-naming.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C052-controller-parsing.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C060-superclass-logic.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/C067-no-hardcoded-config.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S003-open-redirect.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S004-no-log-credentials.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S005-server-authorization.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S006-default-credentials.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S007-output-encoding.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S009-approved-crypto.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S010-csprng.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S011-encrypted-client-hello.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S012-secrets-management.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S013-tls-connections.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S016-no-sensitive-query-string.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S017-parameterized-queries.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S019-email-input-sanitization.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S020-eval-code-execution.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S022-context-escaping.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S023-dynamic-js-encoding.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S025-server-validation.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S026-tls-encryption.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S027-mtls-validation.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S028-upload-limits.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S029-csrf-protection.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S030-directory-browsing.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S031-secure-cookie-flag.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S032-httponly-cookie.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S033-samesite-cookie.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S034-host-prefix-cookie.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S035-app-hostnames.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S036-internal-file-paths.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S037-anti-cache-headers.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S039-tls-certificate-validation.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S041-logout-invalidation.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S042-long-lived-sessions.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S044-critical-changes-reauth.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S045-brute-force-protection.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S047-oauth-csrf-protection.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S048-oauth-redirect-validation.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S049-auth-code-expiry.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S050-token-entropy.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S051-password-length.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S052-otp-entropy.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S053-generic-error-messages.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S054-no-default-admin.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S055-content-type-validation.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S056-log-injection.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S057-synchronized-time.md +18 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/S058-ssrf-protection.md +39 -0
|
@@ -203,6 +203,22 @@ class SunlintRuleAdapter {
|
|
|
203
203
|
return Array.from(this.rulesCache.keys());
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Get rules registry for severity resolution
|
|
208
|
+
* Returns object with rules property compatible with ConfigValidator
|
|
209
|
+
*/
|
|
210
|
+
getRulesRegistry() {
|
|
211
|
+
if (this.registryCache) {
|
|
212
|
+
return { rules: this.registryCache };
|
|
213
|
+
}
|
|
214
|
+
// Convert cache map to registry format
|
|
215
|
+
const rules = {};
|
|
216
|
+
this.rulesCache.forEach((rule, ruleId) => {
|
|
217
|
+
rules[ruleId] = rule;
|
|
218
|
+
});
|
|
219
|
+
return { rules };
|
|
220
|
+
}
|
|
221
|
+
|
|
206
222
|
/**
|
|
207
223
|
* Validate rule ID
|
|
208
224
|
*/
|
|
@@ -10,11 +10,13 @@ const fs = require('fs');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const RuleMappingService = require('./rule-mapping-service');
|
|
12
12
|
const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter');
|
|
13
|
+
const ConfigValidator = require('./config-validator');
|
|
13
14
|
|
|
14
15
|
class RuleSelectionService {
|
|
15
16
|
constructor() {
|
|
16
17
|
this.ruleAdapter = SunlintRuleAdapter.getInstance();
|
|
17
18
|
this.ruleMappingService = new RuleMappingService();
|
|
19
|
+
this.configValidator = new ConfigValidator();
|
|
18
20
|
this.initialized = false;
|
|
19
21
|
// Path works both in dev (from pages/) and npm package (from config/)
|
|
20
22
|
this.releasedRulesPath = path.join(__dirname, '../config/released-rules.json');
|
|
@@ -209,14 +211,33 @@ class RuleSelectionService {
|
|
|
209
211
|
selectedRules = selectedRules.filter(ruleId => !disabledRules.has(ruleId));
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
//
|
|
214
|
+
// Get rules registry for severity resolution
|
|
215
|
+
const rulesRegistry = this.ruleAdapter.getRulesRegistry();
|
|
216
|
+
|
|
217
|
+
// Convert to rule objects with proper severity from config
|
|
213
218
|
return selectedRules.map(ruleId => {
|
|
214
219
|
const adapterRule = this.ruleAdapter.getRuleById(ruleId);
|
|
220
|
+
|
|
221
|
+
// Resolve severity from config (config.rules > config.categories > rule default)
|
|
222
|
+
const effectiveConfig = this.configValidator.getEffectiveRuleConfiguration(
|
|
223
|
+
ruleId,
|
|
224
|
+
config,
|
|
225
|
+
rulesRegistry
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Map config values to severity (error/warn)
|
|
229
|
+
let severity = 'warning';
|
|
230
|
+
if (effectiveConfig === 'error' || effectiveConfig === 2) {
|
|
231
|
+
severity = 'error';
|
|
232
|
+
} else if (effectiveConfig === 'warn' || effectiveConfig === 'warning' || effectiveConfig === 1) {
|
|
233
|
+
severity = 'warning';
|
|
234
|
+
}
|
|
235
|
+
|
|
215
236
|
return {
|
|
237
|
+
...(adapterRule || {}),
|
|
216
238
|
id: ruleId,
|
|
217
239
|
name: this.getRuleName(ruleId),
|
|
218
|
-
severity
|
|
219
|
-
...(adapterRule || {})
|
|
240
|
+
severity // Must come AFTER spread to override adapter's severity
|
|
220
241
|
};
|
|
221
242
|
}).filter(rule => rule.id);
|
|
222
243
|
}
|
|
@@ -1002,7 +1002,12 @@ class HeuristicEngine extends AnalysisEngineInterface {
|
|
|
1002
1002
|
fileResult = { file: filePath, violations: [] };
|
|
1003
1003
|
results.results.push(fileResult);
|
|
1004
1004
|
}
|
|
1005
|
-
|
|
1005
|
+
// Apply rule severity to each violation (from config)
|
|
1006
|
+
const violationsWithSeverity = violations.map(v => ({
|
|
1007
|
+
...v,
|
|
1008
|
+
severity: rule.severity || v.severity || 'warning'
|
|
1009
|
+
}));
|
|
1010
|
+
fileResult.violations.push(...violationsWithSeverity);
|
|
1006
1011
|
}
|
|
1007
1012
|
}
|
|
1008
1013
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sunlint",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.47",
|
|
4
4
|
"description": "☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"eslint-plugin-react": "^7.37.5",
|
|
65
65
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
66
66
|
"espree": "^10.3.0",
|
|
67
|
-
"glob": "^
|
|
67
|
+
"glob": "^10.5.0",
|
|
68
68
|
"minimatch": "^10.0.3",
|
|
69
69
|
"node-fetch": "^3.3.2",
|
|
70
70
|
"table": "^6.8.2",
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
Methods in Ruby should describe actions clearly. Action verbs make the purpose manifest.
|
|
11
|
+
|
|
12
|
+
**Incorrect (vague names):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def user # Noun only
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def user_data # Noun only
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def do_something # Vague
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def handle_stuff # Vague
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Correct (action verbs):**
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
def fetch_user
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_user_account
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_email_format?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def calculate_total_price
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def send_confirmation_email
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def convert_currency_to_usd
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Verb categories:**
|
|
51
|
+
|
|
52
|
+
| Category | Verbs |
|
|
53
|
+
|----------|-------|
|
|
54
|
+
| Retrieval | `get`, `fetch`, `find`, `load`, `query` |
|
|
55
|
+
| Creation | `create`, `build`, `make`, `generate` |
|
|
56
|
+
| Modification | `set`, `update`, `modify`, `change` |
|
|
57
|
+
| Deletion | `delete`, `remove`, `destroy`, `clear` |
|
|
58
|
+
| Validation | `validate`, `verify`, `check`, `ensure` |
|
|
59
|
+
| Computation | `calculate`, `compute`, `parse`, `format` |
|
|
60
|
+
| Boolean | `is`, `has`, `can`, `should`, `will` |
|
|
61
|
+
|
|
62
|
+
**Tools:** PR review, RuboCop
|
|
63
|
+
---
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Use Dead Code
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces codebase noise and maintenance effort
|
|
5
|
+
tags: dead-code, cleanup, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Use Dead Code
|
|
9
|
+
|
|
10
|
+
Dead code confuses readers and clutters the codebase. Git history preserves deleted code, so there is no need to keep it in the active source files.
|
|
11
|
+
|
|
12
|
+
**Incorrect (keeping dead code):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def process_order(order)
|
|
16
|
+
# Old implementation - keeping for reference
|
|
17
|
+
# total = order.items.inject(0) do |sum, item|
|
|
18
|
+
# sum + item.price * item.quantity
|
|
19
|
+
# end
|
|
20
|
+
|
|
21
|
+
total = calculate_total(order)
|
|
22
|
+
total
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Unused function - someone might need it later
|
|
26
|
+
def legacy_calculation
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (clean code):**
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
def process_order(order)
|
|
34
|
+
calculate_total(order)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Delete unused methods - git history preserves them
|
|
38
|
+
# Delete commented code - git history preserves it
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Types of dead code:**
|
|
42
|
+
- Commented-out code
|
|
43
|
+
- Unused methods/classes
|
|
44
|
+
- Unreachable code
|
|
45
|
+
- Unused variables
|
|
46
|
+
|
|
47
|
+
**Tools:** RuboCop, debride
|
|
48
|
+
---
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Dependency Injection
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: enables testable business logic and decupling
|
|
5
|
+
tags: dependency-injection, decoupling, testability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Dependency Injection
|
|
9
|
+
|
|
10
|
+
Hardcoding dependencies inside classes makes them difficult to test and tightly coupled. Pass dependencies through the constructor (`initialize`).
|
|
11
|
+
|
|
12
|
+
**Incorrect (tightly coupled):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class OrderService
|
|
16
|
+
def process(order)
|
|
17
|
+
# Hardcoded dependency
|
|
18
|
+
mailer = UserMailer.new
|
|
19
|
+
mailer.send_confirmation(order.user)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (dependency injection):**
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class OrderService
|
|
28
|
+
def initialize(mailer = UserMailer.new)
|
|
29
|
+
@mailer = mailer
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def process(order)
|
|
33
|
+
@mailer.send_confirmation(order.user)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# In tests:
|
|
38
|
+
# service = OrderService.new(MockMailer.new)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** PR review
|
|
42
|
+
---
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Complex Logic in Constructor
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: makes objects easy to instantiate and test
|
|
5
|
+
tags: constructor, initialization, testability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Complex Logic in Constructor
|
|
9
|
+
|
|
10
|
+
The `initialize` method should only set up initial state. Avoid performing computations, database queries, or network calls during object construction.
|
|
11
|
+
|
|
12
|
+
**Incorrect (complex initialize):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class UserProfile
|
|
16
|
+
def initialize(user_id)
|
|
17
|
+
@user = User.find(user_id) # DB query in constructor
|
|
18
|
+
@stats = RemoteApiService.fetch_stats(user_id) # Network call
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (simple initialize):**
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
class UserProfile
|
|
27
|
+
def initialize(user, stats)
|
|
28
|
+
@user = user
|
|
29
|
+
@stats = stats
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Use a factory method or dependency injection
|
|
33
|
+
def self.build(user_id)
|
|
34
|
+
user = User.find(user_id)
|
|
35
|
+
stats = RemoteApiService.fetch_stats(user_id)
|
|
36
|
+
new(user, stats)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** PR review
|
|
42
|
+
---
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Catch Specific Exceptions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents hiding critical system errors and unexpected behavior
|
|
5
|
+
tags: error-handling, maintenance, safety, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Catch Specific Exceptions
|
|
9
|
+
|
|
10
|
+
Avoid catching the generic `Exception` or `StandardError` classes without a good reason. This can hide bugs and system-level issues like `NoMemoryError` or `SignalException`.
|
|
11
|
+
|
|
12
|
+
**Incorrect (generic rescue):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
begin
|
|
16
|
+
do_something
|
|
17
|
+
rescue => e # Rescues StandardError
|
|
18
|
+
log_error(e)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
do_something
|
|
23
|
+
rescue Exception => e # Even worse, rescues everything
|
|
24
|
+
log_error(e)
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Correct (specific rescue):**
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
begin
|
|
32
|
+
do_something
|
|
33
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
34
|
+
handle_missing_record(e)
|
|
35
|
+
rescue JSON::ParserError => e
|
|
36
|
+
handle_invalid_json(e)
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Tools:** RuboCop (`Style/RescueStandardError`)
|
|
41
|
+
---
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Appropriate Log Levels
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: facilitates effective monitoring and troubleshooting
|
|
5
|
+
tags: logging, observability, monitoring, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Appropriate Log Levels
|
|
9
|
+
|
|
10
|
+
Use the correct log level (`debug`, `info`, `warn`, `error`, `fatal`) to distinguish between operational noise and critical issues.
|
|
11
|
+
|
|
12
|
+
**Incorrect (misused levels):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
begin
|
|
16
|
+
process_data
|
|
17
|
+
rescue => e
|
|
18
|
+
Rails.logger.info "Error happened: #{e.message}" # Error logged as Info
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Rails.logger.error "Variable x is #{x}" # Debugging info logged as Error
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (appropriate levels):**
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
Rails.logger.debug "Processing payload: #{payload}"
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
process_data
|
|
31
|
+
rescue => e
|
|
32
|
+
Rails.logger.error "Data processing failed: #{e.message}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if retry_count > 0
|
|
36
|
+
Rails.logger.warn "Retry attempt #{retry_count} for user #{user_id}"
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Tools:** Manual Review
|
|
41
|
+
---
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Unused Requires
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces startup time and avoids unnecessary dependencies
|
|
5
|
+
tags: cleanup, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Unused Requires
|
|
9
|
+
|
|
10
|
+
Avoid requiring libraries or files that are not used in the current file. This clutters the code and may slightly impact load times.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unused requires):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
require 'json' # Not used
|
|
16
|
+
require 'net/http' # Not used
|
|
17
|
+
|
|
18
|
+
class MyService
|
|
19
|
+
def call
|
|
20
|
+
puts "Hello"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (clean requires):**
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
class MyService
|
|
29
|
+
def call
|
|
30
|
+
puts "Hello"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Tools:** RuboCop
|
|
36
|
+
---
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Unused Variables
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: reduces confusion and identifies potential logic errors
|
|
5
|
+
tags: cleanup, quality, maintenance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No Unused Variables
|
|
9
|
+
|
|
10
|
+
Variables that are declared but never used often indicate a typo or a forgotten piece of logic.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unused variables):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def calculate(a, b)
|
|
16
|
+
result = a + b
|
|
17
|
+
temp_data = fetch_additional_info # Fetched but never used
|
|
18
|
+
result
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (clean variables):**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
def calculate(a, b)
|
|
26
|
+
a + b
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** RuboCop (`Lint/UselessAssignment`)
|
|
31
|
+
---
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Duplicate Naming
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: prevents confusion and shadowing
|
|
5
|
+
tags: naming, quality, maintenance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Duplicate Naming
|
|
9
|
+
|
|
10
|
+
Avoid using the same name for local variables, method arguments, and instance variables if it causes confusion or shadowing.
|
|
11
|
+
|
|
12
|
+
**Incorrect (shadowing/ambiguity):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class Processor
|
|
16
|
+
attr_reader :data
|
|
17
|
+
|
|
18
|
+
def process(data) # Argument shadows attribute
|
|
19
|
+
data = data.clean # Shadowing
|
|
20
|
+
@data = data
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (distinct names):**
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
class Processor
|
|
29
|
+
attr_reader :data
|
|
30
|
+
|
|
31
|
+
def process(raw_data)
|
|
32
|
+
cleaned_data = raw_data.clean
|
|
33
|
+
@data = cleaned_data
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Tools:** RuboCop (`Lint/ShadowingOuterLocalVariable`)
|
|
39
|
+
---
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Centralize Constants
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: improves maintainability and avoids magic numbers
|
|
5
|
+
tags: constants, maintainability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Centralize Constants
|
|
9
|
+
|
|
10
|
+
Avoid hardcoding magic strings and numbers throughout your logic. Define them as constants at the top of the class or in a specific module.
|
|
11
|
+
|
|
12
|
+
**Incorrect (magic values):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class User < ApplicationRecord
|
|
16
|
+
def premium?
|
|
17
|
+
self.score > 1000 # What is 1000?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (constants):**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class User < ApplicationRecord
|
|
26
|
+
PREMIUM_THRESHOLD = 1000
|
|
27
|
+
|
|
28
|
+
def premium?
|
|
29
|
+
score > PREMIUM_THRESHOLD
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Tools:** RuboCop
|
|
35
|
+
---
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Catch and Log Root Cause
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: facilitates faster debugging and resolution
|
|
5
|
+
tags: error-handling, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Catch and Log Root Cause
|
|
9
|
+
|
|
10
|
+
When catching an error, ensure you log the original error message and backtrace to facilitate debugging.
|
|
11
|
+
|
|
12
|
+
**Incorrect (losing context):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
begin
|
|
16
|
+
process_data
|
|
17
|
+
rescue => e
|
|
18
|
+
Rails.logger.error "Something went wrong" # No root cause logged
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (logging root cause):**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
begin
|
|
26
|
+
process_data
|
|
27
|
+
rescue => e
|
|
28
|
+
Rails.logger.error "Data processing failed: #{e.message}"
|
|
29
|
+
Rails.logger.error e.backtrace.join("\n") # Log backtrace for context
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Tools:** Manual Review, Sentry/Honeybadger
|
|
34
|
+
---
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Custom Error Classes
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: allows specific rescue blocks and better error hierarchy
|
|
5
|
+
tags: error-handling, architecture, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Custom Error Classes
|
|
9
|
+
|
|
10
|
+
Define specific error classes for your application domain rather than relying on generic ones.
|
|
11
|
+
|
|
12
|
+
**Incorrect (generic errors):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def process(amount)
|
|
16
|
+
raise "Invalid amount" if amount <= 0 # Generic RuntimeError
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (custom errors):**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class PaymentError < StandardError; end
|
|
24
|
+
class InvalidAmountError < PaymentError; end
|
|
25
|
+
|
|
26
|
+
def process(amount)
|
|
27
|
+
raise InvalidAmountError, "Amount must be positive" if amount <= 0
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Tools:** PR review
|
|
32
|
+
---
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Separate Processing and Data Access
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables testable business logic and better code organization
|
|
5
|
+
tags: separation-of-concerns, repository, service, architecture, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Separate Processing and Data Access
|
|
9
|
+
|
|
10
|
+
Keep business logic separate from direct database access. Use Service Objects to handle logic and ActiveRecord models primarily for data access and persistence.
|
|
11
|
+
|
|
12
|
+
**Incorrect (mixed concerns):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class OrderController < ApplicationController
|
|
16
|
+
def create
|
|
17
|
+
# Business logic in controller
|
|
18
|
+
if params[:amount] > 100
|
|
19
|
+
discount = params[:amount] * 0.1
|
|
20
|
+
total = params[:amount] - discount
|
|
21
|
+
end
|
|
22
|
+
Order.create(amount: total, user_id: current_user.id)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Correct (separated concerns):**
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# app/services/create_order_service.rb
|
|
31
|
+
class CreateOrderService
|
|
32
|
+
def initialize(user, params)
|
|
33
|
+
@user = user
|
|
34
|
+
@params = params
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def call
|
|
38
|
+
total = calculate_total(@params[:amount])
|
|
39
|
+
Order.create!(amount: total, user: @user)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def calculate_total(amount)
|
|
45
|
+
return amount if amount <= 100
|
|
46
|
+
amount * 0.9
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Tools:** Manual Review
|
|
52
|
+
---
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Include Context in Error Logging
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: provides necessary details for triaging issues
|
|
5
|
+
tags: logging, error-handling, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Include Context in Error Logging
|
|
9
|
+
|
|
10
|
+
When an error occurs, include relevant contextual data (IDs, parameters, state) in the log message.
|
|
11
|
+
|
|
12
|
+
**Incorrect (missing context):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
begin
|
|
16
|
+
update_profile(params)
|
|
17
|
+
rescue => e
|
|
18
|
+
Rails.logger.error "Profile update failed: #{e.message}"
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (with context):**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
begin
|
|
26
|
+
update_profile(params)
|
|
27
|
+
rescue => e
|
|
28
|
+
# Log which record failed and with what data
|
|
29
|
+
Rails.logger.error "Profile update failed for User ID: #{current_user.id}. Params: #{params.inspect}. Error: #{e.message}"
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Tools:** Manual Review
|
|
34
|
+
---
|