@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,406 @@
1
+ /**
2
+ * Custom ESLint rule for: C042 – Boolean variable names should start with `is`, `has`, or `should`
3
+ * Rule ID: custom/c042
4
+ * Purpose: Ensure boolean variables have clear naming conventions
5
+ */
6
+
7
+ const c042Rule = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Boolean variable names should start with `is`, `has`, or `should`",
12
+ recommended: true
13
+ },
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ allowedPrefixes: {
19
+ type: "array",
20
+ items: { type: "string" },
21
+ description: "Additional boolean prefixes to allow (default: is, has, should, can, will, must, may)"
22
+ },
23
+ strictMode: {
24
+ type: "boolean",
25
+ description: "Whether to enforce strict boolean prefixes only (default: false)"
26
+ },
27
+ ignoredNames: {
28
+ type: "array",
29
+ items: { type: "string" },
30
+ description: "Variable names to ignore (e.g., common patterns like 'flag', 'enabled')"
31
+ },
32
+ checkReturnTypes: {
33
+ type: "boolean",
34
+ description: "Whether to check function return types for boolean naming (default: true)"
35
+ }
36
+ },
37
+ additionalProperties: false
38
+ }
39
+ ],
40
+ messages: {
41
+ booleanNaming: "Boolean variable '{{name}}' should start with a descriptive prefix like 'is', 'has', or 'should'. Consider: {{suggestions}}.",
42
+ booleanFunction: "Function '{{name}}' returns boolean but name doesn't follow boolean naming convention. Consider: {{suggestions}}.",
43
+ improperPrefix: "Variable '{{name}}' uses prefix '{{prefix}}' but is not a boolean type.",
44
+ strictPrefix: "Boolean variable '{{name}}' must use one of the allowed prefixes: {{allowedPrefixes}}."
45
+ }
46
+ },
47
+
48
+ create(context) {
49
+ const options = context.options[0] || {};
50
+
51
+ // Default boolean prefixes
52
+ const defaultPrefixes = ['is', 'has', 'should', 'can', 'will', 'must', 'may', 'was', 'were'];
53
+ const allowedPrefixes = new Set([
54
+ ...defaultPrefixes,
55
+ ...(options.allowedPrefixes || [])
56
+ ]);
57
+
58
+ const strictMode = options.strictMode || false;
59
+ const ignoredNames = new Set(options.ignoredNames || ['flag', 'enabled', 'disabled', 'active', 'valid', 'ok']);
60
+ const checkReturnTypes = options.checkReturnTypes !== false;
61
+
62
+ // Common boolean value patterns
63
+ const booleanValuePatterns = [
64
+ /^(true|false)$/,
65
+ /^!(.*)/, // Negation
66
+ /^.*\s*(===|!==|==|!=|<|>|<=|>=)\s*.*/, // Comparison
67
+ /^.*\s*(&&|\|\|)\s*.*/, // Logical operators
68
+ ];
69
+
70
+ // Function call patterns that typically return boolean
71
+ const booleanFunctionPatterns = [
72
+ /^(test|check|validate|verify|confirm|ensure).*$/i,
73
+ /^.*\.(test|includes|startsWith|endsWith|match)$/i,
74
+ /^(Array\.isArray|Number\.isNaN|Number\.isFinite)$/i
75
+ ];
76
+
77
+ function startsWithBooleanPrefix(name) {
78
+ const lowerName = name.toLowerCase();
79
+ return Array.from(allowedPrefixes).some(prefix =>
80
+ lowerName.startsWith(prefix.toLowerCase())
81
+ );
82
+ }
83
+
84
+ function generateSuggestions(name) {
85
+ const suggestions = [
86
+ `is${name.charAt(0).toUpperCase() + name.slice(1)}`,
87
+ `has${name.charAt(0).toUpperCase() + name.slice(1)}`,
88
+ `should${name.charAt(0).toUpperCase() + name.slice(1)}`
89
+ ];
90
+ return suggestions.join(', ');
91
+ }
92
+
93
+ function isBooleanLiteral(node) {
94
+ if (!node) return false;
95
+
96
+ if (node.type === 'Literal') {
97
+ return typeof node.value === 'boolean';
98
+ }
99
+
100
+ if (node.type === 'UnaryExpression' && node.operator === '!') {
101
+ return true;
102
+ }
103
+
104
+ if (node.type === 'BinaryExpression') {
105
+ const comparisonOps = ['===', '!==', '==', '!=', '<', '>', '<=', '>='];
106
+ const logicalOps = ['&&', '||'];
107
+ return comparisonOps.includes(node.operator) || logicalOps.includes(node.operator);
108
+ }
109
+
110
+ if (node.type === 'LogicalExpression') {
111
+ return ['&&', '||'].includes(node.operator);
112
+ }
113
+
114
+ if (node.type === 'CallExpression') {
115
+ try {
116
+ const source = context.getSourceCode();
117
+ const callText = source.getText(node);
118
+ return booleanFunctionPatterns.some(pattern => pattern.test(callText));
119
+ } catch (error) {
120
+ // If we can't get source text, assume it's not boolean
121
+ return false;
122
+ }
123
+ }
124
+
125
+ return false;
126
+ }
127
+
128
+ function isBooleanTypeAnnotation(node) {
129
+ // TypeScript type annotations
130
+ if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
131
+ const typeNode = node.typeAnnotation.typeAnnotation;
132
+ return typeNode.type === 'TSBooleanKeyword';
133
+ }
134
+ return false;
135
+ }
136
+
137
+ function getFunctionReturnType(node) {
138
+ // Check TypeScript return type annotation
139
+ if (node.returnType && node.returnType.typeAnnotation) {
140
+ return node.returnType.typeAnnotation.type === 'TSBooleanKeyword';
141
+ }
142
+
143
+ // Analyze return statements with safe traversal
144
+ const returnStatements = [];
145
+ const visitedNodes = new WeakSet();
146
+
147
+ function findReturnStatements(astNode, depth = 0) {
148
+ // Prevent infinite recursion
149
+ if (depth > 50 || !astNode || typeof astNode !== 'object' || visitedNodes.has(astNode)) {
150
+ return;
151
+ }
152
+
153
+ visitedNodes.add(astNode);
154
+
155
+ if (astNode.type === 'ReturnStatement') {
156
+ returnStatements.push(astNode);
157
+ return; // Don't traverse further into return statements
158
+ }
159
+
160
+ // Stop traversing into nested functions to avoid checking their returns
161
+ if (astNode.type === 'FunctionDeclaration' ||
162
+ astNode.type === 'FunctionExpression' ||
163
+ astNode.type === 'ArrowFunctionExpression') {
164
+ if (astNode !== node) { // Don't skip the original node
165
+ return;
166
+ }
167
+ }
168
+
169
+ // Safely traverse specific node properties that might contain return statements
170
+ const propertiesToCheck = ['body', 'consequent', 'alternate', 'cases', 'statements'];
171
+
172
+ for (const prop of propertiesToCheck) {
173
+ if (astNode[prop]) {
174
+ if (Array.isArray(astNode[prop])) {
175
+ astNode[prop].forEach(child => {
176
+ if (child && typeof child === 'object') {
177
+ findReturnStatements(child, depth + 1);
178
+ }
179
+ });
180
+ } else if (typeof astNode[prop] === 'object') {
181
+ findReturnStatements(astNode[prop], depth + 1);
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ if (node.body) {
188
+ findReturnStatements(node.body);
189
+ }
190
+
191
+ // Check if all return statements return boolean values
192
+ if (returnStatements.length > 0) {
193
+ return returnStatements.every(stmt =>
194
+ stmt.argument && isBooleanLiteral(stmt.argument)
195
+ );
196
+ }
197
+
198
+ return false;
199
+ }
200
+
201
+ function checkVariableName(node, name, init = null) {
202
+ if (!name) return;
203
+
204
+ // Skip ignored names
205
+ if (ignoredNames.has(name.toLowerCase())) {
206
+ return;
207
+ }
208
+
209
+ // Skip very short names (likely not descriptive anyway)
210
+ if (name.length <= 2) {
211
+ return;
212
+ }
213
+
214
+ const hasBooleanPrefix = startsWithBooleanPrefix(name);
215
+ const isBooleanType = isBooleanTypeAnnotation(node);
216
+
217
+ let isBooleanValue = false;
218
+ try {
219
+ isBooleanValue = init && isBooleanLiteral(init);
220
+ } catch (error) {
221
+ // Skip boolean value check if we can't determine it safely
222
+ isBooleanValue = false;
223
+ }
224
+
225
+ // Case 1: Variable has boolean type annotation or boolean value
226
+ if (isBooleanType || isBooleanValue) {
227
+ if (!hasBooleanPrefix) {
228
+ if (strictMode) {
229
+ context.report({
230
+ node,
231
+ messageId: "strictPrefix",
232
+ data: {
233
+ name,
234
+ allowedPrefixes: Array.from(allowedPrefixes).join(', ')
235
+ }
236
+ });
237
+ } else {
238
+ context.report({
239
+ node,
240
+ messageId: "booleanNaming",
241
+ data: {
242
+ name,
243
+ suggestions: generateSuggestions(name)
244
+ }
245
+ });
246
+ }
247
+ }
248
+ }
249
+
250
+ // Case 2: Variable has boolean prefix but not boolean type/value
251
+ else if (hasBooleanPrefix && !isBooleanType && !isBooleanValue) {
252
+ // Only warn if we can determine it's definitely not boolean
253
+ if (init && init.type === 'Literal' && typeof init.value !== 'boolean') {
254
+ const prefix = Array.from(allowedPrefixes).find(p =>
255
+ name.toLowerCase().startsWith(p.toLowerCase())
256
+ );
257
+ context.report({
258
+ node,
259
+ messageId: "improperPrefix",
260
+ data: {
261
+ name,
262
+ prefix
263
+ }
264
+ });
265
+ }
266
+ }
267
+ }
268
+
269
+ function checkFunctionName(node, name) {
270
+ if (!checkReturnTypes || !name) return;
271
+
272
+ // Skip ignored names
273
+ if (ignoredNames.has(name.toLowerCase())) {
274
+ return;
275
+ }
276
+
277
+ // Skip very short names and common patterns
278
+ if (name.length <= 3 || name === 'main' || name === 'init' || name === 'setup') {
279
+ return;
280
+ }
281
+
282
+ // Skip constructor functions
283
+ if (name[0] === name[0].toUpperCase()) {
284
+ return;
285
+ }
286
+
287
+ // Skip event handlers and common callback patterns
288
+ if (name.startsWith('handle') || name.startsWith('on') || name.includes('Handler') || name.includes('Callback')) {
289
+ return;
290
+ }
291
+
292
+ const hasBooleanPrefix = startsWithBooleanPrefix(name);
293
+
294
+ try {
295
+ const returnsBooleanType = getFunctionReturnType(node);
296
+
297
+ // Function returns boolean but doesn't have boolean name
298
+ if (returnsBooleanType && !hasBooleanPrefix) {
299
+ context.report({
300
+ node: node.id || node,
301
+ messageId: "booleanFunction",
302
+ data: {
303
+ name,
304
+ suggestions: generateSuggestions(name)
305
+ }
306
+ });
307
+ }
308
+ } catch (error) {
309
+ // Skip this check if we encounter an error to prevent crashes
310
+ return;
311
+ }
312
+ }
313
+
314
+ return {
315
+ VariableDeclarator(node) {
316
+ if (node.id && node.id.type === 'Identifier') {
317
+ checkVariableName(node.id, node.id.name, node.init);
318
+ } else if (node.id && node.id.type === 'ObjectPattern') {
319
+ // Handle destructuring: const {isActive, hasPermission} = obj;
320
+ node.id.properties.forEach(prop => {
321
+ if (prop.type === 'Property' && prop.value && prop.value.type === 'Identifier') {
322
+ checkVariableName(prop.value, prop.value.name);
323
+ }
324
+ });
325
+ } else if (node.id && node.id.type === 'ArrayPattern') {
326
+ // Handle array destructuring: const [isEnabled, hasAccess] = flags;
327
+ node.id.elements.forEach(element => {
328
+ if (element && element.type === 'Identifier') {
329
+ checkVariableName(element, element.name);
330
+ }
331
+ });
332
+ }
333
+ },
334
+
335
+ FunctionDeclaration(node) {
336
+ if (node.id && node.id.name) {
337
+ checkFunctionName(node, node.id.name);
338
+ }
339
+
340
+ // Check parameters
341
+ if (node.params) {
342
+ node.params.forEach(param => {
343
+ if (param.type === 'Identifier') {
344
+ checkVariableName(param, param.name);
345
+ }
346
+ });
347
+ }
348
+ },
349
+
350
+ FunctionExpression(node) {
351
+ // Check named function expressions
352
+ if (node.id && node.id.name) {
353
+ checkFunctionName(node, node.id.name);
354
+ }
355
+
356
+ // Check parameters
357
+ if (node.params) {
358
+ node.params.forEach(param => {
359
+ if (param.type === 'Identifier') {
360
+ checkVariableName(param, param.name);
361
+ }
362
+ });
363
+ }
364
+ },
365
+
366
+ ArrowFunctionExpression(node) {
367
+ // For arrow functions assigned to variables
368
+ if (node.parent && node.parent.type === 'VariableDeclarator' && node.parent.id) {
369
+ checkFunctionName(node, node.parent.id.name);
370
+ }
371
+
372
+ // Check parameters
373
+ if (node.params) {
374
+ node.params.forEach(param => {
375
+ if (param.type === 'Identifier') {
376
+ checkVariableName(param, param.name);
377
+ }
378
+ });
379
+ }
380
+ },
381
+
382
+ MethodDefinition(node) {
383
+ // Check class methods
384
+ if (node.key && node.key.name && node.kind === 'method') {
385
+ checkFunctionName(node.value, node.key.name);
386
+ }
387
+ },
388
+
389
+ Property(node) {
390
+ // Check object method properties
391
+ if (node.method && node.key && node.key.name) {
392
+ checkFunctionName(node.value, node.key.name);
393
+ }
394
+
395
+ // Check boolean properties
396
+ if (!node.method && node.key && node.key.name && node.value) {
397
+ if (isBooleanLiteral(node.value)) {
398
+ checkVariableName(node.key, node.key.name, node.value);
399
+ }
400
+ }
401
+ }
402
+ };
403
+ }
404
+ };
405
+
406
+ module.exports = c042Rule;
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Custom ESLint rule for: C043 – Do not use `console.log` or `print` in production code
3
+ * Rule ID: custom/c043
4
+ * Purpose: Prevent usage of console logging in production code to maintain clean logs and security
5
+ */
6
+
7
+ const c045Rule = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Do not use `console.log` or `print` in production code",
12
+ recommended: false
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ noConsole: "Do not use console.log in production code. Use proper logging instead.",
17
+ noPrint: "Do not use print in production code. Use proper logging instead."
18
+ },
19
+ fixable: "code"
20
+ },
21
+
22
+ create(context) {
23
+ const options = context.options[0] || {};
24
+
25
+ // Default allowed console methods (typically for errors/warnings)
26
+ const allowedMethods = new Set(options.allowedMethods || ['error', 'warn']);
27
+
28
+ const allowInDevelopment = options.allowInDevelopment !== false;
29
+ const allowInTests = options.allowInTests !== false;
30
+
31
+ // Default patterns for test files
32
+ const testFilePatterns = options.testFilePatterns || [
33
+ '.test.', '.spec.', '__tests__', '/test/', '/tests/', '.test.ts', '.test.js',
34
+ '.spec.ts', '.spec.js', 'test.tsx', 'spec.tsx'
35
+ ];
36
+
37
+ // Default patterns for development files
38
+ const developmentPatterns = options.developmentPatterns || [
39
+ '.dev.', '.development.', '.debug.', '/dev/', '/development/'
40
+ ];
41
+
42
+ // Development environment flags
43
+ const developmentFlags = new Set(options.developmentFlags || [
44
+ '__DEV__', 'DEBUG', 'process.env.NODE_ENV', 'process.env.ENVIRONMENT',
45
+ 'process.env.ENABLE_LOGGING', 'FEATURES.debug', 'BUILD_TYPE'
46
+ ]);
47
+
48
+ // Other forbidden objects/functions
49
+ const allowedObjects = options.allowedObjects || ['print', 'alert', 'confirm', 'prompt'];
50
+ const forbiddenObjects = new Set(['console', ...allowedObjects]);
51
+
52
+ // Common console methods to check
53
+ const consoleMethods = new Set([
54
+ 'log', 'info', 'debug', 'trace', 'dir', 'dirxml', 'table',
55
+ 'count', 'countReset', 'time', 'timeEnd', 'timeLog',
56
+ 'assert', 'clear', 'group', 'groupCollapsed', 'groupEnd',
57
+ 'profile', 'profileEnd', 'timeStamp'
58
+ ]);
59
+
60
+ // Get current file information
61
+ const filename = context.getFilename();
62
+ const sourceCode = context.getSourceCode();
63
+
64
+ function isTestFile() {
65
+ return testFilePatterns.some(pattern => filename.includes(pattern));
66
+ }
67
+
68
+ function isDevelopmentFile() {
69
+ return developmentPatterns.some(pattern => filename.includes(pattern));
70
+ }
71
+
72
+ // Recursively check all sub-expressions for development flags
73
+ function containsDevelopmentFlag(node) {
74
+ if (!node) return false;
75
+ // Direct flag usage (__DEV__, DEBUG)
76
+ if (node.type === 'Identifier' && developmentFlags.has(node.name)) {
77
+ return true;
78
+ }
79
+ // process.env.NODE_ENV === 'development'
80
+ if (node.type === 'BinaryExpression' && node.operator === '===' && node.right.value === 'development') {
81
+ const left = node.left;
82
+ if (left.type === 'MemberExpression' &&
83
+ left.object.type === 'MemberExpression' &&
84
+ left.object.object.name === 'process' &&
85
+ left.object.property.name === 'env') {
86
+ const envVar = left.property.name;
87
+ if (envVar === 'NODE_ENV' || developmentFlags.has(`process.env.${envVar}`)) {
88
+ return true;
89
+ }
90
+ }
91
+ }
92
+ // process.env.<VAR> === 'true'
93
+ if (node.type === 'BinaryExpression' && node.operator === '===' && node.right.value === 'true') {
94
+ const left = node.left;
95
+ if (left.type === 'MemberExpression' &&
96
+ left.object.type === 'MemberExpression' &&
97
+ left.object.object.name === 'process' &&
98
+ left.object.property.name === 'env') {
99
+ const envVar = left.property.name;
100
+ if (developmentFlags.has(`process.env.${envVar}`)) {
101
+ return true;
102
+ }
103
+ }
104
+ }
105
+ // Feature flags (FEATURES.debug, etc.)
106
+ if (node.type === 'MemberExpression' &&
107
+ node.object.type === 'Identifier' &&
108
+ node.property.type === 'Identifier') {
109
+ const objectName = node.object.name;
110
+ const propertyName = node.property.name;
111
+ if (developmentFlags.has(`${objectName}.${propertyName}`)) {
112
+ return true;
113
+ }
114
+ }
115
+ // Logical expressions (||, &&)
116
+ if (node.type === 'LogicalExpression') {
117
+ return containsDevelopmentFlag(node.left) || containsDevelopmentFlag(node.right);
118
+ }
119
+ // Nested binary/logical expressions
120
+ if (node.left && containsDevelopmentFlag(node.left)) return true;
121
+ if (node.right && containsDevelopmentFlag(node.right)) return true;
122
+ return false;
123
+ }
124
+
125
+ // Walk up the AST to see if inside any IfStatement with a dev/debug flag
126
+ function isInDevelopmentContext(node) {
127
+ let current = node;
128
+ while (current && current.parent) {
129
+ if (current.parent.type === 'IfStatement') {
130
+ const test = current.parent.test;
131
+ if (containsDevelopmentFlag(test)) {
132
+ return true;
133
+ }
134
+ }
135
+ current = current.parent;
136
+ }
137
+ return false;
138
+ }
139
+
140
+ function shouldAllowCall(node) {
141
+ if (allowInTests && isTestFile()) {
142
+ return true;
143
+ }
144
+ if (allowInDevelopment && (isDevelopmentFile() || isInDevelopmentContext(node))) {
145
+ return true;
146
+ }
147
+ return false;
148
+ }
149
+
150
+ function checkConsoleCall(node) {
151
+ if (!node.callee) return;
152
+
153
+ // Check for console.method() calls
154
+ if (node.callee.type === 'MemberExpression') {
155
+ const object = node.callee.object;
156
+ const property = node.callee.property;
157
+
158
+ if (object && property &&
159
+ object.type === 'Identifier' &&
160
+ property.type === 'Identifier') {
161
+
162
+ const objectName = object.name;
163
+ const methodName = property.name;
164
+
165
+ // Check console calls
166
+ if (objectName === 'console') {
167
+ // Skip if method is explicitly allowed
168
+ if (allowedMethods.has(methodName)) {
169
+ return;
170
+ }
171
+
172
+ // Skip if file context allows it
173
+ if (shouldAllowCall(node)) {
174
+ return;
175
+ }
176
+
177
+ // Report console violation
178
+ if (consoleMethods.has(methodName) || methodName === 'log') {
179
+ context.report({
180
+ node,
181
+ messageId: methodName === 'log' ? "noConsole" : "noConsole",
182
+ data: { method: methodName },
183
+ fix(fixer) {
184
+ // Offer to remove the entire statement
185
+ const statement = findStatementNode(node);
186
+ if (statement) {
187
+ return fixer.remove(statement);
188
+ }
189
+ return null;
190
+ }
191
+ });
192
+ }
193
+ }
194
+
195
+ // Check other forbidden objects (print, alert, etc.)
196
+ else if (forbiddenObjects.has(objectName) && objectName !== 'console') {
197
+ if (shouldAllowCall(node)) {
198
+ return;
199
+ }
200
+
201
+ context.report({
202
+ node,
203
+ messageId: objectName === 'alert' ? "noConsole" : "noPrint",
204
+ data: { object: objectName, method: methodName }
205
+ });
206
+ }
207
+ }
208
+ }
209
+
210
+ // Check for direct function calls (print(), alert(), etc.)
211
+ else if (node.callee.type === 'Identifier') {
212
+ const functionName = node.callee.name;
213
+
214
+ // Check for standalone forbidden functions
215
+ if (['print', 'alert', 'confirm', 'prompt'].includes(functionName)) {
216
+ if (shouldAllowCall(node)) {
217
+ return;
218
+ }
219
+
220
+ context.report({
221
+ node,
222
+ messageId: functionName === 'alert' ? "noConsole" : "noPrint",
223
+ data: { method: functionName }
224
+ });
225
+ }
226
+ }
227
+ }
228
+
229
+ function findStatementNode(node) {
230
+ let current = node;
231
+ while (current && current.parent) {
232
+ if (current.parent.type === 'ExpressionStatement') {
233
+ return current.parent;
234
+ }
235
+ if (current.parent.type === 'Program' ||
236
+ current.parent.type === 'BlockStatement') {
237
+ break;
238
+ }
239
+ current = current.parent;
240
+ }
241
+ return null;
242
+ }
243
+
244
+ function checkTemplateLiteral(node) {
245
+ // Check for console calls in template literals (less common but possible)
246
+ if (node.expressions) {
247
+ node.expressions.forEach(expr => {
248
+ if (expr.type === 'CallExpression') {
249
+ checkConsoleCall(expr);
250
+ }
251
+ });
252
+ }
253
+ }
254
+
255
+ function checkDebugger(node) {
256
+ // Also flag debugger statements
257
+ if (!shouldAllowCall(node)) {
258
+ context.report({
259
+ node,
260
+ message: "Debugger statements should not be used in production code.",
261
+ fix(fixer) {
262
+ const statement = findStatementNode(node);
263
+ if (statement) {
264
+ return fixer.remove(statement);
265
+ }
266
+ return fixer.remove(node);
267
+ }
268
+ });
269
+ }
270
+ }
271
+
272
+ return {
273
+ CallExpression: checkConsoleCall,
274
+ TemplateLiteral: checkTemplateLiteral,
275
+ DebuggerStatement: checkDebugger,
276
+
277
+ // Also check for console calls in other contexts
278
+ MemberExpression(node) {
279
+ // Flag console object access even without calls
280
+ if (node.object &&
281
+ node.object.type === 'Identifier' &&
282
+ node.object.name === 'console' &&
283
+ node.property &&
284
+ node.property.type === 'Identifier') {
285
+
286
+ const methodName = node.property.name;
287
+ if (!allowedMethods.has(methodName) && !shouldAllowCall(node)) {
288
+ context.report({
289
+ node,
290
+ messageId: "noConsole",
291
+ data: { method: methodName }
292
+ });
293
+ }
294
+ }
295
+ }
296
+ };
297
+ }
298
+ };
299
+
300
+ module.exports = c045Rule;