@sun-asterisk/sunlint 1.0.5

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 (192) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/LICENSE +21 -0
  3. package/README.md +490 -0
  4. package/cli-legacy.js +355 -0
  5. package/cli.js +35 -0
  6. package/config/default.json +22 -0
  7. package/config/presets/beginner.json +36 -0
  8. package/config/presets/ci.json +46 -0
  9. package/config/presets/recommended.json +24 -0
  10. package/config/presets/strict.json +32 -0
  11. package/config/rules-registry.json +681 -0
  12. package/config/sunlint-schema.json +166 -0
  13. package/config/typescript/custom-rules-new.js +0 -0
  14. package/config/typescript/custom-rules.js +9 -0
  15. package/config/typescript/eslint.config.js +110 -0
  16. package/config/typescript/package-lock.json +1585 -0
  17. package/config/typescript/package.json +13 -0
  18. package/config/typescript/security-rules/index.js +90 -0
  19. package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
  20. package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
  21. package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
  22. package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
  23. package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
  24. package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
  25. package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
  26. package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
  27. package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
  28. package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
  29. package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
  30. package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
  31. package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
  32. package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
  33. package/config/typescript/security-rules/s022-output-encoding.js +78 -0
  34. package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
  35. package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
  36. package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
  37. package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
  38. package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
  39. package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
  40. package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
  41. package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
  42. package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
  43. package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
  44. package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
  45. package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
  46. package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
  47. package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
  48. package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
  49. package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
  50. package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
  51. package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
  52. package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
  53. package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
  54. package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
  55. package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
  56. package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
  57. package/config/typescript/security-rules/s057-utc-logging.js +54 -0
  58. package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
  59. package/config/typescript/test-s005-working.ts +22 -0
  60. package/config/typescript/tsconfig.json +29 -0
  61. package/core/ai-analyzer.js +169 -0
  62. package/core/analysis-orchestrator.js +705 -0
  63. package/core/cli-action-handler.js +230 -0
  64. package/core/cli-program.js +106 -0
  65. package/core/config-manager.js +396 -0
  66. package/core/config-merger.js +136 -0
  67. package/core/config-override-processor.js +74 -0
  68. package/core/config-preset-resolver.js +65 -0
  69. package/core/config-source-loader.js +152 -0
  70. package/core/config-validator.js +126 -0
  71. package/core/dependency-manager.js +105 -0
  72. package/core/eslint-engine-service.js +312 -0
  73. package/core/eslint-instance-manager.js +104 -0
  74. package/core/eslint-integration-service.js +363 -0
  75. package/core/git-utils.js +170 -0
  76. package/core/multi-rule-runner.js +239 -0
  77. package/core/output-service.js +250 -0
  78. package/core/report-generator.js +320 -0
  79. package/core/rule-mapping-service.js +309 -0
  80. package/core/rule-selection-service.js +121 -0
  81. package/core/sunlint-engine-service.js +23 -0
  82. package/core/typescript-analyzer.js +262 -0
  83. package/core/typescript-engine.js +313 -0
  84. package/docs/AI.md +163 -0
  85. package/docs/ARCHITECTURE.md +78 -0
  86. package/docs/CI-CD-GUIDE.md +315 -0
  87. package/docs/COMMAND-EXAMPLES.md +256 -0
  88. package/docs/DEBUG.md +86 -0
  89. package/docs/DISTRIBUTION.md +153 -0
  90. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  91. package/docs/ESLINT_INTEGRATION.md +238 -0
  92. package/docs/FOLDER_STRUCTURE.md +59 -0
  93. package/docs/HEURISTIC_VS_AI.md +113 -0
  94. package/docs/README.md +32 -0
  95. package/docs/RELEASE_GUIDE.md +230 -0
  96. package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
  97. package/eslint-integration/.eslintrc.js +98 -0
  98. package/eslint-integration/cli.js +35 -0
  99. package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
  100. package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
  101. package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
  102. package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
  103. package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
  104. package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
  105. package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
  106. package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
  107. package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
  108. package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
  109. package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
  110. package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
  111. package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
  112. package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
  113. package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
  114. package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
  115. package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
  116. package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
  117. package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
  118. package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
  119. package/eslint-integration/eslint-plugin-custom/index.js +155 -0
  120. package/eslint-integration/eslint-plugin-custom/package.json +13 -0
  121. package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
  122. package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
  123. package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
  124. package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
  125. package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
  126. package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
  127. package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
  128. package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
  129. package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
  130. package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
  131. package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
  132. package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
  133. package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
  134. package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
  135. package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
  136. package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
  137. package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
  138. package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
  139. package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
  140. package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
  141. package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
  142. package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
  143. package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
  144. package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
  145. package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
  146. package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
  147. package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
  148. package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
  149. package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
  150. package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
  151. package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
  152. package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
  153. package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
  154. package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
  155. package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
  156. package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
  157. package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
  158. package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
  159. package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
  160. package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
  161. package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
  162. package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  163. package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
  164. package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
  165. package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
  166. package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
  167. package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
  168. package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
  169. package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
  170. package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
  171. package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
  172. package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
  173. package/eslint-integration/eslint.config.js +125 -0
  174. package/eslint-integration/eslint.config.simple.js +24 -0
  175. package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
  176. package/eslint-integration/package.json +23 -0
  177. package/eslint-integration/sample.ts +53 -0
  178. package/eslint-integration/test-s003.js +5 -0
  179. package/eslint-integration/tsconfig.json +27 -0
  180. package/examples/.github/workflows/code-quality.yml +111 -0
  181. package/examples/.sunlint.json +42 -0
  182. package/examples/README.md +47 -0
  183. package/examples/package.json +33 -0
  184. package/package.json +100 -0
  185. package/rules/C006_function_naming/analyzer.js +338 -0
  186. package/rules/C006_function_naming/config.json +86 -0
  187. package/rules/C019_log_level_usage/analyzer.js +359 -0
  188. package/rules/C019_log_level_usage/config.json +121 -0
  189. package/rules/C029_catch_block_logging/analyzer.js +339 -0
  190. package/rules/C029_catch_block_logging/config.json +59 -0
  191. package/rules/C031_validation_separation/README.md +72 -0
  192. package/rules/C031_validation_separation/analyzer.js +186 -0
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Custom ESLint rule for: C018 – Do not throw generic errors, always use specific messages
3
+ * Rule ID: custom/c018
4
+ * Purpose: Enforce specific error messages when throwing errors to improve debugging and error handling
5
+ */
6
+
7
+ const c018Rule = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Do not throw generic errors, always use specific messages",
12
+ recommended: false
13
+ },
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ allowGenericInTests: {
19
+ type: "boolean",
20
+ description: "Whether to allow generic throws in test files (default: true)"
21
+ },
22
+ allowRethrow: {
23
+ type: "boolean",
24
+ description: "Whether to allow rethrowing caught errors (default: true)"
25
+ },
26
+ allowThrowVariable: {
27
+ type: "boolean",
28
+ description: "Whether to allow throwing variables/identifiers (default: false)"
29
+ },
30
+ requiredMessagePatterns: {
31
+ type: "array",
32
+ items: { type: "string" },
33
+ description: "Regex patterns that error messages must match"
34
+ },
35
+ minimumMessageLength: {
36
+ type: "number",
37
+ description: "Minimum length for error messages (default: 10)"
38
+ },
39
+ allowedGenericMessages: {
40
+ type: "array",
41
+ items: { type: "string" },
42
+ description: "List of allowed generic messages"
43
+ },
44
+ customErrorClasses: {
45
+ type: "array",
46
+ items: { type: "string" },
47
+ description: "Custom error class names that are allowed"
48
+ },
49
+ strictMode: {
50
+ type: "boolean",
51
+ description: "Enable strict mode with additional checks (default: false)"
52
+ }
53
+ },
54
+ additionalProperties: false
55
+ }
56
+ ],
57
+ messages: {
58
+ genericError: "Do not throw generic errors. Provide a specific error message.",
59
+ emptyError: "Error message cannot be empty. Provide a specific error message.",
60
+ messageToolShort: "Error message too short (minimum {{minLength}} characters). Vietnamese: 'Message error quá ngắn (tối thiểu {{minLength}} ký tự)'",
61
+ genericErrorMessage: "Generic error message '{{message}}' should be more specific. Vietnamese: 'Message error generic nên cụ thể hơn'",
62
+ throwWithoutMessage: "Throw statement must include a specific error message. Vietnamese: 'Throw statement phải có message error cụ thể'",
63
+ useSpecificErrorClass: "Use specific error class instead of generic Error. Vietnamese: 'Dùng error class cụ thể thay vì Error generic'",
64
+ invalidMessagePattern: "Error message doesn't match required patterns. Vietnamese: 'Message error không khớp pattern yêu cầu'",
65
+ throwBareString: "Throwing bare string is not recommended, use Error object with message. Vietnamese: 'Throw string trực tiếp không khuyến khích, dùng Error object với message'"
66
+ },
67
+ fixable: null
68
+ },
69
+
70
+ create(context) {
71
+ const options = context.options[0] || {};
72
+
73
+ // Default configuration
74
+ const allowGenericInTests = options.allowGenericInTests !== false;
75
+ const allowRethrow = options.allowRethrow !== false;
76
+ const allowThrowVariable = options.allowThrowVariable || false;
77
+ const requiredMessagePatterns = options.requiredMessagePatterns || [];
78
+ const minimumMessageLength = options.minimumMessageLength || 10;
79
+ const allowedGenericMessages = new Set(options.allowedGenericMessages || []);
80
+ const customErrorClasses = new Set(options.customErrorClasses || ['ValidationError', 'BusinessError', 'NetworkError', 'AuthenticationError', 'AuthorizationError']);
81
+ const strictMode = options.strictMode || false;
82
+
83
+ const sourceCode = context.getSourceCode();
84
+ const filename = context.getFilename();
85
+
86
+ // Generic error messages to detect
87
+ const genericMessages = new Set([
88
+ 'error',
89
+ 'Error',
90
+ 'ERROR',
91
+ 'something went wrong',
92
+ 'something failed',
93
+ 'operation failed',
94
+ 'invalid',
95
+ 'invalid input',
96
+ 'bad input',
97
+ 'error occurred',
98
+ 'an error occurred',
99
+ 'failed',
100
+ 'failure',
101
+ 'exception',
102
+ 'unexpected error',
103
+ 'internal error',
104
+ 'system error',
105
+ 'unknown error'
106
+ ]);
107
+
108
+ function isTestFile() {
109
+ if (!allowGenericInTests) return false;
110
+
111
+ const testPatterns = ['.test.', '.spec.', '__tests__', '/test/', '/tests/', '.e2e.'];
112
+ return testPatterns.some(pattern => filename.includes(pattern));
113
+ }
114
+
115
+ function isRethrowStatement(node) {
116
+ if (!allowRethrow) return false;
117
+
118
+ // Check if throwing a caught error parameter
119
+ if (node.argument && node.argument.type === 'Identifier') {
120
+ // Look for catch clauses in parent scopes
121
+ let parent = node.parent;
122
+ while (parent) {
123
+ if (parent.type === 'CatchClause' &&
124
+ parent.param &&
125
+ parent.param.name === node.argument.name) {
126
+ return true;
127
+ }
128
+ parent = parent.parent;
129
+ }
130
+ }
131
+
132
+ return false;
133
+ }
134
+
135
+ function getErrorMessage(node) {
136
+ if (!node.argument) return null;
137
+
138
+ // Direct string literal
139
+ if (node.argument.type === 'Literal') {
140
+ return node.argument.value;
141
+ }
142
+
143
+ // new Error("message")
144
+ if (node.argument.type === 'NewExpression' &&
145
+ node.argument.callee &&
146
+ node.argument.arguments &&
147
+ node.argument.arguments.length > 0) {
148
+ const firstArg = node.argument.arguments[0];
149
+ if (firstArg.type === 'Literal') {
150
+ return firstArg.value;
151
+ }
152
+ }
153
+
154
+ return null;
155
+ }
156
+
157
+ function getErrorClassName(node) {
158
+ if (node.argument &&
159
+ node.argument.type === 'NewExpression' &&
160
+ node.argument.callee) {
161
+ if (node.argument.callee.type === 'Identifier') {
162
+ return node.argument.callee.name;
163
+ }
164
+ }
165
+ return null;
166
+ }
167
+
168
+ function isGenericErrorMessage(message) {
169
+ if (typeof message !== 'string') return false;
170
+
171
+ const normalizedMessage = message.toLowerCase().trim();
172
+ return genericMessages.has(normalizedMessage) ||
173
+ genericMessages.has(message.trim());
174
+ }
175
+
176
+ function isMessageTooShort(message) {
177
+ if (typeof message !== 'string') return false;
178
+ return message.trim().length < minimumMessageLength;
179
+ }
180
+
181
+ function matchesRequiredPatterns(message) {
182
+ if (requiredMessagePatterns.length === 0) return true;
183
+ if (typeof message !== 'string') return false;
184
+
185
+ return requiredMessagePatterns.some(pattern => {
186
+ try {
187
+ const regex = new RegExp(pattern);
188
+ return regex.test(message);
189
+ } catch (e) {
190
+ return false;
191
+ }
192
+ });
193
+ }
194
+
195
+ function isAllowedGenericMessage(message) {
196
+ if (typeof message !== 'string') return false;
197
+ return allowedGenericMessages.has(message.trim());
198
+ }
199
+
200
+ function checkThrowStatement(node) {
201
+ // Skip if in test file and allowed
202
+ if (isTestFile()) {
203
+ return;
204
+ }
205
+
206
+ // Skip if this is a rethrow
207
+ if (isRethrowStatement(node)) {
208
+ return;
209
+ }
210
+
211
+ // Check for throw without argument
212
+ if (!node.argument) {
213
+ context.report({
214
+ node,
215
+ messageId: 'throwWithoutMessage'
216
+ });
217
+ return;
218
+ }
219
+
220
+ // Check for throwing variables/identifiers
221
+ if (node.argument.type === 'Identifier' && !allowThrowVariable) {
222
+ context.report({
223
+ node,
224
+ messageId: 'genericError'
225
+ });
226
+ return;
227
+ }
228
+
229
+ // Check for throwing bare strings
230
+ if (node.argument.type === 'Literal' && typeof node.argument.value === 'string') {
231
+ if (strictMode) {
232
+ context.report({
233
+ node,
234
+ messageId: 'throwBareString'
235
+ });
236
+ return;
237
+ }
238
+
239
+ const message = node.argument.value;
240
+ validateMessage(node, message);
241
+ return;
242
+ }
243
+
244
+ // Check for new Error() constructions
245
+ if (node.argument.type === 'NewExpression') {
246
+ const errorClassName = getErrorClassName(node);
247
+ const errorMessage = getErrorMessage(node);
248
+
249
+ // Check error class
250
+ if (strictMode && errorClassName === 'Error') {
251
+ context.report({
252
+ node,
253
+ messageId: 'useSpecificErrorClass'
254
+ });
255
+ }
256
+
257
+ // Check error message
258
+ if (errorMessage !== null) {
259
+ validateMessage(node, errorMessage);
260
+ } else if (node.argument.arguments.length === 0) {
261
+ context.report({
262
+ node,
263
+ messageId: 'throwWithoutMessage'
264
+ });
265
+ }
266
+ return;
267
+ }
268
+
269
+ // Generic throw statement
270
+ context.report({
271
+ node,
272
+ messageId: 'genericError'
273
+ });
274
+ }
275
+
276
+ function validateMessage(node, message) {
277
+ if (!message || typeof message !== 'string') {
278
+ context.report({
279
+ node,
280
+ messageId: 'throwWithoutMessage'
281
+ });
282
+ return;
283
+ }
284
+
285
+ // Check for empty or whitespace-only message
286
+ if (message.trim() === '') {
287
+ context.report({
288
+ node,
289
+ messageId: 'emptyError'
290
+ });
291
+ return;
292
+ }
293
+
294
+ // Check if message is allowed generic
295
+ if (isAllowedGenericMessage(message)) {
296
+ return;
297
+ }
298
+
299
+ // Check for generic messages
300
+ if (isGenericErrorMessage(message)) {
301
+ context.report({
302
+ node,
303
+ messageId: 'genericErrorMessage',
304
+ data: { message: message.trim() }
305
+ });
306
+ return;
307
+ }
308
+
309
+ // Check message length
310
+ if (isMessageTooShort(message)) {
311
+ context.report({
312
+ node,
313
+ messageId: 'messageToolShort',
314
+ data: { minLength: minimumMessageLength }
315
+ });
316
+ return;
317
+ }
318
+
319
+ // Check required patterns
320
+ if (!matchesRequiredPatterns(message)) {
321
+ context.report({
322
+ node,
323
+ messageId: 'invalidMessagePattern'
324
+ });
325
+ return;
326
+ }
327
+ }
328
+
329
+ return {
330
+ ThrowStatement: checkThrowStatement
331
+ };
332
+ }
333
+ };
334
+
335
+ module.exports = c018Rule;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Custom ESLint rule for: C023 – Do not use duplicate variable names in the same scope
3
+ * Rule ID: custom/c023
4
+ * Purpose: Prevent variable name shadowing and maintain clear variable scoping
5
+ */
6
+
7
+ const c023Rule = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Do not use duplicate variable names in the same scope",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ duplicateVariable: "Variable '{{name}}' is already declared in this scope"
17
+ }
18
+ },
19
+ create(context) {
20
+ const scopeStack = [];
21
+ const variableMap = new Map();
22
+
23
+ function enterScope() {
24
+ scopeStack.push(new Map());
25
+ }
26
+
27
+ function exitScope() {
28
+ const scope = scopeStack.pop();
29
+ // Clean up variables from the exited scope
30
+ for (const [name, info] of scope) {
31
+ const globalInfo = variableMap.get(name);
32
+ if (globalInfo) {
33
+ globalInfo.scopes.delete(scope);
34
+ if (globalInfo.scopes.size === 0) {
35
+ variableMap.delete(name);
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ function checkVariable(node, name) {
42
+ if (scopeStack.length === 0) return;
43
+
44
+ const currentScope = scopeStack[scopeStack.length - 1];
45
+
46
+ // Check if variable is already declared in current scope
47
+ if (currentScope.has(name)) {
48
+ context.report({
49
+ node,
50
+ messageId: "duplicateVariable",
51
+ data: { name }
52
+ });
53
+ return;
54
+ }
55
+
56
+ // Add variable to current scope
57
+ currentScope.set(name, {
58
+ node,
59
+ scopes: new Set([currentScope])
60
+ });
61
+
62
+ // Update global variable map
63
+ if (!variableMap.has(name)) {
64
+ variableMap.set(name, {
65
+ scopes: new Set([currentScope])
66
+ });
67
+ } else {
68
+ variableMap.get(name).scopes.add(currentScope);
69
+ }
70
+ }
71
+
72
+ return {
73
+ Program() {
74
+ enterScope();
75
+ },
76
+ "Program:exit"() {
77
+ exitScope();
78
+ },
79
+ FunctionDeclaration() {
80
+ enterScope();
81
+ },
82
+ "FunctionDeclaration:exit"() {
83
+ exitScope();
84
+ },
85
+ FunctionExpression() {
86
+ enterScope();
87
+ },
88
+ "FunctionExpression:exit"() {
89
+ exitScope();
90
+ },
91
+ ArrowFunctionExpression() {
92
+ enterScope();
93
+ },
94
+ "ArrowFunctionExpression:exit"() {
95
+ exitScope();
96
+ },
97
+ BlockStatement() {
98
+ enterScope();
99
+ },
100
+ "BlockStatement:exit"() {
101
+ exitScope();
102
+ },
103
+ CatchClause() {
104
+ enterScope();
105
+ },
106
+ "CatchClause:exit"() {
107
+ exitScope();
108
+ },
109
+ ForStatement() {
110
+ enterScope();
111
+ },
112
+ "ForStatement:exit"() {
113
+ exitScope();
114
+ },
115
+ ForInStatement() {
116
+ enterScope();
117
+ },
118
+ "ForInStatement:exit"() {
119
+ exitScope();
120
+ },
121
+ ForOfStatement() {
122
+ enterScope();
123
+ },
124
+ "ForOfStatement:exit"() {
125
+ exitScope();
126
+ },
127
+ SwitchStatement() {
128
+ enterScope();
129
+ },
130
+ "SwitchStatement:exit"() {
131
+ exitScope();
132
+ },
133
+ VariableDeclarator(node) {
134
+ if (node.id.type === "Identifier") {
135
+ checkVariable(node, node.id.name);
136
+ }
137
+ }
138
+ };
139
+ }
140
+ };
141
+
142
+ module.exports = c023Rule;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Custom ESLint rule for: C027 – Avoid deeply nested functions (maximum 2 levels)
3
+ * Rule ID: custom/c027
4
+ * Goal: Limit function nesting to maximum 2 levels to improve readability and maintainability
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Avoid deeply nested functions (maximum 2 levels)",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ tooDeep: "Function is nested too deeply (level {{depth}}). Should be maximum 2 levels."
17
+ }
18
+ },
19
+ create(context) {
20
+ let functionStack = [];
21
+
22
+ function enterFunction(node) {
23
+ functionStack.push(node);
24
+ const depth = functionStack.length;
25
+
26
+ if (depth > 2) {
27
+ context.report({
28
+ node,
29
+ messageId: "tooDeep",
30
+ data: {
31
+ depth
32
+ }
33
+ });
34
+ }
35
+ }
36
+
37
+ function exitFunction() {
38
+ functionStack.pop();
39
+ }
40
+
41
+ return {
42
+ FunctionDeclaration: enterFunction,
43
+ FunctionExpression: enterFunction,
44
+ ArrowFunctionExpression: enterFunction,
45
+ 'FunctionDeclaration:exit': exitFunction,
46
+ 'FunctionExpression:exit': exitFunction,
47
+ 'ArrowFunctionExpression:exit': exitFunction
48
+ };
49
+ }
50
+ };
@@ -0,0 +1,80 @@
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 console.log, console.error, console.warn
46
+ if (stmt.type === "ExpressionStatement" &&
47
+ stmt.expression.type === "CallExpression" &&
48
+ stmt.expression.callee &&
49
+ stmt.expression.callee.type === "MemberExpression" &&
50
+ stmt.expression.callee.object.name === "console" &&
51
+ (stmt.expression.callee.property.name === "log" ||
52
+ stmt.expression.callee.property.name === "error" ||
53
+ stmt.expression.callee.property.name === "warn")) {
54
+ return true;
55
+ }
56
+
57
+ // Check for custom logger calls (logger.error, log.error, etc.)
58
+ if (stmt.type === "ExpressionStatement" &&
59
+ stmt.expression.type === "CallExpression" &&
60
+ stmt.expression.callee &&
61
+ stmt.expression.callee.type === "MemberExpression" &&
62
+ (stmt.expression.callee.property.name === "error" ||
63
+ stmt.expression.callee.property.name === "warn" ||
64
+ stmt.expression.callee.property.name === "log")) {
65
+ return true;
66
+ }
67
+
68
+ return false;
69
+ });
70
+
71
+ if (!hasLogOrThrow) {
72
+ context.report({
73
+ node,
74
+ messageId: "silentCatch"
75
+ });
76
+ }
77
+ }
78
+ };
79
+ }
80
+ };