@sun-asterisk/sunlint 1.0.7 → 1.1.3

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 (214) 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 +73 -52
  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} +22 -0
  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 +147 -0
  23. package/core/ast-modules/parsers/eslint-ts-parser.js +106 -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/enhanced-rules-registry.js +331 -0
  32. package/core/file-targeting-service.js +92 -23
  33. package/core/interfaces/analysis-engine.interface.js +100 -0
  34. package/core/multi-rule-runner.js +0 -221
  35. package/core/output-service.js +1 -1
  36. package/core/rule-mapping-service.js +1 -1
  37. package/core/rule-selection-service.js +10 -2
  38. package/docs/AI.md +163 -0
  39. package/docs/ARCHITECTURE.md +78 -0
  40. package/docs/CI-CD-GUIDE.md +315 -0
  41. package/docs/COMMAND-EXAMPLES.md +256 -0
  42. package/docs/CONFIGURATION.md +414 -0
  43. package/docs/DEBUG.md +86 -0
  44. package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
  45. package/docs/DISTRIBUTION.md +153 -0
  46. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  47. package/docs/ESLINT_INTEGRATION.md +238 -0
  48. package/docs/FOLDER_STRUCTURE.md +59 -0
  49. package/docs/HEURISTIC_VS_AI.md +113 -0
  50. package/docs/README.md +32 -0
  51. package/docs/RELEASE_GUIDE.md +230 -0
  52. package/engines/eslint-engine.js +601 -0
  53. package/engines/heuristic-engine.js +860 -0
  54. package/engines/openai-engine.js +374 -0
  55. package/engines/tree-sitter-parser.js +0 -0
  56. package/engines/universal-ast-engine.js +0 -0
  57. package/integrations/eslint/README.md +99 -0
  58. package/integrations/eslint/configs/.eslintrc.js +98 -0
  59. package/integrations/eslint/configs/eslint.config.js +133 -0
  60. package/integrations/eslint/configs/eslint.config.simple.js +24 -0
  61. package/integrations/eslint/package.json +23 -0
  62. package/integrations/eslint/plugin/index.js +164 -0
  63. package/integrations/eslint/plugin/package.json +13 -0
  64. package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
  65. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
  66. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
  67. package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
  68. package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
  69. package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
  70. package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
  71. package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
  72. package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
  73. package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
  74. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
  75. package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
  76. package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
  77. package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
  78. package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
  79. package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
  80. package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
  81. package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
  82. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
  83. package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
  84. package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
  85. package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
  86. package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
  87. package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
  88. package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
  89. package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  90. package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
  91. package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
  92. package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
  93. package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
  94. package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
  95. package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
  96. package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
  97. package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
  98. package/integrations/eslint/test-c041-rule.js +87 -0
  99. package/integrations/eslint/tsconfig.json +27 -0
  100. package/package.json +29 -16
  101. package/rules/README.md +252 -0
  102. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  103. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  104. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  105. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  106. package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
  107. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  108. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  109. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  110. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  111. package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
  112. package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
  113. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  114. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  115. package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
  116. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
  117. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  118. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  119. package/rules/docs/C002_no_duplicate_code.md +57 -0
  120. package/rules/index.js +149 -0
  121. package/rules/migration/converter.js +385 -0
  122. package/rules/migration/mapping.json +164 -0
  123. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  124. package/rules/security/S026_json_schema_validation/config.json +27 -0
  125. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
  126. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  127. package/rules/security/S029_csrf_protection/analyzer.js +264 -0
  128. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  129. package/rules/universal/C010/generic.js +0 -0
  130. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  131. package/rules/utils/ast-utils.js +191 -0
  132. package/rules/utils/base-analyzer.js +98 -0
  133. package/rules/utils/pattern-matchers.js +239 -0
  134. package/rules/utils/rule-helpers.js +264 -0
  135. package/rules/utils/severity-constants.js +93 -0
  136. package/scripts/build-release.sh +117 -0
  137. package/scripts/ci-report.js +179 -0
  138. package/scripts/install.sh +196 -0
  139. package/scripts/manual-release.sh +338 -0
  140. package/scripts/merge-reports.js +424 -0
  141. package/scripts/pre-release-test.sh +175 -0
  142. package/scripts/prepare-release.sh +202 -0
  143. package/scripts/setup-github-registry.sh +42 -0
  144. package/scripts/test-scripts/README.md +22 -0
  145. package/scripts/test-scripts/test-c041-comparison.js +114 -0
  146. package/scripts/test-scripts/test-c041-eslint.js +67 -0
  147. package/scripts/test-scripts/test-eslint-rules.js +146 -0
  148. package/scripts/test-scripts/test-real-world.js +44 -0
  149. package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
  150. package/scripts/trigger-release.sh +285 -0
  151. package/scripts/validate-rule-structure.js +148 -0
  152. package/scripts/verify-install.sh +82 -0
  153. package/config/sunlint-schema.json +0 -159
  154. package/config/typescript/custom-rules.js +0 -9
  155. package/config/typescript/package-lock.json +0 -1585
  156. package/config/typescript/package.json +0 -13
  157. package/config/typescript/security-rules/index.js +0 -90
  158. package/config/typescript/tsconfig.json +0 -29
  159. package/core/ai-analyzer.js +0 -169
  160. package/core/eslint-engine-service.js +0 -312
  161. package/core/eslint-instance-manager.js +0 -104
  162. package/core/eslint-integration-service.js +0 -363
  163. package/core/sunlint-engine-service.js +0 -23
  164. package/core/typescript-analyzer.js +0 -262
  165. package/core/typescript-engine.js +0 -313
  166. /package/config/{default.json → defaults/default.json} +0 -0
  167. /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
  168. /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
  169. /package/config/{typescript → testing}/test-s005-working.ts +0 -0
  170. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
  171. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
  172. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
  173. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
  174. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
  175. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
  176. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
  177. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
  178. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
  179. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
  180. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
  181. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
  182. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
  183. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
  184. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
  185. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
  186. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
  187. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
  188. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
  189. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
  190. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
  191. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
  192. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
  193. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
  194. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
  195. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
  196. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
  197. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
  198. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
  199. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
  200. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
  201. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
  202. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
  203. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
  204. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
  205. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
  206. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
  207. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
  208. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
  209. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
  210. /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
  211. /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
  212. /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
  213. /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
  214. /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Custom ESLint rule for: C003 – Clear variable names, avoid arbitrary abbreviations
