@sun-asterisk/sunlint 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/LICENSE +21 -0
  3. package/README.md +490 -0
  4. package/cli-legacy.js +355 -0
  5. package/cli.js +35 -0
  6. package/config/default.json +22 -0
  7. package/config/presets/beginner.json +36 -0
  8. package/config/presets/ci.json +46 -0
  9. package/config/presets/recommended.json +24 -0
  10. package/config/presets/strict.json +32 -0
  11. package/config/rules-registry.json +681 -0
  12. package/config/sunlint-schema.json +166 -0
  13. package/config/typescript/custom-rules-new.js +0 -0
  14. package/config/typescript/custom-rules.js +9 -0
  15. package/config/typescript/eslint.config.js +110 -0
  16. package/config/typescript/package-lock.json +1585 -0
  17. package/config/typescript/package.json +13 -0
  18. package/config/typescript/security-rules/index.js +90 -0
  19. package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
  20. package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
  21. package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
  22. package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
  23. package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
  24. package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
  25. package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
  26. package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
  27. package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
  28. package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
  29. package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
  30. package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
  31. package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
  32. package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
  33. package/config/typescript/security-rules/s022-output-encoding.js +78 -0
  34. package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
  35. package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
  36. package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
  37. package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
  38. package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
  39. package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
  40. package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
  41. package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
  42. package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
  43. package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
  44. package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
  45. package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
  46. package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
  47. package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
  48. package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
  49. package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
  50. package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
  51. package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
  52. package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
  53. package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
  54. package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
  55. package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
  56. package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
  57. package/config/typescript/security-rules/s057-utc-logging.js +54 -0
  58. package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
  59. package/config/typescript/test-s005-working.ts +22 -0
  60. package/config/typescript/tsconfig.json +29 -0
  61. package/core/ai-analyzer.js +169 -0
  62. package/core/analysis-orchestrator.js +705 -0
  63. package/core/cli-action-handler.js +230 -0
  64. package/core/cli-program.js +106 -0
  65. package/core/config-manager.js +396 -0
  66. package/core/config-merger.js +136 -0
  67. package/core/config-override-processor.js +74 -0
  68. package/core/config-preset-resolver.js +65 -0
  69. package/core/config-source-loader.js +152 -0
  70. package/core/config-validator.js +126 -0
  71. package/core/dependency-manager.js +105 -0
  72. package/core/eslint-engine-service.js +312 -0
  73. package/core/eslint-instance-manager.js +104 -0
  74. package/core/eslint-integration-service.js +363 -0
  75. package/core/git-utils.js +170 -0
  76. package/core/multi-rule-runner.js +239 -0
  77. package/core/output-service.js +250 -0
  78. package/core/report-generator.js +320 -0
  79. package/core/rule-mapping-service.js +309 -0
  80. package/core/rule-selection-service.js +121 -0
  81. package/core/sunlint-engine-service.js +23 -0
  82. package/core/typescript-analyzer.js +262 -0
  83. package/core/typescript-engine.js +313 -0
  84. package/docs/AI.md +163 -0
  85. package/docs/ARCHITECTURE.md +78 -0
  86. package/docs/CI-CD-GUIDE.md +315 -0
  87. package/docs/COMMAND-EXAMPLES.md +256 -0
  88. package/docs/DEBUG.md +86 -0
  89. package/docs/DISTRIBUTION.md +153 -0
  90. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  91. package/docs/ESLINT_INTEGRATION.md +238 -0
  92. package/docs/FOLDER_STRUCTURE.md +59 -0
  93. package/docs/HEURISTIC_VS_AI.md +113 -0
  94. package/docs/README.md +32 -0
  95. package/docs/RELEASE_GUIDE.md +230 -0
  96. package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
  97. package/eslint-integration/.eslintrc.js +98 -0
  98. package/eslint-integration/cli.js +35 -0
  99. package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
  100. package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
  101. package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
  102. package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
  103. package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
  104. package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
  105. package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
  106. package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
  107. package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
  108. package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
  109. package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
  110. package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
  111. package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
  112. package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
  113. package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
  114. package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
  115. package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
  116. package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
  117. package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
  118. package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
  119. package/eslint-integration/eslint-plugin-custom/index.js +155 -0
  120. package/eslint-integration/eslint-plugin-custom/package.json +13 -0
  121. package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
  122. package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
  123. package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
  124. package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
  125. package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
  126. package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
  127. package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
  128. package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
  129. package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
  130. package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
  131. package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
  132. package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
  133. package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
  134. package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
  135. package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
  136. package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
  137. package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
  138. package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
  139. package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
  140. package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
  141. package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
  142. package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
  143. package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
  144. package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
  145. package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
  146. package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
  147. package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
  148. package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
  149. package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
  150. package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
  151. package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
  152. package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
  153. package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
  154. package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
  155. package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
  156. package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
  157. package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
  158. package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
  159. package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
  160. package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
  161. package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
  162. package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  163. package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
  164. package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
  165. package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
  166. package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
  167. package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
  168. package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
  169. package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
  170. package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
  171. package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
  172. package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
  173. package/eslint-integration/eslint.config.js +125 -0
  174. package/eslint-integration/eslint.config.simple.js +24 -0
  175. package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
  176. package/eslint-integration/package.json +23 -0
  177. package/eslint-integration/sample.ts +53 -0
  178. package/eslint-integration/test-s003.js +5 -0
  179. package/eslint-integration/tsconfig.json +27 -0
  180. package/examples/.github/workflows/code-quality.yml +111 -0
  181. package/examples/.sunlint.json +42 -0
  182. package/examples/README.md +47 -0
  183. package/examples/package.json +33 -0
  184. package/package.json +100 -0
  185. package/rules/C006_function_naming/analyzer.js +338 -0
  186. package/rules/C006_function_naming/config.json +86 -0
  187. package/rules/C019_log_level_usage/analyzer.js +359 -0
  188. package/rules/C019_log_level_usage/config.json +121 -0
  189. package/rules/C029_catch_block_logging/analyzer.js +339 -0
  190. package/rules/C029_catch_block_logging/config.json +59 -0
  191. package/rules/C031_validation_separation/README.md +72 -0
  192. package/rules/C031_validation_separation/analyzer.js +186 -0
