@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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SunLint Rule Constants
|
|
3
|
+
* Constants related to rules, their metadata, and analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rule severity levels (ordered by importance)
|
|
8
|
+
*/
|
|
9
|
+
const RULE_SEVERITIES = {
|
|
10
|
+
ERROR: 'error',
|
|
11
|
+
WARNING: 'warning',
|
|
12
|
+
INFO: 'info',
|
|
13
|
+
HINT: 'hint'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Rule execution status
|
|
18
|
+
*/
|
|
19
|
+
const RULE_STATUS = {
|
|
20
|
+
PENDING: 'pending',
|
|
21
|
+
RUNNING: 'running',
|
|
22
|
+
COMPLETED: 'completed',
|
|
23
|
+
FAILED: 'failed',
|
|
24
|
+
SKIPPED: 'skipped',
|
|
25
|
+
TIMEOUT: 'timeout'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Rule types based on analysis approach
|
|
30
|
+
*/
|
|
31
|
+
const RULE_TYPES = {
|
|
32
|
+
HEURISTIC: 'heuristic', // Pattern-based analysis
|
|
33
|
+
AST: 'ast', // Abstract Syntax Tree analysis
|
|
34
|
+
SEMANTIC: 'semantic', // Semantic analysis
|
|
35
|
+
AI: 'ai', // AI-powered analysis
|
|
36
|
+
HYBRID: 'hybrid' // Combination of approaches
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Rule scopes - what level the rule operates on
|
|
41
|
+
*/
|
|
42
|
+
const RULE_SCOPES = {
|
|
43
|
+
FILE: 'file', // Single file analysis
|
|
44
|
+
PROJECT: 'project', // Project-wide analysis
|
|
45
|
+
MODULE: 'module', // Module/package analysis
|
|
46
|
+
FUNCTION: 'function', // Function-level analysis
|
|
47
|
+
CLASS: 'class', // Class-level analysis
|
|
48
|
+
EXPRESSION: 'expression' // Expression-level analysis
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Rule language patterns for quick identification
|
|
53
|
+
*/
|
|
54
|
+
const RULE_LANGUAGE_PATTERNS = {
|
|
55
|
+
COMMON: /^C\d{3}$/, // C001, C002, etc.
|
|
56
|
+
JAVASCRIPT: /^J\d{3}$/, // J001, J002, etc.
|
|
57
|
+
TYPESCRIPT: /^T\d{3}$/, // T001, T002, etc.
|
|
58
|
+
JAVA: /^JV\d{3}$/, // JV001, JV002, etc.
|
|
59
|
+
KOTLIN: /^K\d{3}$/, // K001, K002, etc.
|
|
60
|
+
DART: /^D\d{3}$/, // D001, D002, etc.
|
|
61
|
+
SWIFT: /^SW\d{3}$/, // SW001, SW002, etc.
|
|
62
|
+
PYTHON: /^PY\d{3}$/, // PY001, PY002, etc.
|
|
63
|
+
SECURITY: /^S\d{3}$/, // S001, S002, etc.
|
|
64
|
+
REACT: /^R\d{3}$/, // R001, R002, etc.
|
|
65
|
+
CUSTOM: /^CUSTOM_\w+$/ // Custom rules
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Rule execution timeouts by type (in milliseconds)
|
|
70
|
+
*/
|
|
71
|
+
const RULE_TIMEOUTS = {
|
|
72
|
+
[RULE_TYPES.HEURISTIC]: 5000, // 5 seconds
|
|
73
|
+
[RULE_TYPES.AST]: 10000, // 10 seconds
|
|
74
|
+
[RULE_TYPES.SEMANTIC]: 15000, // 15 seconds
|
|
75
|
+
[RULE_TYPES.AI]: 30000, // 30 seconds
|
|
76
|
+
[RULE_TYPES.HYBRID]: 20000 // 20 seconds
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Rule confidence levels for AI/heuristic analysis
|
|
81
|
+
*/
|
|
82
|
+
const CONFIDENCE_LEVELS = {
|
|
83
|
+
VERY_HIGH: 0.9,
|
|
84
|
+
HIGH: 0.8,
|
|
85
|
+
MEDIUM: 0.6,
|
|
86
|
+
LOW: 0.4,
|
|
87
|
+
VERY_LOW: 0.2
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Default rule metadata template
|
|
92
|
+
*/
|
|
93
|
+
const DEFAULT_RULE_METADATA = {
|
|
94
|
+
severity: RULE_SEVERITIES.WARNING,
|
|
95
|
+
type: RULE_TYPES.HEURISTIC,
|
|
96
|
+
scope: RULE_SCOPES.FILE,
|
|
97
|
+
category: 'quality',
|
|
98
|
+
languages: [],
|
|
99
|
+
description: '',
|
|
100
|
+
examples: {
|
|
101
|
+
good: [],
|
|
102
|
+
bad: []
|
|
103
|
+
},
|
|
104
|
+
tags: [],
|
|
105
|
+
fixable: false,
|
|
106
|
+
confidence: CONFIDENCE_LEVELS.HIGH
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Rule performance metrics template
|
|
111
|
+
*/
|
|
112
|
+
const RULE_PERFORMANCE_TEMPLATE = {
|
|
113
|
+
executionTime: 0,
|
|
114
|
+
filesAnalyzed: 0,
|
|
115
|
+
violationsFound: 0,
|
|
116
|
+
falsePositives: 0,
|
|
117
|
+
memoryUsage: 0,
|
|
118
|
+
cacheHits: 0
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get language from rule ID
|
|
123
|
+
* @param {string} ruleId - Rule identifier (e.g., "C001", "J005")
|
|
124
|
+
* @returns {string|null} Language name or null if not found
|
|
125
|
+
*/
|
|
126
|
+
function getLanguageFromRuleId(ruleId) {
|
|
127
|
+
const patterns = {
|
|
128
|
+
javascript: RULE_LANGUAGE_PATTERNS.JAVASCRIPT,
|
|
129
|
+
typescript: RULE_LANGUAGE_PATTERNS.TYPESCRIPT,
|
|
130
|
+
java: RULE_LANGUAGE_PATTERNS.JAVA,
|
|
131
|
+
kotlin: RULE_LANGUAGE_PATTERNS.KOTLIN,
|
|
132
|
+
dart: RULE_LANGUAGE_PATTERNS.DART,
|
|
133
|
+
swift: RULE_LANGUAGE_PATTERNS.SWIFT,
|
|
134
|
+
python: RULE_LANGUAGE_PATTERNS.PYTHON,
|
|
135
|
+
react: RULE_LANGUAGE_PATTERNS.REACT
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
for (const [language, pattern] of Object.entries(patterns)) {
|
|
139
|
+
if (pattern.test(ruleId)) {
|
|
140
|
+
return language;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check for common rules
|
|
145
|
+
if (RULE_LANGUAGE_PATTERNS.COMMON.test(ruleId)) {
|
|
146
|
+
return 'common';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check for security rules
|
|
150
|
+
if (RULE_LANGUAGE_PATTERNS.SECURITY.test(ruleId)) {
|
|
151
|
+
return 'security';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check if rule ID is valid format
|
|
159
|
+
* @param {string} ruleId - Rule identifier
|
|
160
|
+
* @returns {boolean} True if valid format
|
|
161
|
+
*/
|
|
162
|
+
function isValidRuleId(ruleId) {
|
|
163
|
+
const allPatterns = Object.values(RULE_LANGUAGE_PATTERNS);
|
|
164
|
+
return allPatterns.some(pattern => pattern.test(ruleId));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get rule timeout by type
|
|
169
|
+
* @param {string} ruleType - Rule type
|
|
170
|
+
* @returns {number} Timeout in milliseconds
|
|
171
|
+
*/
|
|
172
|
+
function getRuleTimeout(ruleType) {
|
|
173
|
+
return RULE_TIMEOUTS[ruleType] || RULE_TIMEOUTS[RULE_TYPES.HEURISTIC];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get default rule metadata with overrides
|
|
178
|
+
* @param {Object} overrides - Metadata overrides
|
|
179
|
+
* @returns {Object} Merged metadata
|
|
180
|
+
*/
|
|
181
|
+
function getDefaultRuleMetadata(overrides = {}) {
|
|
182
|
+
return {
|
|
183
|
+
...DEFAULT_RULE_METADATA,
|
|
184
|
+
...overrides
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if severity level is valid
|
|
190
|
+
* @param {string} severity - Severity level
|
|
191
|
+
* @returns {boolean} True if valid
|
|
192
|
+
*/
|
|
193
|
+
function isValidSeverity(severity) {
|
|
194
|
+
return Object.values(RULE_SEVERITIES).includes(severity);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
// Rule constants
|
|
199
|
+
RULE_SEVERITIES,
|
|
200
|
+
RULE_STATUS,
|
|
201
|
+
RULE_TYPES,
|
|
202
|
+
RULE_SCOPES,
|
|
203
|
+
RULE_LANGUAGE_PATTERNS,
|
|
204
|
+
RULE_TIMEOUTS,
|
|
205
|
+
CONFIDENCE_LEVELS,
|
|
206
|
+
DEFAULT_RULE_METADATA,
|
|
207
|
+
RULE_PERFORMANCE_TEMPLATE,
|
|
208
|
+
|
|
209
|
+
// Utility functions
|
|
210
|
+
getLanguageFromRuleId,
|
|
211
|
+
isValidRuleId,
|
|
212
|
+
getRuleTimeout,
|
|
213
|
+
getDefaultRuleMetadata,
|
|
214
|
+
isValidSeverity
|
|
215
|
+
};
|
|
@@ -100,7 +100,8 @@ class EnhancedRulesRegistry {
|
|
|
100
100
|
'C006': ['eslint', 'heuristic', 'openai'],
|
|
101
101
|
'C007': ['eslint', 'heuristic', 'openai'],
|
|
102
102
|
'C014': ['eslint', 'heuristic', 'openai'],
|
|
103
|
-
'C033': ['
|
|
103
|
+
'C033': ['heuristic', 'eslint'],
|
|
104
|
+
'C035': ['heuristic', 'eslint'],
|
|
104
105
|
'C040': ['eslint', 'heuristic'],
|
|
105
106
|
|
|
106
107
|
// AI-enhanced rules (complex logic analysis)
|
|
@@ -109,7 +110,6 @@ class EnhancedRulesRegistry {
|
|
|
109
110
|
'C015': ['openai', 'heuristic'],
|
|
110
111
|
'C032': ['openai', 'heuristic'],
|
|
111
112
|
'C034': ['openai', 'heuristic'],
|
|
112
|
-
'C035': ['openai', 'heuristic'],
|
|
113
113
|
'C037': ['openai', 'heuristic', 'eslint'],
|
|
114
114
|
'C038': ['openai', 'heuristic']
|
|
115
115
|
};
|
|
@@ -328,4 +328,4 @@ class EnhancedRulesRegistry {
|
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
module.exports = EnhancedRulesRegistry;
|
|
331
|
+
module.exports = EnhancedRulesRegistry;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const chalk = require('chalk');
|
|
3
4
|
const { minimatch } = require('minimatch');
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -15,24 +16,42 @@ class FileTargetingService {
|
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Get target files based on enhanced configuration
|
|
18
|
-
*
|
|
19
|
+
* ENHANCED: Uses metadata for intelligent file targeting
|
|
19
20
|
*/
|
|
20
21
|
async getTargetFiles(inputPaths, config, cliOptions = {}) {
|
|
21
22
|
try {
|
|
22
|
-
const
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
const metadata = config._metadata;
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (cliOptions.verbose) {
|
|
27
|
+
console.log(chalk.cyan(`📁 File Targeting: ${this.getTargetingMode(metadata)}`));
|
|
28
|
+
if (metadata?.shouldBypassProjectDiscovery) {
|
|
29
|
+
console.log(chalk.blue(`🎯 Optimized targeting for ${metadata.analysisScope}`));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let allFiles = [];
|
|
34
|
+
|
|
35
|
+
// Use enhanced targeting based on metadata
|
|
36
|
+
if (metadata?.shouldBypassProjectDiscovery) {
|
|
37
|
+
allFiles = await this.collectTargetedFiles(inputPaths, config, cliOptions);
|
|
38
|
+
} else {
|
|
39
|
+
allFiles = await this.collectProjectFiles(inputPaths, config, cliOptions);
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
// Apply filtering logic
|
|
31
43
|
const targetFiles = this.applyFiltering(allFiles, config, cliOptions);
|
|
32
44
|
|
|
45
|
+
const duration = Date.now() - startTime;
|
|
46
|
+
|
|
47
|
+
if (cliOptions.verbose) {
|
|
48
|
+
console.log(chalk.green(`✅ File targeting completed in ${duration}ms (${targetFiles.length} files)`));
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
return {
|
|
34
52
|
files: targetFiles,
|
|
35
|
-
stats: this.generateStats(targetFiles, config)
|
|
53
|
+
stats: this.generateStats(targetFiles, config),
|
|
54
|
+
timing: { duration, filesPerMs: targetFiles.length / Math.max(duration, 1) }
|
|
36
55
|
};
|
|
37
56
|
} catch (error) {
|
|
38
57
|
console.error('❌ FileTargetingService error:', error);
|
|
@@ -40,6 +59,108 @@ class FileTargetingService {
|
|
|
40
59
|
}
|
|
41
60
|
}
|
|
42
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Get targeting mode description
|
|
64
|
+
*/
|
|
65
|
+
getTargetingMode(metadata) {
|
|
66
|
+
if (!metadata) return 'legacy';
|
|
67
|
+
|
|
68
|
+
if (metadata.shouldBypassProjectDiscovery) {
|
|
69
|
+
return metadata.analysisScope === 'file' ? 'single_file' : 'folder_targeted';
|
|
70
|
+
} else {
|
|
71
|
+
return 'project_wide';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Collect files with targeted approach (bypassing project discovery)
|
|
77
|
+
*/
|
|
78
|
+
async collectTargetedFiles(inputPaths, config, cliOptions) {
|
|
79
|
+
const files = [];
|
|
80
|
+
|
|
81
|
+
for (const inputPath of inputPaths) {
|
|
82
|
+
const resolvedPath = path.resolve(inputPath);
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
85
|
+
if (cliOptions.verbose) {
|
|
86
|
+
console.log(chalk.yellow(`⚠️ Path not found: ${inputPath}`));
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const stat = fs.statSync(resolvedPath);
|
|
92
|
+
|
|
93
|
+
if (stat.isFile()) {
|
|
94
|
+
// Single file targeting
|
|
95
|
+
files.push(resolvedPath);
|
|
96
|
+
} else if (stat.isDirectory()) {
|
|
97
|
+
// Folder-only targeting (no recursive project scan)
|
|
98
|
+
const folderFiles = await this.collectFolderFiles(resolvedPath);
|
|
99
|
+
files.push(...folderFiles);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return files;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Collect files from specific folder (no project-wide scanning)
|
|
108
|
+
*/
|
|
109
|
+
async collectFolderFiles(folderPath) {
|
|
110
|
+
const files = [];
|
|
111
|
+
const targetExtensions = ['.ts', '.tsx', '.js', '.jsx', '.dart', '.kt', '.kts'];
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const entries = fs.readdirSync(folderPath);
|
|
115
|
+
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const fullPath = path.join(folderPath, entry);
|
|
118
|
+
const stat = fs.statSync(fullPath);
|
|
119
|
+
|
|
120
|
+
if (stat.isFile()) {
|
|
121
|
+
const ext = path.extname(fullPath);
|
|
122
|
+
if (targetExtensions.includes(ext)) {
|
|
123
|
+
files.push(path.resolve(fullPath));
|
|
124
|
+
}
|
|
125
|
+
} else if (stat.isDirectory() && !this.shouldSkipDirectory(entry)) {
|
|
126
|
+
// Recursive collection within target folder only
|
|
127
|
+
const subFiles = await this.collectFolderFiles(fullPath);
|
|
128
|
+
files.push(...subFiles);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.warn(`⚠️ Error reading folder ${folderPath}: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return files;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Collect files with project-wide approach (original logic)
|
|
140
|
+
*/
|
|
141
|
+
async collectProjectFiles(inputPaths, config, cliOptions) {
|
|
142
|
+
const allFiles = [];
|
|
143
|
+
|
|
144
|
+
// Use original collection logic for project-wide analysis
|
|
145
|
+
for (const inputPath of inputPaths) {
|
|
146
|
+
const files = await this.collectFiles(inputPath);
|
|
147
|
+
allFiles.push(...files);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return allFiles;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if directory should be skipped
|
|
155
|
+
*/
|
|
156
|
+
shouldSkipDirectory(dirName) {
|
|
157
|
+
const skipDirs = [
|
|
158
|
+
'node_modules', '.git', 'dist', 'build', 'coverage',
|
|
159
|
+
'.next', '.nuxt', 'vendor', 'target', 'generated'
|
|
160
|
+
];
|
|
161
|
+
return skipDirs.includes(dirName);
|
|
162
|
+
}
|
|
163
|
+
|
|
43
164
|
/**
|
|
44
165
|
* Apply comprehensive filtering logic
|
|
45
166
|
* Priority: CLI > Config > Default
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Plugin Interface
|
|
3
|
+
* Defines the contract for all rule plugins in SunLint
|
|
4
|
+
* Following Rule C014: Dependency injection - Plugin interface
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class RulePluginInterface {
|
|
8
|
+
constructor(ruleId, metadata = {}) {
|
|
9
|
+
if (this.constructor === RulePluginInterface) {
|
|
10
|
+
throw new Error('RulePluginInterface is abstract and cannot be instantiated');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.ruleId = ruleId;
|
|
14
|
+
this.metadata = {
|
|
15
|
+
name: metadata.name || ruleId,
|
|
16
|
+
description: metadata.description || '',
|
|
17
|
+
category: metadata.category || 'custom',
|
|
18
|
+
severity: metadata.severity || 'warning',
|
|
19
|
+
languages: metadata.languages || ['javascript', 'typescript'],
|
|
20
|
+
version: metadata.version || '1.0.0',
|
|
21
|
+
author: metadata.author || '',
|
|
22
|
+
tags: metadata.tags || [],
|
|
23
|
+
...metadata
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initialize the rule plugin
|
|
29
|
+
* @param {Object} config - Rule configuration
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
async initialize(config = {}) {
|
|
33
|
+
throw new Error('initialize() must be implemented by rule plugin');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Analyze files for violations
|
|
38
|
+
* @param {string[]} files - Files to analyze
|
|
39
|
+
* @param {string} language - Programming language
|
|
40
|
+
* @param {Object} options - Analysis options
|
|
41
|
+
* @returns {Promise<Object[]>} Array of violations
|
|
42
|
+
*/
|
|
43
|
+
async analyze(files, language, options = {}) {
|
|
44
|
+
throw new Error('analyze() must be implemented by rule plugin');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get rule metadata
|
|
49
|
+
* @returns {Object} Rule metadata
|
|
50
|
+
*/
|
|
51
|
+
getMetadata() {
|
|
52
|
+
return this.metadata;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if rule supports a language
|
|
57
|
+
* @param {string} language - Language to check
|
|
58
|
+
* @returns {boolean} True if supported
|
|
59
|
+
*/
|
|
60
|
+
supportsLanguage(language) {
|
|
61
|
+
return this.metadata.languages.includes(language) ||
|
|
62
|
+
this.metadata.languages.includes('all');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate rule configuration
|
|
67
|
+
* @param {Object} config - Configuration to validate
|
|
68
|
+
* @returns {Object} Validation result
|
|
69
|
+
*/
|
|
70
|
+
validateConfig(config) {
|
|
71
|
+
return {
|
|
72
|
+
isValid: true,
|
|
73
|
+
errors: [],
|
|
74
|
+
warnings: []
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get rule documentation
|
|
80
|
+
* @returns {Object} Documentation object
|
|
81
|
+
*/
|
|
82
|
+
getDocumentation() {
|
|
83
|
+
return {
|
|
84
|
+
name: this.metadata.name,
|
|
85
|
+
description: this.metadata.description,
|
|
86
|
+
examples: [],
|
|
87
|
+
configuration: {},
|
|
88
|
+
links: []
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Cleanup rule resources
|
|
94
|
+
* @returns {Promise<void>}
|
|
95
|
+
*/
|
|
96
|
+
async cleanup() {
|
|
97
|
+
// Default implementation - can be overridden
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Semantic Rule Interface
|
|
103
|
+
* For rules that use symbol table and semantic analysis
|
|
104
|
+
*/
|
|
105
|
+
class SemanticRuleInterface extends RulePluginInterface {
|
|
106
|
+
constructor(ruleId, metadata = {}) {
|
|
107
|
+
super(ruleId, { ...metadata, type: 'semantic' });
|
|
108
|
+
this.semanticEngine = null;
|
|
109
|
+
this.violations = [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Initialize with semantic engine
|
|
114
|
+
* @param {Object} semanticEngine - Semantic analysis engine
|
|
115
|
+
*/
|
|
116
|
+
initializeSemanticEngine(semanticEngine) {
|
|
117
|
+
this.semanticEngine = semanticEngine;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Analyze a single file using semantic analysis
|
|
122
|
+
* @param {string} filePath - File to analyze
|
|
123
|
+
* @param {Object} options - Analysis options
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
async analyzeFile(filePath, options = {}) {
|
|
127
|
+
throw new Error('analyzeFile() must be implemented by semantic rule');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get violations found during analysis
|
|
132
|
+
* @returns {Object[]} Array of violations
|
|
133
|
+
*/
|
|
134
|
+
getViolations() {
|
|
135
|
+
return this.violations;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Clear violations for next analysis
|
|
140
|
+
*/
|
|
141
|
+
clearViolations() {
|
|
142
|
+
this.violations = [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Add a violation
|
|
147
|
+
* @param {Object} violation - Violation object
|
|
148
|
+
*/
|
|
149
|
+
addViolation(violation) {
|
|
150
|
+
this.violations.push({
|
|
151
|
+
ruleId: this.ruleId,
|
|
152
|
+
severity: this.metadata.severity,
|
|
153
|
+
...violation
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Custom Rule Interface
|
|
160
|
+
* For user-defined custom rules
|
|
161
|
+
*/
|
|
162
|
+
class CustomRuleInterface extends RulePluginInterface {
|
|
163
|
+
constructor(ruleId, metadata = {}) {
|
|
164
|
+
super(ruleId, { ...metadata, type: 'custom', source: 'user' });
|
|
165
|
+
this.configSchema = null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Set configuration schema for validation
|
|
170
|
+
* @param {Object} schema - JSON schema for configuration
|
|
171
|
+
*/
|
|
172
|
+
setConfigSchema(schema) {
|
|
173
|
+
this.configSchema = schema;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Validate configuration against schema
|
|
178
|
+
* @param {Object} config - Configuration to validate
|
|
179
|
+
* @returns {Object} Validation result
|
|
180
|
+
*/
|
|
181
|
+
validateConfig(config) {
|
|
182
|
+
if (!this.configSchema) {
|
|
183
|
+
return super.validateConfig(config);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// TODO: Implement JSON schema validation
|
|
187
|
+
return {
|
|
188
|
+
isValid: true,
|
|
189
|
+
errors: [],
|
|
190
|
+
warnings: []
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Register custom helper methods
|
|
196
|
+
* @param {Object} helpers - Helper methods
|
|
197
|
+
*/
|
|
198
|
+
registerHelpers(helpers) {
|
|
199
|
+
this.helpers = helpers;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
RulePluginInterface,
|
|
205
|
+
SemanticRuleInterface,
|
|
206
|
+
CustomRuleInterface
|
|
207
|
+
};
|