@sun-asterisk/sunlint 1.3.45 → 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 (71) hide show
  1. package/core/adapters/sunlint-rule-adapter.js +16 -0
  2. package/core/cli-action-handler.js +3 -0
  3. package/core/cli-program.js +7 -0
  4. package/core/rule-selection-service.js +96 -3
  5. package/engines/heuristic-engine.js +6 -1
  6. package/package.json +2 -2
  7. package/skill-assets/sunlint-code-quality/rules/ruby/C006-verb-noun-functions.md +63 -0
  8. package/skill-assets/sunlint-code-quality/rules/ruby/C013-no-dead-code.md +48 -0
  9. package/skill-assets/sunlint-code-quality/rules/ruby/C014-dependency-injection.md +42 -0
  10. package/skill-assets/sunlint-code-quality/rules/ruby/C017-no-constructor-logic.md +42 -0
  11. package/skill-assets/sunlint-code-quality/rules/ruby/C018-generic-errors.md +41 -0
  12. package/skill-assets/sunlint-code-quality/rules/ruby/C019-error-log-level.md +41 -0
  13. package/skill-assets/sunlint-code-quality/rules/ruby/C020-no-unused-imports.md +36 -0
  14. package/skill-assets/sunlint-code-quality/rules/ruby/C022-no-unused-variables.md +31 -0
  15. package/skill-assets/sunlint-code-quality/rules/ruby/C023-no-duplicate-names.md +39 -0
  16. package/skill-assets/sunlint-code-quality/rules/ruby/C024-centralize-constants.md +35 -0
  17. package/skill-assets/sunlint-code-quality/rules/ruby/C029-catch-log-root-cause.md +34 -0
  18. package/skill-assets/sunlint-code-quality/rules/ruby/C030-custom-error-classes.md +32 -0
  19. package/skill-assets/sunlint-code-quality/rules/ruby/C033-separate-data-access.md +52 -0
  20. package/skill-assets/sunlint-code-quality/rules/ruby/C035-error-context-logging.md +34 -0
  21. package/skill-assets/sunlint-code-quality/rules/ruby/C041-no-hardcoded-secrets.md +29 -0
  22. package/skill-assets/sunlint-code-quality/rules/ruby/C042-boolean-naming.md +38 -0
  23. package/skill-assets/sunlint-code-quality/rules/ruby/C052-controller-parsing.md +37 -0
  24. package/skill-assets/sunlint-code-quality/rules/ruby/C060-superclass-logic.md +38 -0
  25. package/skill-assets/sunlint-code-quality/rules/ruby/C067-no-hardcoded-config.md +37 -0
  26. package/skill-assets/sunlint-code-quality/rules/ruby/S003-open-redirect.md +58 -0
  27. package/skill-assets/sunlint-code-quality/rules/ruby/S004-no-log-credentials.md +38 -0
  28. package/skill-assets/sunlint-code-quality/rules/ruby/S005-server-authorization.md +37 -0
  29. package/skill-assets/sunlint-code-quality/rules/ruby/S006-default-credentials.md +29 -0
  30. package/skill-assets/sunlint-code-quality/rules/ruby/S007-output-encoding.md +31 -0
  31. package/skill-assets/sunlint-code-quality/rules/ruby/S009-approved-crypto.md +31 -0
  32. package/skill-assets/sunlint-code-quality/rules/ruby/S010-csprng.md +30 -0
  33. package/skill-assets/sunlint-code-quality/rules/ruby/S011-encrypted-client-hello.md +27 -0
  34. package/skill-assets/sunlint-code-quality/rules/ruby/S012-secrets-management.md +28 -0
  35. package/skill-assets/sunlint-code-quality/rules/ruby/S013-tls-connections.md +30 -0
  36. package/skill-assets/sunlint-code-quality/rules/ruby/S016-no-sensitive-query-string.md +37 -0
  37. package/skill-assets/sunlint-code-quality/rules/ruby/S017-parameterized-queries.md +33 -0
  38. package/skill-assets/sunlint-code-quality/rules/ruby/S019-email-input-sanitization.md +31 -0
  39. package/skill-assets/sunlint-code-quality/rules/ruby/S020-eval-code-execution.md +36 -0
  40. package/skill-assets/sunlint-code-quality/rules/ruby/S022-context-escaping.md +36 -0
  41. package/skill-assets/sunlint-code-quality/rules/ruby/S023-dynamic-js-encoding.md +33 -0
  42. package/skill-assets/sunlint-code-quality/rules/ruby/S025-server-validation.md +30 -0
  43. package/skill-assets/sunlint-code-quality/rules/ruby/S026-tls-encryption.md +30 -0
  44. package/skill-assets/sunlint-code-quality/rules/ruby/S027-mtls-validation.md +26 -0
  45. package/skill-assets/sunlint-code-quality/rules/ruby/S028-upload-limits.md +33 -0
  46. package/skill-assets/sunlint-code-quality/rules/ruby/S029-csrf-protection.md +32 -0
  47. package/skill-assets/sunlint-code-quality/rules/ruby/S030-directory-browsing.md +30 -0
  48. package/skill-assets/sunlint-code-quality/rules/ruby/S031-secure-cookie-flag.md +27 -0
  49. package/skill-assets/sunlint-code-quality/rules/ruby/S032-httponly-cookie.md +26 -0
  50. package/skill-assets/sunlint-code-quality/rules/ruby/S033-samesite-cookie.md +29 -0
  51. package/skill-assets/sunlint-code-quality/rules/ruby/S034-host-prefix-cookie.md +30 -0
  52. package/skill-assets/sunlint-code-quality/rules/ruby/S035-app-hostnames.md +28 -0
  53. package/skill-assets/sunlint-code-quality/rules/ruby/S036-internal-file-paths.md +37 -0
  54. package/skill-assets/sunlint-code-quality/rules/ruby/S037-anti-cache-headers.md +31 -0
  55. package/skill-assets/sunlint-code-quality/rules/ruby/S039-tls-certificate-validation.md +29 -0
  56. package/skill-assets/sunlint-code-quality/rules/ruby/S041-logout-invalidation.md +31 -0
  57. package/skill-assets/sunlint-code-quality/rules/ruby/S042-long-lived-sessions.md +27 -0
  58. package/skill-assets/sunlint-code-quality/rules/ruby/S044-critical-changes-reauth.md +34 -0
  59. package/skill-assets/sunlint-code-quality/rules/ruby/S045-brute-force-protection.md +33 -0
  60. package/skill-assets/sunlint-code-quality/rules/ruby/S047-oauth-csrf-protection.md +33 -0
  61. package/skill-assets/sunlint-code-quality/rules/ruby/S048-oauth-redirect-validation.md +29 -0
  62. package/skill-assets/sunlint-code-quality/rules/ruby/S049-auth-code-expiry.md +31 -0
  63. package/skill-assets/sunlint-code-quality/rules/ruby/S050-token-entropy.md +26 -0
  64. package/skill-assets/sunlint-code-quality/rules/ruby/S051-password-length.md +38 -0
  65. package/skill-assets/sunlint-code-quality/rules/ruby/S052-otp-entropy.md +25 -0
  66. package/skill-assets/sunlint-code-quality/rules/ruby/S053-generic-error-messages.md +33 -0
  67. package/skill-assets/sunlint-code-quality/rules/ruby/S054-no-default-admin.md +29 -0
  68. package/skill-assets/sunlint-code-quality/rules/ruby/S055-content-type-validation.md +24 -0
  69. package/skill-assets/sunlint-code-quality/rules/ruby/S056-log-injection.md +28 -0
  70. package/skill-assets/sunlint-code-quality/rules/ruby/S057-synchronized-time.md +18 -0
  71. 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
  */
