@sun-asterisk/sunlint 1.2.2 → 1.3.1

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 (124) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/CONTRIBUTING.md +1654 -66
  3. package/README.md +19 -6
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/engines/engines-enhanced.json +86 -0
  7. package/config/engines/semantic-config.json +114 -0
  8. package/config/eslint-rule-mapping.json +50 -38
  9. package/config/large-project.json +143 -0
  10. package/config/presets/all.json +0 -1
  11. package/config/release.json +70 -0
  12. package/config/rule-analysis-strategies.js +23 -4
  13. package/config/rules/S027-categories.json +122 -0
  14. package/config/rules/enhanced-rules-registry.json +2564 -0
  15. package/config/rules/rules-registry-generated.json +785 -837
  16. package/config/rules/rules-registry.json +13 -1
  17. package/core/adapters/sunlint-rule-adapter.js +25 -30
  18. package/core/analysis-orchestrator.js +42 -2
  19. package/core/categories.js +52 -0
  20. package/core/category-constants.js +39 -0
  21. package/core/cli-action-handler.js +53 -32
  22. package/core/cli-program.js +11 -3
  23. package/core/config-manager.js +111 -0
  24. package/core/config-merger.js +88 -0
  25. package/core/constants/categories.js +168 -0
  26. package/core/constants/defaults.js +165 -0
  27. package/core/constants/engines.js +185 -0
  28. package/core/constants/index.js +30 -0
  29. package/core/constants/rules.js +215 -0
  30. package/core/enhanced-rules-registry.js +3 -3
  31. package/core/file-targeting-service.js +128 -7
  32. package/core/interfaces/rule-plugin.interface.js +207 -0
  33. package/core/plugin-manager.js +448 -0
  34. package/core/rule-selection-service.js +42 -15
  35. package/core/semantic-engine.js +658 -0
  36. package/core/semantic-rule-base.js +433 -0
  37. package/core/unified-rule-registry.js +484 -0
  38. package/docs/COMMAND-EXAMPLES.md +134 -0
  39. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  40. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  41. package/engines/core/base-engine.js +249 -0
  42. package/engines/engine-factory.js +275 -0
  43. package/engines/eslint-engine.js +171 -19
  44. package/engines/heuristic-engine.js +569 -78
  45. package/integrations/eslint/plugin/index.js +26 -28
  46. package/origin-rules/common-en.md +8 -8
  47. package/package.json +10 -6
  48. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  49. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  50. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  51. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  52. package/rules/common/C033_separate_service_repository/README.md +78 -0
  53. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  54. package/rules/common/C033_separate_service_repository/config.json +50 -0
  55. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  56. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  57. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  58. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  59. package/rules/common/C035_error_logging_context/config.json +54 -0
  60. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  61. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  62. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  63. package/rules/common/C040_centralized_validation/config.json +46 -0
  64. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  65. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  66. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  67. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  68. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  69. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  70. package/rules/common/C076_explicit_function_types/README.md +30 -0
  71. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  72. package/rules/common/C076_explicit_function_types/config.json +15 -0
  73. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  74. package/rules/index.js +8 -0
  75. package/rules/parser/rule-parser.js +13 -2
  76. package/rules/security/S005_no_origin_auth/README.md +226 -0
  77. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  78. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  79. package/rules/security/S005_no_origin_auth/config.json +85 -0
  80. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  81. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  82. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  83. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  84. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  85. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  86. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  87. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  88. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  89. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  90. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  91. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  92. package/scripts/category-manager.js +150 -0
  93. package/scripts/generate-rules-registry.js +88 -0
  94. package/scripts/migrate-rule-registry.js +157 -0
  95. package/scripts/prepare-release.sh +1 -1
  96. package/scripts/validate-system.js +48 -0
  97. package/.sunlint.json +0 -35
  98. package/config/README.md +0 -88
  99. package/config/engines/eslint-rule-mapping.json +0 -74
  100. package/config/schemas/sunlint-schema.json +0 -0
  101. package/config/testing/test-s005-working.ts +0 -22
  102. package/core/multi-rule-runner.js +0 -0
  103. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  104. package/docs/FUTURE_PACKAGES.md +0 -83
  105. package/docs/HEURISTIC_VS_AI.md +0 -113
  106. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  107. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  108. package/docs/RELEASE_GUIDE.md +0 -230
  109. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  110. package/engines/tree-sitter-parser.js +0 -0
  111. package/engines/universal-ast-engine.js +0 -0
  112. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  113. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  114. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  115. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  116. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  117. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  118. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  119. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  120. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  121. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  122. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  123. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  124. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -59,6 +59,18 @@
