@sun-asterisk/sunlint 1.3.47 → 1.3.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/config/rules/rules-registry-generated.json +1717 -282
  2. package/core/architecture-integration.js +57 -15
  3. package/core/cli-action-handler.js +51 -36
  4. package/core/config-manager.js +6 -0
  5. package/core/config-merger.js +33 -0
  6. package/core/config-validator.js +37 -2
  7. package/core/file-targeting-service.js +148 -15
  8. package/core/init-command.js +118 -70
  9. package/core/output-service.js +12 -3
  10. package/core/project-detector.js +517 -0
  11. package/core/scoring-service.js +12 -6
  12. package/core/summary-report-service.js +9 -4
  13. package/core/tui-select.js +245 -0
  14. package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
  15. package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
  16. package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
  17. package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
  18. package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
  19. package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
  20. package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
  21. package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
  22. package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
  23. package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
  24. package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
  25. package/engines/impact/cli.js +54 -39
  26. package/engines/impact/config/default-config.js +105 -5
  27. package/engines/impact/core/impact-analyzer.js +12 -15
  28. package/engines/impact/core/utils/gitignore-parser.js +123 -0
  29. package/engines/impact/core/utils/method-call-graph.js +272 -87
  30. package/origin-rules/dart-en.md +1 -1
  31. package/origin-rules/go-en.md +231 -0
  32. package/origin-rules/php-en.md +107 -0
  33. package/origin-rules/python-en.md +113 -0
  34. package/origin-rules/ruby-en.md +607 -0
  35. package/package.json +1 -1
  36. package/scripts/copy-arch-detect.js +5 -1
  37. package/scripts/copy-impact-analyzer.js +5 -1
  38. package/scripts/generate-rules-registry.js +30 -14
  39. package/skill-assets/sunlint-code-quality/SKILL.md +3 -2
  40. package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
  41. package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
  42. package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
  43. package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
  44. package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
  45. package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
  46. package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
  47. package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
  48. package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
  49. package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
  50. package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
  51. package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
  52. package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
  53. package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
  54. package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
  55. package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
  56. package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
  57. package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
  58. package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
  59. package/skill-assets/sunlint-code-quality/rules/go/G001-explicit-error-handling.md +53 -0
  60. package/skill-assets/sunlint-code-quality/rules/go/G002-context-first-argument.md +44 -0
  61. package/skill-assets/sunlint-code-quality/rules/go/G003-receiver-consistency.md +38 -0
  62. package/skill-assets/sunlint-code-quality/rules/go/G004-avoid-panic.md +49 -0
  63. package/skill-assets/sunlint-code-quality/rules/go/G005-goroutine-leak-prevention.md +49 -0
  64. package/skill-assets/sunlint-code-quality/rules/go/G006-interface-consumer-definition.md +45 -0
  65. package/skill-assets/sunlint-code-quality/rules/go/GN001-gin-binding-validation.md +57 -0
  66. package/skill-assets/sunlint-code-quality/rules/go/GN002-gin-error-response.md +48 -0
  67. package/skill-assets/sunlint-code-quality/rules/go/GN003-graceful-shutdown.md +57 -0
  68. package/skill-assets/sunlint-code-quality/rules/go/GN004-gin-route-logical-grouping.md +54 -0
  69. package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
  70. package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
  71. package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
  72. package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
  73. package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
  74. package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
  75. package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
  76. package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
  77. package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
  78. package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
  79. package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
  80. package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
  81. package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
  82. package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
  83. package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
  84. package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
  85. package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
  86. package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
  87. package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
  88. package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
  89. package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
  90. package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
  91. package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
  92. package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
  93. package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
  94. package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
  95. package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
  96. package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
  97. package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
  98. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
  99. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
  100. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
  101. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
  102. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
  103. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
  104. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
  105. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
  106. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
  107. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
  108. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
  109. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
  110. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
  111. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
  112. package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
  113. package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
  114. package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
  115. package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
  116. package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
  117. package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
  118. package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
  119. package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
  120. package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
  121. package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
  122. package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
  123. package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
  124. package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
  125. package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
  126. package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
  127. package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
  128. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
  129. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
  130. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
  131. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
  132. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
  133. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
  134. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
  135. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
  136. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
  137. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
  138. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
  139. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
  140. package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
  141. package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
  142. package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
  143. package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
  144. package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
  145. package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
  146. package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
  147. package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
  148. package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
  149. package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
  150. package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
  151. package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
  152. package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
  153. package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
  154. package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
  155. package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
  156. package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
  157. package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
  158. package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
  159. package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
  160. package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
  161. package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
  162. package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
  163. package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
  164. package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
  165. package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
  166. package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
  167. package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
  168. package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
  169. package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
  170. package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
  171. package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
  172. package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
  173. package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
  174. package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
  175. package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
  176. package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
  177. package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
  178. package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
  179. package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
  180. package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
  181. package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
  182. package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
  183. package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
  184. package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
  185. package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: Use java.time Instead of Legacy Date and Calendar
