@sun-asterisk/sunlint 1.2.2 → 1.3.0
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.
- package/CHANGELOG.md +40 -1
- package/CONTRIBUTING.md +533 -70
- package/README.md +16 -2
- package/config/engines/engines-enhanced.json +86 -0
- package/config/engines/semantic-config.json +114 -0
- package/config/eslint-rule-mapping.json +50 -38
- package/config/rules/enhanced-rules-registry.json +2503 -0
- package/config/rules/rules-registry-generated.json +785 -837
- package/core/adapters/sunlint-rule-adapter.js +25 -30
- package/core/analysis-orchestrator.js +42 -2
- package/core/categories.js +52 -0
- package/core/category-constants.js +39 -0
- package/core/cli-action-handler.js +32 -5
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +61 -0
- package/core/constants/categories.js +168 -0
- package/core/constants/defaults.js +165 -0
- package/core/constants/engines.js +185 -0
- package/core/constants/index.js +30 -0
- package/core/constants/rules.js +215 -0
- package/core/file-targeting-service.js +128 -7
- package/core/interfaces/rule-plugin.interface.js +207 -0
- package/core/plugin-manager.js +448 -0
- package/core/rule-selection-service.js +42 -15
- package/core/semantic-engine.js +560 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
- package/engines/core/base-engine.js +249 -0
- package/engines/engine-factory.js +275 -0
- package/engines/eslint-engine.js +171 -19
- package/engines/heuristic-engine.js +511 -78
- package/integrations/eslint/plugin/index.js +27 -27
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
- package/rules/index.js +7 -0
- package/scripts/category-manager.js +150 -0
- package/scripts/generate-rules-registry.js +88 -0
- package/scripts/migrate-rule-registry.js +157 -0
- package/scripts/validate-system.js +48 -0
- package/.sunlint.json +0 -35
- package/config/README.md +0 -88
- package/config/engines/eslint-rule-mapping.json +0 -74
- package/config/schemas/sunlint-schema.json +0 -0
- package/config/testing/test-s005-working.ts +0 -22
- package/core/multi-rule-runner.js +0 -0
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
|
@@ -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
|
-
//
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -129,7 +131,9 @@ class CliActionHandler {
|
|
|
129
131
|
config: {
|
|
130
132
|
...config,
|
|
131
133
|
verbose: this.options.verbose,
|
|
132
|
-
quiet: this.options.quiet
|
|
134
|
+
quiet: this.options.quiet,
|
|
135
|
+
// Pass requested engine to enable strict engine mode (no fallback)
|
|
136
|
+
requestedEngine: this.options.engine
|
|
133
137
|
}
|
|
134
138
|
});
|
|
135
139
|
|
|
@@ -141,7 +145,9 @@ class CliActionHandler {
|
|
|
141
145
|
const analysisConfig = {
|
|
142
146
|
...config,
|
|
143
147
|
verbose: this.options.verbose,
|
|
144
|
-
quiet: this.options.quiet
|
|
148
|
+
quiet: this.options.quiet,
|
|
149
|
+
// Pass requested engine to enable strict engine mode (no fallback)
|
|
150
|
+
requestedEngine: this.options.engine
|
|
145
151
|
};
|
|
146
152
|
|
|
147
153
|
return await this.orchestrator.runAnalysis(rulesToRun, this.options, analysisConfig);
|
|
@@ -329,15 +335,36 @@ class CliActionHandler {
|
|
|
329
335
|
}
|
|
330
336
|
}
|
|
331
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Get preset name based on CLI options
|
|
340
|
+
*/
|
|
341
|
+
getPresetName() {
|
|
342
|
+
if (this.options.all) return 'all preset';
|
|
343
|
+
if (this.options.quality) return 'quality preset';
|
|
344
|
+
if (this.options.security) return 'security preset';
|
|
345
|
+
if (this.options.category) return `${this.options.category} preset`;
|
|
346
|
+
if (this.options.rule) return 'single rule';
|
|
347
|
+
if (this.options.rules) return 'multiple rules';
|
|
348
|
+
return 'config-based';
|
|
349
|
+
}
|
|
350
|
+
|
|
332
351
|
/**
|
|
333
352
|
* Show dry run preview
|
|
334
353
|
* Following Rule C006: Verb-noun naming
|
|
335
354
|
*/
|
|
336
|
-
async showDryRunPreview(config) {
|
|
355
|
+
async showDryRunPreview(config, rulesToRun = null) {
|
|
337
356
|
console.log(chalk.blue('🔍 Dry Run Preview'));
|
|
338
357
|
console.log(chalk.gray('This would analyze the following configuration:'));
|
|
358
|
+
|
|
359
|
+
let rulesInfo;
|
|
360
|
+
if (rulesToRun) {
|
|
361
|
+
rulesInfo = `${rulesToRun.length} rules (${this.getPresetName()})`;
|
|
362
|
+
} else {
|
|
363
|
+
rulesInfo = this.options.rules || 'config-based';
|
|
364
|
+
}
|
|
365
|
+
|
|
339
366
|
console.log(JSON.stringify({
|
|
340
|
-
rules:
|
|
367
|
+
rules: rulesInfo,
|
|
341
368
|
files: this.options.targetFiles?.length || 'auto-detected',
|
|
342
369
|
engines: this.determineEnabledEngines(config),
|
|
343
370
|
mode: this.isModernMode ? 'modern' : 'legacy'
|
package/core/config-manager.js
CHANGED
|
@@ -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;
|
package/core/config-merger.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const { minimatch } = require('minimatch');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Handles configuration merging and CLI overrides
|
|
3
6
|
* Rule C005: Single responsibility - chỉ merge và override config
|
|
@@ -89,6 +92,11 @@ class ConfigMerger {
|
|
|
89
92
|
// Performance overrides
|
|
90
93
|
overrides.performance = this.applyPerformanceOverrides(overrides.performance, options);
|
|
91
94
|
|
|
95
|
+
// Auto-expand include patterns if input doesn't match any existing patterns
|
|
96
|
+
const expandedOverrides = this.autoExpandIncludePatterns(overrides, options);
|
|
97
|
+
// Copy expanded properties back to overrides
|
|
98
|
+
Object.assign(overrides, expandedOverrides);
|
|
99
|
+
|
|
92
100
|
return overrides;
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -161,6 +169,59 @@ class ConfigMerger {
|
|
|
161
169
|
return updatedConfig;
|
|
162
170
|
}
|
|
163
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Rule C006: autoExpandIncludePatterns - verb-noun naming
|
|
174
|
+
* Rule C005: Single responsibility - auto-expand include patterns for input paths
|
|
175
|
+
*/
|
|
176
|
+
autoExpandIncludePatterns(config, options) {
|
|
177
|
+
if (!options.input) {
|
|
178
|
+
return config;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const result = { ...config };
|
|
182
|
+
const inputPaths = Array.isArray(options.input) ? options.input : [options.input];
|
|
183
|
+
const currentInclude = result.include || [];
|
|
184
|
+
|
|
185
|
+
console.log(chalk.gray(`🔍 AUTO-EXPANSION: Checking input paths: ${inputPaths.join(', ')}`));
|
|
186
|
+
console.log(chalk.gray(`🔍 AUTO-EXPANSION: Current include patterns: ${currentInclude.join(', ')}`));
|
|
187
|
+
|
|
188
|
+
let needsExpansion = false;
|
|
189
|
+
for (const inputPath of inputPaths) {
|
|
190
|
+
const matchesExisting = currentInclude.some(pattern => {
|
|
191
|
+
const match1 = minimatch(inputPath, pattern);
|
|
192
|
+
const match2 = minimatch(inputPath + '/**', pattern);
|
|
193
|
+
const match3 = minimatch('**/' + inputPath, pattern);
|
|
194
|
+
const match4 = minimatch('**/' + inputPath + '/**', pattern);
|
|
195
|
+
|
|
196
|
+
console.log(chalk.gray(` AUTO-EXPANSION: ${inputPath} vs ${pattern}: ${match1 || match2 || match3 || match4}`));
|
|
197
|
+
|
|
198
|
+
return match1 || match2 || match3 || match4;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (!matchesExisting) {
|
|
202
|
+
needsExpansion = true;
|
|
203
|
+
console.log(chalk.gray(` 🔄 AUTO-EXPANSION: ${inputPath} needs expansion`));
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (needsExpansion) {
|
|
209
|
+
// Add flexible patterns for input paths
|
|
210
|
+
const expandedInclude = [...currentInclude];
|
|
211
|
+
for (const inputPath of inputPaths) {
|
|
212
|
+
expandedInclude.push(inputPath + '/**');
|
|
213
|
+
expandedInclude.push('**/' + inputPath + '/**');
|
|
214
|
+
}
|
|
215
|
+
result.include = expandedInclude;
|
|
216
|
+
|
|
217
|
+
console.log(chalk.gray(`📁 AUTO-EXPANSION: Auto-expanded include patterns: ${expandedInclude.join(', ')}`));
|
|
218
|
+
} else {
|
|
219
|
+
console.log(chalk.gray(`✅ AUTO-EXPANSION: No expansion needed`));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
164
225
|
/**
|
|
165
226
|
* Rule C006: processIgnorePatterns - verb-noun naming
|
|
166
227
|
* Convert deprecated ignorePatterns to exclude for backward compatibility
|