@@ -406,6 +406,7 @@ class CliActionHandler {
406
406
  */
407
407
  getPresetName() {
408
408
  if (this.options.all) return 'all preset';
409
+ if (this.options.specific) return 'language-specific preset';
409
410
  if (this.options.quality) return 'quality preset';
410
411
  if (this.options.security) return 'security preset';
411
412
  if (this.options.category) return `${this.options.category} preset`;
@@ -525,6 +526,7 @@ class CliActionHandler {
525
526
  return this.options.architecture &&
526
527
  !this.options.impact &&
527
528
  !this.options.all &&
529
+ !this.options.specific &&
528
530
  !this.options.rule &&
529
531
  !this.options.rules &&
530
532
  !this.options.quality &&
@@ -540,6 +542,7 @@ class CliActionHandler {
540
542
  return this.options.impact &&
541
543
  !this.options.architecture &&
542
544
  !this.options.all &&
545
+ !this.options.specific &&
543
546
  !this.options.rule &&
544
547
  !this.options.rules &&
545
548
  !this.options.quality &&
@@ -22,6 +22,7 @@ function createCliProgram() {
22
22
  .option('--rules <rules>', 'Run multiple rules (comma-separated)')
23
23
  .option('-a, --all', 'Run all available rules')
24
24
  .option('-c, --category <category>', 'Run rules by category (quality, security, logging, naming)')
25
+ .option('-s, --specific', 'Run language-specific rules (requires --languages)')
25
26
  .option('--quality', 'Run all code quality rules')
26
27
  .option('--security', 'Run all secure coding rules');
27
28
 
@@ -108,6 +109,12 @@ Examples:
108
109
  sunlint --quality --input=src # Quality rules only
109
110
  sunlint --security --input=src # Security rules only
110
111
 
112
+ Language-Specific:
113
+ sunlint --specific --languages=dart --input=lib # Dart rules only
114
+ sunlint --specific --languages=kotlin --input=app/src # Kotlin rules only
115
+ sunlint --specific --languages=dart,kotlin --input=. # Dart + Kotlin rules
116
+ sunlint --all --languages=dart --input=lib # All rules + Dart rules
117
+
111
118
  Git Integration:
112
119
  sunlint --all --changed-files --input=. # Changed files only
113
120
  sunlint --all --staged-files --input=. # Staged files only
@@ -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');
@@ -60,6 +62,48 @@ class RuleSelectionService {
60
62
  }
61
63
  }
62
64
 
65
+ /**
66
+ * Collect language-specific rules from ALL versions of released-rules.json
67
+ * Searches across all versions because the latest version may have empty categories
68
+ * @param {string[]} languages - Array of language names (e.g., ['dart', 'kotlin'])
69
+ * @returns {string[]} Array of rule IDs matching the specified languages
70
+ */
71
+ collectLanguageSpecificRules(languages) {
72
+ const { getLanguageFromRuleId } = require('./constants/rules');
73
+
74
+ try {
75
+ if (!fs.existsSync(this.releasedRulesPath)) {
76
+ return [];
77
+ }
78
+
79
+ const data = JSON.parse(fs.readFileSync(this.releasedRulesPath, 'utf8'));
80
+ const versions = data.versions || [];
81
+
82
+ if (versions.length === 0) {
83
+ return [];
84
+ }
85
+
86
+ const targetLanguages = new Set(languages.map(l => l.toLowerCase()));
87
+ const matchingRuleIds = new Set();
88
+
89
+ for (const versionEntry of versions) {
90
+ const rulesByCategory = versionEntry.rulesByCategory || {};
91
+ for (const categoryRules of Object.values(rulesByCategory)) {
92
+ for (const ruleId of categoryRules) {
93
+ const ruleLanguage = getLanguageFromRuleId(ruleId);
94
+ if (ruleLanguage && targetLanguages.has(ruleLanguage)) {
95
+ matchingRuleIds.add(ruleId);
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ return Array.from(matchingRuleIds);
102
+ } catch (error) {
103
+ return [];
104
+ }
105
+ }
106
+
63
107
  async selectRules(config, options) {
64
108
  // Ensure adapter is initialized
65
109
  await this.initialize();
@@ -75,6 +119,22 @@ class RuleSelectionService {
75
119
  selectedRules = [options.rule];
76
120
  } else if (options.rules) {
77
121
  selectedRules = options.rules.split(',').map(r => r.trim());
122
+ } else if (options.specific) {
123
+ // --specific requires --languages to determine which language rules to load
124
+ if (!options.languages) {
125
+ throw new Error(
126
+ chalk.red('--specific requires --languages to be specified\n') +
127
+ chalk.gray('Example: sunlint --specific --languages=dart --input=lib')
128
+ );
129
+ }
130
+ const targetLanguages = options.languages.split(',').map(l => l.trim());
131
+ selectedRules = this.collectLanguageSpecificRules(targetLanguages);
132
+
133
+ if (selectedRules.length === 0) {
134
+ console.warn(
135
+ chalk.yellow(`\u26A0\uFE0F No language-specific rules found for: ${targetLanguages.join(', ')}`)
136
+ );
137
+ }
78
138
  } else if (options.all) {
79
139
  // Load all rules from released-rules.json
80
140
  if (releasedRules) {
@@ -89,6 +149,20 @@ class RuleSelectionService {
89
149
  // Fallback to preset file
90
150
  selectedRules = this.loadPresetRules('all');
91
151
  }
152
+
153
+ // Augment with language-specific rules when --languages is specified
154
+ // This handles the case where the latest version has empty categories
155
+ if (options.languages) {
156
+ const targetLanguages = options.languages.split(',').map(l => l.trim());
157
+ const languageRules = this.collectLanguageSpecificRules(targetLanguages);
158
+ const existingRuleSet = new Set(selectedRules);
159
+
160
+ for (const ruleId of languageRules) {
161
+ if (!existingRuleSet.has(ruleId)) {
162
+ selectedRules.push(ruleId);
163
+ }
164
+ }
165
+ }
92
166
  } else if (options.quality) {
93
167
  // Load Common rules from released-rules.json
94
168
  if (releasedRules && releasedRules.Common) {
@@ -137,14 +211,33 @@ class RuleSelectionService {
137
211
  selectedRules = selectedRules.filter(ruleId => !disabledRules.has(ruleId));
138
212
  }
139
213
 
140
- // 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
141
218
  return selectedRules.map(ruleId => {
142
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
+
143
236
  return {
237
+ ...(adapterRule || {}),
144
238
  id: ruleId,
145
239
  name: this.getRuleName(ruleId),
146
- severity: 'warning',
147
- ...(adapterRule || {})
240
+ severity // Must come AFTER spread to override adapter's severity
148
241
  };
149
242
  }).filter(rule => rule.id);
150
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.45",
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
+ ---