@sun-asterisk/sunlint 1.0.7 → 1.1.4

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 (219) 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 +146 -58
  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} +30 -7
  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 +153 -0
  23. package/core/ast-modules/parsers/eslint-ts-parser.js +98 -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/dependency-checker.js +125 -0
  32. package/core/enhanced-rules-registry.js +331 -0
  33. package/core/file-targeting-service.js +92 -23
  34. package/core/interfaces/analysis-engine.interface.js +100 -0
  35. package/core/multi-rule-runner.js +0 -221
  36. package/core/output-service.js +1 -1
  37. package/core/rule-mapping-service.js +1 -1
  38. package/core/rule-selection-service.js +10 -2
  39. package/core/smart-installer.js +164 -0
  40. package/docs/AI.md +163 -0
  41. package/docs/ARCHITECTURE.md +78 -0
  42. package/docs/CI-CD-GUIDE.md +315 -0
  43. package/docs/COMMAND-EXAMPLES.md +256 -0
  44. package/docs/CONFIGURATION.md +414 -0
  45. package/docs/DEBUG.md +86 -0
  46. package/docs/DEPENDENCIES.md +90 -0
  47. package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
  48. package/docs/DISTRIBUTION.md +153 -0
  49. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  50. package/docs/ESLINT_INTEGRATION.md +238 -0
  51. package/docs/FOLDER_STRUCTURE.md +59 -0
  52. package/docs/FUTURE_PACKAGES.md +83 -0
  53. package/docs/HEURISTIC_VS_AI.md +113 -0
  54. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +112 -0
  55. package/docs/PRODUCTION_SIZE_IMPACT.md +183 -0
  56. package/docs/README.md +32 -0
  57. package/docs/RELEASE_GUIDE.md +230 -0
  58. package/engines/eslint-engine.js +610 -0
  59. package/engines/heuristic-engine.js +864 -0
  60. package/engines/openai-engine.js +374 -0
  61. package/engines/tree-sitter-parser.js +0 -0
  62. package/engines/universal-ast-engine.js +0 -0
  63. package/integrations/eslint/README.md +99 -0
  64. package/integrations/eslint/configs/.eslintrc.js +98 -0
  65. package/integrations/eslint/configs/eslint.config.js +133 -0
  66. package/integrations/eslint/configs/eslint.config.simple.js +24 -0
  67. package/integrations/eslint/package.json +23 -0
  68. package/integrations/eslint/plugin/index.js +164 -0
  69. package/integrations/eslint/plugin/package.json +13 -0
  70. package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
  71. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
  72. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
  73. package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
  74. package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
  75. package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
  76. package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
  77. package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
  78. package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
  79. package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
  80. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
  81. package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
  82. package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
  83. package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
  84. package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
  85. package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
  86. package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
  87. package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
  88. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
  89. package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
  90. package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
  91. package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
  92. package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
  93. package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
  94. package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
  95. package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  96. package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
  97. package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
  98. package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
  99. package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
  100. package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
  101. package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
  102. package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
  103. package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
  104. package/integrations/eslint/tsconfig.json +27 -0
  105. package/package.json +61 -21
  106. package/rules/README.md +252 -0
  107. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  108. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  109. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  110. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  111. package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
  112. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  113. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  114. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  115. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  116. package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
  117. package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
  118. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  119. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  120. package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
  121. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
  122. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  123. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  124. package/rules/docs/C002_no_duplicate_code.md +57 -0
  125. package/rules/index.js +149 -0
  126. package/rules/migration/converter.js +385 -0
  127. package/rules/migration/mapping.json +164 -0
  128. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  129. package/rules/security/S026_json_schema_validation/config.json +27 -0
  130. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
  131. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  132. package/rules/security/S029_csrf_protection/analyzer.js +264 -0
  133. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  134. package/rules/universal/C010/generic.js +0 -0
  135. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  136. package/rules/utils/ast-utils.js +191 -0
  137. package/rules/utils/base-analyzer.js +98 -0
  138. package/rules/utils/pattern-matchers.js +239 -0
  139. package/rules/utils/rule-helpers.js +264 -0
  140. package/rules/utils/severity-constants.js +93 -0
  141. package/scripts/build-release.sh +117 -0
  142. package/scripts/ci-report.js +179 -0
  143. package/scripts/install.sh +196 -0
  144. package/scripts/manual-release.sh +338 -0
  145. package/scripts/merge-reports.js +424 -0
  146. package/scripts/pre-release-test.sh +175 -0
  147. package/scripts/prepare-release.sh +202 -0
  148. package/scripts/setup-github-registry.sh +42 -0
  149. package/scripts/test-scripts/README.md +22 -0
  150. package/scripts/test-scripts/test-c041-comparison.js +114 -0
  151. package/scripts/test-scripts/test-c041-eslint.js +67 -0
  152. package/scripts/test-scripts/test-eslint-rules.js +146 -0
  153. package/scripts/test-scripts/test-real-world.js +44 -0
  154. package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
  155. package/scripts/trigger-release.sh +285 -0
  156. package/scripts/validate-rule-structure.js +148 -0
  157. package/scripts/verify-install.sh +82 -0
  158. package/config/sunlint-schema.json +0 -159
  159. package/config/typescript/custom-rules.js +0 -9
  160. package/config/typescript/package-lock.json +0 -1585
  161. package/config/typescript/package.json +0 -13
  162. package/config/typescript/security-rules/index.js +0 -90
  163. package/config/typescript/tsconfig.json +0 -29
  164. package/core/ai-analyzer.js +0 -169
  165. package/core/eslint-engine-service.js +0 -312
  166. package/core/eslint-instance-manager.js +0 -104
  167. package/core/eslint-integration-service.js +0 -363
  168. package/core/sunlint-engine-service.js +0 -23
  169. package/core/typescript-analyzer.js +0 -262
  170. package/core/typescript-engine.js +0 -313
  171. /package/config/{default.json → defaults/default.json} +0 -0
  172. /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
  173. /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
  174. /package/config/{typescript → testing}/test-s005-working.ts +0 -0
  175. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
  176. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
  177. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
  178. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
  179. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
  180. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
  181. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
  182. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
  183. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
  184. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
  185. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
  186. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
  187. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
  188. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
  189. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
  190. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
  191. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
  192. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
  193. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
  194. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
  195. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
  196. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
  197. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
  198. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
  199. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
  200. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
  201. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
  202. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
  203. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
  204. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
  205. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
  206. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
  207. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
  208. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
  209. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
  210. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
  211. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
  212. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
  213. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
  214. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
  215. /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
  216. /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
  217. /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
  218. /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
  219. /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
