@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,121 @@
1
+ /**
2
+ * Rule Selection Service
3
+ * Following Rule C005: Single responsibility - only handle rule selection
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const RuleMappingService = require('./rule-mapping-service');
8
+
9
+ class RuleSelectionService {
10
+ constructor() {
11
+ this.rulesRegistry = null;
12
+ this.ruleMappingService = new RuleMappingService();
13
+ }
14
+
15
+ async selectRules(config, options) {
16
+ // Load rules registry
17
+ try {
18
+ this.rulesRegistry = require('../config/rules-registry.json');
19
+ } catch (error) {
20
+ console.log(chalk.yellow('⚠️ Rules registry not found, using minimal rule set'));
21
+ this.rulesRegistry = this.getMinimalRuleSet();
22
+ }
23
+
24
+ const allRules = config.rules || {};
25
+ let selectedRules = [];
26
+
27
+ // Determine rule selection strategy
28
+ if (options.rule) {
29
+ selectedRules = [options.rule];
30
+ } else if (options.rules) {
31
+ selectedRules = options.rules.split(',').map(r => r.trim());
32
+ } else if (options.all) {
33
+ // For --all, use all supported rules from both registry and ESLint integration
34
+ const registryRules = Object.keys(this.rulesRegistry.rules || {});
35
+ const eslintSupportedRules = this.ruleMappingService.getSupportedSunLintRules();
36
+
37
+ // Combine and deduplicate
38
+ selectedRules = [...new Set([...registryRules, ...eslintSupportedRules])];
39
+
40
+ if (options.verbose) {
41
+ console.log(chalk.blue(`📋 Found ${selectedRules.length} total rules (${registryRules.length} registry + ${eslintSupportedRules.length} ESLint)`));
42
+ }
43
+ } else if (options.category) {
44
+ const categoryRules = this.getRulesByCategory(options.category);
45
+ selectedRules = categoryRules;
46
+ } else {
47
+ // Default: use config rules or minimal set
48
+ selectedRules = Object.keys(allRules).filter(ruleId =>
49
+ allRules[ruleId] !== 'off' && allRules[ruleId] !== false
50
+ );
51
+
52
+ if (selectedRules.length === 0) {
53
+ selectedRules = ['C006', 'C019']; // Default minimal set
54
+ }
55
+ }
56
+
57
+ // Convert to rule objects
58
+ return selectedRules.map(ruleId => ({
59
+ id: ruleId,
60
+ name: this.getRuleName(ruleId),
61
+ severity: 'warning',
62
+ ...((this.rulesRegistry.rules && this.rulesRegistry.rules[ruleId]) || {})
63
+ })).filter(rule => rule.id);
64
+ }
65
+
66
+ getMinimalRuleSet() {
67
+ return {
68
+ rules: {
69
+ 'C006': {
70
+ name: 'Function Naming Convention',
71
+ description: 'Function names should follow verb-noun pattern',
72
+ category: 'naming',
73
+ severity: 'warning'
74
+ },
75
+ 'C019': {
76
+ name: 'Log Level Usage',
77
+ description: 'Use appropriate log levels',
78
+ category: 'logging',
79
+ severity: 'warning'
80
+ }
81
+ }
82
+ };
83
+ }
84
+
85
+ getRulesByCategory(category) {
86
+ // Get rules from registry if available
87
+ if (this.rulesRegistry && this.rulesRegistry.categories && this.rulesRegistry.categories[category]) {
88
+ return this.rulesRegistry.categories[category].rules;
89
+ }
90
+
91
+ // Fallback to hardcoded mapping
92
+ const categoryMap = {
93
+ 'quality': [
94
+ 'C002', 'C003', 'C006', 'C010', 'C013', 'C014', 'C017', 'C018', 'C019',
95
+ 'C023', 'C027', 'C029', 'C030', 'C031', 'C034', 'C035', 'C041', 'C042',
96
+ 'C043', 'C047', 'C048', 'C076', 'T002', 'T003', 'T004', 'T007', 'T011',
97
+ 'T019', 'T025', 'T026'
98
+ ],
99
+ 'security': ['S003', 'S005', 'S006', 'S008', 'S009', 'S010', 'S011', 'S012', 'S014', 'S015', 'S016', 'S017', 'S018', 'S019', 'S020', 'S022', 'S023', 'S025', 'S026', 'S027', 'S029', 'S030', 'S033', 'S034', 'S035', 'S036', 'S037', 'S038', 'S039', 'S041', 'S042', 'S043', 'S044', 'S045', 'S046', 'S047', 'S048', 'S050', 'S052', 'S054', 'S055', 'S057', 'S058'],
100
+ 'naming': ['C006'],
101
+ 'logging': ['C019', 'S057'],
102
+ 'validation': ['C031', 'S018', 'S025', 'S026']
103
+ };
104
+
105
+ return categoryMap[category] || [];
106
+ }
107
+
108
+ getRuleName(ruleId) {
109
+ const ruleNames = {
110
+ 'C006': 'Function Naming Convention',
111
+ 'C019': 'Log Level Usage',
112
+ 'C002': 'No Duplicate Code',
113
+ 'C003': 'No Vague Abbreviations',
114
+ 'C029': 'Catch Block Logging'
115
+ };
116
+
117
+ return ruleNames[ruleId] || `Rule ${ruleId}`;
118
+ }
119
+ }
120
+
121
+ module.exports = RuleSelectionService;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SunLint Engine Service
3
+ * Handles native SunLint analysis
4
+ * Following Rule C005: Single responsibility - only handle SunLint execution
5
+ */
6
+
7
+ const MultiRuleRunner = require('./multi-rule-runner');
8
+
9
+ class SunLintEngineService {
10
+ constructor() {
11
+ this.multiRuleRunner = new MultiRuleRunner();
12
+ }
13
+
14
+ /**
15
+ * Command: Run SunLint analysis
16
+ */
17
+ async runAnalysis(rulesToRun, options) {
18
+ // Use existing multi-rule runner
19
+ return await this.multiRuleRunner.runRules(rulesToRun, options.input, options);
20
+ }
21
+ }
22
+
23
+ module.exports = SunLintEngineService;
@@ -0,0 +1,262 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Handles TypeScript file analysis using ESLint
7
+ * Rule C005: Single responsibility - only file analysis
8
+ * Rule C015: Domain language - TypescriptAnalyzer
9
+ * Rule C033: Separates analysis logic from data queries
10
+ */
11
+ class TypescriptAnalyzer {
12
+ constructor(eslintInstanceManager) {
13
+ // Rule C014: Dependency injection
14
+ this.eslintInstanceManager = eslintInstanceManager;
15
+ }
16
+
17
+ /**
18
+ * Rule C006: analyzeTypeScriptFiles - verb-noun naming
19
+ * Rule C012: Command method - performs analysis
20
+ * Rule C033: Analysis logic separated from data queries
21
+ */
22
+ async analyzeTypeScriptFiles(filePaths, options = {}) {
23
+ // Expand directories to actual files
24
+ const expandedFiles = this.expandDirectories(filePaths);
25
+
26
+ if (expandedFiles.length === 0) {
27
+ console.warn(chalk.yellow('⚠️ No TypeScript/JavaScript files found'));
28
+ return this.formatAnalysisResults([], options);
29
+ }
30
+
31
+ if (options.verbose) {
32
+ console.log(chalk.blue(`📁 Found ${expandedFiles.length} files to analyze`));
33
+ }
34
+
35
+ const eslint = this.eslintInstanceManager.getEslintInstance();
36
+ const results = [];
37
+
38
+ for (const filePath of expandedFiles) {
39
+ try {
40
+ const analysisResult = await this.analyzeFile(eslint, filePath, options);
41
+ if (analysisResult) {
42
+ results.push(analysisResult);
43
+ }
44
+ } catch (error) {
45
+ console.error(chalk.red(`❌ Error analyzing ${filePath}:`), error.message);
46
+
47
+ // Add error result
48
+ results.push({
49
+ filePath: filePath,
50
+ messages: [{
51
+ ruleId: null,
52
+ severity: 2,
53
+ message: `Analysis failed: ${error.message}`,
54
+ line: 1,
55
+ column: 1
56
+ }],
57
+ errorCount: 1,
58
+ warningCount: 0,
59
+ fixableErrorCount: 0,
60
+ fixableWarningCount: 0
61
+ });
62
+ }
63
+ }
64
+
65
+ return this.formatAnalysisResults(results, options);
66
+ }
67
+
68
+ /**
69
+ * Rule C006: analyzeFile - verb-noun naming
70
+ * Rule C005: Single responsibility - analyze one file
71
+ */
72
+ async analyzeFile(eslint, filePath, options) {
73
+ if (!this.validateFilePath(filePath)) {
74
+ return null;
75
+ }
76
+
77
+ if (options.verbose) {
78
+ console.log(chalk.gray(`🔍 Analyzing: ${filePath}`));
79
+ }
80
+
81
+ try {
82
+ const results = await eslint.lintFiles([filePath]);
83
+ const result = results[0]; // ESLint returns array, we need first result
84
+
85
+ // Ensure filePath is preserved in result
86
+ if (result && !result.filePath) {
87
+ result.filePath = filePath;
88
+ }
89
+
90
+ return result;
91
+ } catch (error) {
92
+ throw new Error(`ESLint analysis failed for ${filePath}: ${error.message}`);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Rule C006: validateFilePath - verb-noun naming
98
+ * Rule C031: Validation logic separated
99
+ */
100
+ validateFilePath(filePath) {
101
+ if (!fs.existsSync(filePath)) {
102
+ console.warn(chalk.yellow(`⚠️ File not found: ${filePath}`));
103
+ return false;
104
+ }
105
+
106
+ // Check if it's a directory
107
+ const stats = fs.statSync(filePath);
108
+ if (stats.isDirectory()) {
109
+ return true; // Directories are valid, will be expanded later
110
+ }
111
+
112
+ // Check file extension for files
113
+ const ext = path.extname(filePath);
114
+ const supportedExtensions = ['.ts', '.tsx', '.js', '.jsx'];
115
+
116
+ if (!supportedExtensions.includes(ext)) {
117
+ console.warn(chalk.yellow(`⚠️ Unsupported file type: ${filePath}`));
118
+ return false;
119
+ }
120
+
121
+ return true;
122
+ }
123
+
124
+ /**
125
+ * Rule C006: formatAnalysisResults - verb-noun naming
126
+ * Rule C005: Single responsibility - format results
127
+ */
128
+ formatAnalysisResults(results, options) {
129
+ const totalFiles = results.length;
130
+ const totalErrors = results.reduce((sum, result) => sum + (result.errorCount || 0), 0);
131
+ const totalWarnings = results.reduce((sum, result) => sum + (result.warningCount || 0), 0);
132
+
133
+ const summary = {
134
+ totalFiles,
135
+ totalErrors,
136
+ totalWarnings,
137
+ results: results.filter(result => result.messages && result.messages.length > 0)
138
+ };
139
+
140
+ if (options.verbose) {
141
+ console.log(chalk.blue(`📊 Analysis Summary: ${totalFiles} files, ${totalErrors} errors, ${totalWarnings} warnings`));
142
+ }
143
+
144
+ return summary;
145
+ }
146
+
147
+ /**
148
+ * Rule C006: extractProblemsFromResults - verb-noun naming
149
+ * Rule C012: Query method - extracts data without side effects
150
+ */
151
+ extractProblemsFromResults(results) {
152
+ const problems = [];
153
+
154
+ for (const result of results.results || []) {
155
+ for (const message of result.messages || []) {
156
+ problems.push({
157
+ filePath: result.filePath,
158
+ ruleId: message.ruleId,
159
+ severity: message.severity,
160
+ message: message.message,
161
+ line: message.line,
162
+ column: message.column,
163
+ endLine: message.endLine,
164
+ endColumn: message.endColumn
165
+ });
166
+ }
167
+ }
168
+
169
+ return problems;
170
+ }
171
+
172
+ /**
173
+ * Rule C006: filterResultsByRules - verb-noun naming
174
+ * Rule C012: Query method - filters without modifying original
175
+ */
176
+ filterResultsByRules(results, ruleIds) {
177
+ if (!ruleIds || ruleIds.length === 0) {
178
+ return results;
179
+ }
180
+
181
+ const filteredResults = {
182
+ ...results,
183
+ results: results.results.map(result => ({
184
+ ...result,
185
+ messages: result.messages.filter(message =>
186
+ ruleIds.includes(message.ruleId)
187
+ )
188
+ })).filter(result => result.messages.length > 0)
189
+ };
190
+
191
+ // Recalculate counts
192
+ filteredResults.totalErrors = filteredResults.results.reduce(
193
+ (sum, result) => sum + result.messages.filter(m => m.severity === 2).length,
194
+ 0
195
+ );
196
+ filteredResults.totalWarnings = filteredResults.results.reduce(
197
+ (sum, result) => sum + result.messages.filter(m => m.severity === 1).length,
198
+ 0
199
+ );
200
+
201
+ return filteredResults;
202
+ }
203
+
204
+ /**
205
+ * Rule C006: expandDirectories - verb-noun naming
206
+ * Rule C005: Single responsibility - expand directories to files
207
+ */
208
+ expandDirectories(filePaths) {
209
+ const expandedFiles = [];
210
+ const supportedExtensions = ['.ts', '.tsx', '.js', '.jsx'];
211
+
212
+ for (const filePath of filePaths) {
213
+ if (!fs.existsSync(filePath)) {
214
+ continue;
215
+ }
216
+
217
+ const stats = fs.statSync(filePath);
218
+ if (stats.isDirectory()) {
219
+ // Recursively find TypeScript/JavaScript files
220
+ const files = this.findFilesRecursively(filePath, supportedExtensions);
221
+ expandedFiles.push(...files);
222
+ } else if (stats.isFile() && supportedExtensions.includes(path.extname(filePath))) {
223
+ expandedFiles.push(filePath);
224
+ }
225
+ }
226
+
227
+ return expandedFiles;
228
+ }
229
+
230
+ /**
231
+ * Rule C006: findFilesRecursively - verb-noun naming
232
+ * Rule C005: Single responsibility - recursive file finding
233
+ */
234
+ findFilesRecursively(dir, extensions) {
235
+ const files = [];
236
+
237
+ try {
238
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
239
+
240
+ for (const entry of entries) {
241
+ const fullPath = path.join(dir, entry.name);
242
+
243
+ // Skip node_modules and other common ignore patterns
244
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
245
+ continue;
246
+ }
247
+
248
+ if (entry.isDirectory()) {
249
+ files.push(...this.findFilesRecursively(fullPath, extensions));
250
+ } else if (entry.isFile() && extensions.includes(path.extname(entry.name))) {
251
+ files.push(fullPath);
252
+ }
253
+ }
254
+ } catch (error) {
255
+ console.warn(chalk.yellow(`⚠️ Cannot read directory: ${dir}`));
256
+ }
257
+
258
+ return files;
259
+ }
260
+ }
261
+
262
+ module.exports = TypescriptAnalyzer;