3
+ * Rule ID: custom/c003
4
+ * Purpose: Ensure clear, understandable variable names without arbitrary abbreviations
5
+ */
6
+
7
+ const c003Rule = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Clear variable names, avoid arbitrary abbreviations",
12
+ recommended: false
13
+ },
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ allowedSingleChar: {
19
+ type: "array",
20
+ items: { type: "string" },
21
+ description: "Single character variables that are allowed (default: i, j, k)"
22
+ },
23
+ allowedAbbreviations: {
24
+ type: "array",
25
+ items: { type: "string" },
26
+ description: "Common abbreviations that are allowed"
27
+ },
28
+ minLength: {
29
+ type: "integer",
30
+ minimum: 1,
31
+ description: "Minimum variable name length (default: 2)"
32
+ }
33
+ },
34
+ additionalProperties: false
35
+ }
36
+ ],
37
+ messages: {
38
+ singleChar: "Variable '{{name}}' is only 1 character long. Use descriptive names (except for counters like i, j, k).",
39
+ tooShort: "Variable '{{name}}' is too short ({{length}} characters). Use descriptive names with at least {{minLength}} characters.",
40
+ abbreviation: "Variable '{{name}}' appears to be an unclear abbreviation. Use full descriptive names.",
41
+ unclear: "Variable '{{name}}' is unclear or ambiguous. Use more specific descriptive names."
42
+ }
43
+ },
44
+
45
+ create(context) {
46
+ const options = context.options[0] || {};
47
+ const allowedSingleChar = new Set(options.allowedSingleChar || ['i', 'j', 'k', 'x', 'y', 'z']);
48
+ const allowedAbbreviations = new Set(options.allowedAbbreviations || [
49
+ 'id', 'url', 'api', 'ui', 'db', 'config', 'env', 'app',
50
+ 'btn', 'img', 'src', 'dest', 'req', 'res', 'ctx',
51
+ 'min', 'max', 'len', 'num', 'str', 'json'
52
+ ]);
53
+ const minLength = options.minLength || 2;
54
+
55
+ // Common abbreviation patterns that should be avoided
56
+ const suspiciousAbbreviations = [
57
+ /^[a-z]{1,2}[0-9]*$/, // e.g., 'u', 'usr', 'n1', 'v2'
58
+ /^[a-z]*[aeiou]*[bcdfghjklmnpqrstvwxyz]{3,}$/, // too many consonants
59
+ /^[bcdfghjklmnpqrstvwxyz]{3,}[aeiou]*$/, // consonants at start
60
+ /^(tmp|temp|val|var|data|info|item|elem|el|obj|arr)([A-Z0-9].*)?$/, // generic names
61
+ /^[a-z]+(Mgr|Ctrl|Svc|Repo|Util|Hlpr|Mngr)$/, // manager/helper patterns
62
+ ];
63
+
64
+ // Generic/unclear variable names that should be avoided
65
+ const unclearNames = new Set([
66
+ 'data', 'info', 'item', 'element', 'object', 'value', 'result',
67
+ 'response', 'request', 'temp', 'tmp', 'var', 'variable',
68
+ 'stuff', 'thing', 'something', 'anything', 'everything',
69
+ 'flag', 'check', 'test', 'validate', 'process', 'handle',
70
+ 'obj', 'arg', 'val', 'fn'
71
+ ]);
72
+
73
+ function isCounterContext(node) {
74
+ // Check if variable is used as a loop counter
75
+ if (!node || !node.parent) return false;
76
+
77
+ let parent = node.parent;
78
+
79
+ // Go up the tree to find ForStatement/ForInStatement/ForOfStatement
80
+ while (parent) {
81
+ if (parent.type === 'ForStatement') {
82
+ return parent.init && parent.init.declarations &&
83
+ parent.init.declarations.some(decl => decl && decl.id === node);
84
+ }
85
+ if (parent.type === 'ForInStatement' || parent.type === 'ForOfStatement') {
86
+ return parent.left && (parent.left === node ||
87
+ (parent.left.type === 'VariableDeclaration' &&
88
+ parent.left.declarations.some(decl => decl && decl.id === node)));
89
+ }
90
+ parent = parent.parent;
91
+ }
92
+ return false;
93
+ }
94
+
95
+ function checkVariableName(node, name) {
96
+ // Safety checks
97
+ if (!node || !name || typeof name !== 'string') {
98
+ return;
99
+ }
100
+
101
+ // Skip if it's a destructuring pattern with specific exceptions
102
+ if (node.parent && node.parent.type === 'Property' && node.parent.shorthand) {
103
+ return; // Allow destructuring shorthand
104
+ }
105
+
106
+ // Skip TypeScript type annotations and interface properties
107
+ if (node.parent && (node.parent.type === 'TSTypeAnnotation' ||
108
+ node.parent.type === 'TSPropertySignature' ||
109
+ node.parent.type === 'TSMethodSignature')) {
110
+ return;
111
+ }
112
+
113
+ // Single character check
114
+ if (name.length === 1) {
115
+ if (!allowedSingleChar.has(name.toLowerCase()) && !isCounterContext(node)) {
116
+ context.report({
117
+ node,
118
+ messageId: "singleChar",
119
+ data: { name }
120
+ });
121
+ }
122
+ return;
123
+ }
124
+
125
+ // Minimum length check
126
+ if (name.length < minLength) {
127
+ context.report({
128
+ node,
129
+ messageId: "tooShort",
130
+ data: { name, length: name.length, minLength }
131
+ });
132
+ return;
133
+ }
134
+
135
+ // Skip allowed abbreviations
136
+ if (allowedAbbreviations.has(name.toLowerCase())) {
137
+ return;
138
+ }
139
+
140
+ // Check for unclear/generic names
141
+ if (unclearNames.has(name.toLowerCase())) {
142
+ context.report({
143
+ node,
144
+ messageId: "unclear",
145
+ data: { name }
146
+ });
147
+ return;
148
+ }
149
+
150
+ // Check for suspicious abbreviation patterns
151
+ for (const pattern of suspiciousAbbreviations) {
152
+ if (pattern.test(name)) {
153
+ context.report({
154
+ node,
155
+ messageId: "abbreviation",
156
+ data: { name }
157
+ });
158
+ return;
159
+ }
160
+ }
161
+ }
162
+
163
+ function checkParameter(param) {
164
+ if (!param) return;
165
+
166
+ if (param.type === 'Identifier') {
167
+ checkVariableName(param, param.name);
168
+ } else if (param.type === 'AssignmentPattern' && param.left && param.left.type === 'Identifier') {
169
+ // Handle default parameters
170
+ checkVariableName(param.left, param.left.name);
171
+ } else if (param.type === 'ObjectPattern' && param.properties) {
172
+ // Handle object destructuring in parameters
173
+ param.properties.forEach(prop => {
174
+ if (prop && prop.type === 'Property' && prop.value && prop.value.type === 'Identifier') {
175
+ checkVariableName(prop.value, prop.value.name);
176
+ }
177
+ });
178
+ } else if (param.type === 'ArrayPattern' && param.elements) {
179
+ // Handle array destructuring in parameters
180
+ param.elements.forEach(element => {
181
+ if (element && element.type === 'Identifier') {
182
+ checkVariableName(element, element.name);
183
+ }
184
+ });
185
+ }
186
+ }
187
+
188
+ return {
189
+ VariableDeclarator(node) {
190
+ if (!node || !node.id) return;
191
+
192
+ if (node.id.type === 'Identifier') {
193
+ checkVariableName(node.id, node.id.name);
194
+ } else if (node.id.type === 'ObjectPattern') {
195
+ // Handle destructuring
196
+ if (node.id.properties) {
197
+ node.id.properties.forEach(prop => {
198
+ if (prop && prop.type === 'Property' && prop.value && prop.value.type === 'Identifier') {
199
+ checkVariableName(prop.value, prop.value.name);
200
+ }
201
+ });
202
+ }
203
+ } else if (node.id.type === 'ArrayPattern') {
204
+ // Handle array destructuring
205
+ if (node.id.elements) {
206
+ node.id.elements.forEach(element => {
207
+ if (element && element.type === 'Identifier') {
208
+ checkVariableName(element, element.name);
209
+ }
210
+ });
211
+ }
212
+ }
213
+ },
214
+
215
+ FunctionDeclaration(node) {
216
+ // Check function parameters
217
+ if (node && node.params) {
218
+ node.params.forEach(param => checkParameter(param));
219
+ }
220
+ },
221
+
222
+ ArrowFunctionExpression(node) {
223
+ // Check arrow function parameters
224
+ if (node && node.params) {
225
+ node.params.forEach(param => checkParameter(param));
226
+ }
227
+ },
228
+
229
+ FunctionExpression(node) {
230
+ // Check function expression parameters
231
+ if (node && node.params) {
232
+ node.params.forEach(param => checkParameter(param));
233
+ }
234
+ },
235
+
236
+ CatchClause(node) {
237
+ // Check catch clause parameters
238
+ if (node && node.param && node.param.type === 'Identifier') {
239
+ checkVariableName(node.param, node.param.name);
240
+ }
241
+ }
242
+ };
243
+ }
244
+ };
245
+
246
+ module.exports = c003Rule;
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Custom ESLint rule for: C006 – Function names must be verbs or verb-noun phrases
3
+ * Rule ID: custom/c006
4
+ * Purpose: Enforce function naming convention using verbs or verb-noun phrases to clearly indicate actions
5
+ */
6
+
7
+ const c006Rule = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Function names must be verbs or verb-noun phrases",
12
+ recommended: false
13
+ },
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ allowedVerbs: {
19
+ type: "array",
20
+ items: { type: "string" },
21
+ description: "Additional verbs to allow (beyond common ones)"
22
+ },
23
+ allowedPrefixes: {
24
+ type: "array",
25
+ items: { type: "string" },
26
+ description: "Allowed verb prefixes (default: get, set, is, has, can, should, etc.)"
27
+ },
28
+ allowConstructors: {
29
+ type: "boolean",
30
+ description: "Allow constructor functions (PascalCase) (default: true)"
31
+ }
32
+ },
33
+ additionalProperties: false
34
+ }
35
+ ],
36
+ messages: {
37
+ notVerbNoun: "Function '{{name}}' should be a verb or verb-noun phrase (e.g., 'getData', 'calculateTotal', 'validateInput').",
38
+ useVerbForm: "Function '{{name}}' should start with a verb. Consider: '{{suggestions}}'.",
39
+ avoidNounOnly: "Function '{{name}}' appears to be a noun only. Use verb form like 'get{{name}}', 'create{{name}}', or 'process{{name}}'."
40
+ }
41
+ },
42
+
43
+ create(context) {
44
+ const options = context.options[0] || {};
45
+
46
+ // Common verb prefixes that indicate action
47
+ const commonVerbPrefixes = new Set([
48
+ 'get', 'set', 'fetch', 'load', 'save', 'store', 'update', 'delete', 'remove',
49
+ 'create', 'make', 'build', 'generate', 'produce', 'construct',
50
+ 'add', 'insert', 'append', 'push', 'pop', 'shift', 'unshift',
51
+ 'find', 'search', 'filter', 'sort', 'map', 'reduce', 'transform',
52
+ 'validate', 'verify', 'check', 'test', 'confirm', 'ensure',
53
+ 'calculate', 'compute', 'process', 'parse', 'format', 'convert',
54
+ 'send', 'receive', 'transmit', 'broadcast', 'emit', 'dispatch',
55
+ 'open', 'close', 'start', 'stop', 'begin', 'end', 'finish',
56
+ 'show', 'hide', 'display', 'render', 'draw', 'paint',
57
+ 'connect', 'disconnect', 'link', 'unlink', 'attach', 'detach',
58
+ 'enable', 'disable', 'activate', 'deactivate', 'toggle',
59
+ 'is', 'has', 'can', 'should', 'will', 'must', 'may', 'does',
60
+ 'handle', 'manage', 'control', 'execute', 'run', 'invoke',
61
+ 'reset', 'clear', 'clean', 'refresh', 'reload', 'restore',
62
+
63
+ // Based on user feedback - missing important verbs
64
+ 'count', 'reopen', 'request', 'use', 'go', 'retry', 'redirect',
65
+
66
+ // Event handler prefixes
67
+ 'on',
68
+
69
+ // Common patterns that should be allowed as verbs
70
+ 'process', // can be both noun and verb - when standalone should be allowed
71
+ ]);
72
+
73
+ const allowedPrefixes = new Set([
74
+ ...commonVerbPrefixes,
75
+ ...(options.allowedPrefixes || [])
76
+ ]);
77
+
78
+ const allowedVerbs = new Set([
79
+ ...commonVerbPrefixes,
80
+ ...(options.allowedVerbs || [])
81
+ ]);
82
+
83
+ const allowConstructors = options.allowConstructors !== false;
84
+
85
+ // Helper function to check if a name is PascalCase (likely a constructor)
86
+ function isPascalCase(name) {
87
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
88
+ }
89
+
90
+ // Helper function to check if a name is camelCase starting with a verb
91
+ function isVerbNounPattern(name) {
92
+ if (!name || name.length === 0) return false;
93
+
94
+ // Check if it starts with a known verb prefix
95
+ const lowerName = name.toLowerCase();
96
+ for (const verb of allowedPrefixes) {
97
+ if (lowerName.startsWith(verb.toLowerCase())) {
98
+ return true;
99
+ }
100
+ }
101
+
102
+ return false;
103
+ }
104
+
105
+ // Generate suggestions for a noun-based function name
106
+ function generateSuggestions(name) {
107
+ const suggestions = [
108
+ `get${name.charAt(0).toUpperCase() + name.slice(1)}`,
109
+ `create${name.charAt(0).toUpperCase() + name.slice(1)}`,
110
+ `process${name.charAt(0).toUpperCase() + name.slice(1)}`
111
+ ];
112
+ return suggestions.join(', ');
113
+ }
114
+
115
+ // Check if the name appears to be just a noun
116
+ function isLikelyNounOnly(name) {
117
+ const nounPatterns = [
118
+ /^(user|data|info|item|list|array|object|config|settings|options)$/i,
119
+ /^(file|document|record|entry|element|component|widget)$/i,
120
+ /^(message|notification|alert|error|warning|success)$/i,
121
+ /^(report|summary|total|count|number|value|result)$/i
122
+ ];
123
+
124
+ return nounPatterns.some(pattern => pattern.test(name));
125
+ }
126
+
127
+ function checkFunctionName(node, name) {
128
+ // Safety checks
129
+ if (!node || !name || typeof name !== 'string') {
130
+ return;
131
+ }
132
+
133
+ // Allow constructor functions (PascalCase)
134
+ if (allowConstructors && isPascalCase(name)) {
135
+ return;
136
+ }
137
+
138
+ // Skip very short names (likely okay: a, b, fn, etc.)
139
+ if (name.length <= 2) {
140
+ return;
141
+ }
142
+
143
+ // Check if it follows verb-noun pattern
144
+ if (isVerbNounPattern(name)) {
145
+ return; // Good! Follows the pattern
146
+ }
147
+
148
+ // Check if it's likely a noun-only name
149
+ if (isLikelyNounOnly(name)) {
150
+ context.report({
151
+ node,
152
+ messageId: "avoidNounOnly",
153
+ data: {
154
+ name,
155
+ suggestions: generateSuggestions(name)
156
+ }
157
+ });
158
+ return;
159
+ }
160
+
161
+ // General violation - doesn't start with verb
162
+ context.report({
163
+ node,
164
+ messageId: "notVerbNoun",
165
+ data: { name }
166
+ });
167
+ }
168
+
169
+ return {
170
+ FunctionDeclaration(node) {
171
+ if (node.id && node.id.name) {
172
+ checkFunctionName(node.id, node.id.name);
173
+ }
174
+ },
175
+
176
+ FunctionExpression(node) {
177
+ // Check named function expressions
178
+ if (node.id && node.id.name) {
179
+ checkFunctionName(node.id, node.id.name);
180
+ }
181
+ },
182
+
183
+ ArrowFunctionExpression(node) {
184
+ // For arrow functions assigned to variables
185
+ if (node.parent && node.parent.type === 'VariableDeclarator' && node.parent.id) {
186
+ checkFunctionName(node.parent.id, node.parent.id.name);
187
+ }
188
+ },
189
+
190
+ MethodDefinition(node) {
191
+ // Check class methods
192
+ if (node.key && node.key.name && node.kind === 'method') {
193
+ // Skip constructor methods
194
+ if (node.key.name !== 'constructor') {
195
+ checkFunctionName(node.key, node.key.name);
196
+ }
197
+ }
198
+ },
199
+
200
+ Property(node) {
201
+ // Check object method properties
202
+ if (node.method && node.key && node.key.name) {
203
+ checkFunctionName(node.key, node.key.name);
204
+ }
205
+ // Check function values assigned to object properties
206
+ if (!node.method && node.value &&
207
+ (node.value.type === 'FunctionExpression' || node.value.type === 'ArrowFunctionExpression') &&
208
+ node.key && node.key.name) {
209
+ checkFunctionName(node.key, node.key.name);
210
+ }
211
+ }
212
+ };
213
+ }
214
+ };
215
+
216
+ module.exports = c006Rule;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Custom ESLint rule for: C010 – Không nên có hơn 3 cấp lồng nhau (nested block)
3
+ * Rule ID: custom/c010
4
+ * Goal: Limit nested blocks (if/for/while/switch) to maximum 3 levels to improve readability and maintainability
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Không nên có hơn 3 cấp lồng nhau (nested block)",
12
+ recommended: false
13
+ },
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ maxDepth: {
19
+ type: "integer",
20
+ minimum: 1
21
+ }
22
+ },
23
+ additionalProperties: false
24
+ }
25
+ ],
26
+ messages: {
27
+ tooDeep: "Block nesting is too deep (level {{depth}}). Maximum allowed is {{maxDepth}} levels."
28
+ }
29
+ },
30
+ create(context) {
31
+ const options = context.options[0] || {};
32
+ const maxDepth = options.maxDepth || 3;
33
+ let blockStack = [];
34
+
35
+ function enterBlock(node) {
36
+ blockStack.push(node);
37
+ const depth = blockStack.length;
38
+
39
+ if (depth > maxDepth) {
40
+ context.report({
41
+ node,
42
+ messageId: "tooDeep",
43
+ data: {
44
+ depth,
45
+ maxDepth
46
+ }
47
+ });
48
+ }
49
+ }
50
+
51
+ function exitBlock() {
52
+ blockStack.pop();
53
+ }
54
+
55
+ return {
56
+ // Handle if statements
57
+ IfStatement: enterBlock,
58
+ 'IfStatement:exit': exitBlock,
59
+
60
+ // Handle for loops
61
+ ForStatement: enterBlock,
62
+ 'ForStatement:exit': exitBlock,
63
+
64
+ ForInStatement: enterBlock,
65
+ 'ForInStatement:exit': exitBlock,
66
+
67
+ ForOfStatement: enterBlock,
68
+ 'ForOfStatement:exit': exitBlock,
69
+
70
+ // Handle while loops
71
+ WhileStatement: enterBlock,
72
+ 'WhileStatement:exit': exitBlock,
73
+
74
+ DoWhileStatement: enterBlock,
75
+ 'DoWhileStatement:exit': exitBlock,
76
+
77
+ // Handle switch statements
78
+ SwitchStatement: enterBlock,
79
+ 'SwitchStatement:exit': exitBlock,
80
+
81
+ // Handle try-catch blocks
82
+ TryStatement: enterBlock,
83
+ 'TryStatement:exit': exitBlock,
84
+
85
+ // Handle with statements (though rarely used)
86
+ WithStatement: enterBlock,
87
+ 'WithStatement:exit': exitBlock
88
+ };
89
+ }
90
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Custom ESLint rule for: C013 – Do not leave dead code commented out
3
+ * Rule ID: custom/c013
4
+ * Purpose: Prevent commented-out code from being left in the codebase to maintain cleanliness
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Do not leave dead code commented out",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ deadCode: "Unreachable code detected after return statement. Remove dead code or restructure logic.",
17
+ commentedCode: "Do not leave dead code commented out. Remove it or use version control to track changes."
18
+ }
19
+ },
20
+ create(context) {
21
+ function isTerminatingStatement(stmt) {
22
+ return stmt.type === "ReturnStatement" ||
23
+ stmt.type === "ThrowStatement" ||
24
+ stmt.type === "ContinueStatement" ||
25
+ stmt.type === "BreakStatement";
26
+ }
27
+
28
+ function isExecutableStatement(stmt) {
29
+ // Exclude declarations and empty statements
30
+ return stmt.type !== "EmptyStatement" &&
31
+ stmt.type !== "FunctionDeclaration" &&
32
+ stmt.type !== "VariableDeclaration" &&
33
+ !stmt.type.includes("Declaration");
34
+ }
35
+
36
+ function checkBlockForUnreachableCode(node) {
37
+ let unreachable = false;
38
+ for (let i = 0; i < node.body.length; i++) {
39
+ const stmt = node.body[i];
40
+
41
+ if (unreachable && isExecutableStatement(stmt)) {
42
+ context.report({
43
+ node: stmt,
44
+ messageId: "deadCode"
45
+ });
46
+ }
47
+
48
+ if (isTerminatingStatement(stmt)) {
49
+ unreachable = true;
50
+ }
51
+ }
52
+ }
53
+
54
+ return {
55
+ // Handle unreachable code in all block statements
56
+ BlockStatement: checkBlockForUnreachableCode,
57
+
58
+ // Handle switch cases
59
+ SwitchCase(node) {
60
+ let unreachable = false;
61
+ for (let i = 0; i < node.consequent.length; i++) {
62
+ const stmt = node.consequent[i];
63
+
64
+ if (unreachable && isExecutableStatement(stmt)) {
65
+ context.report({
66
+ node: stmt,
67
+ messageId: "deadCode"
68
+ });
69
+ }
70
+
71
+ if (isTerminatingStatement(stmt)) {
72
+ unreachable = true;
73
+ }
74
+ }
75
+ }
76
+ };
77
+ }
78
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Custom ESLint rule for: C014 – Use Dependency Injection instead of direct instantiation
3
+ * Rule ID: custom/c014
4
+ * Purpose: Enforce dependency injection pattern by preventing direct class instantiation
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Use Dependency Injection instead of direct instantiation",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ directInstantiation: "Avoid direct class instantiation. Use dependency injection instead."
17
+ }
18
+ },
19
+ create(context) {
20
+ return {
21
+ NewExpression(node) {
22
+ if (
23
+ node.callee &&
24
+ node.callee.type === "Identifier" &&
25
+ /^[A-Z]/.test(node.callee.name) // Class name starts with uppercase
26
+ ) {
27
+ context.report({
28
+ node,
29
+ messageId: "directInstantiation",
30
+ data: {
31
+ name: node.callee.name
32
+ }
33
+ });
34
+ }
35
+ }
36
+ };
37
+ }
38
+ };