@sun-asterisk/sunlint 1.0.7 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/.sunlint.json +35 -0
  2. package/CHANGELOG.md +30 -3
  3. package/CONTRIBUTING.md +235 -0
  4. package/PROJECT_STRUCTURE.md +60 -0
  5. package/README.md +73 -52
  6. package/cli.js +1 -0
  7. package/config/README.md +88 -0
  8. package/config/defaults/ai-rules-context.json +231 -0
  9. package/config/engines/engines.json +49 -0
  10. package/config/engines/eslint-rule-mapping.json +74 -0
  11. package/config/eslint-rule-mapping.json +126 -0
  12. package/config/integrations/eslint/base.config.js +125 -0
  13. package/config/integrations/eslint/simple.config.js +24 -0
  14. package/config/presets/strict.json +0 -1
  15. package/config/rule-analysis-strategies.js +74 -0
  16. package/config/{rules-registry.json → rules/rules-registry.json} +22 -0
  17. package/core/analysis-orchestrator.js +383 -591
  18. package/core/ast-modules/README.md +103 -0
  19. package/core/ast-modules/base-parser.js +90 -0
  20. package/core/ast-modules/index.js +97 -0
  21. package/core/ast-modules/package.json +37 -0
  22. package/core/ast-modules/parsers/eslint-js-parser.js +147 -0
  23. package/core/ast-modules/parsers/eslint-ts-parser.js +106 -0
  24. package/core/ast-modules/parsers/javascript-parser.js +187 -0
  25. package/core/ast-modules/parsers/typescript-parser.js +187 -0
  26. package/core/cli-action-handler.js +271 -255
  27. package/core/cli-program.js +18 -4
  28. package/core/config-manager.js +9 -3
  29. package/core/config-merger.js +40 -1
  30. package/core/config-validator.js +2 -2
  31. package/core/enhanced-rules-registry.js +331 -0
  32. package/core/file-targeting-service.js +92 -23
  33. package/core/interfaces/analysis-engine.interface.js +100 -0
  34. package/core/multi-rule-runner.js +0 -221
  35. package/core/output-service.js +1 -1
  36. package/core/rule-mapping-service.js +1 -1
  37. package/core/rule-selection-service.js +10 -2
  38. package/docs/AI.md +163 -0
  39. package/docs/ARCHITECTURE.md +78 -0
  40. package/docs/CI-CD-GUIDE.md +315 -0
  41. package/docs/COMMAND-EXAMPLES.md +256 -0
  42. package/docs/CONFIGURATION.md +414 -0
  43. package/docs/DEBUG.md +86 -0
  44. package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
  45. package/docs/DISTRIBUTION.md +153 -0
  46. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  47. package/docs/ESLINT_INTEGRATION.md +238 -0
  48. package/docs/FOLDER_STRUCTURE.md +59 -0
  49. package/docs/HEURISTIC_VS_AI.md +113 -0
  50. package/docs/README.md +32 -0
  51. package/docs/RELEASE_GUIDE.md +230 -0
  52. package/engines/eslint-engine.js +601 -0
  53. package/engines/heuristic-engine.js +860 -0
  54. package/engines/openai-engine.js +374 -0
  55. package/engines/tree-sitter-parser.js +0 -0
  56. package/engines/universal-ast-engine.js +0 -0
  57. package/integrations/eslint/README.md +99 -0
  58. package/integrations/eslint/configs/.eslintrc.js +98 -0
  59. package/integrations/eslint/configs/eslint.config.js +133 -0
  60. package/integrations/eslint/configs/eslint.config.simple.js +24 -0
  61. package/integrations/eslint/package.json +23 -0
  62. package/integrations/eslint/plugin/index.js +164 -0
  63. package/integrations/eslint/plugin/package.json +13 -0
  64. package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
  65. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
  66. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
  67. package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
  68. package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
  69. package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
  70. package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
  71. package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
  72. package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
  73. package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
  74. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
  75. package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
  76. package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
  77. package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
  78. package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
  79. package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
  80. package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
  81. package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
  82. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
  83. package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
  84. package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
  85. package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
  86. package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
  87. package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
  88. package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
  89. package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  90. package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
  91. package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
  92. package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
  93. package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
  94. package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
  95. package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
  96. package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
  97. package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
  98. package/integrations/eslint/test-c041-rule.js +87 -0
  99. package/integrations/eslint/tsconfig.json +27 -0
  100. package/package.json +29 -16
  101. package/rules/README.md +252 -0
  102. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  103. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  104. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  105. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  106. package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
  107. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  108. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  109. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  110. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  111. package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
  112. package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
  113. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  114. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  115. package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
  116. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
  117. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  118. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  119. package/rules/docs/C002_no_duplicate_code.md +57 -0
  120. package/rules/index.js +149 -0
  121. package/rules/migration/converter.js +385 -0
  122. package/rules/migration/mapping.json +164 -0
  123. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  124. package/rules/security/S026_json_schema_validation/config.json +27 -0
  125. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
  126. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  127. package/rules/security/S029_csrf_protection/analyzer.js +264 -0
  128. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  129. package/rules/universal/C010/generic.js +0 -0
  130. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  131. package/rules/utils/ast-utils.js +191 -0
  132. package/rules/utils/base-analyzer.js +98 -0
  133. package/rules/utils/pattern-matchers.js +239 -0
  134. package/rules/utils/rule-helpers.js +264 -0
  135. package/rules/utils/severity-constants.js +93 -0
  136. package/scripts/build-release.sh +117 -0
  137. package/scripts/ci-report.js +179 -0
  138. package/scripts/install.sh +196 -0
  139. package/scripts/manual-release.sh +338 -0
  140. package/scripts/merge-reports.js +424 -0
  141. package/scripts/pre-release-test.sh +175 -0
  142. package/scripts/prepare-release.sh +202 -0
  143. package/scripts/setup-github-registry.sh +42 -0
  144. package/scripts/test-scripts/README.md +22 -0
  145. package/scripts/test-scripts/test-c041-comparison.js +114 -0
  146. package/scripts/test-scripts/test-c041-eslint.js +67 -0
  147. package/scripts/test-scripts/test-eslint-rules.js +146 -0
  148. package/scripts/test-scripts/test-real-world.js +44 -0
  149. package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
  150. package/scripts/trigger-release.sh +285 -0
  151. package/scripts/validate-rule-structure.js +148 -0
  152. package/scripts/verify-install.sh +82 -0
  153. package/config/sunlint-schema.json +0 -159
  154. package/config/typescript/custom-rules.js +0 -9
  155. package/config/typescript/package-lock.json +0 -1585
  156. package/config/typescript/package.json +0 -13
  157. package/config/typescript/security-rules/index.js +0 -90
  158. package/config/typescript/tsconfig.json +0 -29
  159. package/core/ai-analyzer.js +0 -169
  160. package/core/eslint-engine-service.js +0 -312
  161. package/core/eslint-instance-manager.js +0 -104
  162. package/core/eslint-integration-service.js +0 -363
  163. package/core/sunlint-engine-service.js +0 -23
  164. package/core/typescript-analyzer.js +0 -262
  165. package/core/typescript-engine.js +0 -313
  166. /package/config/{default.json → defaults/default.json} +0 -0
  167. /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
  168. /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
  169. /package/config/{typescript → testing}/test-s005-working.ts +0 -0
  170. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
  171. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
  172. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
  173. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
  174. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
  175. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
  176. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
  177. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
  178. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
  179. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
  180. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
  181. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
  182. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
  183. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
  184. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
  185. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
  186. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
  187. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
  188. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
  189. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
  190. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
  191. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
  192. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
  193. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
  194. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
  195. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
  196. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
  197. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
  198. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
  199. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
  200. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
  201. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
  202. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
  203. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
  204. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
  205. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
  206. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
  207. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
  208. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
  209. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
  210. /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
  211. /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
  212. /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
  213. /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
  214. /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