3
+ impact: MEDIUM
4
+ impactDescription: java.util.Date and Calendar are mutable, thread-unsafe, and poorly designed; java.time provides a modern, immutable, thread-safe API
5
+ tags: best-practice, java, modernization, thread-safety
6
+ ---
7
+
8
+ ## Use java.time Instead of Legacy Date and Calendar
9
+
10
+ `java.util.Date` and `java.util.Calendar` were introduced in Java 1.0 and 1.1 respectively. They are mutable (not thread-safe), have confusing APIs (months are 0-indexed in `Calendar`), and are generally considered a design failure. Since Java 8, the `java.time` package (JSR-310) provides a comprehensive, immutable, thread-safe date/time API.
11
+
12
+ **Incorrect (using legacy Date/Calendar):**
13
+
14
+ ```java
15
+ import java.util.Date;
16
+ import java.util.Calendar;
17
+
18
+ public class OrderService {
19
+ public Date calculateDeliveryDate(Date orderDate, int days) {
20
+ Calendar cal = Calendar.getInstance();
21
+ cal.setTime(orderDate);
22
+ cal.add(Calendar.DAY_OF_MONTH, days); // Calendar.DAY_OF_MONTH is error-prone
23
+ return cal.getTime(); // mutable — caller can modify the returned value
24
+ }
25
+
26
+ public boolean isExpired(Date expiryDate) {
27
+ return new Date().after(expiryDate); // new Date() for "now" is verbose
28
+ }
29
+
30
+ // Storing timestamps
31
+ private Date createdAt = new Date(); // mutable — bad for records
32
+ }
33
+ ```
34
+
35
+ **Correct (using java.time):**
36
+
37
+ ```java
38
+ import java.time.LocalDate;
39
+ import java.time.LocalDateTime;
40
+ import java.time.ZonedDateTime;
41
+ import java.time.ZoneId;
42
+ import java.time.Instant;
43
+ import java.time.Duration;
44
+
45
+ public class OrderService {
46
+ public LocalDate calculateDeliveryDate(LocalDate orderDate, int days) {
47
+ return orderDate.plusDays(days); // fluent, immutable, clear
48
+ }
49
+
50
+ public boolean isExpired(LocalDateTime expiryDateTime) {
51
+ return LocalDateTime.now().isAfter(expiryDateTime);
52
+ }
53
+
54
+ // Storing timestamps
55
+ private final Instant createdAt = Instant.now(); // immutable
56
+
57
+ // Working with timezones
58
+ public ZonedDateTime getOrderTimeInJST(Instant instant) {
59
+ return instant.atZone(ZoneId.of("Asia/Tokyo"));
60
+ }
61
+
62
+ // Duration/period calculation
63
+ public long getDaysUntilDeadline(LocalDate deadline) {
64
+ return Duration.between(
65
+ LocalDate.now().atStartOfDay(),
66
+ deadline.atStartOfDay()
67
+ ).toDays();
68
+ }
69
+ }
70
+ ```
71
+
72
+ **Migration guide:**
73
+
74
+ | Legacy | java.time replacement |
75
+ |--------|----------------------|
76
+ | `java.util.Date` | `java.time.Instant` (for UTC timestamps) |
77
+ | `java.util.Calendar` | `java.time.ZonedDateTime` |
78
+ | `java.sql.Date` | `java.time.LocalDate` |
79
+ | `java.sql.Timestamp` | `java.time.LocalDateTime` or `Instant` |
80
+ | `SimpleDateFormat` | `java.time.format.DateTimeFormatter` |
81
+
82
+ **Key types in java.time:**
83
+ - `LocalDate` — date without time/timezone (e.g., 2024-01-15)
84
+ - `LocalTime` — time without date/timezone
85
+ - `LocalDateTime` — date + time, no timezone
86
+ - `ZonedDateTime` — date + time + timezone
87
+ - `Instant` — a Unix timestamp (UTC)
88
+ - `Duration` — time-based amount (seconds, nanoseconds)
89
+ - `Period` — date-based amount (years, months, days)
90
+
91
+ **Tools:** PMD (`ReplaceJavaUtilDate`, `ReplaceJavaUtilCalendar`), SonarQube (`S2143`)
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: Do Not Use printStackTrace(); Use a Logger
3
+ impact: MEDIUM
4
+ impactDescription: printStackTrace() outputs to stderr, cannot be controlled by log configuration, and may expose sensitive stack traces in production
5
+ tags: logging, best-practice, java, security
6
+ ---
7
+
8
+ ## Do Not Use printStackTrace(); Use a Logger
9
+
10
+ `e.printStackTrace()` writes directly to `System.err` — it bypasses the logging framework entirely, cannot be filtered, redirected, or formatted by log configuration, and in production environments the output may be lost or may expose sensitive internal details (class names, method names, library versions) to attackers.
11
+
12
+ **Incorrect (using printStackTrace):**
13
+
14
+ ```java
15
+ public void processPayment(Payment payment) {
16
+ try {
17
+ paymentGateway.charge(payment);
18
+ } catch (PaymentException e) {
19
+ e.printStackTrace(); // writes to System.err — uncontrolled output
20
+ }
21
+ }
22
+
23
+ public User findUser(Long id) {
24
+ try {
25
+ return userRepository.findById(id).orElseThrow();
26
+ } catch (Exception e) {
27
+ e.printStackTrace(); // loses structured logging, no context
28
+ return null;
29
+ }
30
+ }
31
+ ```
32
+
33
+ **Correct (using a logger):**
34
+
35
+ ```java
36
+ import org.slf4j.Logger;
37
+ import org.slf4j.LoggerFactory;
38
+
39
+ public class PaymentService {
40
+ private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);
41
+
42
+ public void processPayment(Payment payment) {
43
+ try {
44
+ paymentGateway.charge(payment);
45
+ } catch (PaymentException e) {
46
+ // Log at ERROR with context AND the exception (includes stack trace in log output)
47
+ logger.error("Payment failed for orderId={}, amount={}",
48
+ payment.getOrderId(), payment.getAmount(), e);
49
+ throw new PaymentProcessingException("Payment failed", e);
50
+ }
51
+ }
52
+
53
+ public User findUser(Long id) {
54
+ try {
55
+ return userRepository.findById(id)
56
+ .orElseThrow(() -> new UserNotFoundException("User not found: " + id));
57
+ } catch (UserNotFoundException e) {
58
+ logger.warn("User lookup failed: userId={}", id, e);
59
+ throw e;
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ **Differences between logging levels for exceptions:**
66
+
67
+ ```java
68
+ // ERROR: unexpected, system-level failure, requires immediate attention
69
+ logger.error("Database connection lost", e);
70
+
71
+ // WARN: recoverable error, degraded functionality
72
+ logger.warn("Payment gateway timeout, retrying: orderId={}", orderId, e);
73
+
74
+ // INFO: expected business exception (user not found, validation fail)
75
+ logger.info("Login attempt failed: username={}", username, e);
76
+ ```
77
+
78
+ **Passing the exception as the last argument** to SLF4J/Logback/Log4j2 automatically appends a full stack trace to the log entry — equivalent to calling `printStackTrace()` but through the proper logging pipeline.
79
+
80
+ **Tools:** PMD (`AvoidPrintStackTrace`), SonarQube (`S1148`), SpotBugs
@@ -0,0 +1,89 @@
1
+ ---
2
+ title: Do Not Use System.out.println in Production Code
3
+ impact: MEDIUM
4
+ impactDescription: System.out/err output bypasses logging configuration, cannot be disabled without code changes, and may leak sensitive data
5
+ tags: logging, best-practice, java, production
6
+ ---
7
+
8
+ ## Do Not Use System.out.println in Production Code
9
+
10
+ `System.out.println` and `System.err.println` write directly to standard output/error streams. In production:
11
+ - They cannot be filtered, leveled, or structured.
12
+ - They cannot be turned off without changing code and redeploying.
13
+ - They do not carry context (timestamp, thread, class name) unless added manually.
14
+ - Output may be lost in containerized or cloud environments.
15
+ - They may print sensitive data (credentials, PII) in server logs.
16
+
17
+ **Incorrect:**
18
+
19
+ ```java
20
+ public class AuthService {
21
+ public User authenticate(String username, String password) {
22
+ System.out.println("Authenticating user: " + username); // logs PII
23
+ System.out.println("password = " + password); // SECURITY RISK
24
+ try {
25
+ User user = userRepository.findByUsername(username);
26
+ System.out.println("User found: " + user.getEmail()); // PII leak
27
+ return user;
28
+ } catch (Exception e) {
29
+ System.err.println("Auth failed: " + e.getMessage()); // unstructured
30
+ return null;
31
+ }
32
+ }
33
+ }
34
+
35
+ public class DataProcessor {
36
+ public void process(List<Order> orders) {
37
+ System.out.println("Processing " + orders.size() + " orders"); // no structured log
38
+ for (Order order : orders) {
39
+ System.out.println("Order: " + order); // cannot be filtered
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ **Correct (using SLF4J/Logback):**
46
+
47
+ ```java
48
+ import org.slf4j.Logger;
49
+ import org.slf4j.LoggerFactory;
50
+
51
+ public class AuthService {
52
+ private static final Logger logger = LoggerFactory.getLogger(AuthService.class);
53
+
54
+ public User authenticate(String username, String password) {
55
+ logger.debug("Authenticating user: username={}", username);
56
+ // Never log the password
57
+ try {
58
+ User user = userRepository.findByUsername(username);
59
+ logger.info("Authentication successful: userId={}", user.getId());
60
+ return user;
61
+ } catch (UserNotFoundException e) {
62
+ logger.warn("Authentication failed: username={}", username);
63
+ throw e;
64
+ }
65
+ }
66
+ }
67
+
68
+ public class DataProcessor {
69
+ private static final Logger logger = LoggerFactory.getLogger(DataProcessor.class);
70
+
71
+ public void process(List<Order> orders) {
72
+ logger.info("Starting order processing: count={}", orders.size());
73
+ for (Order order : orders) {
74
+ logger.debug("Processing order: orderId={}", order.getId());
75
+ }
76
+ logger.info("Order processing complete: count={}", orders.size());
77
+ }
78
+ }
79
+ ```
80
+
81
+ **Exception for tests and scripts:**
82
+
83
+ `System.out.println` is acceptable only in:
84
+ - `main()` methods of CLI tools and standalone scripts
85
+ - Unit test setup code (though test frameworks' reporters are better)
86
+
87
+ Mark these with a `@SuppressWarnings("PMD.SystemPrintln")` annotation if needed.
88
+
89
+ **Tools:** PMD (`SystemPrintln`), SonarQube (`S106`), Checkstyle
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: Logger Must Be private static final
3
+ impact: MEDIUM
4
+ impactDescription: improper logger declaration can cause memory leaks, serialization issues, and incorrect class attribution in log output
5
+ tags: logging, best-practice, java, naming
6
+ ---
7
+
8
+ ## Logger Must Be private static final
9
+
10
+ A logger field should always be declared as `private static final` and named after the class it belongs to. This ensures:
11
+ - **`private`**: Not accessible from outside the class, encapsulated properly.
12
+ - **`static`**: Shared across all instances — not re-created per object, avoiding memory overhead.
13
+ - **`final`**: Cannot be reassigned — prevents accidental reassignment.
14
+ - **Named with the correct class**: Ensures log entries display the correct source class.
15
+
16
+ **Incorrect:**
17
+
18
+ ```java
19
+ // Not static — a new logger is created for every object instance
20
+ public class OrderService {
21
+ private final Logger logger = LoggerFactory.getLogger(OrderService.class); // bad: not static
22
+ }
23
+
24
+ // Not private — accessible from outside
25
+ public class UserService {
26
+ public static final Logger LOGGER = LoggerFactory.getLogger(UserService.class); // bad: public
27
+ }
28
+
29
+ // Wrong class name — log entries will show wrong class
30
+ public class PaymentService {
31
+ private static final Logger logger = LoggerFactory.getLogger(OrderService.class); // bad: wrong class
32
+ }
33
+
34
+ // Using java.util.logging without naming it LOG or logger consistently
35
+ public class ReportService {
36
+ private static Logger l = Logger.getLogger("reports"); // bad: name "l" is vague; string name is wrong
37
+ }
38
+ ```
39
+
40
+ **Correct:**
41
+
42
+ ```java
43
+ import org.slf4j.Logger;
44
+ import org.slf4j.LoggerFactory;
45
+
46
+ public class OrderService {
47
+ private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
48
+
49
+ public void createOrder(Order order) {
50
+ logger.info("Creating order: orderId={}", order.getId());
51
+ }
52
+ }
53
+
54
+ // For java.util.logging (standard library):
55
+ import java.util.logging.Logger;
56
+
57
+ public class ReportService {
58
+ private static final Logger logger = Logger.getLogger(ReportService.class.getName());
59
+ }
60
+
61
+ // For Log4j 2:
62
+ import org.apache.logging.log4j.Logger;
63
+ import org.apache.logging.log4j.LogManager;
64
+
65
+ public class NotificationService {
66
+ private static final Logger logger = LogManager.getLogger(NotificationService.class);
67
+ }
68
+
69
+ // With Lombok @Slf4j annotation (auto-generates private static final logger):
70
+ import lombok.extern.slf4j.Slf4j;
71
+
72
+ @Slf4j
73
+ public class InventoryService {
74
+ public void checkStock(Long productId) {
75
+ log.info("Checking stock: productId={}", productId); // 'log' is auto-generated
76
+ }
77
+ }
78
+ ```
79
+
80
+ **Standard conventions:**
81
+ - Variable name: `logger` (preferred) or `log` (common with Lombok)
82
+ - Avoid `LOG`, `LOGGER` (all-caps suggests a compile-time constant, loggers are runtime objects)
83
+ - Always pass the owning class literal: `LoggerFactory.getLogger(MyClass.class)`
84
+
85
+ **Frameworks supported:**
86
+ - SLF4J + Logback (recommended)
87
+ - Log4j 2
88
+ - `java.util.logging` (JUL, standard library)
89
+ - Lombok `@Slf4j`, `@Log4j2`, `@CommonsLog`
90
+
91
+ **Tools:** PMD (`ProperLogger`), SonarQube (`S1312`), IntelliJ Inspections
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Implement Thread-Safe Singleton Correctly
3
+ impact: HIGH
4
+ impactDescription: a non-thread-safe singleton can create multiple instances under concurrent load, breaking application invariants
5
+ tags: concurrency, design-pattern, java, thread-safety
6
+ ---
7
+
8
+ ## Implement Thread-Safe Singleton Correctly
9
+
10
+ The Singleton pattern ensures a class has only one instance. In multi-threaded Java applications, a naive singleton implementation can create multiple instances when two threads enter `getInstance()` simultaneously. This is a classic race condition.
11
+
12
+ **Incorrect (not thread-safe):**
13
+
14
+ ```java
15
+ // Lazy initialization — race condition when two threads call getInstance() simultaneously
16
+ public class ConfigManager {
17
+ private static ConfigManager instance;
18
+
19
+ private ConfigManager() {
20
+ loadConfiguration();
21
+ }
22
+
23
+ public static ConfigManager getInstance() {
24
+ if (instance == null) { // thread A and B both see null
25
+ instance = new ConfigManager(); // both create an instance — BUG
26
+ }
27
+ return instance;
28
+ }
29
+ }
30
+ ```
31
+
32
+ **Incorrect (broken double-checked locking without volatile):**
33
+
34
+ ```java
35
+ public class DatabasePool {
36
+ private static DatabasePool instance; // missing volatile — broken!
37
+
38
+ public static DatabasePool getInstance() {
39
+ if (instance == null) {
40
+ synchronized (DatabasePool.class) {
41
+ if (instance == null) {
42
+ instance = new DatabasePool(); // may be visible as partially initialized
43
+ }
44
+ }
45
+ }
46
+ return instance;
47
+ }
48
+ }
49
+ ```
50
+
51
+ **Correct (Initialization-on-demand holder idiom — preferred):**
52
+
53
+ ```java
54
+ public class ConfigManager {
55
+ private ConfigManager() {
56
+ loadConfiguration();
57
+ }
58
+
59
+ // JVM class loading guarantees thread-safe, lazy initialization
60
+ private static final class Holder {
61
+ private static final ConfigManager INSTANCE = new ConfigManager();
62
+ }
63
+
64
+ public static ConfigManager getInstance() {
65
+ return Holder.INSTANCE;
66
+ }
67
+ }
68
+ ```
69
+
70
+ **Correct (double-checked locking with volatile — acceptable):**
71
+
72
+ ```java
73
+ public class DatabasePool {
74
+ private static volatile DatabasePool instance; // volatile is required
75
+
76
+ private DatabasePool() {}
77
+
78
+ public static DatabasePool getInstance() {
79
+ if (instance == null) {
80
+ synchronized (DatabasePool.class) {
81
+ if (instance == null) {
82
+ instance = new DatabasePool();
83
+ }
84
+ }
85
+ }
86
+ return instance;
87
+ }
88
+ }
89
+ ```
90
+
91
+ **Correct (enum singleton — simplest and safest):**
92
+
93
+ ```java
94
+ // Enum singletons are thread-safe by JVM specification and handle serialization correctly
95
+ public enum AppConfig {
96
+ INSTANCE;
97
+
98
+ private final String dbUrl;
99
+
100
+ AppConfig() {
101
+ dbUrl = System.getenv("DB_URL");
102
+ }
103
+
104
+ public String getDbUrl() {
105
+ return dbUrl;
106
+ }
107
+ }
108
+
109
+ // Usage:
110
+ AppConfig.INSTANCE.getDbUrl();
111
+ ```
112
+
113
+ **Preferred approaches (in order):**
114
+ 1. **Enum singleton** — safest, handles serialization automatically
115
+ 2. **Initialization-on-demand holder** — lazy, thread-safe, no synchronization overhead
116
+ 3. **Double-checked locking with `volatile`** — acceptable for legacy code
117
+ 4. **Spring `@Component` / `@Service`** — let the DI container manage lifecycle (best for application code)
118
+
119
+ **Tools:** PMD (`NonThreadSafeSingleton`, `DoubleCheckedLocking`), FindBugs/SpotBugs (`DC_DOUBLECHECK`, `ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD`), SonarQube (`S2168`)
@@ -0,0 +1,82 @@
1
+ ---
2
+ title: Utility Classes Must Have a Private Constructor
3
+ impact: LOW
4
+ impactDescription: utility classes with public or default constructors can be accidentally instantiated, wasting memory and exposing confusing API
5
+ tags: design, best-practice, java, clean-code
6
+ ---
7
+
8
+ ## Utility Classes Must Have a Private Constructor
9
+
10
+ A **utility class** is a class that consists only of `static` methods and/or constants — it is never meant to be instantiated (e.g., `java.util.Collections`, `java.util.Arrays`, `Math`). Without a private constructor, a utility class:
11
+ - Can be instantiated by mistake, creating useless objects.
12
+ - May confuse callers about whether an instance has state.
13
+ - May be accidentally extended, inheriting a public constructor.
14
+
15
+ **Incorrect:**
16
+
17
+ ```java
18
+ // No constructor — Java adds a public default constructor automatically
19
+ public class StringUtils {
20
+ public static String capitalize(String s) { ... }
21
+ public static boolean isBlank(String s) { ... }
22
+ }
23
+
24
+ // Default-visibility constructor — package-accessible
25
+ public class MathUtils {
26
+ MathUtils() {} // package-private, should be private
27
+ public static int add(int a, int b) { return a + b; }
28
+ }
29
+
30
+ // Can be instantiated:
31
+ StringUtils utils = new StringUtils(); // meaningless object
32
+ ```
33
+
34
+ **Correct:**
35
+
36
+ ```java
37
+ public final class StringUtils { // final prevents subclassing
38
+ private StringUtils() {
39
+ // Utility class — do not instantiate
40
+ throw new UnsupportedOperationException("Utility class cannot be instantiated");
41
+ }
42
+
43
+ public static String capitalize(String s) {
44
+ if (s == null || s.isEmpty()) return s;
45
+ return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
46
+ }
47
+
48
+ public static boolean isBlank(String s) {
49
+ return s == null || s.trim().isEmpty();
50
+ }
51
+ }
52
+
53
+ public final class DateUtils {
54
+ private DateUtils() {
55
+ throw new UnsupportedOperationException("Utility class");
56
+ }
57
+
58
+ public static LocalDate parseDate(String date) {
59
+ return LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE);
60
+ }
61
+ }
62
+ ```
63
+
64
+ **Using Lombok `@UtilityClass`:**
65
+
66
+ ```java
67
+ import lombok.experimental.UtilityClass;
68
+
69
+ @UtilityClass // auto-generates private constructor + throws exception + makes class final
70
+ public class ValidationUtils {
71
+ public boolean isValidEmail(String email) {
72
+ return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
73
+ }
74
+ }
75
+ ```
76
+
77
+ **Conditions for a utility class:**
78
+ - All methods are `static`
79
+ - No instance fields (or only `static final` constants)
80
+ - The class is not designed as a base class
81
+
82
+ **Tools:** Checkstyle (`HideUtilityClassConstructor`), PMD (`UseUtilityClass`), SonarQube (`S1118`)
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Preserve Stack Trace When Rethrowing Exceptions
3
+ impact: HIGH
4
+ impactDescription: discarding the original exception's stack trace makes debugging nearly impossible — the root cause is permanently lost
5
+ tags: error-handling, best-practice, java, debugging
6
+ ---
7
+
8
+ ## Preserve Stack Trace When Rethrowing Exceptions
9
+
10
+ When catching an exception and throwing a new one, always pass the original exception as the **cause**. If you only rethrow a new exception with `e.getMessage()` or no cause at all, the original stack trace — which shows where the error actually originated — is permanently discarded.
11
+
12
+ **Incorrect (losing the stack trace):**
13
+
14
+ ```java
15
+ public void processOrder(Order order) throws OrderException {
16
+ try {
17
+ paymentService.charge(order.getPayment());
18
+ } catch (PaymentException e) {
19
+ // Message-only: root cause stack trace is lost
20
+ throw new OrderException("Payment failed: " + e.getMessage());
21
+ }
22
+ }
23
+
24
+ public User loadUser(Long id) throws ServiceException {
25
+ try {
26
+ return userRepository.findById(id).orElseThrow();
27
+ } catch (Exception e) {
28
+ // No cause: impossible to trace back to the original error
29
+ throw new ServiceException("User not found");
30
+ }
31
+ }
32
+
33
+ public void sendEmail(String to, String body) {
34
+ try {
35
+ emailClient.send(to, body);
36
+ } catch (EmailException e) {
37
+ logger.error("Email failed"); // No exception logged — stack trace lost
38
+ throw new RuntimeException("Email failed"); // No cause chained
39
+ }
40
+ }
41
+ ```
42
+
43
+ **Correct (preserving the stack trace):**
44
+
45
+ ```java
46
+ public void processOrder(Order order) throws OrderException {
47
+ try {
48
+ paymentService.charge(order.getPayment());
49
+ } catch (PaymentException e) {
50
+ // Pass the original exception as the cause
51
+ throw new OrderException("Payment failed for orderId=" + order.getId(), e);
52
+ }
53
+ }
54
+
55
+ public User loadUser(Long id) throws ServiceException {
56
+ try {
57
+ return userRepository.findById(id)
58
+ .orElseThrow(() -> new UserNotFoundException("User not found: id=" + id));
59
+ } catch (UserNotFoundException e) {
60
+ throw new ServiceException("User lookup failed: id=" + id, e); // chained
61
+ }
62
+ }
63
+
64
+ public void sendEmail(String to, String body) {
65
+ try {
66
+ emailClient.send(to, body);
67
+ } catch (EmailException e) {
68
+ logger.error("Email sending failed: recipient={}", to, e); // log with cause
69
+ throw new NotificationException("Email failed", e); // chain the cause
70
+ }
71
+ }
72
+ ```
73
+
74
+ **Using initCause for exceptions without cause constructors:**
75
+
76
+ ```java
77
+ try {
78
+ someOperation();
79
+ } catch (SomeException e) {
80
+ RuntimeException wrapped = new RuntimeException("Operation failed");
81
+ wrapped.initCause(e); // alternative to constructor argument
82
+ throw wrapped;
83
+ }
84
+ ```
85
+
86
+ **Inspecting cause chains:**
87
+
88
+ ```java
89
+ // Cause chain is accessible at runtime
90
+ try {
91
+ processOrder(order);
92
+ } catch (OrderException e) {
93
+ Throwable cause = e.getCause(); // retrieves PaymentException
94
+ logger.error("Root cause: {}", cause.getMessage());
95
+ }
96
+ ```
97
+
98
+ **Adding suppressed exceptions (try-with-resources pattern):**
99
+
100
+ ```java
101
+ // When closing a resource also throws — use addSuppressed
102
+ Exception primary = null;
103
+ try {
104
+ doWork();
105
+ } catch (Exception e) {
106
+ primary = e;
107
+ throw e;
108
+ } finally {
109
+ try {
110
+ resource.close();
111
+ } catch (Exception closeEx) {
112
+ if (primary != null) {
113
+ primary.addSuppressed(closeEx); // preserves both
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ **Tools:** PMD (`PreserveStackTrace`), SpotBugs (`REC_CATCH_EXCEPTION`), SonarQube (`S1166`)