@sun-asterisk/sunlint 1.3.0 → 1.3.2
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 +115 -1
- package/CONTRIBUTING.md +249 -605
- package/README.md +3 -4
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- 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 +38 -3
- package/config/rules/enhanced-rules-registry.json +474 -1179
- package/config/rules/rules-registry-generated.json +3 -3
- package/core/cli-action-handler.js +24 -30
- package/core/cli-program.js +11 -3
- package/core/config-merger.js +29 -2
- package/core/enhanced-rules-registry.js +3 -2
- package/core/semantic-engine.js +129 -19
- package/core/semantic-rule-base.js +4 -2
- package/core/unified-rule-registry.js +1 -1
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/heuristic-engine.js +135 -16
- package/integrations/eslint/plugin/index.js +0 -2
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +19 -15
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- 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 +232 -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/{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 +6 -1
- 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/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -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/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/consolidate-config.js +116 -0
- package/scripts/prepare-release.sh +1 -1
- package/config/rules/rules-registry.json +0 -765
- 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/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# C076: Explicit Function Argument Types
|
|
2
|
+
|
|
3
|
+
## 📋 **Rule Overview**
|
|
4
|
+
|
|
5
|
+
**Rule ID**: C076
|
|
6
|
+
**Category**: Type Safety
|
|
7
|
+
**Severity**: Error
|
|
8
|
+
**Analysis**: **Semantic-Only** (requires ts-morph)
|
|
9
|
+
|
|
10
|
+
## 🎯 **Description**
|
|
11
|
+
|
|
12
|
+
All public functions must declare explicit types for their arguments. This rule enforces type safety at API boundaries by ensuring no `any`, `unknown`, or missing type annotations on public function parameters.
|
|
13
|
+
|
|
14
|
+
## 🚨 **Why Semantic-Only?**
|
|
15
|
+
|
|
16
|
+
Unlike rules C033, C035, C040 which have regex fallbacks, **C076 is semantic-only** because:
|
|
17
|
+
|
|
18
|
+
1. **Type System Complexity**: Detecting `any`, `unknown`, generics, union types requires type checker
|
|
19
|
+
2. **Public vs Private**: Determining function visibility needs symbol resolution
|
|
20
|
+
3. **Type Resolution**: Following imports and type aliases requires cross-file analysis
|
|
21
|
+
4. **Accuracy**: Regex fallback would produce 90%+ false positives/negatives
|
|
22
|
+
|
|
23
|
+
## ⚡ **Requirements**
|
|
24
|
+
|
|
25
|
+
- ✅ **ts-morph** library installed
|
|
26
|
+
- ✅ **TypeScript project** with tsconfig.json
|
|
27
|
+
- ✅ **Semantic engine** enabled
|
|
28
|
+
- ❌ **No regex fallback available**
|
|
29
|
+
|
|
30
|
+
## 🔍 **What It Detects**
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C076 Main Analyzer - Explicit Function Argument Types
|
|
3
|
+
*
|
|
4
|
+
* SEMANTIC-ONLY RULE:
|
|
5
|
+
* This rule requires ts-morph and semantic analysis for accurate type checking.
|
|
6
|
+
* No regex fallback is provided because type system analysis cannot be reliably
|
|
7
|
+
* done with regex patterns.
|
|
8
|
+
*
|
|
9
|
+
* Primary: Symbol-based analysis (100% of cases)
|
|
10
|
+
* Fallback: None - will gracefully fail if ts-morph unavailable
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const C076SemanticAnalyzer = require('./semantic-analyzer');
|
|
14
|
+
|
|
15
|
+
class C076Analyzer {
|
|
16
|
+
constructor(semanticEngine = null) {
|
|
17
|
+
this.ruleId = 'C076';
|
|
18
|
+
this.ruleName = 'Explicit Function Argument Types';
|
|
19
|
+
this.description = 'All public functions must declare explicit types for arguments';
|
|
20
|
+
this.semanticEngine = semanticEngine;
|
|
21
|
+
this.verbose = false;
|
|
22
|
+
|
|
23
|
+
// Initialize analyzer
|
|
24
|
+
this.semanticAnalyzer = new C076SemanticAnalyzer();
|
|
25
|
+
|
|
26
|
+
// Configuration - semantic only
|
|
27
|
+
this.config = {
|
|
28
|
+
semanticOnly: true, // This rule requires semantic analysis
|
|
29
|
+
fallbackToRegex: false, // No regex fallback available
|
|
30
|
+
requiresTypeChecker: true // Type checker is mandatory
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize with semantic engine
|
|
36
|
+
*/
|
|
37
|
+
async initialize(semanticEngine = null) {
|
|
38
|
+
if (semanticEngine) {
|
|
39
|
+
this.semanticEngine = semanticEngine;
|
|
40
|
+
}
|
|
41
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
42
|
+
|
|
43
|
+
// Check if semantic engine is available
|
|
44
|
+
if (!this.semanticEngine?.project) {
|
|
45
|
+
if (this.verbose) {
|
|
46
|
+
console.log(`[DEBUG] ⚠️ C076: No semantic engine available - this rule requires ts-morph for type analysis`);
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Initialize semantic analyzer
|
|
52
|
+
await this.semanticAnalyzer.initialize(semanticEngine);
|
|
53
|
+
|
|
54
|
+
if (this.verbose) {
|
|
55
|
+
console.log(`[DEBUG] 🔧 C076: Analyzer initialized - Semantic-only mode ✅`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async analyze(files, language, options = {}) {
|
|
62
|
+
const violations = [];
|
|
63
|
+
|
|
64
|
+
// Check if semantic engine is available
|
|
65
|
+
if (!this.semanticEngine?.project) {
|
|
66
|
+
if (this.verbose) {
|
|
67
|
+
console.log(`[DEBUG] ❌ C076: Skipping analysis - semantic engine required but not available`);
|
|
68
|
+
console.log(`[DEBUG] 💡 C076: Install ts-morph and ensure TypeScript project setup for type checking`);
|
|
69
|
+
}
|
|
70
|
+
return violations;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let analyzedCount = 0;
|
|
74
|
+
let skippedCount = 0;
|
|
75
|
+
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
if (file.language === 'typescript' || file.language === 'javascript') {
|
|
78
|
+
try {
|
|
79
|
+
const fileViolations = await this.analyzeFile(file.path, options);
|
|
80
|
+
violations.push(...fileViolations);
|
|
81
|
+
analyzedCount++;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
skippedCount++;
|
|
84
|
+
if (this.verbose) {
|
|
85
|
+
console.log(`[DEBUG] ⚠️ C076: Error analyzing ${file.path}: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
skippedCount++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Summary
|
|
94
|
+
if (this.verbose && (analyzedCount > 0 || skippedCount > 0)) {
|
|
95
|
+
console.log(`[DEBUG] 📊 C076: Analysis summary:`);
|
|
96
|
+
console.log(`[DEBUG] 🧠 Semantic analysis: ${analyzedCount} files`);
|
|
97
|
+
console.log(`[DEBUG] ⏭️ Skipped: ${skippedCount} files`);
|
|
98
|
+
console.log(`[DEBUG] 📈 Type-checked: ${analyzedCount}/${analyzedCount + skippedCount} files`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return violations;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async analyzeFile(filePath, options = {}) {
|
|
105
|
+
// Check if semantic engine and type checker are available
|
|
106
|
+
if (!this.semanticEngine?.project) {
|
|
107
|
+
if (this.verbose) {
|
|
108
|
+
console.log(`[DEBUG] ⚠️ C076: ${filePath}: No semantic engine - type analysis requires ts-morph`);
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const sourceFile = this.semanticEngine.project.getSourceFileByFilePath(filePath);
|
|
115
|
+
if (sourceFile) {
|
|
116
|
+
const violations = await this.semanticAnalyzer.analyzeFileBasic(filePath, options);
|
|
117
|
+
|
|
118
|
+
if (this.verbose) {
|
|
119
|
+
console.log(`[DEBUG] 🧠 C076: ${filePath}: Found ${violations.length} violations`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return violations.map(v => ({
|
|
123
|
+
...v,
|
|
124
|
+
analysisStrategy: 'semantic-only',
|
|
125
|
+
requiresTypeChecker: true
|
|
126
|
+
}));
|
|
127
|
+
} else {
|
|
128
|
+
if (this.verbose) {
|
|
129
|
+
console.log(`[DEBUG] ⚠️ C076: ${filePath}: Source file not found in ts-morph project`);
|
|
130
|
+
}
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (this.verbose) {
|
|
135
|
+
console.log(`[DEBUG] ❌ C076: ${filePath}: Semantic analysis failed: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Compatibility method for heuristic engine
|
|
142
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
143
|
+
return await this.analyzeFile(filePath, options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Configuration methods
|
|
147
|
+
enableSemanticOnly() {
|
|
148
|
+
this.config.semanticOnly = true;
|
|
149
|
+
this.config.fallbackToRegex = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Information methods
|
|
153
|
+
getCapabilities() {
|
|
154
|
+
return {
|
|
155
|
+
requiresSemanticEngine: true,
|
|
156
|
+
requiresTypeChecker: true,
|
|
157
|
+
supportsRegexFallback: false,
|
|
158
|
+
analysisAccuracy: 'high',
|
|
159
|
+
recommendedFor: 'TypeScript projects with strict type checking'
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getRequirements() {
|
|
164
|
+
return {
|
|
165
|
+
dependencies: ['ts-morph'],
|
|
166
|
+
projectSetup: 'TypeScript project with tsconfig.json',
|
|
167
|
+
minimumAccuracy: 'semantic-only'
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = C076Analyzer;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ruleId": "C076",
|
|
3
|
+
"name": "Explicit Function Argument Types",
|
|
4
|
+
"description": "All public functions must declare explicit types for arguments",
|
|
5
|
+
"severity": "warning",
|
|
6
|
+
"category": "type-safety",
|
|
7
|
+
"languages": ["typescript", "javascript"],
|
|
8
|
+
"disallow": ["any", "Object", "object", "{}", "unknown"],
|
|
9
|
+
"requireGenericConstraints": false,
|
|
10
|
+
"checkCollections": true,
|
|
11
|
+
"ignorePatterns": ["**/*.spec.ts", "**/__tests__/**", "**/*.test.ts"],
|
|
12
|
+
"exemptPrivateFunctions": true,
|
|
13
|
+
"allowDefaultParameters": false,
|
|
14
|
+
"strictGenericTypes": true
|
|
15
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C076 Semantic Analyzer - Explicit Function Argument Types
|
|
3
|
+
* Purpose: Use AST + Symbol Resolution to enforce explicit types on public functions
|
|
4
|
+
*
|
|
5
|
+
* NOTE: This rule REQUIRES semantic analysis and ts-morph.
|
|
6
|
+
* Unlike C033/C035/C040, C076 does NOT have regex fallback because:
|
|
7
|
+
* 1. Type system analysis is too complex for regex patterns
|
|
8
|
+
* 2. Public vs private function detection requires symbol resolution
|
|
9
|
+
* 3. Type resolution (any, unknown, generics) needs type checker
|
|
10
|
+
* 4. Regex fallback would produce 90%+ false positives/negatives
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { SyntaxKind } = require('ts-morph');
|
|
14
|
+
const SemanticRuleBase = require('../../../core/semantic-rule-base');
|
|
15
|
+
|
|
16
|
+
class C076SemanticAnalyzer extends SemanticRuleBase {
|
|
17
|
+
constructor() {
|
|
18
|
+
super('C076', {
|
|
19
|
+
description: 'All public functions must declare explicit types for arguments',
|
|
20
|
+
category: 'type-safety',
|
|
21
|
+
severity: 'error',
|
|
22
|
+
requiresTypeChecker: true,
|
|
23
|
+
crossFileAnalysis: false,
|
|
24
|
+
semanticOnly: true // This rule requires semantic analysis - no regex fallback
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Main entry point called by the semantic engine
|
|
30
|
+
*/
|
|
31
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
32
|
+
return await this.analyzeFile(filePath, null, options);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Analyze a file for explicit function argument type violations
|
|
37
|
+
* @param {string} filePath - Path to the file
|
|
38
|
+
* @param {Object} options - Analysis options
|
|
39
|
+
*/
|
|
40
|
+
async analyzeFile(filePath, sourceFile, config = {}) {
|
|
41
|
+
const verbose = this.config.verbose || false;
|
|
42
|
+
|
|
43
|
+
if (verbose) {
|
|
44
|
+
console.log(`[DEBUG] 🔍 C076: Analyzing file ${filePath}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Get configuration with defaults
|
|
48
|
+
const {
|
|
49
|
+
disallow = ['any', 'Object', 'object', '{}', 'unknown'],
|
|
50
|
+
requireGenericConstraints = false,
|
|
51
|
+
checkCollections = true,
|
|
52
|
+
ignorePatterns = ['**/*.spec.ts', '**/__tests__/**']
|
|
53
|
+
} = config;
|
|
54
|
+
|
|
55
|
+
// Check if file should be ignored
|
|
56
|
+
if (this.shouldIgnoreFile(filePath, ignorePatterns)) {
|
|
57
|
+
if (verbose) {
|
|
58
|
+
console.log(`[DEBUG] ⏭️ C076: Ignoring file ${filePath} due to ignore patterns`);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Get sourceFile from semantic engine
|
|
64
|
+
if (!this.semanticEngine?.project) {
|
|
65
|
+
if (verbose) {
|
|
66
|
+
console.warn('[DEBUG] 🔍 C076: No semantic engine available, skipping analysis');
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const tsSourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
73
|
+
if (!tsSourceFile) {
|
|
74
|
+
if (verbose) {
|
|
75
|
+
console.warn(`[DEBUG] 🔍 C076: Could not find sourceFile for ${filePath}`);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Find all exported functions
|
|
81
|
+
const exportedFunctions = this.findExportedFunctions(tsSourceFile);
|
|
82
|
+
if (verbose) {
|
|
83
|
+
console.log(`[DEBUG] 🎯 C076: Found ${exportedFunctions.length} exported functions`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const func of exportedFunctions) {
|
|
87
|
+
this.analyzeFunction(func, config, filePath, verbose);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Find exported classes and analyze their public methods
|
|
91
|
+
const exportedClasses = this.findExportedClasses(tsSourceFile);
|
|
92
|
+
if (verbose) {
|
|
93
|
+
console.log(`[DEBUG] 📦 C076: Found ${exportedClasses.length} exported classes`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const cls of exportedClasses) {
|
|
97
|
+
this.analyzeClassMethods(cls, config, filePath, verbose);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`❌ C076: Error analyzing ${filePath}:`, error.message);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (verbose) {
|
|
105
|
+
console.log(`[DEBUG] ✅ C076: Analysis complete. Found ${this.violations.length} violations in ${filePath}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
shouldIgnoreFile(filePath, ignorePatterns) {
|
|
110
|
+
return ignorePatterns.some(pattern => {
|
|
111
|
+
// Convert glob pattern to regex
|
|
112
|
+
const regexPattern = pattern
|
|
113
|
+
.replace(/\*\*/g, '.*')
|
|
114
|
+
.replace(/\*/g, '[^/]*')
|
|
115
|
+
.replace(/\./g, '\\.');
|
|
116
|
+
return new RegExp(regexPattern).test(filePath);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
findExportedFunctions(sourceFile) {
|
|
121
|
+
const functions = [];
|
|
122
|
+
|
|
123
|
+
// Find all function declarations
|
|
124
|
+
const functionDecls = sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration);
|
|
125
|
+
for (const func of functionDecls) {
|
|
126
|
+
if (this.isExported(func)) {
|
|
127
|
+
functions.push({
|
|
128
|
+
type: 'function',
|
|
129
|
+
node: func,
|
|
130
|
+
name: func.getName() || 'anonymous'
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Find variable declarations that are arrow functions
|
|
136
|
+
const variableStmts = sourceFile.getDescendantsOfKind(SyntaxKind.VariableStatement);
|
|
137
|
+
for (const stmt of variableStmts) {
|
|
138
|
+
if (this.isExported(stmt)) {
|
|
139
|
+
const declarations = stmt.getDeclarationList().getDeclarations();
|
|
140
|
+
for (const decl of declarations) {
|
|
141
|
+
const initializer = decl.getInitializer();
|
|
142
|
+
if (initializer && initializer.getKind() === SyntaxKind.ArrowFunction) {
|
|
143
|
+
functions.push({
|
|
144
|
+
type: 'arrow',
|
|
145
|
+
node: initializer,
|
|
146
|
+
name: decl.getName(),
|
|
147
|
+
declaration: decl
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return functions;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
findExportedClasses(sourceFile) {
|
|
158
|
+
const classes = [];
|
|
159
|
+
|
|
160
|
+
const classDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ClassDeclaration);
|
|
161
|
+
for (const cls of classDecls) {
|
|
162
|
+
if (this.isExported(cls)) {
|
|
163
|
+
classes.push({
|
|
164
|
+
node: cls,
|
|
165
|
+
name: cls.getName() || 'anonymous'
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return classes;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
isExported(node) {
|
|
174
|
+
const modifiers = node.getModifiers();
|
|
175
|
+
return modifiers.some(mod => mod.getKind() === SyntaxKind.ExportKeyword);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
analyzeFunction(funcInfo, config, filePath, verbose = false) {
|
|
179
|
+
const { node, name, type } = funcInfo;
|
|
180
|
+
if (verbose) {
|
|
181
|
+
console.log(`[DEBUG] 🔎 C076: Analyzing function '${name}' (${type})`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const parameters = node.getParameters();
|
|
185
|
+
|
|
186
|
+
parameters.forEach((param, index) => {
|
|
187
|
+
this.analyzeParameter(param, index, name, config, filePath, verbose);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
analyzeClassMethods(classInfo, config, filePath, verbose = false) {
|
|
192
|
+
const { node, name } = classInfo;
|
|
193
|
+
if (verbose) {
|
|
194
|
+
console.log(`[DEBUG] 🔎 C076: Analyzing class '${name}'`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const methods = node.getMethods();
|
|
198
|
+
|
|
199
|
+
methods.forEach(method => {
|
|
200
|
+
// Only check public methods
|
|
201
|
+
if (this.isPublicMethod(method)) {
|
|
202
|
+
const methodName = method.getName();
|
|
203
|
+
if (verbose) {
|
|
204
|
+
console.log(`[DEBUG] 🔎 C076: Analyzing method '${methodName}'`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const parameters = method.getParameters();
|
|
208
|
+
parameters.forEach((param, index) => {
|
|
209
|
+
this.analyzeParameter(param, index, `${name}.${methodName}`, config, filePath, verbose);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
isPublicMethod(method) {
|
|
216
|
+
// Method is public if no private/protected modifier
|
|
217
|
+
const modifiers = method.getModifiers();
|
|
218
|
+
return !modifiers.some(mod =>
|
|
219
|
+
mod.getKind() === SyntaxKind.PrivateKeyword ||
|
|
220
|
+
mod.getKind() === SyntaxKind.ProtectedKeyword
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
analyzeParameter(param, index, functionName, config, filePath, verbose = false) {
|
|
225
|
+
const {
|
|
226
|
+
disallow = ['any', 'Object', 'object', '{}', 'unknown'],
|
|
227
|
+
requireGenericConstraints = false,
|
|
228
|
+
checkCollections = true
|
|
229
|
+
} = config;
|
|
230
|
+
const paramName = param.getName();
|
|
231
|
+
|
|
232
|
+
if (verbose) {
|
|
233
|
+
console.log(`[DEBUG] 🔍 C076: Checking parameter '${paramName}' in '${functionName}'`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check for missing type annotation
|
|
237
|
+
const typeNode = param.getTypeNode();
|
|
238
|
+
if (!typeNode) {
|
|
239
|
+
this.violations.push(this.createViolation(
|
|
240
|
+
param,
|
|
241
|
+
`Parameter '${paramName}' at position ${index} in function '${functionName}' is missing type annotation`,
|
|
242
|
+
'missing-type',
|
|
243
|
+
filePath
|
|
244
|
+
));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Get type text for analysis
|
|
249
|
+
const typeText = typeNode.getText();
|
|
250
|
+
if (verbose) {
|
|
251
|
+
console.log(`[DEBUG] 🔍 C076: Parameter '${paramName}' has type: ${typeText}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check for disallowed types
|
|
255
|
+
if (Array.isArray(disallow) && disallow.includes(typeText)) {
|
|
256
|
+
this.violations.push(this.createViolation(
|
|
257
|
+
param,
|
|
258
|
+
`Parameter '${paramName}' in function '${functionName}' uses disallowed type '${typeText}'`,
|
|
259
|
+
'disallowed-type',
|
|
260
|
+
filePath
|
|
261
|
+
));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check for generic collections without proper typing
|
|
266
|
+
if (checkCollections && this.isUnparameterizedCollection(typeText)) {
|
|
267
|
+
this.violations.push(this.createViolation(
|
|
268
|
+
param,
|
|
269
|
+
`Parameter '${paramName}' in function '${functionName}' uses unparameterized collection type '${typeText}'`,
|
|
270
|
+
'unparameterized-collection',
|
|
271
|
+
filePath
|
|
272
|
+
));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check for generic constraints if required
|
|
277
|
+
if (requireGenericConstraints && this.isUnconstrainedGeneric(typeText)) {
|
|
278
|
+
this.violations.push(this.createViolation(
|
|
279
|
+
param,
|
|
280
|
+
`Parameter '${paramName}' in function '${functionName}' uses unconstrained generic type`,
|
|
281
|
+
'unconstrained-generic',
|
|
282
|
+
filePath
|
|
283
|
+
));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
isUnparameterizedCollection(typeText) {
|
|
288
|
+
const unparameterizedPatterns = [
|
|
289
|
+
/^Array$/,
|
|
290
|
+
/^Map$/,
|
|
291
|
+
/^Set$/,
|
|
292
|
+
/^WeakMap$/,
|
|
293
|
+
/^WeakSet$/,
|
|
294
|
+
/^Promise$/,
|
|
295
|
+
/^Observable$/
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
return unparameterizedPatterns.some(pattern => pattern.test(typeText));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
isUnconstrainedGeneric(typeText) {
|
|
302
|
+
// Single letter types are often unconstrained generics
|
|
303
|
+
return /^[A-Z]$/.test(typeText);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
createViolation(node, message, subtype, filePath) {
|
|
307
|
+
const startPos = node.getStart();
|
|
308
|
+
const sourceFile = node.getSourceFile();
|
|
309
|
+
const lineAndChar = sourceFile.getLineAndColumnAtPos(startPos);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
ruleId: this.ruleId,
|
|
313
|
+
severity: 'error',
|
|
314
|
+
message,
|
|
315
|
+
source: this.ruleId,
|
|
316
|
+
file: filePath,
|
|
317
|
+
line: lineAndChar.line + 1,
|
|
318
|
+
column: lineAndChar.column + 1,
|
|
319
|
+
description: `[SEMANTIC] ${message}. Ensure all public API functions have explicit type annotations for better type safety.`,
|
|
320
|
+
suggestion: this.getSuggestion(subtype),
|
|
321
|
+
category: 'type-safety'
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
getSuggestion(subtype) {
|
|
326
|
+
switch (subtype) {
|
|
327
|
+
case 'missing-type':
|
|
328
|
+
return 'Add explicit type annotation: function(param: Type)';
|
|
329
|
+
case 'disallowed-type':
|
|
330
|
+
return 'Replace with specific type: string, number, UserData, etc.';
|
|
331
|
+
case 'unparameterized-collection':
|
|
332
|
+
return 'Add generic type: Array<Type>, Map<Key, Value>, Set<Type>';
|
|
333
|
+
case 'unconstrained-generic':
|
|
334
|
+
return 'Add generic constraint: <T extends BaseType>';
|
|
335
|
+
default:
|
|
336
|
+
return 'Use explicit, specific types for better type safety';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = C076SemanticAnalyzer;
|
package/rules/index.js
CHANGED
|
@@ -52,7 +52,11 @@ const commonRules = {
|
|
|
52
52
|
C012: loadRule('common', 'C012_command_query_separation'),
|
|
53
53
|
C013: loadRule('common', 'C013_no_dead_code'),
|
|
54
54
|
C014: loadRule('common', 'C014_dependency_injection'),
|
|
55
|
+
C018: loadRule('common', 'C018_no_throw_generic_error'),
|
|
55
56
|
C019: loadRule('common', 'C019_log_level_usage'),
|
|
57
|
+
C030: loadRule('common', 'C030_use_custom_error_classes'),
|
|
58
|
+
C023: loadRule('common', 'C023_no_duplicate_variable'),
|
|
59
|
+
C024: loadRule('common', 'C024_no_scatter_hardcoded_constants'),
|
|
56
60
|
C029: loadRule('common', 'C029_catch_block_logging'),
|
|
57
61
|
C031: loadRule('common', 'C031_validation_separation'),
|
|
58
62
|
C041: loadRule('common', 'C041_no_sensitive_hardcode'),
|
|
@@ -62,6 +66,7 @@ const commonRules = {
|
|
|
62
66
|
|
|
63
67
|
// 🔒 Security Rules (S-series) - Ready for migration
|
|
64
68
|
const securityRules = {
|
|
69
|
+
S006: loadRule('security', 'S006_no_plaintext_recovery_codes'),
|
|
65
70
|
S015: loadRule('security', 'S015_insecure_tls_certificate'),
|
|
66
71
|
S023: loadRule('security', 'S023_no_json_injection'),
|
|
67
72
|
S026: loadRule('security', 'S026_json_schema_validation'),
|
|
@@ -159,4 +164,4 @@ module.exports = {
|
|
|
159
164
|
common: commonRules,
|
|
160
165
|
security: securityRules,
|
|
161
166
|
typescript: typescriptRules
|
|
162
|
-
};
|
|
167
|
+
};
|
|
@@ -221,8 +221,12 @@ class RuleParser {
|
|
|
221
221
|
// Process lines with keywords
|
|
222
222
|
processKeywordLine(line, rule, state) {
|
|
223
223
|
if (this.containsKeyword(line, "OBJECTIVE")) {
|
|
224
|
-
|
|
225
|
-
this.
|
|
224
|
+
state.currentSection = "objective"
|
|
225
|
+
const objectiveText = this.extractValueAfterKeyword(line, "OBJECTIVE")
|
|
226
|
+
if (objectiveText) {
|
|
227
|
+
rule.description = objectiveText
|
|
228
|
+
}
|
|
229
|
+
this.resetParsingFlags(state)
|
|
226
230
|
} else if (this.containsKeyword(line, "DETAILS")) {
|
|
227
231
|
state.currentSection = "details"
|
|
228
232
|
const detailText = this.extractValueAfterKeyword(line, "DETAILS")
|
|
@@ -279,6 +283,13 @@ class RuleParser {
|
|
|
279
283
|
state.currentSection = null
|
|
280
284
|
state.currentConfigType = this.extractConfigType(line)
|
|
281
285
|
console.log(`Found config section in rule ${rule.id}: ${line} (type: ${state.currentConfigType})`)
|
|
286
|
+
} else if (state.currentSection === "objective" && line.trim() && !line.trim().startsWith("**") && !line.trim().startsWith("-")) {
|
|
287
|
+
// Continue reading objective content on next lines
|
|
288
|
+
if (rule.description) {
|
|
289
|
+
rule.description += " " + line.trim()
|
|
290
|
+
} else {
|
|
291
|
+
rule.description = line.trim()
|
|
292
|
+
}
|
|
282
293
|
} else if (state.currentSection === "details" && line.trim() && !line.trim().startsWith("**")) {
|
|
283
294
|
rule.details.push(line.trim())
|
|
284
295
|
}
|