@@ -29,12 +29,12 @@ function createCliProgram() {
29
29
  .option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,sunlint,hybrid)', 'sunlint')
30
30
  .option('--ensure-deps', 'Ensure ESLint dependencies are installed');
31
31
 
32
- // Input/Output options
32
+ // Input/Output options (v1.x: explicit --input required)
33
33
  program
34
- .option('-i, --input <path>', 'Input file or directory to analyze', 'src')
34
+ .option('-i, --input <path>', 'Input file or directory to analyze (REQUIRED)')
35
35
  .option('-f, --format <format>', 'Output format (eslint,json,summary,table)', 'eslint')
36
36
  .option('-o, --output <file>', 'Output file path')
37
- .option('--config <file>', 'Configuration file path', '.sunlint.json');
37
+ .option('--config <file>', 'Configuration file path (default: auto-discover)');
38
38
 
39
39
  // File targeting options
40
40
  program
@@ -58,12 +58,16 @@ function createCliProgram() {
58
58
 
59
59
  // Advanced options
60
60
  program
61
+ .option('--engine <engine>', 'Force specific analysis engine (eslint,sunlint)', '')
61
62
  .option('--dry-run', 'Show what would be analyzed without running')
62
63
  .option('--verbose', 'Enable verbose logging')
63
64
  .option('--quiet', 'Suppress non-error output')
64
65
  .option('--debug', 'Enable debug mode')
65
66
  .option('--ai', 'Enable AI-powered analysis')
66
- .option('--no-ai', 'Force disable AI analysis (use heuristic only)');
67
+ .option('--no-ai', 'Force disable AI analysis (use heuristic only)')
68
+ .option('--legacy', 'Use legacy analysis architecture')
69
+ .option('--modern', 'Use modern plugin-based architecture (default)')
70
+ .option('--list-engines', 'List available analysis engines');
67
71
 
68
72
  // ESLint Integration options
69
73
  program
@@ -77,6 +81,7 @@ function createCliProgram() {
77
81
  program.addHelpText('after', `
78
82
  Examples:
79
83
  $ sunlint --rule=C019 --input=src
84
+ $ sunlint --rule C019 --input src
80
85
  $ sunlint --rules=C019,C006,S005 --input=src
81
86
  $ sunlint --all --input=src
82
87
  $ sunlint --quality --input=src
@@ -95,6 +100,15 @@ TypeScript Analysis (Phase 1):
95
100
  $ sunlint --rules=C019,S005 --typescript --input=src
96
101
  $ sunlint --typescript-engine=eslint --input=src
97
102
 
103
+ Version Strategy:
104
+ v1.x: ESLint-first with SunLint fallback (current)
105
+ v2.x: SunLint-first with ESLint integration (--eslint-integration)
106
+
107
+ Engine Configuration:
108
+ $ sunlint --all --input=src # Use config engine setting
109
+ $ sunlint --all --input=src --engine=eslint # Force ESLint engine
110
+ $ sunlint --all --input=src --engine=sunlint # Force SunLint engine
111
+
98
112
  CI/CD Integration:
99
113
  $ sunlint --all --changed-files --format=summary --no-ai
100
114
  $ sunlint --all --changed-files --diff-base=origin/main --fail-on-new-violations
@@ -166,7 +166,8 @@ class ConfigManager {
166
166
 
167
167
  // 4. Auto-discover project config if not explicitly provided
168
168
  let resolvedConfigPath = configPath;
169
- if (!configPath || configPath === '.sunlint.json') {
169
+ if (!configPath) {
170
+ // Only auto-discover if no config path was provided
170
171
  const discoveredConfig = this.findConfigFile(cliOptions.input || process.cwd());
171
172
  if (discoveredConfig) {
172
173
  resolvedConfigPath = discoveredConfig;
@@ -174,6 +175,11 @@ class ConfigManager {
174
175
  console.log(chalk.gray(`🔍 Auto-discovered config: ${discoveredConfig}`));
175
176
  }
176
177
  }
178
+ } else {
179
+ // Use the explicitly provided config path
180
+ if (cliOptions.verbose) {
181
+ console.log(chalk.gray(`📄 Using explicit config: ${configPath}`));
182
+ }
177
183
  }
178
184
 
179
185
  // 5. Load project config (explicit or discovered)
@@ -331,7 +337,7 @@ class ConfigManager {
331
337
  if (extendPath.startsWith('@sun/sunlint/')) {
332
338
  // Load preset from rules registry
333
339
  const presetName = extendPath.replace('@sun/sunlint/', '');
334
- const rulesRegistry = require('../config/rules-registry.json');
340
+ const rulesRegistry = require('../config/rules/rules-registry.json');
335
341
  return this.presetResolver.resolvePresetFromRegistry(presetName, rulesRegistry);
336
342
  } else {
337
343
  // Load from file path
@@ -357,7 +363,7 @@ class ConfigManager {
357
363
  * Rule C014: Delegate to validator
358
364
  */
359
365
  getEffectiveRuleConfiguration(ruleId, config) {
360
- const rulesRegistry = require('../config/rules-registry.json');
366
+ const rulesRegistry = require('../config/rules/rules-registry.json');
361
367
  return this.validator.getEffectiveRuleConfiguration(ruleId, config, rulesRegistry);
362
368
  }
363
369
 
@@ -14,7 +14,16 @@ class ConfigMerger {
14
14
 
15
15
  for (const [key, value] of Object.entries(override)) {
16
16
  if (key === 'rules' && typeof value === 'object') {
17
- merged.rules = { ...merged.rules, ...value };
17
+ if (Array.isArray(value)) {
18
+ // Convert array of rule names to object format
19
+ const rulesObj = {};
20
+ value.forEach(rule => {
21
+ rulesObj[rule] = 'warn';
22
+ });
23
+ merged.rules = { ...merged.rules, ...rulesObj };
24
+ } else {
25
+ merged.rules = { ...merged.rules, ...value };
26
+ }
18
27
  } else if (key === 'categories' && typeof value === 'object') {
19
28
  merged.categories = { ...merged.categories, ...value };
20
29
  } else if (typeof value === 'object' && !Array.isArray(value)) {
@@ -34,6 +43,36 @@ class ConfigMerger {
34
43
  applyCLIOverrides(config, options) {
35
44
  const overrides = { ...config };
36
45
 
46
+ // Rules override - only override if config file didn't specify rules
47
+ if (options.rules && (!config.rules || Object.keys(config.rules).length === 0)) {
48
+ if (typeof options.rules === 'string') {
49
+ const ruleList = options.rules.split(',').map(r => r.trim());
50
+ overrides.rules = {};
51
+ ruleList.forEach(rule => {
52
+ overrides.rules[rule] = 'warn';
53
+ });
54
+ } else if (Array.isArray(options.rules)) {
55
+ overrides.rules = {};
56
+ options.rules.forEach(rule => {
57
+ overrides.rules[rule] = 'warn';
58
+ });
59
+ }
60
+ }
61
+
62
+ // Include/Input override - only if config file didn't specify
63
+ if (options.include && (!config.include || config.include.length === 0)) {
64
+ overrides.include = Array.isArray(options.include) ? options.include : [options.include];
65
+ }
66
+ if (options.input && (!config.input || config.input.length === 0)) {
67
+ overrides.input = Array.isArray(options.input) ? options.input : [options.input];
68
+ }
69
+
70
+ // Exclude override - merge with config file
71
+ if (options.exclude) {
72
+ const excludeList = Array.isArray(options.exclude) ? options.exclude : [options.exclude];
73
+ overrides.exclude = [...new Set([...(config.exclude || []), ...excludeList])];
74
+ }
75
+
37
76
  // Languages override
38
77
  if (options.languages) {
39
78
  overrides.languages = options.languages.split(',').map(l => l.trim());
@@ -46,8 +46,8 @@ class ConfigValidator {
46
46
  * Rule C031: Specific validation logic
47
47
  */
48
48
  validateLanguagesSection(languages) {
49
- if (languages && !Array.isArray(languages)) {
50
- throw new Error('Config error: languages must be an array');
49
+ if (languages && !Array.isArray(languages) && typeof languages !== 'object') {
50
+ throw new Error('Config error: languages must be an array or object');
51
51
  }
52
52
  }
53
53
 
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Dependency Checker
3
+ * Checks for optional peer dependencies and provides helpful messages
4
+ */
5
+
6
+ class DependencyChecker {
7
+ constructor() {
8
+ this.checkedDependencies = new Set();
9
+ }
10
+
11
+ /**
12
+ * Check if a dependency is available
13
+ */
14
+ isDependencyAvailable(packageName) {
15
+ try {
16
+ require.resolve(packageName);
17
+ return true;
18
+ } catch (error) {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Check for ESLint engine dependencies
25
+ */
26
+ checkESLintDependencies() {
27
+ const dependencies = {
28
+ 'eslint': { required: false, description: 'ESLint engine support' },
29
+ '@typescript-eslint/eslint-plugin': { required: false, description: 'TypeScript ESLint rules' },
30
+ '@typescript-eslint/parser': { required: false, description: 'TypeScript ESLint parsing' },
31
+ 'typescript': { required: false, description: 'TypeScript compiler' }
32
+ };
33
+
34
+ const missing = [];
35
+ const available = [];
36
+
37
+ for (const [pkg, info] of Object.entries(dependencies)) {
38
+ if (this.isDependencyAvailable(pkg)) {
39
+ available.push(pkg);
40
+ } else {
41
+ missing.push({ pkg, ...info });
42
+ }
43
+ }
44
+
45
+ return { available, missing };
46
+ }
47
+
48
+ /**
49
+ * Check for AST parsing dependencies
50
+ */
51
+ checkASTDependencies() {
52
+ const dependencies = {
53
+ '@babel/parser': { required: false, description: 'JavaScript AST parsing' },
54
+ 'espree': { required: false, description: 'ECMAScript AST parsing' }
55
+ };
56
+
57
+ const missing = [];
58
+ const available = [];
59
+
60
+ for (const [pkg, info] of Object.entries(dependencies)) {
61
+ if (this.isDependencyAvailable(pkg)) {
62
+ available.push(pkg);
63
+ } else {
64
+ missing.push({ pkg, ...info });
65
+ }
66
+ }
67
+
68
+ return { available, missing };
69
+ }
70
+
71
+ /**
72
+ * Show installation instructions for missing dependencies
73
+ */
74
+ showInstallationInstructions(missing, context = 'general') {
75
+ if (missing.length === 0) return;
76
+
77
+ console.log('\n📦 Optional dependencies not found:');
78
+
79
+ for (const { pkg, description } of missing) {
80
+ console.log(` • ${pkg} - ${description}`);
81
+ }
82
+
83
+ const packages = missing.map(m => m.pkg).join(' ');
84
+
85
+ if (context === 'eslint') {
86
+ console.log('\n💡 To enable ESLint engine features, install:');
87
+ console.log(` npm install ${packages}`);
88
+ console.log('\n Or add to your project dependencies if you already have them.');
89
+ } else if (context === 'ast') {
90
+ console.log('\n💡 To enable AST analysis features, install:');
91
+ console.log(` npm install ${packages}`);
92
+ } else {
93
+ console.log('\n💡 To enable full functionality, install:');
94
+ console.log(` npm install ${packages}`);
95
+ }
96
+
97
+ console.log('\n SunLint will continue using heuristic analysis.\n');
98
+ }
99
+
100
+ /**
101
+ * Check and notify about missing dependencies (only once per session)
102
+ */
103
+ checkAndNotify(type = 'all') {
104
+ const key = `checked_${type}`;
105
+ if (this.checkedDependencies.has(key)) return;
106
+
107
+ this.checkedDependencies.add(key);
108
+
109
+ if (type === 'eslint' || type === 'all') {
110
+ const { missing } = this.checkESLintDependencies();
111
+ if (missing.length > 0) {
112
+ this.showInstallationInstructions(missing, 'eslint');
113
+ }
114
+ }
115
+
116
+ if (type === 'ast' || type === 'all') {
117
+ const { missing } = this.checkASTDependencies();
118
+ if (missing.length > 0) {
119
+ this.showInstallationInstructions(missing, 'ast');
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ module.exports = new DependencyChecker();
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Enhanced Rules Registry
3
+ * Following Rule C005: Single responsibility - registry management
4
+ * Following Rule C015: Use domain language - clear registry terms
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+
11
+ class EnhancedRulesRegistry {
12
+ constructor() {
13
+ this.rulesRegistry = {};
14
+ this.enginePreferences = new Map();
15
+ this.ruleAnalyzers = new Map();
16
+ this.aiContexts = {};
17
+ }
18
+
19
+ /**
20
+ * Load enhanced rules registry
21
+ * Following Rule C006: Verb-noun naming
22
+ * @param {Object} options - Options including verbose flag
23
+ */
24
+ async loadRegistry(options = {}) {
25
+ try {
26
+ // Load base rules registry
27
+ await this.loadBaseRulesRegistry(options);
28
+
29
+ // Load AI contexts
30
+ await this.loadAIContexts(options);
31
+
32
+ // Load engine preferences
33
+ await this.loadEnginePreferences(options);
34
+
35
+ // Scan for existing analyzers
36
+ await this.scanExistingAnalyzers(options);
37
+
38
+ if (options.verbose) {
39
+ if (options.verbose) {
40
+ console.log(chalk.green(`📋 Enhanced registry loaded: ${Object.keys(this.rulesRegistry).length} rules`));
41
+ }
42
+ }
43
+
44
+ } catch (error) {
45
+ console.error('Failed to load enhanced rules registry:', error.message);
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Load base rules registry
52
+ * Following Rule C006: Verb-noun naming
53
+ */
54
+ async loadBaseRulesRegistry(options = {}) {
55
+ const registryPath = path.resolve(__dirname, '../config/rules/rules-registry.json');
56
+
57
+ if (fs.existsSync(registryPath)) {
58
+ const registry = require(registryPath);
59
+ this.rulesRegistry = registry.rules || {};
60
+ if (options.verbose) {
61
+ console.log(chalk.blue(`📋 Loaded ${Object.keys(this.rulesRegistry).length} base rules`));
62
+ }
63
+ } else {
64
+ console.warn('⚠️ Base rules registry not found at', registryPath);
65
+ this.rulesRegistry = {};
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Load AI contexts
71
+ * Following Rule C006: Verb-noun naming
72
+ */
73
+ async loadAIContexts(options = {}) {
74
+ const contextsPath = path.resolve(__dirname, '../config/defaults/ai-rules-context.json');
75
+
76
+ if (fs.existsSync(contextsPath)) {
77
+ const contexts = require(contextsPath);
78
+ this.aiContexts = contexts.contexts || {};
79
+ if (options.verbose) {
80
+ console.log(chalk.blue(`🤖 Loaded ${Object.keys(this.aiContexts).length} AI contexts`));
81
+ }
82
+ } else {
83
+ console.warn('⚠️ AI contexts not found at', contextsPath);
84
+ this.aiContexts = {};
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Load engine preferences
90
+ * Following Rule C006: Verb-noun naming
91
+ */
92
+ loadEnginePreferences(options = {}) {
93
+ // Define engine preferences based on rule complexity and capabilities
94
+ const preferences = {
95
+ // ESLint-friendly rules (static analysis)
96
+ 'C001': ['eslint', 'heuristic', 'openai'],
97
+ 'C002': ['eslint', 'heuristic', 'openai'],
98
+ 'C003': ['heuristic', 'eslint', 'openai'],
99
+ 'C004': ['eslint', 'heuristic', 'openai'],
100
+ 'C006': ['eslint', 'heuristic', 'openai'],
101
+ 'C007': ['eslint', 'heuristic', 'openai'],
102
+ 'C014': ['eslint', 'heuristic', 'openai'],
103
+ 'C033': ['eslint', 'heuristic'],
104
+ 'C040': ['eslint', 'heuristic'],
105
+
106
+ // AI-enhanced rules (complex logic analysis)
107
+ 'C005': ['openai', 'heuristic'],
108
+ 'C012': ['openai', 'heuristic'],
109
+ 'C015': ['openai', 'heuristic'],
110
+ 'C032': ['openai', 'heuristic'],
111
+ 'C034': ['openai', 'heuristic'],
112
+ 'C035': ['openai', 'heuristic'],
113
+ 'C037': ['openai', 'heuristic', 'eslint'],
114
+ 'C038': ['openai', 'heuristic']
115
+ };
116
+
117
+ for (const [ruleId, engines] of Object.entries(preferences)) {
118
+ this.enginePreferences.set(ruleId, engines);
119
+ }
120
+
121
+ if (options.verbose) {
122
+ console.log(chalk.blue(`⚙️ Loaded ${this.enginePreferences.size} engine preferences`));
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Scan existing analyzers
128
+ * Following Rule C006: Verb-noun naming
129
+ */
130
+ async scanExistingAnalyzers(options = {}) {
131
+ const rulesDir = path.resolve(__dirname, '../rules');
132
+
133
+ if (!fs.existsSync(rulesDir)) {
134
+ console.warn('⚠️ Rules directory not found');
135
+ return;
136
+ }
137
+
138
+ try {
139
+ const ruleFolders = fs.readdirSync(rulesDir, { withFileTypes: true })
140
+ .filter(dirent => dirent.isDirectory())
141
+ .map(dirent => dirent.name);
142
+
143
+ for (const ruleFolder of ruleFolders) {
144
+ const ruleId = this.extractRuleId(ruleFolder);
145
+ const analyzerPath = path.join(rulesDir, ruleFolder, 'analyzer.js');
146
+
147
+ if (fs.existsSync(analyzerPath)) {
148
+ this.ruleAnalyzers.set(ruleId, {
149
+ path: analyzerPath,
150
+ folder: ruleFolder,
151
+ available: true
152
+ });
153
+ }
154
+ }
155
+
156
+ if (options.verbose) {
157
+ console.log(chalk.blue(`🔍 Found ${this.ruleAnalyzers.size} existing analyzers`));
158
+ }
159
+
160
+ } catch (error) {
161
+ console.warn('⚠️ Failed to scan existing analyzers:', error.message);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Extract rule ID from folder name
167
+ * Following Rule C006: Verb-noun naming
168
+ */
169
+ extractRuleId(folderName) {
170
+ // Extract from patterns like "C019_log_level_usage" or "S005_sql_injection"
171
+ const match = folderName.match(/^([CST]\d{3})/);
172
+ return match ? match[1] : folderName;
173
+ }
174
+
175
+ /**
176
+ * Get rule information with engine preferences
177
+ * Following Rule C006: Verb-noun naming
178
+ */
179
+ getRuleInfo(ruleId) {
180
+ const baseRule = this.rulesRegistry[ruleId];
181
+ if (!baseRule) {
182
+ return null;
183
+ }
184
+
185
+ return {
186
+ ...baseRule,
187
+ id: ruleId,
188
+ enginePreferences: this.enginePreferences.get(ruleId) || ['heuristic', 'openai'],
189
+ hasAnalyzer: this.ruleAnalyzers.has(ruleId),
190
+ hasAIContext: this.aiContexts[ruleId] !== undefined,
191
+ aiContext: this.aiContexts[ruleId] || null,
192
+ analyzer: this.ruleAnalyzers.get(ruleId) || null
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Get rules by category
198
+ * Following Rule C006: Verb-noun naming
199
+ */
200
+ getRulesByCategory(category) {
201
+ const rules = [];
202
+
203
+ for (const [ruleId, rule] of Object.entries(this.rulesRegistry)) {
204
+ if (rule.category === category) {
205
+ rules.push(this.getRuleInfo(ruleId));
206
+ }
207
+ }
208
+
209
+ return rules;
210
+ }
211
+
212
+ /**
213
+ * Get rules by engine preference
214
+ * Following Rule C006: Verb-noun naming
215
+ */
216
+ getRulesByEngine(engineId) {
217
+ const rules = [];
218
+
219
+ for (const [ruleId, preferences] of this.enginePreferences.entries()) {
220
+ if (preferences.includes(engineId)) {
221
+ const ruleInfo = this.getRuleInfo(ruleId);
222
+ if (ruleInfo) {
223
+ rules.push(ruleInfo);
224
+ }
225
+ }
226
+ }
227
+
228
+ return rules;
229
+ }
230
+
231
+ /**
232
+ * Get all available rules
233
+ * Following Rule C006: Verb-noun naming
234
+ */
235
+ getAllRules() {
236
+ const rules = [];
237
+
238
+ for (const ruleId of Object.keys(this.rulesRegistry)) {
239
+ const ruleInfo = this.getRuleInfo(ruleId);
240
+ if (ruleInfo) {
241
+ rules.push(ruleInfo);
242
+ }
243
+ }
244
+
245
+ return rules;
246
+ }
247
+
248
+ /**
249
+ * Get rules with analyzers
250
+ * Following Rule C006: Verb-noun naming
251
+ */
252
+ getRulesWithAnalyzers() {
253
+ return this.getAllRules().filter(rule => rule.hasAnalyzer);
254
+ }
255
+
256
+ /**
257
+ * Get rules with AI context
258
+ * Following Rule C006: Verb-noun naming
259
+ */
260
+ getRulesWithAIContext() {
261
+ return this.getAllRules().filter(rule => rule.hasAIContext);
262
+ }
263
+
264
+ /**
265
+ * Get registry statistics
266
+ * Following Rule C006: Verb-noun naming
267
+ */
268
+ getRegistryStats() {
269
+ const allRules = this.getAllRules();
270
+
271
+ return {
272
+ totalRules: allRules.length,
273
+ rulesWithAnalyzers: allRules.filter(r => r.hasAnalyzer).length,
274
+ rulesWithAIContext: allRules.filter(r => r.hasAIContext).length,
275
+ engineCoverage: {
276
+ heuristic: this.getRulesByEngine('heuristic').length,
277
+ openai: this.getRulesByEngine('openai').length,
278
+ eslint: this.getRulesByEngine('eslint').length
279
+ },
280
+ categories: this.getCategoryStats(allRules)
281
+ };
282
+ }
283
+
284
+ /**
285
+ * Get category statistics
286
+ * Following Rule C006: Verb-noun naming
287
+ */
288
+ getCategoryStats(rules) {
289
+ const categories = {};
290
+
291
+ for (const rule of rules) {
292
+ if (!categories[rule.category]) {
293
+ categories[rule.category] = 0;
294
+ }
295
+ categories[rule.category]++;
296
+ }
297
+
298
+ return categories;
299
+ }
300
+
301
+ /**
302
+ * Display registry overview
303
+ * Following Rule C006: Verb-noun naming
304
+ */
305
+ displayOverview(options = {}) {
306
+ if (!options.verbose) return;
307
+
308
+ const stats = this.getRegistryStats();
309
+
310
+ console.log(chalk.blue.bold('📊 Enhanced Rules Registry Overview'));
311
+ console.log(chalk.gray('='.repeat(50)));
312
+ console.log(chalk.green(`📋 Total Rules: ${stats.totalRules}`));
313
+ console.log(chalk.blue(`🔍 With Analyzers: ${stats.rulesWithAnalyzers}`));
314
+ console.log(chalk.cyan(`🤖 With AI Context: ${stats.rulesWithAIContext}`));
315
+ console.log();
316
+
317
+ console.log(chalk.yellow('🎯 Engine Coverage:'));
318
+ Object.entries(stats.engineCoverage).forEach(([engine, count]) => {
319
+ console.log(` • ${engine}: ${count} rules`);
320
+ });
321
+ console.log();
322
+
323
+ console.log(chalk.yellow('📂 Categories:'));
324
+ Object.entries(stats.categories).forEach(([category, count]) => {
325
+ console.log(` • ${category}: ${count} rules`);
326
+ });
327
+ console.log();
328
+ }
329
+ }
330
+
331
+ module.exports = EnhancedRulesRegistry;