@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,146 @@
1
+ /**
2
+ * Custom ESLint rule for: C017 – Limit constructor logic
3
+ * Rule ID: custom/c017
4
+ * Purpose: Enforce minimal logic in constructors to maintain clean initialization
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Limit constructor logic",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ constructorLogic: "Constructor contains complex logic: {{description}}. Move to initialization methods",
17
+ tooManyStatements: "Constructor has too many statements ({{count}}). Consider simplifying or moving logic to initialization methods"
18
+ }
19
+ },
20
+ create(context) {
21
+ function isSimpleAssignment(node) {
22
+ // Simple property assignments like this.prop = value
23
+ if (node.type === "ExpressionStatement" &&
24
+ node.expression.type === "AssignmentExpression" &&
25
+ node.expression.left.type === "MemberExpression" &&
26
+ node.expression.left.object.type === "ThisExpression") {
27
+
28
+ // Check if right side is a simple value (not complex computation)
29
+ const right = node.expression.right;
30
+ return right.type === "Literal" ||
31
+ right.type === "Identifier" ||
32
+ (right.type === "MemberExpression" && right.property.name === "length") ||
33
+ (right.type === "CallExpression" && right.callee.type === "Identifier" &&
34
+ ['require', 'process'].includes(right.callee.name));
35
+ }
36
+ return false;
37
+ }
38
+
39
+ function isSimpleDeclaration(node) {
40
+ // Simple variable declarations
41
+ return node.type === "VariableDeclaration";
42
+ }
43
+
44
+ function isSuperCall(node) {
45
+ // super() calls
46
+ return node.type === "ExpressionStatement" &&
47
+ node.expression.type === "CallExpression" &&
48
+ node.expression.callee.type === "Super";
49
+ }
50
+
51
+ function isComplexLogic(node) {
52
+ // Complex patterns that shouldn't be in constructor
53
+ switch (node.type) {
54
+ case "IfStatement":
55
+ case "WhileStatement":
56
+ case "ForStatement":
57
+ case "SwitchStatement":
58
+ case "TryStatement":
59
+ return { type: "control_flow", description: "control flow statements" };
60
+
61
+ case "ExpressionStatement":
62
+ if (node.expression.type === "CallExpression") {
63
+ const callee = node.expression.callee;
64
+
65
+ // Method calls that aren't simple setters
66
+ if (callee.type === "MemberExpression") {
67
+ const methodName = callee.property.name;
68
+
69
+ // Allow simple MobX observable setup
70
+ if (methodName === "makeObservable" || methodName === "makeAutoObservable") {
71
+ return null;
72
+ }
73
+
74
+ // Flag complex method calls
75
+ if (!['push', 'set', 'add'].includes(methodName)) {
76
+ return { type: "method_call", description: "complex method calls" };
77
+ }
78
+ }
79
+
80
+ // Direct function calls (not method calls)
81
+ if (callee.type === "Identifier" &&
82
+ !['require', 'parseInt', 'parseFloat', 'Boolean', 'Number', 'String'].includes(callee.name)) {
83
+ return { type: "function_call", description: "function calls" };
84
+ }
85
+ }
86
+
87
+ // Complex assignments with computations
88
+ if (node.expression.type === "AssignmentExpression") {
89
+ const right = node.expression.right;
90
+ if (right.type === "BinaryExpression" ||
91
+ right.type === "ConditionalExpression" ||
92
+ (right.type === "CallExpression" &&
93
+ right.callee.type === "MemberExpression" &&
94
+ !['map', 'filter', 'toString', 'slice'].includes(right.callee.property.name))) {
95
+ return { type: "complex_assignment", description: "complex computations" };
96
+ }
97
+ }
98
+ break;
99
+ }
100
+
101
+ return null;
102
+ }
103
+
104
+ return {
105
+ MethodDefinition(node) {
106
+ if (node.kind === "constructor" && node.value && node.value.body) {
107
+ const statements = node.value.body.body;
108
+ let complexLogicCount = 0;
109
+
110
+ for (const stmt of statements) {
111
+ // Skip simple assignments, declarations, and super calls
112
+ if (isSimpleAssignment(stmt) ||
113
+ isSimpleDeclaration(stmt) ||
114
+ isSuperCall(stmt)) {
115
+ continue;
116
+ }
117
+
118
+ // Check for complex logic
119
+ const complexity = isComplexLogic(stmt);
120
+ if (complexity) {
121
+ context.report({
122
+ node: stmt,
123
+ messageId: "constructorLogic",
124
+ data: {
125
+ description: complexity.description
126
+ }
127
+ });
128
+ complexLogicCount++;
129
+ }
130
+ }
131
+
132
+ // Also flag constructors with too many statements overall
133
+ if (statements.length > 10) {
134
+ context.report({
135
+ node: node,
136
+ messageId: "tooManyStatements",
137
+ data: {
138
+ count: statements.length
139
+ }
140
+ });
141
+ }
142
+ }
143
+ }
144
+ };
145
+ }
146
+ };
@@ -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;