@sun-asterisk/sunlint 1.3.48 → 1.3.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/file-targeting-service.js +148 -15
- package/core/init-command.js +118 -70
- package/core/project-detector.js +517 -0
- package/core/tui-select.js +245 -0
- package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
- package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
- package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
- package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
- package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
- package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
- package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
- package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
- package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Use try-with-resources for AutoCloseable
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents resource leaks by ensuring streams, connections, and other closeable resources are always closed
|
|
5
|
+
tags: resource-management, best-practice, java, error-prone
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Always Use try-with-resources for AutoCloseable
|
|
9
|
+
|
|
10
|
+
Since Java 7, the `try`-with-resources statement automatically closes any resource that implements `AutoCloseable` or `Closeable`. Using manual `finally` blocks to close resources is verbose, error-prone, and can silently swallow exceptions thrown during close.
|
|
11
|
+
|
|
12
|
+
**Incorrect (manual finally block):**
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
public void readFile(String path) throws IOException {
|
|
16
|
+
InputStream in = null;
|
|
17
|
+
try {
|
|
18
|
+
in = new FileInputStream(path);
|
|
19
|
+
// process stream
|
|
20
|
+
int b = in.read();
|
|
21
|
+
} catch (IOException e) {
|
|
22
|
+
logger.error("Failed to read file", e);
|
|
23
|
+
throw e;
|
|
24
|
+
} finally {
|
|
25
|
+
if (in != null) {
|
|
26
|
+
try {
|
|
27
|
+
in.close(); // exception here swallows the original
|
|
28
|
+
} catch (IOException ignored) {}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public void queryDatabase(Connection conn) throws SQLException {
|
|
34
|
+
Statement stmt = null;
|
|
35
|
+
ResultSet rs = null;
|
|
36
|
+
try {
|
|
37
|
+
stmt = conn.createStatement();
|
|
38
|
+
rs = stmt.executeQuery("SELECT * FROM users");
|
|
39
|
+
} finally {
|
|
40
|
+
if (rs != null) rs.close(); // may throw, hiding original
|
|
41
|
+
if (stmt != null) stmt.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Correct (try-with-resources):**
|
|
47
|
+
|
|
48
|
+
```java
|
|
49
|
+
public void readFile(String path) throws IOException {
|
|
50
|
+
try (InputStream in = new FileInputStream(path)) {
|
|
51
|
+
// process stream
|
|
52
|
+
int b = in.read();
|
|
53
|
+
}
|
|
54
|
+
// in is automatically closed even if an exception occurs
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public void queryDatabase(Connection conn) throws SQLException {
|
|
58
|
+
try (Statement stmt = conn.createStatement();
|
|
59
|
+
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
|
|
60
|
+
while (rs.next()) {
|
|
61
|
+
// process results
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// both stmt and rs are closed in reverse declaration order
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Also works with custom AutoCloseable resources
|
|
68
|
+
public void processResource() throws Exception {
|
|
69
|
+
try (MyService service = new MyService()) {
|
|
70
|
+
service.execute();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Key benefits:**
|
|
76
|
+
- Resources are closed in reverse declaration order, automatically.
|
|
77
|
+
- Original exceptions are never swallowed by close failures (suppressed exceptions).
|
|
78
|
+
- Cleaner, less boilerplate code.
|
|
79
|
+
|
|
80
|
+
**Resources that must use try-with-resources:**
|
|
81
|
+
- `InputStream` / `OutputStream` / `Reader` / `Writer`
|
|
82
|
+
- `Connection` / `Statement` / `ResultSet` (JDBC)
|
|
83
|
+
- `HttpClient` / `Socket`
|
|
84
|
+
- Any class implementing `AutoCloseable`
|
|
85
|
+
|
|
86
|
+
**Tools:** PMD (`UseTryWithResources`), SonarQube (`S2093`), IntelliJ Inspections
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Override equals() and hashCode() Together
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: violating the equals-hashCode contract breaks HashMap, HashSet, and other hash-based collections silently
|
|
5
|
+
tags: correctness, contract, java, error-prone
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Override equals() and hashCode() Together
|
|
9
|
+
|
|
10
|
+
In Java, `equals()` and `hashCode()` share a contract: objects that are equal (via `equals()`) **must** have the same hash code. If you override one without overriding the other, hash-based collections (`HashMap`, `HashSet`, `Hashtable`) will malfunction — objects may become unreachable or duplicates may appear.
|
|
11
|
+
|
|
12
|
+
**Incorrect (only overrides equals):**
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
public class User {
|
|
16
|
+
private Long id;
|
|
17
|
+
private String email;
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public boolean equals(Object o) {
|
|
21
|
+
if (this == o) return true;
|
|
22
|
+
if (!(o instanceof User)) return false;
|
|
23
|
+
User user = (User) o;
|
|
24
|
+
return Objects.equals(id, user.id);
|
|
25
|
+
}
|
|
26
|
+
// Missing hashCode! — HashSet will treat two equal Users as different objects
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Result: map.put(user1); map.get(user2) returns null even if user1.equals(user2)
|
|
30
|
+
Set<User> set = new HashSet<>();
|
|
31
|
+
set.add(new User(1L, "a@b.com"));
|
|
32
|
+
set.contains(new User(1L, "a@b.com")); // false! Bug!
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Incorrect (only overrides hashCode):**
|
|
36
|
+
|
|
37
|
+
```java
|
|
38
|
+
public class Order {
|
|
39
|
+
private Long id;
|
|
40
|
+
|
|
41
|
+
@Override
|
|
42
|
+
public int hashCode() {
|
|
43
|
+
return Objects.hash(id);
|
|
44
|
+
}
|
|
45
|
+
// Missing equals! — equals uses identity (==) by default
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Correct (both overridden consistently):**
|
|
50
|
+
|
|
51
|
+
```java
|
|
52
|
+
public class User {
|
|
53
|
+
private Long id;
|
|
54
|
+
private String email;
|
|
55
|
+
|
|
56
|
+
@Override
|
|
57
|
+
public boolean equals(Object o) {
|
|
58
|
+
if (this == o) return true;
|
|
59
|
+
if (!(o instanceof User)) return false;
|
|
60
|
+
User user = (User) o;
|
|
61
|
+
return Objects.equals(id, user.id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Override
|
|
65
|
+
public int hashCode() {
|
|
66
|
+
return Objects.hash(id); // same fields as equals
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Using records (Java 16+): equals and hashCode are auto-generated
|
|
71
|
+
public record User(Long id, String email) {}
|
|
72
|
+
|
|
73
|
+
// Using Lombok:
|
|
74
|
+
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
|
75
|
+
public class User {
|
|
76
|
+
@EqualsAndHashCode.Include
|
|
77
|
+
private Long id;
|
|
78
|
+
private String email;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Rules to follow:**
|
|
83
|
+
- Use the **same fields** in both `equals()` and `hashCode()`.
|
|
84
|
+
- Prefer `Objects.equals()` and `Objects.hash()` over manual null checks.
|
|
85
|
+
- Consider using Lombok `@EqualsAndHashCode` or Java Records for value objects.
|
|
86
|
+
- Never include mutable fields in `hashCode()` if the object will be stored in a hash collection.
|
|
87
|
+
|
|
88
|
+
**Tools:** PMD (`OverrideBothEqualsAndHashcode`), Checkstyle (`EqualsHashCode`), IntelliJ Inspections
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use equals() for String and Object Comparison, Not ==
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: using == compares references, not values, causing silent bugs that only appear at runtime
|
|
5
|
+
tags: correctness, java, error-prone, string
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use equals() for String and Object Comparison, Not ==
|
|
9
|
+
|
|
10
|
+
In Java, `==` checks **reference equality** (are these the same object in memory?). For Strings and other objects, you almost always want **value equality** — use `.equals()`. While string literals may be interned (sharing references), strings from variables, method returns, or `new String(...)` will have different references, making `==` unreliable.
|
|
11
|
+
|
|
12
|
+
**Incorrect (using == for string comparison):**
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
String status = getStatusFromDatabase(); // returns "ACTIVE"
|
|
16
|
+
if (status == "ACTIVE") { // BUG: may be false even when value is "ACTIVE"
|
|
17
|
+
activateUser();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
String s1 = new String("hello");
|
|
21
|
+
String s2 = new String("hello");
|
|
22
|
+
if (s1 == s2) { // always false — different objects
|
|
23
|
+
// never executed
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public boolean isAdmin(String role) {
|
|
27
|
+
return role == "ADMIN"; // unreliable
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct (using equals for string comparison):**
|
|
32
|
+
|
|
33
|
+
```java
|
|
34
|
+
String status = getStatusFromDatabase();
|
|
35
|
+
if ("ACTIVE".equals(status)) { // null-safe: won't NPE if status is null
|
|
36
|
+
activateUser();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Or with Objects.equals for null-safety on both sides:
|
|
40
|
+
if (Objects.equals(status, "ACTIVE")) {
|
|
41
|
+
activateUser();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public boolean isAdmin(String role) {
|
|
45
|
+
return "ADMIN".equals(role); // preferred: literal first to avoid NPE
|
|
46
|
+
// or: return role != null && role.equals("ADMIN");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// For enums: use == (enums are singletons, == is correct and preferred)
|
|
50
|
+
if (status == Status.ACTIVE) { // correct for enums
|
|
51
|
+
activateUser();
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Literal-first pattern:**
|
|
56
|
+
|
|
57
|
+
Placing the literal on the left side of `equals()` is a common defensive pattern — if the variable is `null`, the call returns `false` instead of throwing `NullPointerException`:
|
|
58
|
+
|
|
59
|
+
```java
|
|
60
|
+
// Risky: may throw NPE if name is null
|
|
61
|
+
if (name.equals("John")) { ... }
|
|
62
|
+
|
|
63
|
+
// Safe: returns false if name is null
|
|
64
|
+
if ("John".equals(name)) { ... }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Key distinctions:**
|
|
68
|
+
- `String` and all objects: use `.equals()` or `Objects.equals()`
|
|
69
|
+
- `enum` types: use `==` (enums are singleton instances)
|
|
70
|
+
- Primitives (`int`, `boolean`, etc.): use `==`
|
|
71
|
+
|
|
72
|
+
**Tools:** PMD (`CompareObjectsWithEquals`, `UseEqualsToCompareStrings`), FindBugs/SpotBugs (`ES_COMPARING_STRINGS_WITH_EQ`), IntelliJ Inspections
|
|
@@ -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
|