59
59
  "status": "experimental",
60
60
  "tags": ["validation", "separation", "architecture"]
61
61
  },
62
+ "C035": {
63
+ "name": "Error Logging Context",
64
+ "description": "Khi xử lý lỗi, phải ghi log đầy đủ thông tin liên quan - structured logging with context",
65
+ "category": "logging",
66
+ "severity": "warning",
67
+ "languages": ["typescript", "javascript"],
68
+ "analyzer": "./rules/common/C035_error_logging_context/analyzer.js",
69
+ "config": "./rules/common/C035_error_logging_context/config.json",
70
+ "version": "1.0.0",
71
+ "status": "experimental",
72
+ "tags": ["logging", "error-handling", "context", "structured-logging"]
73
+ },
62
74
  "C043": {
63
75
  "name": "No Console Or Print",
64
76
  "description": "Do not use console.log or print in production code",
@@ -651,7 +663,7 @@
651
663
  "quality": {
652
664
  "name": "Code Quality",
653
665
  "description": "Rules for code quality improvement",
654
- "rules": ["C002", "C003", "C006", "C010", "C013", "C014", "C017", "C018", "C023", "C029", "C030", "C035", "C041", "C042", "C043", "C047", "C072", "C075", "C076", "T002", "T003", "T004", "T007", "T010", "T019", "T020", "T021", "R001", "R002", "R003", "R004", "R005", "R006"],
666
+ "rules": ["C002", "C003", "C006", "C010", "C013", "C014", "C017", "C018", "C023", "C029", "C030", "C035", "C041", "C042", "C043", "C047", "C072", "C075", "T002", "T003", "T004", "T007", "T010", "T019", "T020", "T021", "R001", "R002", "R003", "R004", "R005", "R006"],
655
667
  "severity": "warning"
656
668
  },
657
669
  "security": {
@@ -1,4 +1,5 @@
1
1
  const { SimpleRuleParser } = require('../../rules/parser/rule-parser-simple');
2
+ const { CATEGORY_PRINCIPLE_MAP, getCategoryPrinciples } = require('../constants/categories');
2
3
  const fs = require('fs');
3
4
  const path = require('path');
4
5
 
@@ -139,7 +140,23 @@ class SunlintRuleAdapter {
139
140
  return null;
140
141
  }
141
142
 
142
- // Try registry first
143
+ // Normalize rule ID to uppercase for consistency
144
+ const normalizedRuleId = ruleId.toUpperCase();
145
+
146
+ // Try registry first with normalized ID
147
+ if (this.registryCache && this.registryCache[normalizedRuleId]) {
148
+ return this.normalizeRule({
149
+ id: normalizedRuleId,
150
+ ...this.registryCache[normalizedRuleId]
151
+ });
152
+ }
153
+
154
+ // Try cache with normalized ID
155
+ if (this.rulesCache.has(normalizedRuleId)) {
156
+ return this.rulesCache.get(normalizedRuleId);
157
+ }
158
+
159
+ // Also try original case for backward compatibility
143
160
  if (this.registryCache && this.registryCache[ruleId]) {
144
161
  return this.normalizeRule({
145
162
  id: ruleId,
@@ -147,7 +164,6 @@ class SunlintRuleAdapter {
147
164
  });
148
165
  }
149
166
 
150
- // Try cache
151
167
  if (this.rulesCache.has(ruleId)) {
152
168
  return this.rulesCache.get(ruleId);
153
169
  }
@@ -225,20 +241,9 @@ class SunlintRuleAdapter {
225
241
  * Based on actual principles in the rule catalog
226
242
  */
227
243
  getRulesByStandardCategory(category) {
228
- const categoryPrincipleMap = {
229
- 'security': ['SECURITY'],
230
- 'quality': ['CODE_QUALITY'],
231
- 'performance': ['PERFORMANCE'],
232
- 'maintainability': ['MAINTAINABILITY'],
233
- 'testability': ['TESTABILITY'],
234
- 'reliability': ['RELIABILITY'],
235
- 'design': ['DESIGN_PATTERNS'],
236
- 'integration': ['INTEGRATION'],
237
- 'usability': ['USABILITY']
238
- };
239
-
240
- const principles = categoryPrincipleMap[category.toLowerCase()];
241
- if (!principles) {
244
+ // Use centralized mapping instead of hardcoded
245
+ const principles = getCategoryPrinciples(category);
246
+ if (!principles || principles.length === 0) {
242
247
  console.warn(`⚠️ Unknown category: ${category}`);
243
248
  return [];
244
249
  }
@@ -276,20 +281,10 @@ class SunlintRuleAdapter {
276
281
  */
277
282
  getStandardCategoryRules(category) {
278
283
  const coreRules = this.getCoreRules();
279
- const categoryPrincipleMap = {
280
- 'security': ['SECURITY'],
281
- 'quality': ['CODE_QUALITY'],
282
- 'performance': ['PERFORMANCE'],
283
- 'maintainability': ['MAINTAINABILITY'],
284
- 'testability': ['TESTABILITY'],
285
- 'reliability': ['RELIABILITY'],
286
- 'design': ['DESIGN_PATTERNS'],
287
- 'integration': ['INTEGRATION'],
288
- 'usability': ['USABILITY']
289
- };
290
-
291
- const principles = categoryPrincipleMap[category.toLowerCase()];
292
- if (!principles) {
284
+
285
+ // Use centralized mapping
286
+ const principles = getCategoryPrinciples(category);
287
+ if (!principles || principles.length === 0) {
293
288
  console.warn(`⚠️ Unknown standard category: ${category}`);
294
289
  return [];
295
290
  }
@@ -233,6 +233,8 @@ class AnalysisOrchestrator {
233
233
  */
234
234
  groupRulesByEngine(rulesToRun, config) {
235
235
  const groups = new Map();
236
+ const skippedRules = [];
237
+
236
238
  if (config.verbose) {
237
239
  console.log(`📊 [Orchestrator] Grouping ${rulesToRun.length} rules by engine...`);
238
240
  }
@@ -249,6 +251,16 @@ class AnalysisOrchestrator {
249
251
  }
250
252
 
251
253
  const selectedEngine = this.selectBestEngine(enginePreference, rule, config);
254
+
255
+ // If rule is skipped (no engine supports it when specific engine requested)
256
+ if (selectedEngine === null) {
257
+ skippedRules.push(rule);
258
+ if (config.verbose) {
259
+ console.log(`⚠️ [Orchestrator] Skipped rule ${rule.id} - not supported by requested engine`);
260
+ }
261
+ continue;
262
+ }
263
+
252
264
  if (config.verbose) {
253
265
  console.log(`✅ [Orchestrator] Selected engine for ${rule.id}: ${selectedEngine}`);
254
266
  }
@@ -259,6 +271,12 @@ class AnalysisOrchestrator {
259
271
  groups.get(selectedEngine).push(rule);
260
272
  }
261
273
 
274
+ // Report skipped rules if any
275
+ if (skippedRules.length > 0) {
276
+ const skippedRuleIds = skippedRules.map(r => r.id).join(', ');
277
+ console.warn(`⚠️ [Orchestrator] Skipped ${skippedRules.length} rules not supported by requested engine: ${skippedRuleIds}`);
278
+ }
279
+
262
280
  if (config.verbose) {
263
281
  console.log(`📋 [Orchestrator] Final groups:`, Array.from(groups.entries()).map(([k, v]) => [k, v.length]));
264
282
  }
@@ -273,6 +291,16 @@ class AnalysisOrchestrator {
273
291
  * @returns {string[]} Array of engine names in preference order
274
292
  */
275
293
  getEnginePreference(rule, config) {
294
+ // If user specified a specific engine via --engine option, use only that engine
295
+ if (config.requestedEngine) {
296
+ return [config.requestedEngine];
297
+ }
298
+
299
+ // Special preference for C047: Always use semantic analysis (heuristic engine)
300
+ if (rule.id === 'C047') {
301
+ return ['heuristic', 'openai'];
302
+ }
303
+
276
304
  // Check config-level rule preferences
277
305
  const ruleConfig = config.rules?.[rule.id];
278
306
  if (ruleConfig?.engines) {
@@ -306,13 +334,17 @@ class AnalysisOrchestrator {
306
334
  * @param {string[]} preferences - Engine preferences in order
307
335
  * @param {Object} rule - Rule object
308
336
  * @param {Object} config - Configuration with verbose flag
309
- * @returns {string} Selected engine name
337
+ * @returns {string|null} Selected engine name or null if no engine supports the rule
310
338
  */
311
339
  selectBestEngine(preferences, rule, config) {
312
340
  if (config.verbose) {
313
341
  console.log(`🎯 [Orchestrator] Selecting engine for rule ${rule.id}, preferences:`, preferences);
314
342
  }
315
343
 
344
+ // If user specified a specific engine (--engine=eslint), only use that engine
345
+ // Don't fallback to other engines to maintain consistency
346
+ const isSpecificEngineRequested = config.requestedEngine && preferences.length === 1;
347
+
316
348
  for (const engineName of preferences) {
317
349
  const engine = this.engines.get(engineName);
318
350
  if (config.verbose) {
@@ -327,11 +359,19 @@ class AnalysisOrchestrator {
327
359
  }
328
360
  }
329
361
 
362
+ // If specific engine is requested and it doesn't support the rule, skip fallback
363
+ if (isSpecificEngineRequested) {
364
+ if (config.verbose) {
365
+ console.log(`⚠️ [Orchestrator] Rule ${rule.id} not supported by requested engine ${preferences[0]}, skipping`);
366
+ }
367
+ return null; // Skip this rule
368
+ }
369
+
330
370
  if (config.verbose) {
331
371
  console.log(`🔄 [Orchestrator] No preferred engine supports ${rule.id}, checking all engines...`);
332
372
  }
333
373
 
334
- // Fallback to first available engine that supports the rule
374
+ // Fallback to first available engine that supports the rule (only when no specific engine requested)
335
375
  for (const [engineName, engine] of this.engines) {
336
376
  if (config.verbose) {
337
377
  console.log(`🔍 [Orchestrator] Fallback checking engine ${engineName}`);
@@ -0,0 +1,52 @@
1
+ /**
2
+ * SunLint Categories - Legacy Module
3
+ * @deprecated This module is deprecated. Use core/constants/categories.js instead.
4
+ * Maintained for backward compatibility only.
5
+ */
6
+
7
+ // Import from the new centralized constants
8
+ const {
9
+ SUNLINT_PRINCIPLES,
10
+ CATEGORY_PRINCIPLE_MAP,
11
+ CATEGORY_DESCRIPTIONS,
12
+ getValidCategories,
13
+ getCategoryPrinciples,
14
+ isValidCategory,
15
+ getCategoryDescription,
16
+ getDefaultCategory,
17
+ normalizeCategory,
18
+ getCategoryForPrinciple,
19
+ addCategoryMapping,
20
+ getCategoryStats
21
+ } = require('./constants/categories');
22
+
23
+ // Legacy constants for backward compatibility
24
+ const SUNLINT_CATEGORIES = {
25
+ CODE_QUALITY: 'quality',
26
+ DESIGN_PATTERNS: 'design',
27
+ INTEGRATION: 'integration',
28
+ MAINTAINABILITY: 'maintainability',
29
+ PERFORMANCE: 'performance',
30
+ RELIABILITY: 'reliability',
31
+ SECURITY: 'security',
32
+ TESTABILITY: 'testability',
33
+ USABILITY: 'usability'
34
+ };
35
+
36
+ module.exports = {
37
+ // Legacy exports (for backward compatibility)
38
+ SUNLINT_CATEGORIES,
39
+ CATEGORY_DESCRIPTIONS,
40
+ CATEGORY_PRINCIPLE_MAP,
41
+
42
+ // Function exports (now from centralized source)
43
+ isValidCategory,
44
+ getValidCategories,
45
+ getCategoryDescription,
46
+ getCategoryPrinciples,
47
+
48
+ // New exports from centralized system
49
+ SUNLINT_PRINCIPLES,
50
+ getDefaultCategory,
51
+ normalizeCategory
52
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SunLint Category Constants - Legacy Proxy
3
+ * @deprecated This file is deprecated. Use core/constants/categories.js instead.
4
+ * Maintained for backward compatibility only.
5
+ */
6
+
7
+ // Import from the new centralized location
8
+ const {
9
+ SUNLINT_PRINCIPLES,
10
+ CATEGORY_PRINCIPLE_MAP,
11
+ CATEGORY_DESCRIPTIONS,
12
+ getValidCategories,
13
+ getCategoryPrinciples,
14
+ isValidCategory,
15
+ getCategoryDescription,
16
+ getDefaultCategory,
17
+ normalizeCategory,
18
+ getCategoryForPrinciple,
19
+ addCategoryMapping,
20
+ getCategoryStats
21
+ } = require('./constants/categories');
22
+
23
+ module.exports = {
24
+ // Constants
25
+ SUNLINT_PRINCIPLES,
26
+ CATEGORY_PRINCIPLE_MAP,
27
+ CATEGORY_DESCRIPTIONS,
28
+
29
+ // Utility functions
30
+ getValidCategories,
31
+ getCategoryPrinciples,
32
+ isValidCategory,
33
+ getCategoryDescription,
34
+ getDefaultCategory,
35
+ normalizeCategory,
36
+ getCategoryForPrinciple,
37
+ addCategoryMapping,
38
+ getCategoryStats
39
+ };
@@ -49,7 +49,9 @@ class CliActionHandler {
49
49
 
50
50
  // Show dry run preview if requested
51
51
  if (this.options.dryRun) {
52
- await this.showDryRunPreview(config);
52
+ // We need to get rules first for accurate dry run info
53
+ const rulesToRun = await this.ruleSelectionService.selectRules(config, this.options);
54
+ await this.showDryRunPreview(config, rulesToRun);
53
55
  return;
54
56
  }
55
57
 
@@ -107,21 +109,29 @@ class CliActionHandler {
107
109
  }
108
110
 
109
111
  /**
110
- * Run analysis with modern orchestrator
111
- * Following Rule C006: Verb-noun naming
112
- * Following Rule C012: Command Query Separation - analysis is a command
112
+ * Run analysis using modern orchestrator
113
113
  */
114
114
  async runModernAnalysis(rulesToRun, files, config) {
115
115
  if (this.isModernMode) {
116
116
  console.log(chalk.blue('🚀 Using modern engine architecture'));
117
117
 
118
- // Initialize orchestrator with configuration
118
+ // Initialize orchestrator with configuration including targetFiles for optimization
119
119
  await this.orchestrator.initialize({
120
120
  enabledEngines: this.determineEnabledEngines(config),
121
121
  aiConfig: config.ai || {},
122
122
  eslintConfig: config.eslint || {},
123
- heuristicConfig: config.heuristic || {}
123
+ heuristicConfig: {
124
+ ...config.heuristic || {},
125
+ targetFiles: this.options.targetFiles, // Pass filtered files for semantic optimization
126
+ maxSemanticFiles: this.options.maxSemanticFiles !== undefined ? parseInt(this.options.maxSemanticFiles) : 1000, // Configurable semantic file limit
127
+ verbose: this.options.verbose // Pass verbose for debugging
128
+ }
124
129
  });
130
+
131
+ if (this.options.verbose) {
132
+ console.log(`🔧 Debug: maxSemanticFiles option = ${this.options.maxSemanticFiles}`);
133
+ console.log(`🔧 Debug: parsed maxSemanticFiles = ${this.options.maxSemanticFiles !== undefined ? parseInt(this.options.maxSemanticFiles) : 1000}`);
134
+ }
125
135
 
126
136
  // Run analysis with new orchestrator
127
137
  const results = await this.orchestrator.analyze(files, rulesToRun, {
@@ -129,22 +139,12 @@ class CliActionHandler {
129
139
  config: {
130
140
  ...config,
131
141
  verbose: this.options.verbose,
132
- quiet: this.options.quiet
142
+ quiet: this.options.quiet,
143
+ // Pass requested engine to enable strict engine mode (no fallback)
144
+ requestedEngine: this.options.engine
133
145
  }
134
146
  });
135
-
136
147
  return results;
137
- } else {
138
- console.log(chalk.yellow('🔄 Using legacy orchestrator'));
139
-
140
- // Ensure verbose/quiet flags are in config
141
- const analysisConfig = {
142
- ...config,
143
- verbose: this.options.verbose,
144
- quiet: this.options.quiet
145
- };
146
-
147
- return await this.orchestrator.runAnalysis(rulesToRun, this.options, analysisConfig);
148
148
  }
149
149
  }
150
150
 
@@ -262,6 +262,17 @@ class CliActionHandler {
262
262
  * Following Rule C031: Separate validation logic
263
263
  */
264
264
  validateInput(config) {
265
+ // Validate engine option if specified (check this first, always)
266
+ if (this.options.engine) {
267
+ const validEngines = ['eslint', 'heuristic', 'openai'];
268
+ if (!validEngines.includes(this.options.engine)) {
269
+ throw new Error(
270
+ chalk.red(`❌ Invalid engine: ${this.options.engine}\n`) +
271
+ chalk.gray(`Valid engines: ${validEngines.join(', ')}`)
272
+ );
273
+ }
274
+ }
275
+
265
276
  // Priority 1: CLI --input parameter (highest priority)
266
277
  if (this.options.input) {
267
278
  // Validate CLI input path exists
@@ -296,17 +307,6 @@ class CliActionHandler {
296
307
  }
297
308
  return;
298
309
  }
299
-
300
- // Validate engine option if specified
301
- if (this.options.engine) {
302
- const validEngines = ['eslint', 'sunlint', 'heuristic'];
303
- if (!validEngines.includes(this.options.engine)) {
304
- throw new Error(
305
- chalk.red(`❌ Invalid engine: ${this.options.engine}\n`) +
306
- chalk.gray(`Valid engines: ${validEngines.join(', ')}`)
307
- );
308
- }
309
- }
310
310
  }
311
311
 
312
312
  /**
@@ -329,15 +329,36 @@ class CliActionHandler {
329
329
  }
330
330
  }
331
331
 
332
+ /**
333
+ * Get preset name based on CLI options
334
+ */
335
+ getPresetName() {
336
+ if (this.options.all) return 'all preset';
337
+ if (this.options.quality) return 'quality preset';
338
+ if (this.options.security) return 'security preset';
339
+ if (this.options.category) return `${this.options.category} preset`;
340
+ if (this.options.rule) return 'single rule';
341
+ if (this.options.rules) return 'multiple rules';
342
+ return 'config-based';
343
+ }
344
+
332
345
  /**
333
346
  * Show dry run preview
334
347
  * Following Rule C006: Verb-noun naming
335
348
  */
336
- async showDryRunPreview(config) {
349
+ async showDryRunPreview(config, rulesToRun = null) {
337
350
  console.log(chalk.blue('🔍 Dry Run Preview'));
338
351
  console.log(chalk.gray('This would analyze the following configuration:'));
352
+
353
+ let rulesInfo;
354
+ if (rulesToRun) {
355
+ rulesInfo = `${rulesToRun.length} rules (${this.getPresetName()})`;
356
+ } else {
357
+ rulesInfo = this.options.rules || 'config-based';
358
+ }
359
+
339
360
  console.log(JSON.stringify({
340
- rules: this.options.rules || 'all',
361
+ rules: rulesInfo,
341
362
  files: this.options.targetFiles?.length || 'auto-detected',
342
363
  engines: this.determineEnabledEngines(config),
343
364
  mode: this.isModernMode ? 'modern' : 'legacy'
@@ -26,7 +26,7 @@ function createCliProgram() {
26
26
  // TypeScript specific options (Phase 1 focus)
27
27
  program
28
28
  .option('--typescript', 'Enable TypeScript-specific analysis')
29
- .option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,sunlint,hybrid)', 'sunlint')
29
+ .option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,heuristic,hybrid)', 'heuristic')
30
30
  .option('--ensure-deps', 'Ensure ESLint dependencies are installed');
31
31
 
32
32
  // Input/Output options (v1.x: explicit --input required)
@@ -58,7 +58,7 @@ function createCliProgram() {
58
58
 
59
59
  // Advanced options
60
60
  program
61
- .option('--engine <engine>', 'Force specific analysis engine (eslint,sunlint)', '')
61
+ .option('--engine <engine>', 'Force specific analysis engine (eslint,heuristic)', '')
62
62
  .option('--dry-run', 'Show what would be analyzed without running')
63
63
  .option('--verbose', 'Enable verbose logging')
64
64
  .option('--quiet', 'Suppress non-error output')
@@ -67,6 +67,7 @@ function createCliProgram() {
67
67
  .option('--no-ai', 'Force disable AI analysis (use heuristic only)')
68
68
  .option('--legacy', 'Use legacy analysis architecture')
69
69
  .option('--modern', 'Use modern plugin-based architecture (default)')
70
+ .option('--max-semantic-files <number>', 'Control semantic analysis scope: 0=disable, -1=unlimited, >0=limit (default: 1000)', '1000')
70
71
  .option('--list-engines', 'List available analysis engines');
71
72
 
72
73
  // ESLint Integration options
@@ -107,7 +108,7 @@ Version Strategy:
107
108
  Engine Configuration:
108
109
  $ sunlint --all --input=src # Use config engine setting
109
110
  $ sunlint --all --input=src --engine=eslint # Force ESLint engine
110
- $ sunlint --all --input=src --engine=sunlint # Force SunLint engine
111
+ $ sunlint --all --input=src --engine=heuristic # Force Heuristic engine
111
112
 
112
113
  CI/CD Integration:
113
114
  $ sunlint --all --changed-files --format=summary --no-ai
@@ -127,6 +128,13 @@ Advanced File Targeting:
127
128
  $ sunlint --languages=typescript,dart --include="src/**,packages/**" --input=.
128
129
  $ sunlint --all --only-source --exclude-tests --languages=typescript --input=.
129
130
 
131
+ Large Project Optimization:
132
+ $ sunlint --all --input=. --max-semantic-files=500 # Conservative analysis
133
+ $ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
134
+ $ sunlint --all --input=. --max-semantic-files=-1 # Unlimited (all files)
135
+ $ sunlint --all --input=. --max-semantic-files=0 # Disable semantic analysis
136
+ $ sunlint --all --changed-files --max-semantic-files=300 # Fast CI analysis
137
+
130
138
  Sun* Engineering - Coding Standards Made Simple ☀️
131
139
  `);
132
140
 
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const os = require('os');
5
+ const { minimatch } = require('minimatch');
5
6
 
6
7
  // Rule C014: Dependency injection instead of direct instantiation
7
8
  const ConfigSourceLoader = require('./config-source-loader');
@@ -240,6 +241,19 @@ class ConfigManager {
240
241
  // 10. Validate config
241
242
  this.validator.validateConfiguration(config);
242
243
 
244
+ // 11. Add metadata for enhanced file targeting
245
+ const analysisScope = this.determineAnalysisScope(cliOptions.input);
246
+ config._metadata = {
247
+ analysisScope: analysisScope,
248
+ shouldBypassProjectDiscovery: this.shouldBypassProjectDiscovery(analysisScope, cliOptions),
249
+ targetInput: cliOptions.input,
250
+ hasCliRules: this.hasRuleConfigInCLI(cliOptions)
251
+ };
252
+
253
+ if (cliOptions.verbose) {
254
+ console.log(chalk.gray(`📋 Enhanced Config: Scope=${analysisScope}, Bypass=${config._metadata.shouldBypassProjectDiscovery}`));
255
+ }
256
+
243
257
  return config;
244
258
  }
245
259
 
@@ -500,6 +514,103 @@ class ConfigManager {
500
514
  validateConfig(config) {
501
515
  return this.validator.validateConfiguration(config);
502
516
  }
517
+
518
+ /**
519
+ * ENHANCED CONFIG STRATEGY METHODS
520
+ * ================================
521
+ */
522
+
523
+ /**
524
+ * Determine if CLI has rule configuration
525
+ */
526
+ hasRuleConfigInCLI(cliOptions) {
527
+ return !!(
528
+ cliOptions.rule ||
529
+ cliOptions.rules ||
530
+ cliOptions.all ||
531
+ cliOptions.quality ||
532
+ cliOptions.security ||
533
+ cliOptions.category
534
+ );
535
+ }
536
+
537
+ /**
538
+ * Determine analysis scope based on input
539
+ */
540
+ determineAnalysisScope(inputPath) {
541
+ if (!inputPath) return 'project';
542
+
543
+ const resolvedPath = path.resolve(inputPath);
544
+
545
+ if (!fs.existsSync(resolvedPath)) {
546
+ return 'project'; // Fallback for non-existent paths
547
+ }
548
+
549
+ const stat = fs.statSync(resolvedPath);
550
+
551
+ if (stat.isFile()) {
552
+ return 'file';
553
+ } else if (stat.isDirectory()) {
554
+ // More specific logic for directory scope
555
+ const currentDir = process.cwd();
556
+
557
+ // If input is current directory, it's project scope
558
+ if (path.resolve(inputPath) === currentDir) {
559
+ return 'project';
560
+ }
561
+
562
+ // If input is a project root (has project markers), it's project scope
563
+ if (this.isProjectRoot(resolvedPath)) {
564
+ return 'project';
565
+ }
566
+
567
+ // Otherwise it's folder scope
568
+ return 'folder';
569
+ }
570
+
571
+ return 'project';
572
+ }
573
+
574
+ /**
575
+ * Check if directory is a project root
576
+ */
577
+ isProjectRoot(dirPath) {
578
+ const projectMarkers = [
579
+ 'package.json',
580
+ 'pubspec.yaml',
581
+ 'build.gradle',
582
+ 'build.gradle.kts',
583
+ 'pom.xml',
584
+ 'Cargo.toml',
585
+ 'go.mod',
586
+ '.git',
587
+ 'tsconfig.json',
588
+ 'angular.json',
589
+ 'next.config.js',
590
+ 'nuxt.config.js'
591
+ ];
592
+
593
+ return projectMarkers.some(marker =>
594
+ fs.existsSync(path.join(dirPath, marker))
595
+ );
596
+ }
597
+
598
+ /**
599
+ * Determine if project discovery should be bypassed for performance
600
+ */
601
+ shouldBypassProjectDiscovery(analysisScope, cliOptions) {
602
+ // Single file input - always bypass
603
+ if (analysisScope === 'file') {
604
+ return true;
605
+ }
606
+
607
+ // Folder scope (not project root) and has CLI rules - target folder only
608
+ if (analysisScope === 'folder' && this.hasRuleConfigInCLI(cliOptions)) {
609
+ return true;
610
+ }
611
+
612
+ return false; // Use project-wide discovery
613
+ }
503
614
  }
504
615
 
505
616
  module.exports = ConfigManager;