@sun-asterisk/sunlint 1.0.7 → 1.1.4

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 (219) hide show
  1. package/.sunlint.json +35 -0
  2. package/CHANGELOG.md +30 -3
  3. package/CONTRIBUTING.md +235 -0
  4. package/PROJECT_STRUCTURE.md +60 -0
  5. package/README.md +146 -58
  6. package/cli.js +1 -0
  7. package/config/README.md +88 -0
  8. package/config/defaults/ai-rules-context.json +231 -0
  9. package/config/engines/engines.json +49 -0
  10. package/config/engines/eslint-rule-mapping.json +74 -0
  11. package/config/eslint-rule-mapping.json +126 -0
  12. package/config/integrations/eslint/base.config.js +125 -0
  13. package/config/integrations/eslint/simple.config.js +24 -0
  14. package/config/presets/strict.json +0 -1
  15. package/config/rule-analysis-strategies.js +74 -0
  16. package/config/{rules-registry.json → rules/rules-registry.json} +30 -7
  17. package/core/analysis-orchestrator.js +383 -591
  18. package/core/ast-modules/README.md +103 -0
  19. package/core/ast-modules/base-parser.js +90 -0
  20. package/core/ast-modules/index.js +97 -0
  21. package/core/ast-modules/package.json +37 -0
  22. package/core/ast-modules/parsers/eslint-js-parser.js +153 -0
  23. package/core/ast-modules/parsers/eslint-ts-parser.js +98 -0
  24. package/core/ast-modules/parsers/javascript-parser.js +187 -0
  25. package/core/ast-modules/parsers/typescript-parser.js +187 -0
  26. package/core/cli-action-handler.js +271 -255
  27. package/core/cli-program.js +18 -4
  28. package/core/config-manager.js +9 -3
  29. package/core/config-merger.js +40 -1
  30. package/core/config-validator.js +2 -2
  31. package/core/dependency-checker.js +125 -0
  32. package/core/enhanced-rules-registry.js +331 -0
  33. package/core/file-targeting-service.js +92 -23
  34. package/core/interfaces/analysis-engine.interface.js +100 -0
  35. package/core/multi-rule-runner.js +0 -221
  36. package/core/output-service.js +1 -1
  37. package/core/rule-mapping-service.js +1 -1
  38. package/core/rule-selection-service.js +10 -2
  39. package/core/smart-installer.js +164 -0
  40. package/docs/AI.md +163 -0
  41. package/docs/ARCHITECTURE.md +78 -0
  42. package/docs/CI-CD-GUIDE.md +315 -0
  43. package/docs/COMMAND-EXAMPLES.md +256 -0
  44. package/docs/CONFIGURATION.md +414 -0
  45. package/docs/DEBUG.md +86 -0
  46. package/docs/DEPENDENCIES.md +90 -0
  47. package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
  48. package/docs/DISTRIBUTION.md +153 -0
  49. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  50. package/docs/ESLINT_INTEGRATION.md +238 -0
  51. package/docs/FOLDER_STRUCTURE.md +59 -0
  52. package/docs/FUTURE_PACKAGES.md +83 -0
  53. package/docs/HEURISTIC_VS_AI.md +113 -0
  54. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +112 -0
  55. package/docs/PRODUCTION_SIZE_IMPACT.md +183 -0
  56. package/docs/README.md +32 -0
  57. package/docs/RELEASE_GUIDE.md +230 -0
  58. package/engines/eslint-engine.js +610 -0
  59. package/engines/heuristic-engine.js +864 -0
  60. package/engines/openai-engine.js +374 -0
  61. package/engines/tree-sitter-parser.js +0 -0
  62. package/engines/universal-ast-engine.js +0 -0
  63. package/integrations/eslint/README.md +99 -0
  64. package/integrations/eslint/configs/.eslintrc.js +98 -0
  65. package/integrations/eslint/configs/eslint.config.js +133 -0
  66. package/integrations/eslint/configs/eslint.config.simple.js +24 -0
  67. package/integrations/eslint/package.json +23 -0
  68. package/integrations/eslint/plugin/index.js +164 -0
  69. package/integrations/eslint/plugin/package.json +13 -0
  70. package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
  71. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
  72. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
  73. package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
  74. package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
  75. package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
  76. package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
  77. package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
  78. package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
  79. package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
  80. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
  81. package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
  82. package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
  83. package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
  84. package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
  85. package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
  86. package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
  87. package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
  88. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
  89. package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
  90. package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
  91. package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
  92. package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
  93. package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
  94. package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
  95. package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  96. package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
  97. package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
  98. package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
  99. package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
  100. package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
  101. package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
  102. package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
  103. package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
  104. package/integrations/eslint/tsconfig.json +27 -0
  105. package/package.json +61 -21
  106. package/rules/README.md +252 -0
  107. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  108. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  109. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  110. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  111. package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
  112. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  113. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  114. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  115. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  116. package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
  117. package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
  118. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  119. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  120. package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
  121. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
  122. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  123. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  124. package/rules/docs/C002_no_duplicate_code.md +57 -0
  125. package/rules/index.js +149 -0
  126. package/rules/migration/converter.js +385 -0
  127. package/rules/migration/mapping.json +164 -0
  128. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  129. package/rules/security/S026_json_schema_validation/config.json +27 -0
  130. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
  131. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  132. package/rules/security/S029_csrf_protection/analyzer.js +264 -0
  133. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  134. package/rules/universal/C010/generic.js +0 -0
  135. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  136. package/rules/utils/ast-utils.js +191 -0
  137. package/rules/utils/base-analyzer.js +98 -0
  138. package/rules/utils/pattern-matchers.js +239 -0
  139. package/rules/utils/rule-helpers.js +264 -0
  140. package/rules/utils/severity-constants.js +93 -0
  141. package/scripts/build-release.sh +117 -0
  142. package/scripts/ci-report.js +179 -0
  143. package/scripts/install.sh +196 -0
  144. package/scripts/manual-release.sh +338 -0
  145. package/scripts/merge-reports.js +424 -0
  146. package/scripts/pre-release-test.sh +175 -0
  147. package/scripts/prepare-release.sh +202 -0
  148. package/scripts/setup-github-registry.sh +42 -0
  149. package/scripts/test-scripts/README.md +22 -0
  150. package/scripts/test-scripts/test-c041-comparison.js +114 -0
  151. package/scripts/test-scripts/test-c041-eslint.js +67 -0
  152. package/scripts/test-scripts/test-eslint-rules.js +146 -0
  153. package/scripts/test-scripts/test-real-world.js +44 -0
  154. package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
  155. package/scripts/trigger-release.sh +285 -0
  156. package/scripts/validate-rule-structure.js +148 -0
  157. package/scripts/verify-install.sh +82 -0
  158. package/config/sunlint-schema.json +0 -159
  159. package/config/typescript/custom-rules.js +0 -9
  160. package/config/typescript/package-lock.json +0 -1585
  161. package/config/typescript/package.json +0 -13
  162. package/config/typescript/security-rules/index.js +0 -90
  163. package/config/typescript/tsconfig.json +0 -29
  164. package/core/ai-analyzer.js +0 -169
  165. package/core/eslint-engine-service.js +0 -312
  166. package/core/eslint-instance-manager.js +0 -104
  167. package/core/eslint-integration-service.js +0 -363
  168. package/core/sunlint-engine-service.js +0 -23
  169. package/core/typescript-analyzer.js +0 -262
  170. package/core/typescript-engine.js +0 -313
  171. /package/config/{default.json → defaults/default.json} +0 -0
  172. /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
  173. /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
  174. /package/config/{typescript → testing}/test-s005-working.ts +0 -0
  175. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
  176. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
  177. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
  178. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
  179. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
  180. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
  181. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
  182. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
  183. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
  184. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
  185. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
  186. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
  187. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
  188. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
  189. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
  190. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
  191. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
  192. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
  193. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
  194. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
  195. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
  196. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
  197. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
  198. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
  199. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
  200. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
  201. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
  202. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
  203. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
  204. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
  205. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
  206. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
  207. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
  208. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
  209. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
  210. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
  211. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
  212. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
  213. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
  214. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
  215. /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
  216. /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
  217. /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
  218. /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
  219. /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Custom ESLint rule for: C029 – Every `catch` block must log the error cause
