@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,239 @@
1
+ /**
2
+ * Custom ESLint rule for: C047 – Logic retry không được viết lặp lại nhiều nơi
3
+ * Rule ID: custom/c047
4
+ * Purpose: Detect duplicate retry logic patterns and enforce centralized retry utilities
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Logic retry không được viết lặp lại nhiều nơi - use centralized retry utility instead",
12
+ recommended: false
13
+ },
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ maxRetryPatterns: {
19
+ type: "number",
20
+ minimum: 1,
21
+ default: 2,
22
+ description: "Maximum number of retry patterns allowed before suggesting centralization"
23
+ },
24
+ allowedRetryUtils: {
25
+ type: "array",
26
+ items: { type: "string" },
27
+ default: ["RetryUtil", "retryWithBackoff", "withRetry"],
28
+ description: "Names of allowed centralized retry utilities"
29
+ }
30
+ },
31
+ additionalProperties: false
32
+ }
33
+ ],
34
+ messages: {
35
+ duplicateRetryLogic: "Duplicate retry logic detected ({{count}} occurrences). Consider using a centralized retry utility like RetryUtil.withRetry()",
36
+ inlineRetryLogic: "Inline retry logic found. Consider using a centralized retry utility for consistency and maintainability.",
37
+ suggestRetryUtil: "Use centralized retry utility instead of custom retry logic."
38
+ }
39
+ },
40
+ create(context) {
41
+ const options = context.options[0] || {};
42
+ const maxRetryPatterns = options.maxRetryPatterns || 2;
43
+ const allowedRetryUtils = options.allowedRetryUtils || ["RetryUtil", "retryWithBackoff", "withRetry"];
44
+
45
+ const retryPatterns = [];
46
+ const sourceCode = context.getSourceCode();
47
+
48
+ /**
49
+ * Check if a node represents a retry pattern
50
+ */
51
+ function isRetryPattern(node) {
52
+ // Pattern 1: for/while loop with try-catch for retry
53
+ if ((node.type === "ForStatement" || node.type === "WhileStatement") &&
54
+ node.body && node.body.type === "BlockStatement") {
55
+ const hasRetryLogic = node.body.body.some(stmt =>
56
+ stmt.type === "TryStatement" ||
57
+ (stmt.type === "IfStatement" && hasRetryCondition(stmt))
58
+ );
59
+ return hasRetryLogic;
60
+ }
61
+
62
+ // Pattern 2: do-while with try-catch
63
+ if (node.type === "DoWhileStatement" &&
64
+ node.body && node.body.type === "BlockStatement") {
65
+ return node.body.body.some(stmt => stmt.type === "TryStatement");
66
+ }
67
+
68
+ // Pattern 3: recursive function with retry logic
69
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") {
70
+ return hasRecursiveRetryPattern(node);
71
+ }
72
+
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Check if statement has retry-related conditions
78
+ */
79
+ function hasRetryCondition(ifStmt) {
80
+ if (!ifStmt.test) return false;
81
+
82
+ const testText = sourceCode.getText(ifStmt.test).toLowerCase();
83
+ return testText.includes('retry') ||
84
+ testText.includes('attempt') ||
85
+ testText.includes('tries') ||
86
+ testText.includes('maxretries') ||
87
+ testText.includes('maxattempts');
88
+ }
89
+
90
+ /**
91
+ * Check if function has recursive retry pattern
92
+ */
93
+ function hasRecursiveRetryPattern(funcNode) {
94
+ if (!funcNode.body || !funcNode.body.body) return false;
95
+
96
+ const funcName = funcNode.id ? funcNode.id.name : null;
97
+ if (!funcName) return false;
98
+
99
+ // Look for recursive calls with retry logic
100
+ const hasRecursiveCall = funcNode.body.body.some(stmt => {
101
+ if (stmt.type === "TryStatement" && stmt.handler) {
102
+ // Check if catch block has recursive call
103
+ return containsRecursiveCall(stmt.handler.body, funcName);
104
+ }
105
+ return false;
106
+ });
107
+
108
+ return hasRecursiveCall;
109
+ }
110
+
111
+ /**
112
+ * Check if block contains recursive call to the function
113
+ */
114
+ function containsRecursiveCall(block, funcName) {
115
+ if (!block || !block.body) return false;
116
+
117
+ return block.body.some(stmt => {
118
+ if (stmt.type === "ReturnStatement" && stmt.argument) {
119
+ return containsCallExpression(stmt.argument, funcName);
120
+ }
121
+ if (stmt.type === "ExpressionStatement") {
122
+ return containsCallExpression(stmt.expression, funcName);
123
+ }
124
+ return false;
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Check if expression contains call to specific function
130
+ */
131
+ function containsCallExpression(expr, funcName) {
132
+ if (expr.type === "CallExpression" &&
133
+ expr.callee && expr.callee.name === funcName) {
134
+ return true;
135
+ }
136
+
137
+ if (expr.type === "AwaitExpression" && expr.argument) {
138
+ return containsCallExpression(expr.argument, funcName);
139
+ }
140
+
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * Check if node uses allowed retry utilities
146
+ */
147
+ function usesAllowedRetryUtil(node) {
148
+ const nodeText = sourceCode.getText(node);
149
+ return allowedRetryUtils.some(utilName => nodeText.includes(utilName));
150
+ }
151
+
152
+ /**
153
+ * Get hash for retry pattern to detect duplicates
154
+ */
155
+ function getRetryPatternHash(node) {
156
+ let text = sourceCode.getText(node);
157
+ // Normalize text for comparison (remove variable names, whitespace)
158
+ text = text
159
+ .replace(/\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/g, 'VAR') // Replace identifiers
160
+ .replace(/\s+/g, ' ') // Normalize whitespace
161
+ .replace(/\/\*.*?\*\//g, '') // Remove block comments
162
+ .replace(/\/\/.*$/gm, ''); // Remove line comments
163
+ return text;
164
+ }
165
+
166
+ return {
167
+ // Check for retry patterns in various constructs
168
+ "ForStatement, WhileStatement, DoWhileStatement"(node) {
169
+ if (isRetryPattern(node) && !usesAllowedRetryUtil(node)) {
170
+ const hash = getRetryPatternHash(node);
171
+ const existing = retryPatterns.find(p => p.hash === hash);
172
+
173
+ if (existing) {
174
+ existing.count++;
175
+ existing.nodes.push(node);
176
+ } else {
177
+ retryPatterns.push({
178
+ hash,
179
+ count: 1,
180
+ nodes: [node],
181
+ type: node.type
182
+ });
183
+ }
184
+ }
185
+ },
186
+
187
+ "FunctionDeclaration, FunctionExpression"(node) {
188
+ if (isRetryPattern(node) && !usesAllowedRetryUtil(node)) {
189
+ context.report({
190
+ node,
191
+ messageId: "inlineRetryLogic"
192
+ });
193
+ }
194
+ },
195
+
196
+ // Check for inline setTimeout/setInterval retry patterns
197
+ "CallExpression"(node) {
198
+ if (node.callee &&
199
+ (node.callee.name === "setTimeout" || node.callee.name === "setInterval")) {
200
+ const parent = node.parent;
201
+ // Check if this setTimeout is part of retry logic
202
+ if (parent && parent.type === "ExpressionStatement") {
203
+ let current = parent.parent;
204
+ while (current) {
205
+ if (current.type === "TryStatement" ||
206
+ (current.type === "IfStatement" && hasRetryCondition(current))) {
207
+ if (!usesAllowedRetryUtil(current)) {
208
+ context.report({
209
+ node: current,
210
+ messageId: "inlineRetryLogic"
211
+ });
212
+ }
213
+ break;
214
+ }
215
+ current = current.parent;
216
+ }
217
+ }
218
+ }
219
+ },
220
+
221
+ // Report duplicates at end of program
222
+ "Program:exit"() {
223
+ retryPatterns.forEach(pattern => {
224
+ if (pattern.count >= maxRetryPatterns) {
225
+ pattern.nodes.forEach(node => {
226
+ context.report({
227
+ node,
228
+ messageId: "duplicateRetryLogic",
229
+ data: {
230
+ count: pattern.count
231
+ }
232
+ });
233
+ });
234
+ }
235
+ });
236
+ }
237
+ };
238
+ }
239
+ };
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Custom ESLint rule for: C072 – Each test should assert only one behavior (Single Assert Rule)
3
+ * Rule ID: custom/c072
4
+ * Purpose: A test case should only have one main behavior to test (one expect statement)
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Each test should assert only one behavior (Single Assert Rule)",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ tooMany: "Test contains too many expect statements ({{count}}). Each test should have only one main behavior to verify."
17
+ }
18
+ },
19
+ create(context) {
20
+ function isTestFunction(node) {
21
+ // Safety check for node structure
22
+ if (!node || !node.callee) {
23
+ return false;
24
+ }
25
+
26
+ // Check for test/it calls
27
+ if (node.type === "CallExpression") {
28
+ // Handle direct test/it calls
29
+ if (node.callee.type === "Identifier") {
30
+ return ["test", "it"].includes(node.callee.name);
31
+ }
32
+
33
+ // Handle imported test/it calls
34
+ if (node.callee.type === "MemberExpression" &&
35
+ node.callee.object.type === "Identifier" &&
36
+ ["test", "it"].includes(node.callee.property.name)) {
37
+ return true;
38
+ }
39
+ }
40
+
41
+ return false;
42
+ }
43
+
44
+ function isDescribeBlock(node) {
45
+ if (!node || !node.callee) {
46
+ return false;
47
+ }
48
+
49
+ if (node.type === "CallExpression") {
50
+ // Handle direct describe calls
51
+ if (node.callee.type === "Identifier") {
52
+ return node.callee.name === "describe";
53
+ }
54
+
55
+ // Handle imported describe calls
56
+ if (node.callee.type === "MemberExpression" &&
57
+ node.callee.object.type === "Identifier" &&
58
+ node.callee.property.name === "describe") {
59
+ return true;
60
+ }
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ function isSetupOrTeardown(node) {
67
+ if (!node || !node.callee) {
68
+ return false;
69
+ }
70
+
71
+ if (node.type === "CallExpression") {
72
+ const name = node.callee.type === "Identifier"
73
+ ? node.callee.name
74
+ : node.callee.type === "MemberExpression"
75
+ ? node.callee.property.name
76
+ : null;
77
+
78
+ return ["beforeEach", "afterEach", "beforeAll", "afterAll"].includes(name);
79
+ }
80
+
81
+ return false;
82
+ }
83
+
84
+ function countExpectCalls(body) {
85
+ let count = 0;
86
+
87
+ function traverse(node) {
88
+ // Safety check to ensure node exists and has type property
89
+ if (!node || typeof node !== 'object' || !node.type) {
90
+ return;
91
+ }
92
+
93
+ // Check if this node is an expect call
94
+ if (
95
+ node.type === "CallExpression" &&
96
+ node.callee &&
97
+ node.callee.type === "Identifier" &&
98
+ node.callee.name === "expect"
99
+ ) {
100
+ count++;
101
+ }
102
+
103
+ // Safely traverse child nodes, but don't go into nested test functions
104
+ for (const key in node) {
105
+ if (key === 'parent' || key === 'range' || key === 'loc') {
106
+ continue; // Skip circular references and metadata
107
+ }
108
+
109
+ const child = node[key];
110
+ if (Array.isArray(child)) {
111
+ child.forEach(item => {
112
+ if (item && typeof item === 'object' && item.type) {
113
+ // Don't traverse into nested test functions
114
+ if (!(item.type === "CallExpression" && isTestFunction(item))) {
115
+ traverse(item);
116
+ }
117
+ }
118
+ });
119
+ } else if (child && typeof child === 'object' && child.type) {
120
+ // Don't traverse into nested test functions
121
+ if (!(child.type === "CallExpression" && isTestFunction(child))) {
122
+ traverse(child);
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ traverse(body);
129
+ return count;
130
+ }
131
+
132
+ return {
133
+ CallExpression(node) {
134
+ // Only check test/it function calls
135
+ if (!isTestFunction(node)) {
136
+ return;
137
+ }
138
+
139
+ // Ensure we have the required arguments (name and callback)
140
+ if (!node.arguments || node.arguments.length < 2) {
141
+ return;
142
+ }
143
+
144
+ const testCallback = node.arguments[1];
145
+
146
+ // Check if the second argument is a function (test body)
147
+ if (
148
+ !testCallback ||
149
+ (testCallback.type !== "FunctionExpression" &&
150
+ testCallback.type !== "ArrowFunctionExpression")
151
+ ) {
152
+ return;
153
+ }
154
+
155
+ // Get the function body
156
+ const fnBody = testCallback.body;
157
+ if (!fnBody) {
158
+ return;
159
+ }
160
+
161
+ // Handle both block statements and expression bodies
162
+ let bodyToCheck = fnBody;
163
+ if (testCallback.type === "ArrowFunctionExpression" && fnBody.type !== "BlockStatement") {
164
+ // For arrow functions with expression bodies, wrap in a virtual block
165
+ bodyToCheck = { type: "BlockStatement", body: [{ type: "ExpressionStatement", expression: fnBody }] };
166
+ }
167
+
168
+ // Count expect calls in the test body
169
+ const expectCount = countExpectCalls(bodyToCheck);
170
+
171
+ // Report if more than one expect statement
172
+ if (expectCount > 1) {
173
+ context.report({
174
+ node,
175
+ messageId: "tooMany",
176
+ data: {
177
+ count: expectCount
178
+ }
179
+ });
180
+ }
181
+ }
182
+ };
183
+ }
184
+ };
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Custom ESLint rule for: C075 – Functions must have explicit return type declarations
3
+ * Rule ID: sunlint/c075
4
+ * Purpose: Enforce explicit return type annotations for all functions to improve type safety
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Functions must have explicit return type declarations",
12
+ recommended: true,
13
+ category: "TypeScript"
14
+ },
15
+ schema: [
16
+ {
17
+ type: "object",
18
+ properties: {
19
+ allowExpressions: {
20
+ type: "boolean",
21
+ default: false
22
+ },
23
+ allowTypedFunctionExpressions: {
24
+ type: "boolean",
25
+ default: true
26
+ },
27
+ allowHigherOrderFunctions: {
28
+ type: "boolean",
29
+ default: true
30
+ }
31
+ },
32
+ additionalProperties: false
33
+ }
34
+ ],
35
+ messages: {
36
+ missingReturnType: "Function '{{name}}' is missing explicit return type annotation. Consider adding ': ReturnType'",
37
+ missingReturnTypeArrow: "Arrow function is missing explicit return type annotation. Consider adding ': ReturnType'",
38
+ missingReturnTypeMethod: "Method '{{name}}' is missing explicit return type annotation. Consider adding ': ReturnType'"
39
+ }
40
+ },
41
+ create(context) {
42
+ const options = context.options[0] || {};
43
+ const allowExpressions = options.allowExpressions || false;
44
+ const allowTypedFunctionExpressions = options.allowTypedFunctionExpressions || true;
45
+ const allowHigherOrderFunctions = options.allowHigherOrderFunctions || true;
46
+
47
+ function isTypedFunctionExpression(node) {
48
+ const parent = node.parent;
49
+ if (!parent) return false;
50
+
51
+ // Variable declaration with type annotation
52
+ if (parent.type === "VariableDeclarator" && parent.id && parent.id.typeAnnotation) {
53
+ return true;
54
+ }
55
+
56
+ // Property with type annotation
57
+ if (parent.type === "Property" && parent.typeAnnotation) {
58
+ return true;
59
+ }
60
+
61
+ // Assignment to typed variable
62
+ if (parent.type === "AssignmentExpression" && parent.left && parent.left.typeAnnotation) {
63
+ return true;
64
+ }
65
+
66
+ return false;
67
+ }
68
+
69
+ function isHigherOrderFunction(node) {
70
+ // Check if function returns another function
71
+ if (node.body && node.body.type === "BlockStatement") {
72
+ // Simple heuristic: look for return statements that return functions
73
+ return node.body.body.some(stmt => {
74
+ if (stmt.type === "ReturnStatement" && stmt.argument) {
75
+ return stmt.argument.type === "FunctionExpression" ||
76
+ stmt.argument.type === "ArrowFunctionExpression";
77
+ }
78
+ return false;
79
+ });
80
+ }
81
+
82
+ // Arrow function directly returning function
83
+ if (node.body &&
84
+ (node.body.type === "FunctionExpression" || node.body.type === "ArrowFunctionExpression")) {
85
+ return true;
86
+ }
87
+
88
+ return false;
89
+ }
90
+
91
+ function hasReturnTypeAnnotation(node) {
92
+ return node.returnType !== null && node.returnType !== undefined;
93
+ }
94
+
95
+ function checkFunction(node) {
96
+ // Skip if return type is already present
97
+ if (hasReturnTypeAnnotation(node)) {
98
+ return;
99
+ }
100
+
101
+ // Skip if this is a typed function expression and allowed
102
+ if (allowTypedFunctionExpressions && isTypedFunctionExpression(node)) {
103
+ return;
104
+ }
105
+
106
+ // Skip if this is a higher-order function and allowed
107
+ if (allowHigherOrderFunctions && isHigherOrderFunction(node)) {
108
+ return;
109
+ }
110
+
111
+ // Skip constructors
112
+ if (node.parent && node.parent.type === "MethodDefinition" && node.parent.kind === "constructor") {
113
+ return;
114
+ }
115
+
116
+ // Skip getters/setters (they have implicit return types)
117
+ if (node.parent && node.parent.type === "MethodDefinition" &&
118
+ (node.parent.kind === "get" || node.parent.kind === "set")) {
119
+ return;
120
+ }
121
+
122
+ // Get function name for better error messages
123
+ let functionName = "anonymous";
124
+ if (node.id && node.id.name) {
125
+ functionName = node.id.name;
126
+ } else if (node.parent && node.parent.type === "VariableDeclarator" && node.parent.id) {
127
+ functionName = node.parent.id.name;
128
+ } else if (node.parent && node.parent.type === "Property" && node.parent.key) {
129
+ functionName = node.parent.key.name || node.parent.key.value;
130
+ } else if (node.parent && node.parent.type === "MethodDefinition" && node.parent.key) {
131
+ functionName = node.parent.key.name;
132
+ }
133
+
134
+ // Report the violation
135
+ const messageId = node.type === "ArrowFunctionExpression" ? "missingReturnTypeArrow" :
136
+ node.parent && node.parent.type === "MethodDefinition" ? "missingReturnTypeMethod" :
137
+ "missingReturnType";
138
+
139
+ context.report({
140
+ node,
141
+ messageId,
142
+ data: { name: functionName }
143
+ });
144
+ }
145
+
146
+ return {
147
+ FunctionDeclaration(node) {
148
+ checkFunction(node);
149
+ },
150
+ FunctionExpression(node) {
151
+ // Skip if expressions are allowed and this is a simple expression
152
+ if (allowExpressions && node.parent &&
153
+ (node.parent.type === "CallExpression" || node.parent.type === "ArrayExpression")) {
154
+ return;
155
+ }
156
+ checkFunction(node);
157
+ },
158
+ ArrowFunctionExpression(node) {
159
+ // Skip if expressions are allowed and this is a simple expression
160
+ if (allowExpressions && node.parent &&
161
+ (node.parent.type === "CallExpression" || node.parent.type === "ArrayExpression")) {
162
+ return;
163
+ }
164
+ checkFunction(node);
165
+ }
166
+ };
167
+ }
168
+ };