@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/CONTRIBUTING.md +249 -605
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +38 -3
  10. package/config/rules/enhanced-rules-registry.json +474 -1179
  11. package/config/rules/rules-registry-generated.json +3 -3
  12. package/core/cli-action-handler.js +24 -30
  13. package/core/cli-program.js +11 -3
  14. package/core/config-merger.js +29 -2
  15. package/core/enhanced-rules-registry.js +3 -2
  16. package/core/semantic-engine.js +129 -19
  17. package/core/semantic-rule-base.js +4 -2
  18. package/core/unified-rule-registry.js +1 -1
  19. package/docs/COMMAND-EXAMPLES.md +134 -0
  20. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  21. package/engines/heuristic-engine.js +135 -16
  22. package/integrations/eslint/plugin/index.js +0 -2
  23. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  24. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  25. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  26. package/origin-rules/common-en.md +19 -15
  27. package/package.json +1 -1
  28. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  29. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  30. package/rules/common/C006_function_naming/analyzer.js +29 -3
  31. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  32. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  33. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  34. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  35. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  36. package/rules/common/C013_no_dead_code/config.json +61 -0
  37. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  38. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  39. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  40. package/rules/common/C014_dependency_injection/config.json +26 -0
  41. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  42. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  43. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  44. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  45. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  46. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  47. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  48. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  49. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  50. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  51. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  52. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  53. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  56. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  57. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  58. package/rules/common/C033_separate_service_repository/README.md +78 -0
  59. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  60. package/rules/common/C033_separate_service_repository/config.json +50 -0
  61. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  62. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  63. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  64. package/rules/common/C035_error_logging_context/analyzer.js +232 -0
  65. package/rules/common/C035_error_logging_context/config.json +54 -0
  66. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  67. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  68. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  69. package/rules/common/C040_centralized_validation/config.json +46 -0
  70. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  71. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  72. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  73. package/rules/common/C076_explicit_function_types/README.md +30 -0
  74. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  75. package/rules/common/C076_explicit_function_types/config.json +15 -0
  76. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  77. package/rules/index.js +6 -1
  78. package/rules/parser/rule-parser.js +13 -2
  79. package/rules/security/S005_no_origin_auth/README.md +226 -0
  80. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  81. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  82. package/rules/security/S005_no_origin_auth/config.json +85 -0
  83. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  84. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  85. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  86. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  87. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  88. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  89. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  90. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  91. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  92. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  93. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  94. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  95. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  96. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  97. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  98. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  99. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  100. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  101. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  102. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  103. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  104. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  105. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  106. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  107. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  108. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  109. package/rules/security/S055_content_type_validation/README.md +176 -0
  110. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  111. package/rules/security/S055_content_type_validation/config.json +48 -0
  112. package/rules/utils/rule-helpers.js +140 -1
  113. package/scripts/consolidate-config.js +116 -0
  114. package/scripts/prepare-release.sh +1 -1
  115. package/config/rules/rules-registry.json +0 -765
  116. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  117. package/docs/FUTURE_PACKAGES.md +0 -83
  118. package/docs/HEURISTIC_VS_AI.md +0 -113
  119. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  120. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  121. package/docs/RELEASE_GUIDE.md +0 -230
  122. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  123. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  124. 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
- rule.description = this.extractValueAfterKeyword(line, "OBJECTIVE")
225
- this.resetParsingState(state)
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
  }