@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.
Files changed (69) hide show
  1. package/core/adapters/sunlint-rule-adapter.js +16 -0
  2. package/core/rule-selection-service.js +24 -3
  3. package/engines/heuristic-engine.js +6 -1
  4. package/package.json +2 -2
  5. package/skill-assets/sunlint-code-quality/rules/ruby/C006-verb-noun-functions.md +63 -0
  6. package/skill-assets/sunlint-code-quality/rules/ruby/C013-no-dead-code.md +48 -0
  7. package/skill-assets/sunlint-code-quality/rules/ruby/C014-dependency-injection.md +42 -0
  8. package/skill-assets/sunlint-code-quality/rules/ruby/C017-no-constructor-logic.md +42 -0
  9. package/skill-assets/sunlint-code-quality/rules/ruby/C018-generic-errors.md +41 -0
  10. package/skill-assets/sunlint-code-quality/rules/ruby/C019-error-log-level.md +41 -0
  11. package/skill-assets/sunlint-code-quality/rules/ruby/C020-no-unused-imports.md +36 -0
  12. package/skill-assets/sunlint-code-quality/rules/ruby/C022-no-unused-variables.md +31 -0
  13. package/skill-assets/sunlint-code-quality/rules/ruby/C023-no-duplicate-names.md +39 -0
  14. package/skill-assets/sunlint-code-quality/rules/ruby/C024-centralize-constants.md +35 -0
  15. package/skill-assets/sunlint-code-quality/rules/ruby/C029-catch-log-root-cause.md +34 -0
  16. package/skill-assets/sunlint-code-quality/rules/ruby/C030-custom-error-classes.md +32 -0
  17. package/skill-assets/sunlint-code-quality/rules/ruby/C033-separate-data-access.md +52 -0
  18. package/skill-assets/sunlint-code-quality/rules/ruby/C035-error-context-logging.md +34 -0
  19. package/skill-assets/sunlint-code-quality/rules/ruby/C041-no-hardcoded-secrets.md +29 -0
  20. package/skill-assets/sunlint-code-quality/rules/ruby/C042-boolean-naming.md +38 -0
  21. package/skill-assets/sunlint-code-quality/rules/ruby/C052-controller-parsing.md +37 -0
  22. package/skill-assets/sunlint-code-quality/rules/ruby/C060-superclass-logic.md +38 -0
  23. package/skill-assets/sunlint-code-quality/rules/ruby/C067-no-hardcoded-config.md +37 -0
  24. package/skill-assets/sunlint-code-quality/rules/ruby/S003-open-redirect.md +58 -0
  25. package/skill-assets/sunlint-code-quality/rules/ruby/S004-no-log-credentials.md +38 -0
  26. package/skill-assets/sunlint-code-quality/rules/ruby/S005-server-authorization.md +37 -0
  27. package/skill-assets/sunlint-code-quality/rules/ruby/S006-default-credentials.md +29 -0
  28. package/skill-assets/sunlint-code-quality/rules/ruby/S007-output-encoding.md +31 -0
  29. package/skill-assets/sunlint-code-quality/rules/ruby/S009-approved-crypto.md +31 -0
  30. package/skill-assets/sunlint-code-quality/rules/ruby/S010-csprng.md +30 -0
  31. package/skill-assets/sunlint-code-quality/rules/ruby/S011-encrypted-client-hello.md +27 -0
  32. package/skill-assets/sunlint-code-quality/rules/ruby/S012-secrets-management.md +28 -0
  33. package/skill-assets/sunlint-code-quality/rules/ruby/S013-tls-connections.md +30 -0
  34. package/skill-assets/sunlint-code-quality/rules/ruby/S016-no-sensitive-query-string.md +37 -0
  35. package/skill-assets/sunlint-code-quality/rules/ruby/S017-parameterized-queries.md +33 -0
  36. package/skill-assets/sunlint-code-quality/rules/ruby/S019-email-input-sanitization.md +31 -0
  37. package/skill-assets/sunlint-code-quality/rules/ruby/S020-eval-code-execution.md +36 -0
  38. package/skill-assets/sunlint-code-quality/rules/ruby/S022-context-escaping.md +36 -0
  39. package/skill-assets/sunlint-code-quality/rules/ruby/S023-dynamic-js-encoding.md +33 -0
  40. package/skill-assets/sunlint-code-quality/rules/ruby/S025-server-validation.md +30 -0
  41. package/skill-assets/sunlint-code-quality/rules/ruby/S026-tls-encryption.md +30 -0
  42. package/skill-assets/sunlint-code-quality/rules/ruby/S027-mtls-validation.md +26 -0
  43. package/skill-assets/sunlint-code-quality/rules/ruby/S028-upload-limits.md +33 -0
  44. package/skill-assets/sunlint-code-quality/rules/ruby/S029-csrf-protection.md +32 -0
  45. package/skill-assets/sunlint-code-quality/rules/ruby/S030-directory-browsing.md +30 -0
  46. package/skill-assets/sunlint-code-quality/rules/ruby/S031-secure-cookie-flag.md +27 -0
  47. package/skill-assets/sunlint-code-quality/rules/ruby/S032-httponly-cookie.md +26 -0
  48. package/skill-assets/sunlint-code-quality/rules/ruby/S033-samesite-cookie.md +29 -0
  49. package/skill-assets/sunlint-code-quality/rules/ruby/S034-host-prefix-cookie.md +30 -0
  50. package/skill-assets/sunlint-code-quality/rules/ruby/S035-app-hostnames.md +28 -0
  51. package/skill-assets/sunlint-code-quality/rules/ruby/S036-internal-file-paths.md +37 -0
  52. package/skill-assets/sunlint-code-quality/rules/ruby/S037-anti-cache-headers.md +31 -0
  53. package/skill-assets/sunlint-code-quality/rules/ruby/S039-tls-certificate-validation.md +29 -0
  54. package/skill-assets/sunlint-code-quality/rules/ruby/S041-logout-invalidation.md +31 -0
  55. package/skill-assets/sunlint-code-quality/rules/ruby/S042-long-lived-sessions.md +27 -0
  56. package/skill-assets/sunlint-code-quality/rules/ruby/S044-critical-changes-reauth.md +34 -0
  57. package/skill-assets/sunlint-code-quality/rules/ruby/S045-brute-force-protection.md +33 -0
  58. package/skill-assets/sunlint-code-quality/rules/ruby/S047-oauth-csrf-protection.md +33 -0
  59. package/skill-assets/sunlint-code-quality/rules/ruby/S048-oauth-redirect-validation.md +29 -0
  60. package/skill-assets/sunlint-code-quality/rules/ruby/S049-auth-code-expiry.md +31 -0
  61. package/skill-assets/sunlint-code-quality/rules/ruby/S050-token-entropy.md +26 -0
  62. package/skill-assets/sunlint-code-quality/rules/ruby/S051-password-length.md +38 -0
  63. package/skill-assets/sunlint-code-quality/rules/ruby/S052-otp-entropy.md +25 -0
  64. package/skill-assets/sunlint-code-quality/rules/ruby/S053-generic-error-messages.md +33 -0
  65. package/skill-assets/sunlint-code-quality/rules/ruby/S054-no-default-admin.md +29 -0
  66. package/skill-assets/sunlint-code-quality/rules/ruby/S055-content-type-validation.md +24 -0
  67. package/skill-assets/sunlint-code-quality/rules/ruby/S056-log-injection.md +28 -0
  68. package/skill-assets/sunlint-code-quality/rules/ruby/S057-synchronized-time.md +18 -0
  69. 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
- // Convert to rule objects
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: 'warning',
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
- fileResult.violations.push(...violations);
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.46",
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": "^11.0.3",
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
+ ---