@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.
- package/CHANGELOG.md +107 -1
- package/CONTRIBUTING.md +1654 -66
- package/README.md +19 -6
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- 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/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +23 -4
- package/config/rules/S027-categories.json +122 -0
- package/config/rules/enhanced-rules-registry.json +2564 -0
- package/config/rules/rules-registry-generated.json +785 -837
- package/config/rules/rules-registry.json +13 -1
- 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 +53 -32
- package/core/cli-program.js +11 -3
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +88 -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/enhanced-rules-registry.js +3 -3
- 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 +658 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -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 +569 -78
- package/integrations/eslint/plugin/index.js +26 -28
- package/origin-rules/common-en.md +8 -8
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +230 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- 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/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +8 -0
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -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/prepare-release.sh +1 -1
- 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/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- 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
|
@@ -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", "
|
|
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
|
-
//
|
|
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
|
|
|
@@ -107,21 +109,29 @@ class CliActionHandler {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
|
-
* Run analysis
|
|
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:
|
|
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:
|
|
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'
|
package/core/cli-program.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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=
|
|
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
|
|
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;
|