@sun-asterisk/sunlint 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/CONTRIBUTING.md +1654 -66
  3. package/README.md +19 -6
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/engines/engines-enhanced.json +86 -0
  7. package/config/engines/semantic-config.json +114 -0
  8. package/config/eslint-rule-mapping.json +50 -38
  9. package/config/large-project.json +143 -0
  10. package/config/presets/all.json +0 -1
  11. package/config/release.json +70 -0
  12. package/config/rule-analysis-strategies.js +23 -4
  13. package/config/rules/S027-categories.json +122 -0
  14. package/config/rules/enhanced-rules-registry.json +2564 -0
  15. package/config/rules/rules-registry-generated.json +785 -837
  16. package/config/rules/rules-registry.json +13 -1
  17. package/core/adapters/sunlint-rule-adapter.js +25 -30
  18. package/core/analysis-orchestrator.js +42 -2
  19. package/core/categories.js +52 -0
  20. package/core/category-constants.js +39 -0
  21. package/core/cli-action-handler.js +53 -32
  22. package/core/cli-program.js +11 -3
  23. package/core/config-manager.js +111 -0
  24. package/core/config-merger.js +88 -0
  25. package/core/constants/categories.js +168 -0
  26. package/core/constants/defaults.js +165 -0
  27. package/core/constants/engines.js +185 -0
  28. package/core/constants/index.js +30 -0
  29. package/core/constants/rules.js +215 -0
  30. package/core/enhanced-rules-registry.js +3 -3
  31. package/core/file-targeting-service.js +128 -7
  32. package/core/interfaces/rule-plugin.interface.js +207 -0
  33. package/core/plugin-manager.js +448 -0
  34. package/core/rule-selection-service.js +42 -15
  35. package/core/semantic-engine.js +658 -0
  36. package/core/semantic-rule-base.js +433 -0
  37. package/core/unified-rule-registry.js +484 -0
  38. package/docs/COMMAND-EXAMPLES.md +134 -0
  39. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  40. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  41. package/engines/core/base-engine.js +249 -0
  42. package/engines/engine-factory.js +275 -0
  43. package/engines/eslint-engine.js +171 -19
  44. package/engines/heuristic-engine.js +569 -78
  45. package/integrations/eslint/plugin/index.js +26 -28
  46. package/origin-rules/common-en.md +8 -8
  47. package/package.json +10 -6
  48. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  49. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  50. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  51. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  52. package/rules/common/C033_separate_service_repository/README.md +78 -0
  53. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  54. package/rules/common/C033_separate_service_repository/config.json +50 -0
  55. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  56. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  57. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  58. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  59. package/rules/common/C035_error_logging_context/config.json +54 -0
  60. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  61. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  62. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  63. package/rules/common/C040_centralized_validation/config.json +46 -0
  64. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  65. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  66. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  67. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  68. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  69. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  70. package/rules/common/C076_explicit_function_types/README.md +30 -0
  71. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  72. package/rules/common/C076_explicit_function_types/config.json +15 -0
  73. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  74. package/rules/index.js +8 -0
  75. package/rules/parser/rule-parser.js +13 -2
  76. package/rules/security/S005_no_origin_auth/README.md +226 -0
  77. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  78. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  79. package/rules/security/S005_no_origin_auth/config.json +85 -0
  80. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  81. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  82. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  83. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  84. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  85. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  86. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  87. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  88. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  89. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  90. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  91. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  92. package/scripts/category-manager.js +150 -0
  93. package/scripts/generate-rules-registry.js +88 -0
  94. package/scripts/migrate-rule-registry.js +157 -0
  95. package/scripts/prepare-release.sh +1 -1
  96. package/scripts/validate-system.js +48 -0
  97. package/.sunlint.json +0 -35
  98. package/config/README.md +0 -88
  99. package/config/engines/eslint-rule-mapping.json +0 -74
  100. package/config/schemas/sunlint-schema.json +0 -0
  101. package/config/testing/test-s005-working.ts +0 -22
  102. package/core/multi-rule-runner.js +0 -0
  103. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  104. package/docs/FUTURE_PACKAGES.md +0 -83
  105. package/docs/HEURISTIC_VS_AI.md +0 -113
  106. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  107. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  108. package/docs/RELEASE_GUIDE.md +0 -230
  109. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  110. package/engines/tree-sitter-parser.js +0 -0
  111. package/engines/universal-ast-engine.js +0 -0
  112. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  113. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  114. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  115. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  116. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  117. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  118. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  119. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  120. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  121. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  122. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  123. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  124. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -0,0 +1,71 @@