3
+ * Rule ID: custom/c029
4
+ * Goal: Catching errors without logging/rethrowing can hide bugs
5
+ *
6
+ * NOTE: This ESLint rule provides basic catch block validation.
7
+ * For enhanced analysis with context validation and multi-level severity,
8
+ * use SunLint C029 which offers superior detection capabilities.
9
+ */
10
+
11
+ module.exports = {
12
+ meta: {
13
+ type: "problem",
14
+ docs: {
15
+ description: "Every `catch` block must log the error cause (C029)",
16
+ recommended: true,
17
+ url: "https://coding-standards.sun.com/rules/c029"
18
+ },
19
+ schema: [],
20
+ messages: {
21
+ silentCatch: "Catch block must log error or rethrow - silent error handling hides bugs (C029)",
22
+ emptyCatch: "Empty catch block - error is silently ignored (C029)"
23
+ }
24
+ },
25
+ create(context) {
26
+ return {
27
+ CatchClause(node) {
28
+ const body = node.body && node.body.body;
29
+
30
+ // Check for empty catch blocks
31
+ if (!Array.isArray(body) || body.length === 0) {
32
+ context.report({
33
+ node,
34
+ messageId: "emptyCatch"
35
+ });
36
+ return;
37
+ }
38
+
39
+ const hasLogOrThrow = body.some(stmt => {
40
+ // Check for throw statements
41
+ if (stmt.type === "ThrowStatement") {
42
+ return true;
43
+ }
44
+
45
+ // Check for test assertions (Jest patterns)
46
+ if (stmt.type === "ExpressionStatement" &&
47
+ stmt.expression.type === "CallExpression" &&
48
+ stmt.expression.callee &&
49
+ stmt.expression.callee.name === "expect") {
50
+ return true;
51
+ }
52
+
53
+ // Check for Redux thunk error handling patterns
54
+ if (stmt.type === "VariableDeclaration" &&
55
+ stmt.declarations.some(decl =>
56
+ decl.init &&
57
+ decl.init.type === "CallExpression" &&
58
+ decl.init.callee &&
59
+ decl.init.callee.name === "handleAxiosError")) {
60
+ return true;
61
+ }
62
+
63
+ // Check for return rejectWithValue
64
+ if (stmt.type === "ReturnStatement" &&
65
+ stmt.argument &&
66
+ stmt.argument.type === "CallExpression" &&
67
+ stmt.argument.callee &&
68
+ stmt.argument.callee.name === "rejectWithValue") {
69
+ return true;
70
+ }
71
+
72
+ // Check for dispatch calls (Redux patterns)
73
+ if (stmt.type === "ExpressionStatement" &&
74
+ stmt.expression.type === "CallExpression" &&
75
+ stmt.expression.callee &&
76
+ stmt.expression.callee.name === "dispatch") {
77
+ return true;
78
+ }
79
+
80
+ // Check for console.log, console.error, console.warn
81
+ if (stmt.type === "ExpressionStatement" &&
82
+ stmt.expression.type === "CallExpression" &&
83
+ stmt.expression.callee &&
84
+ stmt.expression.callee.type === "MemberExpression" &&
85
+ stmt.expression.callee.object.name === "console" &&
86
+ (stmt.expression.callee.property.name === "log" ||
87
+ stmt.expression.callee.property.name === "error" ||
88
+ stmt.expression.callee.property.name === "warn")) {
89
+ return true;
90
+ }
91
+
92
+ // Check for custom logger calls (logger.error, log.error, etc.)
93
+ if (stmt.type === "ExpressionStatement" &&
94
+ stmt.expression.type === "CallExpression" &&
95
+ stmt.expression.callee &&
96
+ stmt.expression.callee.type === "MemberExpression" &&
97
+ (stmt.expression.callee.property.name === "error" ||
98
+ stmt.expression.callee.property.name === "warn" ||
99
+ stmt.expression.callee.property.name === "log")) {
100
+ return true;
101
+ }
102
+
103
+ return false;
104
+ });
105
+
106
+ if (!hasLogOrThrow) {
107
+ context.report({
108
+ node,
109
+ messageId: "silentCatch"
110
+ });
111
+ }
112
+ }
113
+ };
114
+ }
115
+ };
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Custom ESLint rule for: C030 – Dùng custom error class thay vì dùng lỗi hệ thống trực tiếp
3
+ * Rule ID: custom/c030
4
+ * Purpose: Enforce using custom error classes instead of generic Error class for better error handling and categorization
5
+ */
6
+
7
+ const c030Rule = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Use custom error classes instead of generic Error class",
12
+ recommended: true
13
+ },
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ allowGenericInTests: {
19
+ type: "boolean",
20
+ description: "Whether to allow generic Error in test files (default: true)"
21
+ },
22
+ allowedBuiltinErrors: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ description: "Built-in error types that are allowed (e.g., TypeError, RangeError)"
26
+ },
27
+ customErrorClasses: {
28
+ type: "array",
29
+ items: { type: "string" },
30
+ description: "Custom error class names that are recommended"
31
+ },
32
+ allowRethrow: {
33
+ type: "boolean",
34
+ description: "Whether to allow rethrowing caught errors (default: true)"
35
+ },
36
+ strictMode: {
37
+ type: "boolean",
38
+ description: "Enable strict mode - only custom errors allowed (default: false)"
39
+ },
40
+ requireErrorCode: {
41
+ type: "boolean",
42
+ description: "Require custom errors to have error codes (default: true)"
43
+ },
44
+ requireStatusCode: {
45
+ type: "boolean",
46
+ description: "Require custom errors for HTTP to have status codes (default: false)"
47
+ }
48
+ },
49
+ additionalProperties: false
50
+ }
51
+ ],
52
+ messages: {
53
+ useCustomError: "Use custom error class instead of generic 'Error'. Consider using specific error types like ValidationError, NotFoundError, BusinessRuleError, etc. Vietnamese: 'Dùng custom error class thay vì Error generic'",
54
+ useSpecificBuiltin: "Consider using a more specific built-in error type like TypeError, RangeError, or a custom error class. Vietnamese: 'Cân nhắc dùng built-in error cụ thể hơn hoặc custom error class'",
55
+ missingErrorCode: "Custom error class should include an error code property. Vietnamese: 'Custom error class nên có thuộc tính error code'",
56
+ missingStatusCode: "HTTP-related error class should include a status code property. Vietnamese: 'Error class liên quan HTTP nên có thuộc tính status code'",
57
+ preferCustomError: "Prefer custom error classes for better error categorization and handling. Vietnamese: 'Ưu tiên custom error classes để phân loại và xử lý lỗi tốt hơn'"
58
+ },
59
+ fixable: null
60
+ },
61
+
62
+ create(context) {
63
+ const options = context.options[0] || {};
64
+
65
+ // Default configuration
66
+ const allowGenericInTests = options.allowGenericInTests !== false;
67
+ const allowedBuiltinErrors = new Set(options.allowedBuiltinErrors || [
68
+ 'TypeError', 'RangeError', 'SyntaxError', 'ReferenceError', 'URIError', 'EvalError'
69
+ ]);
70
+ const customErrorClasses = new Set(options.customErrorClasses || [
71
+ 'ValidationError', 'NotFoundError', 'BusinessRuleError', 'BusinessError',
72
+ 'ExternalServiceError', 'AuthenticationError', 'AuthorizationError',
73
+ 'NetworkError', 'DatabaseError', 'ConfigurationError', 'TimeoutError'
74
+ ]);
75
+ const allowRethrow = options.allowRethrow !== false;
76
+ const strictMode = options.strictMode || false;
77
+ const requireErrorCode = options.requireErrorCode !== false;
78
+ const requireStatusCode = options.requireStatusCode || false;
79
+
80
+ const sourceCode = context.getSourceCode();
81
+ const filename = context.getFilename();
82
+
83
+ function isTestFile() {
84
+ if (!allowGenericInTests) return false;
85
+
86
+ const testPatterns = ['.test.', '.spec.', '__tests__', '/test/', '/tests/', '.e2e.', '.stories.'];
87
+ return testPatterns.some(pattern => filename.includes(pattern));
88
+ }
89
+
90
+ function isRethrowStatement(node) {
91
+ if (!allowRethrow) return false;
92
+
93
+ // Check if throwing a caught error parameter
94
+ if (node.argument && node.argument.type === 'Identifier') {
95
+ // Look for catch clauses in parent scopes
96
+ let parent = node.parent;
97
+ while (parent) {
98
+ if (parent.type === 'CatchClause' &&
99
+ parent.param &&
100
+ parent.param.name === node.argument.name) {
101
+ return true;
102
+ }
103
+ parent = parent.parent;
104
+ }
105
+ }
106
+
107
+ return false;
108
+ }
109
+
110
+ function getErrorClassName(node) {
111
+ if (!node.argument) return null;
112
+
113
+ if (node.argument.type === 'NewExpression' && node.argument.callee) {
114
+ if (node.argument.callee.type === 'Identifier') {
115
+ return node.argument.callee.name;
116
+ }
117
+ if (node.argument.callee.type === 'MemberExpression' &&
118
+ node.argument.callee.property &&
119
+ node.argument.callee.property.type === 'Identifier') {
120
+ return node.argument.callee.property.name;
121
+ }
122
+ }
123
+
124
+ return null;
125
+ }
126
+
127
+ function isCustomErrorClass(className) {
128
+ if (!className) return false;
129
+
130
+ // Check if it's a known custom error class
131
+ if (customErrorClasses.has(className)) return true;
132
+
133
+ // Check if it follows custom error naming patterns
134
+ const customErrorPatterns = [
135
+ /Error$/, // Ends with 'Error'
136
+ /Exception$/, // Ends with 'Exception'
137
+ /Failure$/, // Ends with 'Failure'
138
+ /Fault$/ // Ends with 'Fault'
139
+ ];
140
+
141
+ return customErrorPatterns.some(pattern =>
142
+ pattern.test(className) &&
143
+ !allowedBuiltinErrors.has(className) &&
144
+ className !== 'Error'
145
+ );
146
+ }
147
+
148
+ function checkErrorClassDefinition(node) {
149
+ // Check if custom error class has required properties
150
+ if (node.type === 'ClassDeclaration' &&
151
+ node.id &&
152
+ isCustomErrorClass(node.id.name)) {
153
+
154
+ const className = node.id.name;
155
+ const classBody = node.body.body;
156
+
157
+ if (requireErrorCode) {
158
+ const hasErrorCode = classBody.some(member => {
159
+ if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') {
160
+ return member.key && member.key.name === 'code';
161
+ }
162
+ if (member.type === 'MethodDefinition' && member.kind === 'constructor') {
163
+ // Check if constructor sets error code
164
+ const constructorBody = member.value.body.body;
165
+ return constructorBody.some(stmt => {
166
+ if (stmt.type === 'ExpressionStatement' &&
167
+ stmt.expression.type === 'AssignmentExpression' &&
168
+ stmt.expression.left.type === 'MemberExpression' &&
169
+ stmt.expression.left.property.name === 'code') {
170
+ return true;
171
+ }
172
+ return false;
173
+ });
174
+ }
175
+ return false;
176
+ });
177
+
178
+ if (!hasErrorCode) {
179
+ context.report({
180
+ node: node.id,
181
+ messageId: "missingErrorCode"
182
+ });
183
+ }
184
+ }
185
+
186
+ if (requireStatusCode && /http|api|web|service/i.test(className.toLowerCase())) {
187
+ const hasStatusCode = classBody.some(member => {
188
+ if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') {
189
+ return member.key && (member.key.name === 'statusCode' || member.key.name === 'status');
190
+ }
191
+ if (member.type === 'MethodDefinition' && member.kind === 'constructor') {
192
+ // Check if constructor sets status code
193
+ const constructorBody = member.value.body.body;
194
+ return constructorBody.some(stmt => {
195
+ if (stmt.type === 'ExpressionStatement' &&
196
+ stmt.expression.type === 'AssignmentExpression' &&
197
+ stmt.expression.left.type === 'MemberExpression' &&
198
+ (stmt.expression.left.property.name === 'statusCode' ||
199
+ stmt.expression.left.property.name === 'status')) {
200
+ return true;
201
+ }
202
+ return false;
203
+ });
204
+ }
205
+ return false;
206
+ });
207
+
208
+ if (!hasStatusCode) {
209
+ context.report({
210
+ node: node.id,
211
+ messageId: "missingStatusCode"
212
+ });
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ return {
219
+ ThrowStatement(node) {
220
+ // Skip test files if allowed
221
+ if (isTestFile()) return;
222
+
223
+ // Skip rethrow statements if allowed
224
+ if (isRethrowStatement(node)) return;
225
+
226
+ const errorClassName = getErrorClassName(node);
227
+
228
+ // Check for generic Error usage
229
+ if (errorClassName === 'Error') {
230
+ context.report({
231
+ node: node.argument,
232
+ messageId: "useCustomError"
233
+ });
234
+ return;
235
+ }
236
+
237
+ // In strict mode, only custom errors are allowed
238
+ if (strictMode && errorClassName) {
239
+ if (!isCustomErrorClass(errorClassName) && !allowedBuiltinErrors.has(errorClassName)) {
240
+ context.report({
241
+ node: node.argument,
242
+ messageId: "preferCustomError"
243
+ });
244
+ }
245
+ }
246
+
247
+ // Check for other built-in errors that could be more specific
248
+ if (allowedBuiltinErrors.has(errorClassName) && !strictMode) {
249
+ // Only suggest if it's a generic built-in error
250
+ if (['TypeError', 'RangeError'].includes(errorClassName)) {
251
+ context.report({
252
+ node: node.argument,
253
+ messageId: "useSpecificBuiltin"
254
+ });
255
+ }
256
+ }
257
+ },
258
+
259
+ ClassDeclaration(node) {
260
+ checkErrorClassDefinition(node);
261
+ },
262
+
263
+ // Check for Promise.reject with generic Error
264
+ 'CallExpression[callee.type="MemberExpression"][callee.object.name="Promise"][callee.property.name="reject"]'(node) {
265
+ if (isTestFile()) return;
266
+
267
+ const arg = node.arguments[0];
268
+ if (arg && arg.type === 'NewExpression' &&
269
+ arg.callee && arg.callee.name === 'Error') {
270
+ context.report({
271
+ node: arg,
272
+ messageId: "useCustomError"
273
+ });
274
+ }
275
+ },
276
+
277
+ // Check for async function error throwing
278
+ 'AwaitExpression > CallExpression[callee.type="MemberExpression"][callee.property.name="reject"]'(node) {
279
+ if (isTestFile()) return;
280
+
281
+ const arg = node.arguments[0];
282
+ if (arg && arg.type === 'NewExpression' &&
283
+ arg.callee && arg.callee.name === 'Error') {
284
+ context.report({
285
+ node: arg,
286
+ messageId: "useCustomError"
287
+ });
288
+ }
289
+ }
290
+ };
291
+ }
292
+ };
293
+
294
+ module.exports = c030Rule;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Custom ESLint rule for: C035 – No empty catch blocks (without error handling or logging)
3
+ * Rule ID: custom/c035
4
+ * Purpose: Prevent silently swallowing errors in catch blocks without logging or handling them
5
+ * Note: Primarily intended for backend code. Frontend may handle errors through UI.
6
+ */
7
+
8
+ module.exports = {
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ description: "No empty catch blocks (without error handling or logging)",
13
+ recommended: true
14
+ },
15
+ schema: [
16
+ {
17
+ type: "object",
18
+ properties: {
19
+ allowFrontend: {
20
+ type: "boolean",
21
+ default: true
22
+ },
23
+ allowIgnoredParams: {
24
+ type: "boolean",
25
+ default: true
26
+ }
27
+ },
28
+ additionalProperties: false
29
+ }
30
+ ],
31
+ messages: {
32
+ emptyCatch: "Empty catch blocks are not allowed. Errors should be logged or handled explicitly.",
33
+ emptyCatchBackend: "Backend code must log errors in catch blocks. Consider using console.error(), logger, or proper error handling."
34
+ }
35
+ },
36
+ create(context) {
37
+ const options = context.options[0] || {};
38
+ const allowFrontend = options.allowFrontend !== false;
39
+ const allowIgnoredParams = options.allowIgnoredParams !== false;
40
+
41
+ function isBackendFile(filename) {
42
+ // Heuristics to determine if it's a backend file
43
+ const backendPatterns = [
44
+ /server/i, /backend/i, /api/i, /service/i,
45
+ /controller/i, /model/i, /dao/i, /repository/i,
46
+ /middleware/i, /route/i, /endpoint/i
47
+ ];
48
+
49
+ return backendPatterns.some(pattern => pattern.test(filename));
50
+ }
51
+
52
+ function isFrontendFile(filename) {
53
+ // Heuristics to determine if it's a frontend file
54
+ const frontendPatterns = [
55
+ /component/i, /page/i, /screen/i, /view/i,
56
+ /ui/i, /frontend/i, /client/i, /app/i,
57
+ /hook/i, /context/i, /store/i, /reducer/i
58
+ ];
59
+
60
+ return frontendPatterns.some(pattern => pattern.test(filename));
61
+ }
62
+
63
+ function isIntentionallyIgnored(param) {
64
+ if (!param || !param.name) return false;
65
+
66
+ const ignoredPatterns = [
67
+ 'ignored', '_', '__', 'unused', 'ignore',
68
+ '_error', '_err', '_e', 'ignored_error'
69
+ ];
70
+
71
+ const paramName = param.name.toLowerCase();
72
+ return ignoredPatterns.some(pattern =>
73
+ paramName === pattern || paramName.includes('ignored')
74
+ );
75
+ }
76
+
77
+ function hasFrontendErrorHandling(body) {
78
+ // Check for common frontend error handling patterns
79
+ const bodyText = context.getSourceCode().getText(body);
80
+ const frontendPatterns = [
81
+ /show.*error/i, /display.*error/i, /toast/i, /alert/i,
82
+ /notification/i, /modal/i, /snackbar/i, /message/i,
83
+ /set.*error/i, /error.*state/i, /ui.*error/i
84
+ ];
85
+
86
+ return frontendPatterns.some(pattern => pattern.test(bodyText));
87
+ }
88
+
89
+ return {
90
+ CatchClause(node) {
91
+ const filename = context.getFilename();
92
+ const body = node.body && node.body.body;
93
+ const param = node.param;
94
+
95
+ // Allow intentionally ignored parameters
96
+ if (allowIgnoredParams && isIntentionallyIgnored(param)) {
97
+ return;
98
+ }
99
+
100
+ // Check if catch block is empty
101
+ if (!Array.isArray(body) || body.length === 0) {
102
+ const isBackend = isBackendFile(filename);
103
+ const isFrontend = isFrontendFile(filename);
104
+
105
+ // For backend files, always report
106
+ if (isBackend) {
107
+ context.report({
108
+ node,
109
+ messageId: "emptyCatchBackend"
110
+ });
111
+ return;
112
+ }
113
+
114
+ // For frontend files, be more lenient if option is set
115
+ if (isFrontend && allowFrontend) {
116
+ return; // Allow empty catch in frontend
117
+ }
118
+
119
+ // Default behavior for unclassified files
120
+ context.report({
121
+ node,
122
+ messageId: "emptyCatch"
123
+ });
124
+ return;
125
+ }
126
+
127
+ // Check for proper error handling in non-empty blocks
128
+ const hasLogging = body.some(statement => {
129
+ const text = context.getSourceCode().getText(statement);
130
+ return /console\.(error|warn|log)|logger\.|log\(|throw\s/i.test(text);
131
+ });
132
+
133
+ const isBackend = isBackendFile(filename);
134
+ const isFrontend = isFrontendFile(filename);
135
+
136
+ // Backend should have logging or re-throwing
137
+ if (isBackend && !hasLogging) {
138
+ // Check if there's at least some error handling
139
+ const hasAnyHandling = body.some(statement => {
140
+ const text = context.getSourceCode().getText(statement);
141
+ return /error|err|exception|fail/i.test(text);
142
+ });
143
+
144
+ if (!hasAnyHandling) {
145
+ context.report({
146
+ node,
147
+ messageId: "emptyCatchBackend"
148
+ });
149
+ }
150
+ }
151
+
152
+ // Frontend can handle through UI
153
+ if (isFrontend && allowFrontend) {
154
+ const hasFrontendHandling = hasFrontendErrorHandling(node.body);
155
+ if (hasLogging || hasFrontendHandling) {
156
+ return; // OK for frontend
157
+ }
158
+ }
159
+ }
160
+ };
161
+ }
162
+ };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Custom ESLint rule for: C041 – No Hardcoded Sensitive Information
3
+ * Rule ID: custom/c041
4
+ * Purpose: Detect hardcoded sensitive information while avoiding false positives in UI/component contexts
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "No hardcoded sensitive information",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ inlineConfig: "Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files."
17
+ }
18
+ },
19
+ create(context) {
20
+ function isConfigOrUIContext(line) {
21
+ const lowerLine = line.toLowerCase();
22
+
23
+ // UI/Component contexts - likely false positives
24
+ const uiContexts = [
25
+ 'inputtype', 'type:', 'type =', 'inputtype=',
26
+ 'routes =', 'route:', 'path:', 'routes:',
27
+ 'import {', 'export {', 'from ', 'import ',
28
+ 'interface', 'type ', 'enum ',
29
+ 'props:', 'defaultprops',
30
+ 'schema', 'validator',
31
+ 'hook', 'use', 'const use', 'import.*use',
32
+ // React/UI specific
33
+ 'textinput', 'input ', 'field ', 'form',
34
+ 'component', 'page', 'screen', 'modal',
35
+ // Route/navigation specific
36
+ 'navigation', 'route', 'path', 'url:', 'route:',
37
+ 'setuppassword', 'resetpassword', 'forgotpassword',
38
+ 'changepassword', 'confirmpassword'
39
+ ];
40
+
41
+ return uiContexts.some(context => lowerLine.includes(context));
42
+ }
43
+
44
+ function isFalsePositive(value, sourceCode) {
45
+ const lowerValue = value.toLowerCase();
46
+
47
+ // Global false positive indicators
48
+ const globalFalsePositives = [
49
+ 'test', 'mock', 'example', 'demo', 'sample', 'placeholder', 'dummy', 'fake',
50
+ 'xmlns', 'namespace', 'schema', 'w3.org', 'google.com', 'googleapis.com',
51
+ 'error', 'message', 'missing', 'invalid', 'failed', 'localhost', '127.0.0.1'
52
+ ];
53
+
54
+ // Check global false positives
55
+ if (globalFalsePositives.some(pattern => lowerValue.includes(pattern))) {
56
+ return true;
57
+ }
58
+
59
+ // Check if line context suggests UI/component usage
60
+ if (isConfigOrUIContext(sourceCode)) {
61
+ return true;
62
+ }
63
+
64
+ return false;
65
+ }
66
+
67
+ const sensitivePatterns = [
68
+ { pattern: /password/i, minLength: 4 },
69
+ { pattern: /secret/i, minLength: 6 },
70
+ { pattern: /api[_-]?key/i, minLength: 10 },
71
+ { pattern: /auth[_-]?token/i, minLength: 16 },
72
+ { pattern: /access[_-]?token/i, minLength: 16 },
73
+ { pattern: /(mongodb|mysql|postgres|redis):\/\//i, minLength: 10 }
74
+ ];
75
+
76
+ function reportIfSensitive(node) {
77
+ const sourceCode = context.getSourceCode();
78
+ const lineText = sourceCode.lines[node.loc.start.line - 1];
79
+
80
+ if (typeof node.value !== "string" || node.value.length < 4) return;
81
+
82
+ // Skip if it's in a UI/component context
83
+ if (isFalsePositive(node.value, lineText)) {
84
+ return;
85
+ }
86
+
87
+ // Check against sensitive patterns - both variable name and value
88
+ const lowerLine = lineText.toLowerCase();
89
+ const lowerValue = node.value.toLowerCase();
90
+
91
+ for (const { pattern, minLength } of sensitivePatterns) {
92
+ // Check if pattern matches variable name OR value
93
+ const matchesValue = pattern.test(node.value) && node.value.length >= minLength;
94
+ const matchesLine = pattern.test(lineText) && node.value.length >= minLength;
95
+
96
+ if (matchesValue || matchesLine) {
97
+ context.report({
98
+ node,
99
+ messageId: "inlineConfig"
100
+ });
101
+ break;
102
+ }
103
+ }
104
+ }
105
+
106
+ return {
107
+ Literal(node) {
108
+ reportIfSensitive(node);
109
+ },
110
+ TemplateLiteral(node) {
111
+ if (node.quasis.length === 1) {
112
+ // Create a mock node for template literal value
113
+ const mockNode = {
114
+ ...node,
115
+ value: node.quasis[0].value.raw
116
+ };
117
+ reportIfSensitive(mockNode);
118
+ }
119
+ }
120
+ };
121
+ }
122
+ };