@@ -0,0 +1,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,207 @@
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',
60
+ 'handle', 'manage', 'control', 'execute', 'run', 'invoke',
61
+ 'reset', 'clear', 'clean', 'refresh', 'reload', 'restore'
62
+ ]);
63
+
64
+ const allowedPrefixes = new Set([
65
+ ...commonVerbPrefixes,
66
+ ...(options.allowedPrefixes || [])
67
+ ]);
68
+
69
+ const allowedVerbs = new Set([
70
+ ...commonVerbPrefixes,
71
+ ...(options.allowedVerbs || [])
72
+ ]);
73
+
74
+ const allowConstructors = options.allowConstructors !== false;
75
+
76
+ // Helper function to check if a name is PascalCase (likely a constructor)
77
+ function isPascalCase(name) {
78
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
79
+ }
80
+
81
+ // Helper function to check if a name is camelCase starting with a verb
82
+ function isVerbNounPattern(name) {
83
+ if (!name || name.length === 0) return false;
84
+
85
+ // Check if it starts with a known verb prefix
86
+ const lowerName = name.toLowerCase();
87
+ for (const verb of allowedPrefixes) {
88
+ if (lowerName.startsWith(verb.toLowerCase())) {
89
+ return true;
90
+ }
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ // Generate suggestions for a noun-based function name
97
+ function generateSuggestions(name) {
98
+ const suggestions = [
99
+ `get${name.charAt(0).toUpperCase() + name.slice(1)}`,
100
+ `create${name.charAt(0).toUpperCase() + name.slice(1)}`,
101
+ `process${name.charAt(0).toUpperCase() + name.slice(1)}`
102
+ ];
103
+ return suggestions.join(', ');
104
+ }
105
+
106
+ // Check if the name appears to be just a noun
107
+ function isLikelyNounOnly(name) {
108
+ const nounPatterns = [
109
+ /^(user|data|info|item|list|array|object|config|settings|options)$/i,
110
+ /^(file|document|record|entry|element|component|widget)$/i,
111
+ /^(message|notification|alert|error|warning|success)$/i,
112
+ /^(report|summary|total|count|number|value|result)$/i
113
+ ];
114
+
115
+ return nounPatterns.some(pattern => pattern.test(name));
116
+ }
117
+
118
+ function checkFunctionName(node, name) {
119
+ // Safety checks
120
+ if (!node || !name || typeof name !== 'string') {
121
+ return;
122
+ }
123
+
124
+ // Allow constructor functions (PascalCase)
125
+ if (allowConstructors && isPascalCase(name)) {
126
+ return;
127
+ }
128
+
129
+ // Skip very short names (likely okay: a, b, fn, etc.)
130
+ if (name.length <= 2) {
131
+ return;
132
+ }
133
+
134
+ // Check if it follows verb-noun pattern
135
+ if (isVerbNounPattern(name)) {
136
+ return; // Good! Follows the pattern
137
+ }
138
+
139
+ // Check if it's likely a noun-only name
140
+ if (isLikelyNounOnly(name)) {
141
+ context.report({
142
+ node,
143
+ messageId: "avoidNounOnly",
144
+ data: {
145
+ name,
146
+ suggestions: generateSuggestions(name)
147
+ }
148
+ });
149
+ return;
150
+ }
151
+
152
+ // General violation - doesn't start with verb
153
+ context.report({
154
+ node,
155
+ messageId: "notVerbNoun",
156
+ data: { name }
157
+ });
158
+ }
159
+
160
+ return {
161
+ FunctionDeclaration(node) {
162
+ if (node.id && node.id.name) {
163
+ checkFunctionName(node.id, node.id.name);
164
+ }
165
+ },
166
+
167
+ FunctionExpression(node) {
168
+ // Check named function expressions
169
+ if (node.id && node.id.name) {
170
+ checkFunctionName(node.id, node.id.name);
171
+ }
172
+ },
173
+
174
+ ArrowFunctionExpression(node) {
175
+ // For arrow functions assigned to variables
176
+ if (node.parent && node.parent.type === 'VariableDeclarator' && node.parent.id) {
177
+ checkFunctionName(node.parent.id, node.parent.id.name);
178
+ }
179
+ },
180
+
181
+ MethodDefinition(node) {
182
+ // Check class methods
183
+ if (node.key && node.key.name && node.kind === 'method') {
184
+ // Skip constructor methods
185
+ if (node.key.name !== 'constructor') {
186
+ checkFunctionName(node.key, node.key.name);
187
+ }
188
+ }
189
+ },
190
+
191
+ Property(node) {
192
+ // Check object method properties
193
+ if (node.method && node.key && node.key.name) {
194
+ checkFunctionName(node.key, node.key.name);
195
+ }
196
+ // Check function values assigned to object properties
197
+ if (!node.method && node.value &&
198
+ (node.value.type === 'FunctionExpression' || node.value.type === 'ArrowFunctionExpression') &&
199
+ node.key && node.key.name) {
200
+ checkFunctionName(node.key, node.key.name);
201
+ }
202
+ }
203
+ };
204
+ }
205
+ };
206
+
207
+ 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,43 @@
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: "Do not leave dead code commented out. Remove it or use version control to track changes."
17
+ }
18
+ },
19
+ create(context) {
20
+ return {
21
+ BlockStatement(node) {
22
+ let unreachable = false;
23
+ for (const stmt of node.body) {
24
+ if (unreachable) {
25
+ context.report({
26
+ node: stmt,
27
+ messageId: "deadCode"
28
+ });
29
+ }
30
+
31
+ if (
32
+ stmt.type === "ReturnStatement" ||
33
+ stmt.type === "ThrowStatement" ||
34
+ stmt.type === "ContinueStatement" ||
35
+ stmt.type === "BreakStatement"
36
+ ) {
37
+ unreachable = true;
38
+ }
39
+ }
40
+ }
41
+ };
42
+ }
43
+ };
@@ -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
+ };
@@ -0,0 +1,39 @@
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 should only initialize properties, avoid complex logic"
17
+ }
18
+ },
19
+ create(context) {
20
+ return {
21
+ MethodDefinition(node) {
22
+ if (
23
+ node.kind === "constructor" &&
24
+ node.value &&
25
+ node.value.body &&
26
+ node.value.body.body.length > 5 // threshold for logic lines in constructor
27
+ ) {
28
+ context.report({
29
+ node,
30
+ messageId: "constructorLogic",
31
+ data: {
32
+ count: node.value.body.body.length
33
+ }
34
+ });
35
+ }
36
+ }
37
+ };
38
+ }
39
+ };