1
+ {
2
+ "knownRetryFunctions": [
3
+ "axios.get",
4
+ "axios.post",
5
+ "axios.put",
6
+ "axios.delete",
7
+ "axios.patch",
8
+ "axios.request",
9
+ "axios.head",
10
+ "axios.options",
11
+
12
+ "useQuery",
13
+ "useMutation",
14
+ "useInfiniteQuery",
15
+ "queryClient.fetchQuery",
16
+ "queryClient.prefetchQuery",
17
+
18
+ "apolloClient.query",
19
+ "apolloClient.mutate",
20
+ "apolloClient.watchQuery",
21
+ "useLazyQuery",
22
+
23
+ "apiService.get",
24
+ "apiService.post",
25
+ "apiService.put",
26
+ "apiService.delete",
27
+ "apiService.patch",
28
+ "httpClient.get",
29
+ "httpClient.post",
30
+ "httpClient.request",
31
+
32
+ "retryAsync",
33
+ "withRetry",
34
+ "retry",
35
+ "p-retry",
36
+ "exponentialBackoff",
37
+ "retryPromise",
38
+
39
+ "fetch",
40
+ "fetch-retry",
41
+ "node-fetch",
42
+ "got",
43
+ "superagent",
44
+ "request-promise"
45
+ ],
46
+
47
+ "layerPatterns": {
48
+ "ui": ["component", "view", "page", "modal", "form", "screen", "widget", "/ui/", "/components/"],
49
+ "usecase": ["usecase", "use-case", "usecases", "service", "business", "/usecases/", "/services/"],
50
+ "repository": ["repository", "repo", "dao", "store", "persistence", "/repositories/", "/data/"],
51
+ "api": ["api", "client", "adapter", "gateway", "connector", "/api/", "/clients/", "/gateways/"]
52
+ },
53
+
54
+ "retryDetectionPatterns": {
55
+ "exceptionRetry": {
56
+ "description": "Detect retry logic in try-catch blocks",
57
+ "enabled": true
58
+ },
59
+ "emptyDataRetry": {
60
+ "description": "Detect retry logic when data is empty/null",
61
+ "enabled": true
62
+ },
63
+ "knownRetryConflict": {
64
+ "description": "Detect manual retry conflicting with built-in retry",
65
+ "enabled": true
66
+ }
67
+ },
68
+
69
+ "_description": "Configuration for Symbol-Based Analysis of retry functions using ts-morph",
70
+ "_usage": "Add functions that have built-in retry mechanisms to avoid false positives"
71
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * C076 Rule: Each test should assert only one behavior
2
+ * C072 Rule: Each test should assert only one behavior
3
3
  * Ensures test focus and maintainability by limiting assertions per test
4
4
  * Severity: warning
5
5
  * Category: Quality
@@ -8,9 +8,9 @@
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
 
11
- class C076SingleTestBehaviorAnalyzer {
11
+ class C072SingleTestBehaviorAnalyzer {
12
12
  constructor() {
13
- this.ruleId = 'C076';
13
+ this.ruleId = 'C072';
14
14
  this.ruleName = 'Single Test Behavior';
15
15
  this.description = 'Each test should assert only one behavior';
16
16
  this.severity = 'warning';
@@ -26,7 +26,7 @@ class C076SingleTestBehaviorAnalyzer {
26
26
  const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
27
27
  violations.push(...fileViolations);
28
28
  } catch (error) {
29
- console.warn(`C076 analysis error for ${filePath}:`, error.message);
29
+ console.warn(`C072 analysis error for ${filePath}:`, error.message);
30
30
  }
31
31
  }
32
32
 
@@ -98,7 +98,7 @@ class C076SingleTestBehaviorAnalyzer {
98
98
  }
99
99
 
100
100
  } catch (error) {
101
- console.warn(`C076 analysis error for ${filePath}:`, error.message);
101
+ console.warn(`C072 analysis error for ${filePath}:`, error.message);
102
102
  }
103
103
 
104
104
  return violations;
@@ -118,4 +118,4 @@ class C076SingleTestBehaviorAnalyzer {
118
118
  }
119
119
  }
120
120
 
121
- module.exports = C076SingleTestBehaviorAnalyzer;
121
+ module.exports = C072SingleTestBehaviorAnalyzer;
@@ -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
@@ -15,6 +15,13 @@ const path = require('path');
15
15
  */
16
16
  function loadRule(category, ruleId) {
17
17
  try {
18
+ // Special case for C047: Use semantic analyzer by default
19
+ if (ruleId === 'C047_no_duplicate_retry_logic') {
20
+ const semanticPath = path.join(__dirname, category, ruleId, 'c047-semantic-rule.js');
21
+ console.log(`🔬 Loading C047 semantic analyzer: ${semanticPath}`);
22
+ return require(semanticPath);
23
+ }
24
+
18
25
  const rulePath = path.join(__dirname, category, ruleId, 'analyzer.js');
19
26
  return require(rulePath);
20
27
  } catch (error) {
@@ -55,6 +62,7 @@ const commonRules = {
55
62
 
56
63
  // 🔒 Security Rules (S-series) - Ready for migration
57
64
  const securityRules = {
65
+ S006: loadRule('security', 'S006_no_plaintext_recovery_codes'),
58
66
  S015: loadRule('security', 'S015_insecure_tls_certificate'),
59
67
  S023: loadRule('security', 'S023_no_json_injection'),
60
68
  S026: loadRule('security', 'S026_json_schema_validation'),