@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,42 @@
1
+ /**
2
+ * Custom ESLint rule for: T002 – Interface names should start with 'I'
3
+ * Rule ID: custom/t002
4
+ * Purpose: Enforce consistent interface naming convention by requiring the 'I' prefix
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "suggestion",
12
+ docs: {
13
+ description: "Interface names should start with 'I'",
14
+ recommended: false
15
+ },
16
+ fixable: "code",
17
+ schema: [],
18
+ messages: {
19
+ interfacePrefix: "Interface name '{{name}}' should start with 'I'"
20
+ }
21
+ },
22
+
23
+ create(context) {
24
+ return {
25
+ TSInterfaceDeclaration(node) {
26
+ const interfaceName = node.id.name;
27
+ if (!interfaceName.startsWith("I")) {
28
+ context.report({
29
+ node: node.id,
30
+ messageId: "interfacePrefix",
31
+ data: {
32
+ name: interfaceName,
33
+ },
34
+ fix(fixer) {
35
+ return fixer.replaceText(node.id, `I${interfaceName}`);
36
+ },
37
+ });
38
+ }
39
+ },
40
+ };
41
+ },
42
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Custom ESLint rule for: T003 – Avoid using @ts-ignore without a justification
3
+ * Rule ID: custom/t003
4
+ * Purpose: Ensure @ts-ignore comments include a reason to maintain code quality and documentation
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "suggestion",
12
+ docs: {
13
+ description: "Avoid using @ts-ignore without a justification",
14
+ recommended: false
15
+ },
16
+ fixable: null,
17
+ schema: [],
18
+ messages: {
19
+ tsIgnoreReason: "Please provide a reason for using @ts-ignore"
20
+ }
21
+ },
22
+
23
+ create(context) {
24
+ return {
25
+ Program() {
26
+ const sourceCode = context.getSourceCode();
27
+ const comments = sourceCode.getAllComments();
28
+
29
+ comments.forEach((comment) => {
30
+ if (comment.type === "Line" && comment.value.includes("@ts-ignore")) {
31
+ // Check if there's a justification after @ts-ignore
32
+ const hasJustification = comment.value
33
+ .replace("@ts-ignore", "")
34
+ .trim()
35
+ .length > 0;
36
+
37
+ if (!hasJustification) {
38
+ context.report({
39
+ node: comment,
40
+ messageId: "tsIgnoreReason",
41
+ });
42
+ }
43
+ }
44
+ });
45
+ },
46
+ };
47
+ },
48
+ };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Custom ESLint rule for: T004 – Interface chỉ nên chứa public method
3
+ * Rule ID: custom/t004
4
+ * Purpose: Interface là hợp đồng, không được có logic private/internal
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description: "Interface chỉ nên chứa public method",
14
+ category: "TypeScript",
15
+ recommended: true,
16
+ },
17
+ fixable: null,
18
+ schema: [],
19
+ messages: {
20
+ interfacePrivateMethod: "Interface không được chứa private method '{{methodName}}'. Interface là hợp đồng public, hãy chuyển logic private sang class implementation.",
21
+ interfaceProtectedMethod: "Interface không được chứa protected method '{{methodName}}'. Interface là hợp đồng public, hãy chuyển logic protected sang class implementation.",
22
+ interfacePrivateProperty: "Interface không được chứa private property '{{propertyName}}'. Interface là hợp đồng public, hãy chuyển property private sang class implementation.",
23
+ interfaceProtectedProperty: "Interface không được chứa protected property '{{propertyName}}'. Interface là hợp đồng public, hãy chuyển property protected sang class implementation.",
24
+ interfaceImplementation: "Interface không được chứa implementation logic trong method '{{methodName}}'. Interface chỉ định nghĩa hợp đồng, không có implementation.",
25
+ interfacePrivateNamingMethod: "Interface method '{{methodName}}' có tên gợi ý private (bắt đầu với _). Interface chỉ nên chứa public contract, hãy remove hoặc chuyển sang class implementation.",
26
+ interfacePrivateNamingProperty: "Interface property '{{propertyName}}' có tên gợi ý private (bắt đầu với _). Interface chỉ nên chứa public contract, hãy remove hoặc chuyển sang class implementation.",
27
+ interfaceImplementationDetails: "Interface method '{{methodName}}' leak implementation details. Interface nên abstract, không expose chi tiết implementation như SQL, cache, connection handling.",
28
+ interfaceInternalState: "Interface property '{{propertyName}}' expose internal state management. Interface nên chỉ define public contract, không expose internal resources.",
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ // Helper function to check if a name suggests private/internal usage
34
+ function isPrivateNaming(name) {
35
+ return name && (name.startsWith('_') || name.startsWith('__'));
36
+ }
37
+
38
+ // Helper function to check if method name suggests implementation details
39
+ function isImplementationDetail(name) {
40
+ const implementationKeywords = [
41
+ 'buildSql', 'executeRaw', 'handleConnection', 'logQuery', 'log',
42
+ 'buildQuery', 'executeQuery', 'handleRetry', 'handleError',
43
+ 'internal', 'cache', 'pool', 'connection', 'retry', 'performance'
44
+ ];
45
+
46
+ return name && implementationKeywords.some(keyword =>
47
+ name.toLowerCase().includes(keyword.toLowerCase())
48
+ );
49
+ }
50
+
51
+ // Helper function to check if property suggests internal state
52
+ function isInternalState(name) {
53
+ const internalStateKeywords = [
54
+ 'pool', 'cache', 'connection', 'internal', 'secret', 'config',
55
+ 'state', 'buffer', 'queue', 'stack', 'heap'
56
+ ];
57
+
58
+ return name && internalStateKeywords.some(keyword =>
59
+ name.toLowerCase().includes(keyword.toLowerCase())
60
+ );
61
+ }
62
+
63
+ return {
64
+ TSInterfaceDeclaration(node) {
65
+ // Kiểm tra từng member trong interface
66
+ node.body.body.forEach(member => {
67
+ // Kiểm tra method signature
68
+ if (member.type === 'TSMethodSignature') {
69
+ const methodName = member.key.name || member.key.value;
70
+
71
+ // Kiểm tra accessibility modifiers (for completeness, though TS doesn't allow this)
72
+ if (member.accessibility === 'private') {
73
+ context.report({
74
+ node: member,
75
+ messageId: "interfacePrivateMethod",
76
+ data: { methodName }
77
+ });
78
+ }
79
+
80
+ if (member.accessibility === 'protected') {
81
+ context.report({
82
+ node: member,
83
+ messageId: "interfaceProtectedMethod",
84
+ data: { methodName }
85
+ });
86
+ }
87
+
88
+ // Kiểm tra tên method có gợi ý private không
89
+ if (isPrivateNaming(methodName)) {
90
+ context.report({
91
+ node: member,
92
+ messageId: "interfacePrivateNamingMethod",
93
+ data: { methodName }
94
+ });
95
+ }
96
+
97
+ // Kiểm tra method có leak implementation details không
98
+ if (isImplementationDetail(methodName)) {
99
+ context.report({
100
+ node: member,
101
+ messageId: "interfaceImplementationDetails",
102
+ data: { methodName }
103
+ });
104
+ }
105
+ }
106
+
107
+ // Kiểm tra property signature
108
+ if (member.type === 'TSPropertySignature') {
109
+ const propertyName = member.key.name || member.key.value;
110
+
111
+ // Kiểm tra accessibility modifiers
112
+ if (member.accessibility === 'private') {
113
+ context.report({
114
+ node: member,
115
+ messageId: "interfacePrivateProperty",
116
+ data: { propertyName }
117
+ });
118
+ }
119
+
120
+ if (member.accessibility === 'protected') {
121
+ context.report({
122
+ node: member,
123
+ messageId: "interfaceProtectedProperty",
124
+ data: { propertyName }
125
+ });
126
+ }
127
+
128
+ // Kiểm tra tên property có gợi ý private không
129
+ if (isPrivateNaming(propertyName)) {
130
+ context.report({
131
+ node: member,
132
+ messageId: "interfacePrivateNamingProperty",
133
+ data: { propertyName }
134
+ });
135
+ }
136
+
137
+ // Kiểm tra property có expose internal state không
138
+ if (isInternalState(propertyName)) {
139
+ context.report({
140
+ node: member,
141
+ messageId: "interfaceInternalState",
142
+ data: { propertyName }
143
+ });
144
+ }
145
+ }
146
+
147
+ // Kiểm tra nếu có implementation trong interface (thường không xảy ra trong TS, nhưng để đảm bảo)
148
+ if (member.type === 'MethodDefinition' && member.value && member.value.body) {
149
+ const methodName = member.key.name || member.key.value;
150
+ context.report({
151
+ node: member,
152
+ messageId: "interfaceImplementation",
153
+ data: { methodName }
154
+ });
155
+ }
156
+ });
157
+ }
158
+ };
159
+ },
160
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Custom ESLint rule for: T007 – Avoid declaring functions inside constructors or class bodies
3
+ * Rule ID: custom/t007
4
+ * Purpose: Prevent function declarations inside constructors and class bodies to maintain clean code structure
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Avoid declaring functions inside constructors or class bodies",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ noFunctionInConstructor: "Avoid declaring functions inside class constructors.",
17
+ noFunctionInClassBody: "Avoid declaring nested functions inside class body."
18
+ }
19
+ },
20
+ create(context) {
21
+ return {
22
+ MethodDefinition(node) {
23
+ if (node.kind === "constructor" && node.value && node.value.body && node.value.body.body) {
24
+ const constructorBody = node.value.body.body;
25
+ constructorBody.forEach(element => {
26
+ if (element.type === "FunctionDeclaration" || element.type === "FunctionExpression") {
27
+ context.report({
28
+ node: element,
29
+ messageId: "noFunctionInConstructor"
30
+ });
31
+ }
32
+ });
33
+ }
34
+ },
35
+ ClassBody(node) {
36
+ node.body.forEach(element => {
37
+ if (element.type === "MethodDefinition" && element.value && element.value.body && element.value.body.body) {
38
+ const methodBody = element.value.body.body;
39
+ methodBody.forEach(subNode => {
40
+ if (subNode.type === "FunctionDeclaration") {
41
+ context.report({
42
+ node: subNode,
43
+ messageId: "noFunctionInClassBody"
44
+ });
45
+ }
46
+ });
47
+ }
48
+ });
49
+ }
50
+ };
51
+ }
52
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Custom ESLint rule for: T011 – Test không nên phụ thuộc vào thời gian thực
3
+ * Rule ID: custom/t011
4
+ * Purpose: Detect real-time dependencies in tests like setTimeout, delay, sleep, etc.
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Test không nên phụ thuộc vào thời gian thực (No real-time dependencies in tests)",
12
+ isRecommended: true
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ noRealTimeDelay: "Test should not depend on real-time delays. Use mocks, stubs, or deterministic approaches instead of '{{method}}'.",
17
+ noDateNow: "Test should not depend on current timestamp. Use mock time or inject clock instead of 'Date.now()' or 'new Date()'.",
18
+ noSetTimeout: "Test should not use setTimeout/setInterval for delays. Use mock timers or deterministic test patterns.",
19
+ noThreadSleep: "Test should not use Thread.sleep() or similar blocking delays. Use mock objects or callbacks."
20
+ }
21
+ },
22
+ create(context) {
23
+
24
+ const TEST_FUNCTIONS = ["test", "it", "beforeEach", "beforeAll", "afterEach", "afterAll"];
25
+ const DESCRIBE_FUNCTIONS = ["describe", "context", "suite"];
26
+ const PROBLEMATIC_TIMERS = ["setTimeout", "setInterval", "setImmediate", "delay", "sleep", "wait", "pause"];
27
+
28
+ // Check if current context is inside a test function
29
+ function isInTestContext(node) {
30
+ let parent = node.parent;
31
+ while (parent) {
32
+ if (parent.type === "CallExpression" && isTestOrDescribeFunction(parent)) {
33
+ return true;
34
+ }
35
+ parent = parent.parent;
36
+ }
37
+ return false;
38
+ }
39
+
40
+ function isTestOrDescribeFunction(node) {
41
+ if (!node || !node.callee) {
42
+ return false;
43
+ }
44
+
45
+ if (node.type === "CallExpression") {
46
+ const allTestFunctions = [...TEST_FUNCTIONS, ...DESCRIBE_FUNCTIONS];
47
+
48
+ // Handle direct calls
49
+ if (node.callee.type === "Identifier") {
50
+ return allTestFunctions.includes(node.callee.name);
51
+ }
52
+
53
+ // Handle member expression calls like test.skip, describe.only
54
+ if (node.callee.type === "MemberExpression" &&
55
+ node.callee.object.type === "Identifier") {
56
+ const baseName = node.callee.object.name;
57
+ return allTestFunctions.includes(baseName);
58
+ }
59
+ }
60
+
61
+ return false;
62
+ }
63
+
64
+ function checkProblematicMethod(node, methodName, messageId = "noSetTimeout") {
65
+ if (PROBLEMATIC_TIMERS.includes(methodName)) {
66
+ context.report({
67
+ node,
68
+ messageId,
69
+ data: { method: methodName }
70
+ });
71
+ }
72
+ }
73
+
74
+ return {
75
+ // Detect setTimeout, setInterval calls
76
+ CallExpression(node) {
77
+ if (!isInTestContext(node)) {
78
+ return;
79
+ }
80
+
81
+ const callee = node.callee;
82
+
83
+ // Direct function calls
84
+ if (callee.type === "Identifier") {
85
+ checkProblematicMethod(node, callee.name);
86
+ }
87
+
88
+ // Member expression calls
89
+ if (callee.type === "MemberExpression" && callee.property && callee.property.type === "Identifier") {
90
+ checkProblematicMethod(node, callee.property.name);
91
+ }
92
+
93
+ // Promise-based delays
94
+ if (callee.type === "Identifier" && callee.name === "Promise" &&
95
+ node.arguments.length > 0 && node.arguments[0].type === "ArrowFunctionExpression") {
96
+ const promiseBody = node.arguments[0].body;
97
+ if (promiseBody && promiseBody.type === "CallExpression" &&
98
+ promiseBody.callee && promiseBody.callee.name === "setTimeout") {
99
+ context.report({
100
+ node,
101
+ messageId: "noRealTimeDelay",
102
+ data: { method: "Promise + setTimeout" }
103
+ });
104
+ }
105
+ }
106
+ },
107
+
108
+ // Detect Date.now(), new Date() without parameters
109
+ NewExpression(node) {
110
+ if (!isInTestContext(node)) {
111
+ return;
112
+ }
113
+
114
+ if (node.callee.type === "Identifier" && node.callee.name === "Date" && node.arguments.length === 0) {
115
+ context.report({
116
+ node,
117
+ messageId: "noDateNow"
118
+ });
119
+ }
120
+ },
121
+
122
+ // Detect Date.now() calls
123
+ MemberExpression(node) {
124
+ if (!isInTestContext(node)) {
125
+ return;
126
+ }
127
+
128
+ if (node.object.type === "Identifier" &&
129
+ node.object.name === "Date" &&
130
+ node.property.type === "Identifier" &&
131
+ node.property.name === "now") {
132
+ context.report({
133
+ node,
134
+ messageId: "noDateNow"
135
+ });
136
+ }
137
+ },
138
+
139
+ // Detect await expressions with timing-based patterns
140
+ AwaitExpression(node) {
141
+ if (!isInTestContext(node)) {
142
+ return;
143
+ }
144
+
145
+ const argument = node.argument;
146
+
147
+ // await new Promise(resolve => setTimeout(...))
148
+ if (argument.type === "NewExpression" &&
149
+ argument.callee.type === "Identifier" &&
150
+ argument.callee.name === "Promise") {
151
+ context.report({
152
+ node,
153
+ messageId: "noRealTimeDelay",
154
+ data: { method: "await Promise + delay" }
155
+ });
156
+ }
157
+
158
+ // await sleep(), await delay(), etc.
159
+ if (argument.type === "CallExpression" &&
160
+ argument.callee.type === "Identifier") {
161
+ const methodName = argument.callee.name;
162
+ const problematicAsyncMethods = ["sleep", "delay", "wait", "pause"];
163
+
164
+ if (problematicAsyncMethods.includes(methodName)) {
165
+ context.report({
166
+ node,
167
+ messageId: "noRealTimeDelay",
168
+ data: { method: `await ${methodName}` }
169
+ });
170
+ }
171
+ }
172
+ }
173
+ };
174
+ }
175
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Custom ESLint rule for: T019 – Disallow empty type definitions
3
+ * Rule ID: custom/t019
4
+ * Purpose: Prevent empty type definitions and suggest more specific types
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "suggestion",
12
+ docs: {
13
+ description: "Disallow empty type definitions",
14
+ category: "TypeScript",
15
+ recommended: true,
16
+ },
17
+ fixable: "code",
18
+ schema: [],
19
+ messages: {
20
+ emptyType: "Empty type definition is not allowed. Add at least one property or use a more specific type.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ // Helper function to get a more specific type suggestion based on the name
26
+ function getSuggestedType(name) {
27
+ const lowerName = name.toLowerCase();
28
+
29
+ // Suggest more specific types based on common naming patterns
30
+ if (lowerName.includes("config") || lowerName.includes("options")) {
31
+ return "Record<string, unknown>";
32
+ }
33
+ if (lowerName.includes("props")) {
34
+ return "Record<string, React.ReactNode>";
35
+ }
36
+ if (lowerName.includes("state")) {
37
+ return "Record<string, unknown>";
38
+ }
39
+ if (lowerName.includes("params")) {
40
+ return "Record<string, string | number | boolean>";
41
+ }
42
+ if (lowerName.includes("result") || lowerName.includes("response")) {
43
+ return "Record<string, unknown>";
44
+ }
45
+ if (lowerName.includes("event") || lowerName.includes("handler")) {
46
+ return "(...args: unknown[]) => void";
47
+ }
48
+ if (lowerName.includes("callback") || lowerName.includes("fn")) {
49
+ return "(...args: unknown[]) => unknown";
50
+ }
51
+ if (lowerName.includes("data") || lowerName.includes("item")) {
52
+ return "Record<string, unknown>";
53
+ }
54
+
55
+ // Default suggestion
56
+ return "Record<string, unknown>";
57
+ }
58
+
59
+ return {
60
+ TSInterfaceDeclaration(node) {
61
+ if (node.body.body.length === 0) {
62
+ const suggestedType = getSuggestedType(node.id.name);
63
+ context.report({
64
+ node: node.body,
65
+ messageId: "emptyType",
66
+ fix(fixer) {
67
+ return fixer.replaceText(
68
+ node,
69
+ `type ${node.id.name} = ${suggestedType};`
70
+ );
71
+ },
72
+ });
73
+ }
74
+ },
75
+ TSTypeAliasDeclaration(node) {
76
+ if (
77
+ node.typeAnnotation.type === "TSTypeLiteral" &&
78
+ node.typeAnnotation.members.length === 0
79
+ ) {
80
+ const suggestedType = getSuggestedType(node.id.name);
81
+ context.report({
82
+ node: node.typeAnnotation,
83
+ messageId: "emptyType",
84
+ fix(fixer) {
85
+ return fixer.replaceText(
86
+ node.typeAnnotation,
87
+ suggestedType
88
+ );
89
+ },
90
+ });
91
+ }
92
+ },
93
+ };
94
+ },
95
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Custom ESLint rule for: T025 – Avoid deeply nested union or tuple types
3
+ * Rule ID: custom/t025
4
+ * Purpose: Prevent complex nested union or tuple types to maintain type readability and simplicity
5
+ */
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Avoid deeply nested union or tuple types",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ nestedUnion: "Avoid nested union types.",
17
+ nestedTuple: "Avoid nested tuple types."
18
+ }
19
+ },
20
+ create(context) {
21
+ function checkNestedTypes(node) {
22
+ if (node.type === 'TSUnionType') {
23
+ const nested = node.types.some(t => t.type === 'TSUnionType' || t.type === 'TSTupleType');
24
+ if (nested) {
25
+ context.report({
26
+ node,
27
+ messageId: "nestedUnion"
28
+ });
29
+ }
30
+ }
31
+
32
+ if (node.type === 'TSTupleType') {
33
+ const nested = node.elementTypes.some(t => t.type === 'TSTupleType' || t.type === 'TSUnionType');
34
+ if (nested) {
35
+ context.report({
36
+ node,
37
+ messageId: "nestedTuple"
38
+ });
39
+ }
40
+ }
41
+ }
42
+
43
+ return {
44
+ TSUnionType: checkNestedTypes,
45
+ TSTupleType: checkNestedTypes
46
+ };
47
+ }
48
+ };