@@ -47,43 +47,59 @@ class FileTargetingService {
47
47
  */
48
48
  applyFiltering(files, config, cliOptions) {
49
49
  let filteredFiles = [...files];
50
+ const debug = cliOptions?.debug || false;
51
+
52
+ if (debug) console.log(`🔍 [DEBUG] applyFiltering: start with ${filteredFiles.length} files`);
53
+ if (debug) console.log(`🔍 [DEBUG] config.include:`, config.include);
54
+ if (debug) console.log(`🔍 [DEBUG] cliOptions.include:`, cliOptions.include);
50
55
 
51
56
  // 1. Apply config include patterns first (medium priority)
52
57
  if (config.include && config.include.length > 0) {
53
- filteredFiles = this.applyIncludePatterns(filteredFiles, config.include);
58
+ filteredFiles = this.applyIncludePatterns(filteredFiles, config.include, debug);
59
+ if (debug) console.log(`🔍 [DEBUG] After config include: ${filteredFiles.length} files`);
54
60
  }
55
61
 
56
62
  // 2. Apply CLI include overrides (highest priority - completely overrides config)
57
63
  if (cliOptions.include) {
58
64
  // CLI include completely replaces config include - start fresh from all files
59
- filteredFiles = this.applyIncludePatterns([...files], cliOptions.include);
65
+ filteredFiles = this.applyIncludePatterns([...files], cliOptions.include, debug);
60
66
  }
61
67
 
62
68
  // 3. Apply config exclude patterns
63
69
  if (config.exclude && config.exclude.length > 0) {
64
- filteredFiles = this.applyExcludePatterns(filteredFiles, config.exclude);
70
+ if (debug) console.log(`🔍 [DEBUG] About to apply config exclude patterns: ${config.exclude}`);
71
+ if (debug) console.log(`🔍 [DEBUG] Files before config exclude: ${filteredFiles.length}`);
72
+ filteredFiles = this.applyExcludePatterns(filteredFiles, config.exclude, debug);
73
+ if (debug) console.log(`🔍 [DEBUG] Files after config exclude: ${filteredFiles.length}`);
65
74
  }
66
75
 
67
76
  // 4. Apply CLI exclude overrides (highest priority)
68
77
  if (cliOptions.exclude) {
69
- filteredFiles = this.applyExcludePatterns(filteredFiles, cliOptions.exclude);
78
+ if (debug) console.log(`🔍 [DEBUG] About to apply CLI exclude patterns: ${cliOptions.exclude}`);
79
+ if (debug) console.log(`🔍 [DEBUG] Files before CLI exclude: ${filteredFiles.length}`);
80
+ filteredFiles = this.applyExcludePatterns(filteredFiles, cliOptions.exclude, debug);
81
+ if (debug) console.log(`🔍 [DEBUG] Files after CLI exclude: ${filteredFiles.length}`);
70
82
  }
71
83
 
72
84
  // 5. Apply language-specific filtering
73
85
  if (cliOptions.languages || config.languages) {
74
- filteredFiles = this.applyLanguageFiltering(filteredFiles, config, cliOptions);
86
+ filteredFiles = this.applyLanguageFiltering(filteredFiles, config, cliOptions, debug);
87
+ if (debug) console.log(`🔍 [DEBUG] After language filtering: ${filteredFiles.length} files`);
75
88
  }
76
89
 
77
90
  // 6. Apply only-source filtering (exclude tests, configs, etc.)
78
91
  if (cliOptions.onlySource) {
79
92
  filteredFiles = this.applyOnlySourceFiltering(filteredFiles);
93
+ if (debug) console.log(`🔍 [DEBUG] After onlySource filtering: ${filteredFiles.length} files`);
80
94
  } else {
81
95
  // 8. Handle test files normally
82
96
  if (config.testPatterns) {
83
97
  filteredFiles = this.handleTestFiles(filteredFiles, config.testPatterns, cliOptions, config);
98
+ if (debug) console.log(`🔍 [DEBUG] After test files handling: ${filteredFiles.length} files`);
84
99
  }
85
100
  }
86
101
 
102
+ if (debug) console.log(`🔍 [DEBUG] Final filtered files: ${filteredFiles.length}`);
87
103
  return filteredFiles;
88
104
  }
89
105
 
@@ -91,7 +107,11 @@ class FileTargetingService {
91
107
  * Apply language-specific filtering
92
108
  * Rule C005: Single responsibility - language filtering only
93
109
  */
94
- applyLanguageFiltering(files, config, cliOptions) {
110
+ applyLanguageFiltering(files, config, cliOptions, debug = false) {
111
+ if (debug) console.log(`🔍 [DEBUG] === applyLanguageFiltering ENTRY ===`);
112
+ if (debug) console.log(`🔍 [DEBUG] Input files.length: ${files.length}`);
113
+ if (debug) console.log(`🔍 [DEBUG] Sample input files:`, files.slice(0, 3));
114
+
95
115
  // Determine target languages from CLI or config
96
116
  let targetLanguages;
97
117
  if (cliOptions.languages) {
@@ -102,78 +122,118 @@ class FileTargetingService {
102
122
  targetLanguages = Object.keys(config.languages || {});
103
123
  }
104
124
 
125
+ if (debug) console.log(`🔍 [DEBUG] applyLanguageFiltering: cliOptions.languages = ${cliOptions.languages}`);
126
+ if (debug) console.log(`🔍 [DEBUG] applyLanguageFiltering: config.languages =`, config.languages);
127
+ if (debug) console.log(`🔍 [DEBUG] applyLanguageFiltering: targetLanguages =`, targetLanguages);
128
+
105
129
  if (targetLanguages.length === 0) {
130
+ if (debug) console.log(`🔍 [DEBUG] applyLanguageFiltering: No language filtering, returning all files`);
106
131
  return files; // No language filtering
107
132
  }
108
133
 
109
134
  let languageFiles = [];
110
135
 
111
136
  for (const language of targetLanguages) {
137
+ if (debug) console.log(`🔍 [DEBUG] Processing language: ${language}`);
112
138
  if (Array.isArray(config.languages)) {
113
139
  // New array format - use isLanguageFile method
114
140
  const langFiles = files.filter(file => this.isLanguageFile(file, language));
115
141
  languageFiles.push(...langFiles);
142
+ if (debug) console.log(`🔍 [DEBUG] Array format - found ${langFiles.length} files for ${language}`);
116
143
  } else {
117
144
  // Legacy object format - use include/exclude patterns
118
145
  const langConfig = config.languages[language];
119
- if (!langConfig) continue;
146
+ if (!langConfig) {
147
+ if (debug) console.log(`🔍 [DEBUG] No config for language: ${language}`);
148
+ continue;
149
+ }
120
150
 
121
151
  let langFiles = [...files];
152
+ if (debug) console.log(`🔍 [DEBUG] Starting with ${langFiles.length} files for ${language}`);
122
153
 
123
154
  // Apply language-specific include patterns
124
155
  if (langConfig.include && langConfig.include.length > 0) {
125
- langFiles = this.applyIncludePatterns(langFiles, langConfig.include);
156
+ langFiles = this.applyIncludePatterns(langFiles, langConfig.include, debug);
157
+ if (debug) console.log(`🔍 [DEBUG] After include patterns ${langConfig.include}: ${langFiles.length} files`);
126
158
  }
127
159
 
128
160
  // Apply language-specific exclude patterns
129
161
  if (langConfig.exclude && langConfig.exclude.length > 0) {
130
- langFiles = this.applyExcludePatterns(langFiles, langConfig.exclude);
162
+ langFiles = this.applyExcludePatterns(langFiles, langConfig.exclude, debug);
163
+ if (debug) console.log(`🔍 [DEBUG] After exclude patterns ${langConfig.exclude}: ${langFiles.length} files`);
131
164
  }
132
165
 
133
166
  languageFiles.push(...langFiles);
167
+ if (debug) console.log(`🔍 [DEBUG] Added ${langFiles.length} files for ${language}, total: ${languageFiles.length}`);
134
168
  }
135
169
  }
136
170
 
137
171
  // Remove duplicates
138
- return [...new Set(languageFiles)];
172
+ const finalFiles = [...new Set(languageFiles)];
173
+ if (debug) console.log(`🔍 [DEBUG] Final language files after dedup: ${finalFiles.length}`);
174
+ return finalFiles;
139
175
  }
140
176
 
141
177
  /**
142
178
  * Apply include patterns using minimatch
143
179
  * Rule C006: applyIncludePatterns - verb-noun naming
144
180
  */
145
- applyIncludePatterns(files, patterns) {
181
+ applyIncludePatterns(files, patterns, debug = false) {
146
182
  if (!patterns) return files;
147
183
 
148
184
  // Normalize patterns to array
149
185
  const patternArray = Array.isArray(patterns) ? patterns : [patterns];
150
186
  if (patternArray.length === 0) return files;
151
187
 
152
- return files.filter(file => {
188
+ if (debug) console.log(`🔍 [DEBUG] applyIncludePatterns - input files:`, files.length);
189
+ if (debug) console.log(`🔍 [DEBUG] applyIncludePatterns - patterns:`, patternArray);
190
+ if (debug) console.log(`🔍 [DEBUG] applyIncludePatterns - sample input files:`, files.slice(0, 3));
191
+
192
+ const result = files.filter(file => {
153
193
  return patternArray.some(pattern => {
154
194
  const normalizedFile = this.normalizePath(file);
155
- return minimatch(normalizedFile, pattern, { dot: true });
195
+ const match = minimatch(normalizedFile, pattern, { dot: true });
196
+ if (debug && file.includes('.ts') && !file.includes('.test.')) {
197
+ console.log(`🔍 [DEBUG] Testing: '${file}' -> '${normalizedFile}' vs '${pattern}' = ${match}`);
198
+ }
199
+ return match;
156
200
  });
157
201
  });
202
+
203
+ if (debug) console.log(`🔍 [DEBUG] applyIncludePatterns - result:`, result.length);
204
+ return result;
158
205
  }
159
206
 
160
207
  /**
161
208
  * Apply exclude patterns using minimatch
162
209
  * Rule C006: applyExcludePatterns - verb-noun naming
163
210
  */
164
- applyExcludePatterns(files, patterns) {
211
+ applyExcludePatterns(files, patterns, debug = false) {
165
212
  if (!patterns) return files;
166
213
 
167
214
  // Normalize patterns to array
168
215
  const patternArray = Array.isArray(patterns) ? patterns : [patterns];
169
216
  if (patternArray.length === 0) return files;
170
217
 
171
- return files.filter(file => {
172
- return !patternArray.some(pattern => {
218
+ // Filter out negation patterns (starting with !) - these should not be in exclude patterns
219
+ const excludePatterns = patternArray.filter(pattern => !pattern.startsWith('!'));
220
+
221
+ if (debug) console.log(`🔍 [DEBUG] applyExcludePatterns - input files: ${files.length}`);
222
+ if (debug) console.log(`🔍 [DEBUG] applyExcludePatterns - original patterns:`, patternArray);
223
+ if (debug) console.log(`🔍 [DEBUG] applyExcludePatterns - filtered patterns:`, excludePatterns);
224
+
225
+ if (excludePatterns.length === 0) return files;
226
+
227
+ const result = files.filter(file => {
228
+ return !excludePatterns.some(pattern => {
173
229
  const normalizedFile = this.normalizePath(file);
174
- return minimatch(normalizedFile, pattern, { dot: true });
230
+ const match = minimatch(normalizedFile, pattern, { dot: true });
231
+ return match;
175
232
  });
176
233
  });
234
+
235
+ if (debug) console.log(`🔍 [DEBUG] applyExcludePatterns - result: ${result.length}`);
236
+ return result;
177
237
  }
178
238
 
179
239
  /**
@@ -208,13 +268,22 @@ class FileTargetingService {
208
268
  */
209
269
  async collectFiles(inputPath) {
210
270
  const files = [];
211
- const stats = fs.statSync(inputPath);
271
+
272
+ try {
273
+ const stats = fs.statSync(inputPath);
212
274
 
213
- if (stats.isFile()) {
214
- files.push(path.resolve(inputPath));
215
- } else if (stats.isDirectory()) {
216
- const dirFiles = await this.collectFilesFromDirectory(inputPath);
217
- files.push(...dirFiles);
275
+ if (stats.isFile()) {
276
+ files.push(path.resolve(inputPath));
277
+ } else if (stats.isDirectory()) {
278
+ const dirFiles = await this.collectFilesFromDirectory(inputPath);
279
+ files.push(...dirFiles);
280
+ }
281
+ } catch (error) {
282
+ if (error.code === 'ENOENT') {
283
+ console.warn(`⚠️ Path not found: ${inputPath}`);
284
+ return files; // Return empty array instead of throwing
285
+ }
286
+ throw error; // Re-throw other errors
218
287
  }
219
288
 
220
289
  return files;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Analysis Engine Interface
3
+ * Following Rule C014: Dependency Injection interface
4
+ * Following Rule C015: Use domain language - clear interface naming
5
+ */
6
+
7
+ class AnalysisEngineInterface {
8
+ /**
9
+ * Constructor for Analysis Engine
10
+ * @param {string} id - Engine ID (e.g., 'eslint', 'heuristic', 'openai')
11
+ * @param {string} version - Engine version
12
+ * @param {string[]} supportedLanguages - Array of supported languages
13
+ */
14
+ constructor(id, version, supportedLanguages = []) {
15
+ this.id = id;
16
+ this.version = version;
17
+ this.supportedLanguages = supportedLanguages;
18
+ this.initialized = false;
19
+ }
20
+
21
+ /**
22
+ * Initialize the analysis engine
23
+ * Following Rule C006: Verb-noun naming
24
+ * @param {Object} config - Engine-specific configuration
25
+ * @returns {Promise<void>}
26
+ */
27
+ async initialize(config) {
28
+ throw new Error(`Method initialize() must be implemented by ${this.constructor.name}`);
29
+ }
30
+
31
+ /**
32
+ * Analyze files with given rules
33
+ * Following Rule C006: Verb-noun naming
34
+ * @param {string[]} files - Array of file paths to analyze
35
+ * @param {Object[]} rules - Array of rule objects to apply
36
+ * @param {Object} options - Analysis options
37
+ * @returns {Promise<Object>} Analysis results
38
+ */
39
+ async analyze(files, rules, options) {
40
+ throw new Error(`Method analyze() must be implemented by ${this.constructor.name}`);
41
+ }
42
+
43
+ /**
44
+ * Get supported rules for this engine
45
+ * Following Rule C006: Verb-noun naming
46
+ * @returns {string[]} Array of supported rule IDs
47
+ */
48
+ getSupportedRules() {
49
+ throw new Error(`Method getSupportedRules() must be implemented by ${this.constructor.name}`);
50
+ }
51
+
52
+ /**
53
+ * Check if a specific rule is supported
54
+ * Following Rule C006: Verb-noun naming
55
+ * @param {string} ruleId - Rule ID to check
56
+ * @returns {boolean} True if rule is supported
57
+ */
58
+ isRuleSupported(ruleId) {
59
+ return this.getSupportedRules().includes(ruleId);
60
+ }
61
+
62
+ /**
63
+ * Check if a language is supported
64
+ * Following Rule C006: Verb-noun naming
65
+ * @param {string} language - Language to check
66
+ * @returns {boolean} True if language is supported
67
+ */
68
+ isLanguageSupported(language) {
69
+ return this.supportedLanguages.includes(language) ||
70
+ this.supportedLanguages.includes('all') ||
71
+ this.supportedLanguages.length === 0;
72
+ }
73
+
74
+ /**
75
+ * Get engine metadata
76
+ * Following Rule C006: Verb-noun naming
77
+ * @returns {Object} Engine metadata
78
+ */
79
+ getEngineInfo() {
80
+ return {
81
+ name: this.name,
82
+ version: this.version,
83
+ supportedLanguages: this.supportedLanguages,
84
+ supportedRules: this.getSupportedRules(),
85
+ initialized: this.initialized
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Cleanup engine resources
91
+ * Following Rule C006: Verb-noun naming
92
+ * @returns {Promise<void>}
93
+ */
94
+ async cleanup() {
95
+ // Default implementation - engines can override if needed
96
+ this.initialized = false;
97
+ }
98
+ }
99
+
100
+ module.exports = AnalysisEngineInterface;
@@ -1,221 +0,0 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const glob = require('glob');
4
- const chalk = require('chalk');
5
- const FileTargetingService = require('./file-targeting-service');
6
-
7
- class MultiRuleRunner {
8
- constructor(config, options) {
9
- this.config = config;
10
- this.options = options;
11
- this.ruleCache = new Map();
12
- this.fileTargetingService = new FileTargetingService();
13
- }
14
-
15
- async runRules(rules, inputPath, options) {
16
- console.log(chalk.blue(`🔄 Analyzing ${rules.length} rules...`));
17
-
18
- // Get files to analyze
19
- const files = await this.getFilesToAnalyze(inputPath, options);
20
-
21
- console.log(chalk.gray(`📁 Found ${files ? files.length : 'undefined'} files to analyze`));
22
-
23
- if (!files || files.length === 0) {
24
- console.log(chalk.yellow('⚠️ No files found to analyze'));
25
- return [];
26
- }
27
-
28
- // Group files by language
29
- const filesByLanguage = this.groupFilesByLanguage(files);
30
-
31
- // Run rules in parallel (with concurrency limit)
32
- const maxConcurrent = parseInt(options.maxConcurrent) || 5;
33
- const timeout = parseInt(options.timeout) || 30000;
34
-
35
- const results = [];
36
-
37
- // Process rules in batches
38
- for (let i = 0; i < rules.length; i += maxConcurrent) {
39
- const batch = rules.slice(i, i + maxConcurrent);
40
-
41
- const batchPromises = batch.map(async (rule) => {
42
- try {
43
- return await this.runSingleRule(rule, filesByLanguage, options, timeout);
44
- } catch (error) {
45
- console.error(chalk.red(`❌ Rule ${rule.id} failed:`), error.message);
46
- return {
47
- ruleId: rule.id,
48
- ruleName: rule.name,
49
- violations: [],
50
- error: error.message,
51
- status: 'failed'
52
- };
53
- }
54
- });
55
-
56
- const batchResults = await Promise.all(batchPromises);
57
- results.push(...batchResults);
58
-
59
- if (options.verbose) {
60
- console.log(chalk.gray(`✅ Completed batch ${Math.floor(i/maxConcurrent) + 1}/${Math.ceil(rules.length/maxConcurrent)}`));
61
- }
62
- }
63
-
64
- return this.consolidateResults(results, files.length);
65
- }
66
-
67
- async runSingleRule(rule, filesByLanguage, options, timeout) {
68
- if (options.verbose) {
69
- console.log(chalk.gray(`🔍 Running rule ${rule.id}: ${rule.name}`));
70
- }
71
-
72
- const startTime = Date.now();
73
-
74
- // Load rule analyzer
75
- const analyzer = await this.loadRuleAnalyzer(rule);
76
-
77
- if (!analyzer) {
78
- return {
79
- ruleId: rule.id,
80
- ruleName: rule.name,
81
- violations: [],
82
- error: 'Analyzer not found',
83
- status: 'skipped'
84
- };
85
- }
86
-
87
- const violations = [];
88
-
89
- // Run analyzer for each supported language
90
- for (const language of rule.languages) {
91
- const languageFiles = filesByLanguage[language] || [];
92
-
93
- if (languageFiles.length === 0) {
94
- continue;
95
- }
96
-
97
- try {
98
- // Run with timeout
99
- const languageViolations = await Promise.race([
100
- analyzer.analyze(languageFiles, language, this.config),
101
- new Promise((_, reject) =>
102
- setTimeout(() => reject(new Error('Timeout')), timeout)
103
- )
104
- ]);
105
-
106
- violations.push(...languageViolations);
107
- } catch (error) {
108
- console.error(chalk.yellow(`⚠️ Rule ${rule.id} failed for ${language}:`), error.message);
109
- }
110
- }
111
-
112
- const duration = Date.now() - startTime;
113
-
114
- return {
115
- ruleId: rule.id,
116
- ruleName: rule.name,
117
- category: rule.category,
118
- severity: rule.severity,
119
- violations,
120
- filesAnalyzed: Object.values(filesByLanguage).flat().length,
121
- duration,
122
- status: 'completed'
123
- };
124
- }
125
-
126
- async loadRuleAnalyzer(rule) {
127
- // Check cache first
128
- if (this.ruleCache.has(rule.id)) {
129
- return this.ruleCache.get(rule.id);
130
- }
131
-
132
- try {
133
- const analyzerPath = path.resolve(__dirname, '..', rule.analyzer);
134
-
135
- if (!fs.existsSync(analyzerPath)) {
136
- console.error(chalk.yellow(`⚠️ Analyzer not found for rule ${rule.id}: ${analyzerPath}`));
137
- return null;
138
- }
139
-
140
- const analyzer = require(analyzerPath);
141
- this.ruleCache.set(rule.id, analyzer);
142
- return analyzer;
143
- } catch (error) {
144
- console.error(chalk.red(`❌ Failed to load analyzer for rule ${rule.id}:`), error.message);
145
- return null;
146
- }
147
- }
148
-
149
- async getFilesToAnalyze(inputPath, options) {
150
- // Use centralized file targeting service - convert single path to array
151
- const inputPaths = Array.isArray(inputPath) ? inputPath : [inputPath];
152
- const result = await this.fileTargetingService.getTargetFiles(inputPaths, this.config, options);
153
- return result.files; // Extract files array from result object
154
- }
155
-
156
- groupFilesByLanguage(files) {
157
- const rulesRegistry = require('../config/rules-registry.json');
158
- const languageConfig = rulesRegistry.languages;
159
-
160
- const filesByLanguage = {};
161
-
162
- files.forEach(file => {
163
- const ext = path.extname(file);
164
-
165
- for (const [language, config] of Object.entries(languageConfig)) {
166
- if (config.extensions.includes(ext)) {
167
- if (!filesByLanguage[language]) {
168
- filesByLanguage[language] = [];
169
- }
170
- filesByLanguage[language].push(file);
171
- break; // File belongs to first matching language
172
- }
173
- }
174
- });
175
-
176
- return filesByLanguage;
177
- }
178
-
179
- consolidateResults(results, totalFiles) {
180
- const consolidatedResults = {
181
- filesAnalyzed: totalFiles,
182
- rulesRun: results.length,
183
- totalViolations: 0,
184
- violationsBySeverity: { error: 0, warning: 0, info: 0 },
185
- violationsByRule: {},
186
- violationsByFile: {},
187
- results: []
188
- };
189
-
190
- results.forEach(result => {
191
- consolidatedResults.results.push(result);
192
-
193
- if (result.violations) {
194
- consolidatedResults.totalViolations += result.violations.length;
195
-
196
- // Count by severity
197
- result.violations.forEach(violation => {
198
- const severity = violation.severity || result.severity || 'warning';
199
- consolidatedResults.violationsBySeverity[severity] =
200
- (consolidatedResults.violationsBySeverity[severity] || 0) + 1;
201
- });
202
-
203
- // Count by rule
204
- consolidatedResults.violationsByRule[result.ruleId] = result.violations.length;
205
-
206
- // Count by file
207
- result.violations.forEach(violation => {
208
- const file = violation.file || violation.filePath;
209
- if (file) {
210
- consolidatedResults.violationsByFile[file] =
211
- (consolidatedResults.violationsByFile[file] || 0) + 1;
212
- }
213
- });
214
- }
215
- });
216
-
217
- return consolidatedResults;
218
- }
219
- }
220
-
221
- module.exports = MultiRuleRunner;
@@ -35,7 +35,7 @@ class OutputService {
35
35
 
36
36
  generateReport(results, metadata, options = {}) {
37
37
  const allViolations = [];
38
- let totalFiles = results.filesAnalyzed || results.totalFiles || results.fileCount || 0;
38
+ let totalFiles = results.filesAnalyzed || results.summary?.totalFiles || results.totalFiles || results.fileCount || 0;
39
39
 
40
40
  // Collect all violations - handle both file-based and rule-based results
41
41
  if (results.results) {
@@ -16,7 +16,7 @@ class RuleMappingService {
16
16
  */
17
17
  createEslintToSunlintMapping() {
18
18
  return {
19
- // Custom rules mapping (using actual rule IDs from eslint-plugin-custom)
19
+ // Custom rules mapping (using actual rule IDs from integrations/eslint/plugin/rules)
20
20
  'custom/c002': 'C002',
21
21
  'custom/c003': 'C003',
22
22
  'custom/c006': 'C006',
@@ -15,7 +15,7 @@ class RuleSelectionService {
15
15
  async selectRules(config, options) {
16
16
  // Load rules registry
17
17
  try {
18
- this.rulesRegistry = require('../config/rules-registry.json');
18
+ this.rulesRegistry = require('../config/rules/rules-registry.json');
19
19
  } catch (error) {
20
20
  console.log(chalk.yellow('⚠️ Rules registry not found, using minimal rule set'));
21
21
  this.rulesRegistry = this.getMinimalRuleSet();
@@ -40,6 +40,14 @@ class RuleSelectionService {
40
40
  if (options.verbose) {
41
41
  console.log(chalk.blue(`📋 Found ${selectedRules.length} total rules (${registryRules.length} registry + ${eslintSupportedRules.length} ESLint)`));
42
42
  }
43
+ } else if (options.quality) {
44
+ // Handle --quality shortcut
45
+ const categoryRules = this.getRulesByCategory('quality');
46
+ selectedRules = categoryRules;
47
+ } else if (options.security) {
48
+ // Handle --security shortcut
49
+ const categoryRules = this.getRulesByCategory('security');
50
+ selectedRules = categoryRules;
43
51
  } else if (options.category) {
44
52
  const categoryRules = this.getRulesByCategory(options.category);
45
53
  selectedRules = categoryRules;
@@ -96,7 +104,7 @@ class RuleSelectionService {
96
104
  'C043', 'C047', 'C048', 'C076', 'T002', 'T003', 'T004', 'T007', 'T011',
97
105
  'T019', 'T025', 'T026'
98
106
  ],
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'],
107
+ 'security': ['S001', 'S002', 'S003', 'S005', 'S006', 'S007', 'S008', 'S009', 'S010', 'S011', 'S012', 'S013', '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
108
  'naming': ['C006'],
101
109
  'logging': ['C019', 'S057'],
102
110
  'validation': ['C031', 'S018', 'S025', 'S026']