@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,243 @@
1
+ /**
2
+ * Regex-based analyzer for: C040 - Centralized Validation Logic
3
+ * Purpose: Use regex patterns to detect scattered validation logic (fallback approach)
4
+ */
5
+
6
+ class C040RegexBasedAnalyzer {
7
+ constructor(semanticEngine = null) {
8
+ this.ruleId = 'C040';
9
+ this.ruleName = 'Centralized Validation Logic';
10
+ this.description = 'Don\'t scatter validation logic across multiple classes - Move validation to dedicated validators';
11
+ this.semanticEngine = semanticEngine;
12
+ this.verbose = false;
13
+
14
+ // Patterns for detecting validation logic
15
+ this.validationPatterns = [
16
+ // Function names (more specific - must be standalone words)
17
+ /\b(?:validate|check|ensure|verify|sanitize|normalize)\w*/gi,
18
+ /\b(?:is[A-Z][a-zA-Z]*|has[A-Z][a-zA-Z]*)\s*(?:\(|\s*:)/gi,
19
+
20
+ // Validation frameworks
21
+ /(?:zod|joi|yup|ajv)\.[\w.]+/gi,
22
+ /(?:class-validator|validateSync|checkSchema)/gi,
23
+
24
+ // Common validation patterns
25
+ /\.test\s*\(\s*[^)]*(?:email|phone|url|uuid|password)/gi,
26
+ /(?:email|phone|url|uuid).*\.match\s*\(/gi,
27
+ /typeof\s+\w+\s*[!=]==?\s*['"](?:string|number|boolean)/gi,
28
+ /\.length\s*[<>]=?\s*\d+/gi,
29
+
30
+ // Error throwing patterns for validation
31
+ /throw\s+new\s+(?:ValidationError|BadRequest|InvalidInput|TypeError)/gi,
32
+ /if\s*\([^)]*(?:invalid|empty|null|undefined)\)\s*(?:throw|return.*error)/gi,
33
+
34
+ // Schema validation patterns
35
+ /\.(?:required|optional|string|number|boolean|array|object)\(\)/gi,
36
+ /\.(?:min|max|email|url|uuid|regex)\(\)/gi
37
+ ];
38
+
39
+ // Layer detection patterns
40
+ this.layerPatterns = {
41
+ controller: /\/controllers?\/|controller\.|Controller\./i,
42
+ service: /\/services?\/|service\.|Service\./i,
43
+ repository: /\/repositories?\/|repository\.|Repository\./i,
44
+ validator: /\/validators?\/|validator\.|Validator\.|\/validation\//i,
45
+ middleware: /\/middleware\//i
46
+ };
47
+ }
48
+
49
+ async initialize(semanticEngine = null) {
50
+ if (semanticEngine) {
51
+ this.semanticEngine = semanticEngine;
52
+ }
53
+ this.verbose = semanticEngine?.verbose || false;
54
+
55
+ if (this.verbose) {
56
+ console.log(`[DEBUG] 🔄 C040 Regex-Based: Analyzer initialized`);
57
+ }
58
+ }
59
+
60
+ async analyze(files, language, options = {}) {
61
+ const violations = [];
62
+
63
+ for (const filePath of files) {
64
+ try {
65
+ const fileViolations = await this.analyzeFileBasic(filePath, options);
66
+ violations.push(...fileViolations);
67
+ } catch (error) {
68
+ if (this.verbose) {
69
+ console.warn(`[C040 Regex] Analysis failed for ${filePath}:`, error.message);
70
+ }
71
+ }
72
+ }
73
+
74
+ return violations;
75
+ }
76
+
77
+ async analyzeFileBasic(filePath, options = {}) {
78
+ try {
79
+ const fs = require('fs');
80
+ const content = fs.readFileSync(filePath, 'utf-8');
81
+ const violations = [];
82
+
83
+ const layer = this.detectLayer(filePath);
84
+ const validationMatches = this.findValidationPatterns(content, filePath);
85
+
86
+ if (validationMatches.length > 0) {
87
+ // Check if validation logic is in wrong layer
88
+ if (layer === 'controller' || layer === 'service') {
89
+ const complexValidations = validationMatches.filter(match =>
90
+ this.isComplexValidation(match.pattern)
91
+ );
92
+
93
+ if (complexValidations.length > 0) {
94
+ violations.push({
95
+ ruleId: this.ruleId,
96
+ severity: 'warning',
97
+ message: `Found ${complexValidations.length} validation pattern(s) in ${layer} layer. Consider moving to validators.`,
98
+ file: filePath,
99
+ line: complexValidations[0].line,
100
+ column: complexValidations[0].column,
101
+ details: {
102
+ layer,
103
+ validationCount: complexValidations.length,
104
+ patterns: complexValidations.map(v => v.pattern),
105
+ suggestion: 'Move validation logic to dedicated validator classes',
106
+ ruleName: this.ruleName
107
+ }
108
+ });
109
+ }
110
+ }
111
+
112
+ // Check for potential duplicates (simple heuristic)
113
+ const duplicatePatterns = this.findPotentialDuplicates(validationMatches);
114
+ if (duplicatePatterns.length > 0) {
115
+ violations.push({
116
+ ruleId: this.ruleId,
117
+ severity: 'info',
118
+ message: `Found potentially duplicate validation patterns: ${duplicatePatterns.join(', ')}`,
119
+ file: filePath,
120
+ line: validationMatches[0].line,
121
+ column: validationMatches[0].column,
122
+ details: {
123
+ duplicatePatterns,
124
+ suggestion: 'Consider consolidating similar validation logic',
125
+ ruleName: this.ruleName
126
+ }
127
+ });
128
+ }
129
+ }
130
+
131
+ return violations;
132
+
133
+ } catch (error) {
134
+ if (this.verbose) {
135
+ console.warn(`[C040 Regex] Failed to analyze ${filePath}:`, error.message);
136
+ }
137
+ return [];
138
+ }
139
+ }
140
+
141
+ detectLayer(filePath) {
142
+ const path = filePath.toLowerCase();
143
+
144
+ for (const [layer, pattern] of Object.entries(this.layerPatterns)) {
145
+ if (pattern.test(path)) {
146
+ return layer;
147
+ }
148
+ }
149
+
150
+ return 'unknown';
151
+ }
152
+
153
+ findValidationPatterns(content, filePath) {
154
+ const matches = [];
155
+ const lines = content.split('\n');
156
+
157
+ this.validationPatterns.forEach(pattern => {
158
+ lines.forEach((line, lineIndex) => {
159
+ const match = line.match(pattern);
160
+ if (match) {
161
+ matches.push({
162
+ pattern: match[0],
163
+ line: lineIndex + 1,
164
+ column: line.indexOf(match[0]) + 1,
165
+ type: 'regex',
166
+ fullLine: line.trim()
167
+ });
168
+ }
169
+ });
170
+ });
171
+
172
+ return matches;
173
+ }
174
+
175
+ isComplexValidation(pattern) {
176
+ // Filter out false positives and keep only real validation patterns
177
+ const excludePatterns = [
178
+ /Promise/i, // Promise types
179
+ /Response/i, // HTTP Response
180
+ /Request/i, // HTTP Request
181
+ /Service/i, // Service classes
182
+ /Controller/i, // Controller classes
183
+ /Repository/i, // Repository classes
184
+ /Interface/i, // TypeScript interfaces
185
+ /Type/i, // Type definitions
186
+ /Event/i, // Event objects
187
+ /Error/i, // Error objects (unless ValidationError)
188
+ /Component/i, // React/Vue components
189
+ /Module/i, // Module definitions
190
+ /Config/i, // Configuration objects
191
+ /Context/i, // Context objects
192
+ /Handler/i, // Event handlers
193
+ /Listener/i, // Event listeners
194
+ /Provider/i, // Providers
195
+ /Factory/i, // Factory patterns
196
+ /Builder/i, // Builder patterns
197
+ /Manager/i, // Manager classes
198
+ /Util/i, // Utility functions
199
+ /Helper/i // Helper functions
200
+ ];
201
+
202
+ // Exclude common false positives
203
+ if (excludePatterns.some(excludePattern => excludePattern.test(pattern))) {
204
+ return false;
205
+ }
206
+
207
+ // Include validation-specific patterns
208
+ const validationIndicators = [
209
+ /validate/i,
210
+ /check.*valid/i,
211
+ /ensure.*valid/i,
212
+ /verify/i,
213
+ /sanitize/i,
214
+ /normalize/i,
215
+ /ValidationError/i,
216
+ /BadRequest/i,
217
+ /InvalidInput/i,
218
+ /zod\./i,
219
+ /joi\./i,
220
+ /yup\./i,
221
+ /\.required\(/i,
222
+ /\.string\(/i,
223
+ /\.email\(/i,
224
+ /\.min\(/i,
225
+ /\.max\(/i
226
+ ];
227
+
228
+ return validationIndicators.some(indicator => indicator.test(pattern));
229
+ }
230
+
231
+ findPotentialDuplicates(matches) {
232
+ const patternCounts = {};
233
+
234
+ matches.forEach(match => {
235
+ const normalized = match.pattern.toLowerCase();
236
+ patternCounts[normalized] = (patternCounts[normalized] || 0) + 1;
237
+ });
238
+
239
+ return Object.keys(patternCounts).filter(pattern => patternCounts[pattern] > 1);
240
+ }
241
+ }
242
+
243
+ module.exports = C040RegexBasedAnalyzer;
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Symbol-based analyzer for C040 - Centralized Validation Logic Analysis
3
+ * Purpose: Use AST + Data Flow to detect scattered validation logic across layers
4
+ */
5
+
6
+ const { SyntaxKind } = require('ts-morph');
7
+
8
+ class C040SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.ruleId = 'C040';
11
+ this.ruleName = 'Centralized Validation Logic (Symbol-Based)';
12
+ this.semanticEngine = semanticEngine;
13
+ this.verbose = false;
14
+
15
+ // Validation patterns to detect
16
+ this.validationPatterns = {
17
+ functionNames: [
18
+ 'validate', 'check', 'ensure', 'verify', 'isValid', 'hasValid',
19
+ 'validateInput', 'checkInput', 'validateData', 'sanitize'
20
+ ],
21
+ frameworks: ['zod', 'joi', 'yup', 'class-validator', 'ajv'],
22
+ errorTypes: ['ValidationError', 'BadRequest', 'InvalidInput', 'TypeError']
23
+ };
24
+
25
+ // Layer detection patterns
26
+ this.layerPatterns = {
27
+ controller: /controller|route|handler/i,
28
+ service: /service|business|logic/i,
29
+ repository: /repository|dao|data/i,
30
+ validator: /validator|validation|schema/i
31
+ };
32
+ }
33
+
34
+ async initialize(semanticEngine = null) {
35
+ if (semanticEngine) {
36
+ this.semanticEngine = semanticEngine;
37
+ }
38
+ this.verbose = semanticEngine?.verbose || false;
39
+
40
+ if (this.verbose) {
41
+ console.log(`[DEBUG] 🔧 C040 Symbol-Based: Analyzer initialized`);
42
+ }
43
+ }
44
+
45
+ async analyze(files, language, options = {}) {
46
+ const violations = [];
47
+
48
+ if (!this.semanticEngine?.project) {
49
+ if (this.verbose) {
50
+ console.warn('[C040 Symbol-Based] No semantic engine available, skipping analysis');
51
+ }
52
+ return violations;
53
+ }
54
+
55
+ for (const filePath of files) {
56
+ try {
57
+ const fileViolations = await this.analyzeFile(filePath);
58
+ violations.push(...fileViolations);
59
+ } catch (error) {
60
+ if (this.verbose) {
61
+ console.warn(`[C040] Analysis failed for ${filePath}:`, error.message);
62
+ }
63
+ }
64
+ }
65
+
66
+ return violations;
67
+ }
68
+
69
+ async analyzeFile(filePath) {
70
+ const violations = [];
71
+
72
+ try {
73
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
74
+ if (!sourceFile) {
75
+ if (this.verbose) {
76
+ console.log(`[C040] Source file not found in project: ${filePath}`);
77
+ }
78
+ return violations;
79
+ }
80
+
81
+ if (this.verbose) {
82
+ console.log(`[C040] Analyzing file: ${filePath}`);
83
+ }
84
+
85
+ // Detect layer from file path
86
+ const layer = this.detectLayer(filePath);
87
+
88
+ // Find validation patterns
89
+ const validationScents = this.findValidationPatterns(sourceFile);
90
+
91
+ if (validationScents.length > 0) {
92
+ // Check if validation is in wrong layer
93
+ if (layer === 'controller' || layer === 'service') {
94
+ const complexValidations = validationScents.filter(v => v.isComplex);
95
+ const businessValidations = complexValidations.filter(v =>
96
+ v.context === 'business-validation'
97
+ );
98
+
99
+ if (businessValidations.length > 0) {
100
+ violations.push({
101
+ ruleId: this.ruleId,
102
+ severity: 'warning',
103
+ message: `Found ${businessValidations.length} complex business validation pattern(s) in ${layer} layer. Consider moving to validators.`,
104
+ file: filePath,
105
+ line: businessValidations[0].line,
106
+ column: businessValidations[0].column,
107
+ details: {
108
+ layer,
109
+ validationCount: businessValidations.length,
110
+ patterns: businessValidations.map(v => v.pattern),
111
+ suggestion: 'Move validation logic to dedicated validator classes',
112
+ ruleName: this.ruleName
113
+ }
114
+ });
115
+ }
116
+ }
117
+ }
118
+
119
+ } catch (error) {
120
+ if (this.verbose) {
121
+ console.warn(`[C040] Error analyzing ${filePath}:`, error.message);
122
+ }
123
+ }
124
+
125
+ return violations;
126
+ }
127
+
128
+ detectLayer(filePath) {
129
+ const path = filePath.toLowerCase();
130
+
131
+ for (const [layer, pattern] of Object.entries(this.layerPatterns)) {
132
+ if (pattern.test(path)) {
133
+ return layer;
134
+ }
135
+ }
136
+
137
+ return 'unknown';
138
+ }
139
+
140
+ findValidationPatterns(sourceFile) {
141
+ const patterns = [];
142
+
143
+ // 1. Find business validation functions (semantic analysis)
144
+ sourceFile.getFunctions().forEach(func => {
145
+ const validationInfo = this.analyzeValidationFunction(func);
146
+ if (validationInfo) {
147
+ patterns.push(validationInfo);
148
+ }
149
+ });
150
+
151
+ // 2. Find class methods with validation logic
152
+ sourceFile.getClasses().forEach(cls => {
153
+ cls.getMethods().forEach(method => {
154
+ const validationInfo = this.analyzeValidationMethod(method, cls);
155
+ if (validationInfo) {
156
+ patterns.push(validationInfo);
157
+ }
158
+ });
159
+ });
160
+
161
+ // 3. Find validation decorators and frameworks usage
162
+ sourceFile.getClasses().forEach(cls => {
163
+ cls.getMethods().forEach(method => {
164
+ const decoratorInfo = this.analyzeValidationDecorators(method);
165
+ if (decoratorInfo) {
166
+ patterns.push(decoratorInfo);
167
+ }
168
+ });
169
+ });
170
+
171
+ return patterns;
172
+ }
173
+
174
+ /**
175
+ * Analyze if a function contains actual business validation logic
176
+ */
177
+ analyzeValidationFunction(func) {
178
+ const name = func.getName();
179
+ const body = func.getBodyText();
180
+
181
+ // Skip if it's infrastructure/utility function
182
+ if (this.isInfrastructureFunction(func, name, body)) {
183
+ return null;
184
+ }
185
+
186
+ // Check for actual validation patterns in body
187
+ const validationSignals = this.countValidationSignals(body);
188
+
189
+ if (validationSignals.score > 2) { // Threshold for real validation
190
+ return {
191
+ type: 'function',
192
+ pattern: name,
193
+ line: func.getStartLineNumber(),
194
+ column: func.getStart(),
195
+ isComplex: validationSignals.score > 5,
196
+ signals: validationSignals.patterns,
197
+ context: 'business-validation'
198
+ };
199
+ }
200
+
201
+ return null;
202
+ }
203
+
204
+ /**
205
+ * Analyze if a method contains business validation logic
206
+ */
207
+ analyzeValidationMethod(method, parentClass) {
208
+ const methodName = method.getName();
209
+ const className = parentClass.getName();
210
+ const body = method.getBodyText();
211
+
212
+ // Skip infrastructure methods
213
+ if (this.isInfrastructureMethod(method, methodName, body, className)) {
214
+ return null;
215
+ }
216
+
217
+ // Check for validation patterns
218
+ const validationSignals = this.countValidationSignals(body);
219
+
220
+ // Balanced threshold to catch meaningful validation
221
+ if (validationSignals.score > 3) {
222
+ return {
223
+ type: 'method',
224
+ pattern: `${className}.${methodName}`,
225
+ line: method.getStartLineNumber(),
226
+ column: method.getStart(),
227
+ isComplex: validationSignals.score > 6,
228
+ signals: validationSignals.patterns,
229
+ context: 'business-validation'
230
+ };
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Check for validation framework decorators
238
+ */
239
+ analyzeValidationDecorators(method) {
240
+ const decorators = method.getDecorators();
241
+ const validationDecorators = decorators.filter(dec => {
242
+ const name = dec.getName();
243
+ return ['IsEmail', 'IsString', 'IsNumber', 'Min', 'Max', 'Length', 'Matches'].includes(name);
244
+ });
245
+
246
+ if (validationDecorators.length > 0) {
247
+ return {
248
+ type: 'decorator',
249
+ pattern: validationDecorators.map(d => d.getName()).join(', '),
250
+ line: method.getStartLineNumber(),
251
+ column: method.getStart(),
252
+ isComplex: true,
253
+ context: 'framework-validation'
254
+ };
255
+ }
256
+
257
+ return null;
258
+ }
259
+
260
+ /**
261
+ * Check if function/method is infrastructure/utility rather than business validation
262
+ */
263
+ isInfrastructureFunction(func, name, body) {
264
+ // Health check patterns
265
+ if (name === 'check' && body.includes('health')) return true;
266
+ if (name.includes('health') || name.includes('Health')) return true;
267
+
268
+ // Crypto/hash utilities
269
+ if (name === 'check' && (body.includes('bcrypt') || body.includes('hash') || body.includes('crypto'))) return true;
270
+ if (name.includes('hash') && body.includes('bcrypt')) return true;
271
+
272
+ // Authentication utilities (not business validation)
273
+ if (name.includes('verify') && body.includes('jwt')) return true;
274
+ if (name.includes('verify') && body.includes('token')) return true;
275
+
276
+ // Simple getters/setters
277
+ if (name.startsWith('get') || name.startsWith('set')) return true;
278
+
279
+ return false;
280
+ }
281
+
282
+ /**
283
+ * Check if method is infrastructure/utility
284
+ */
285
+ isInfrastructureMethod(method, methodName, body, className) {
286
+ // Infrastructure keywords in method/class names
287
+ const infraKeywords = [
288
+ 'health', 'hash', 'auth', 'login', 'register', 'encrypt', 'decrypt',
289
+ 'token', 'session', 'cookie', 'cache', 'log', 'monitor', 'metric',
290
+ 's3', 'aws', 'storage', 'upload', 'download', 'file', 'config',
291
+ 'connection', 'database', 'db', 'migration', 'seed', 'error', 'exception'
292
+ ];
293
+
294
+ const lowerMethodName = methodName.toLowerCase();
295
+ const lowerClassName = className.toLowerCase();
296
+
297
+ // Check method/class names
298
+ if (infraKeywords.some(keyword =>
299
+ lowerMethodName.includes(keyword) || lowerClassName.includes(keyword)
300
+ )) {
301
+ return true;
302
+ }
303
+
304
+ // Controller methods that just delegate (not business validation)
305
+ if (className.toLowerCase().includes('controller')) {
306
+ // Simple delegation patterns
307
+ const delegationPatterns = [
308
+ /return\s+await\s+this\.\w+\.\w+\(/,
309
+ /return\s+this\.\w+\.\w+\(/,
310
+ /^\s*return\s+await/m
311
+ ];
312
+
313
+ if (delegationPatterns.some(pattern => pattern.test(body))) {
314
+ return true;
315
+ }
316
+ }
317
+
318
+ // Check method body for infrastructure patterns
319
+ const infraPatterns = [
320
+ /bcrypt|crypto|jwt/i,
321
+ /\.hash\(|\.compare\(/i,
322
+ /redis|cache/i,
323
+ /aws|s3|bucket/i,
324
+ /winston|logger/i,
325
+ /fileExist|checkExist/i,
326
+ /uploadFile|downloadFile/i,
327
+ /HttpStatus\./i // Pure HTTP status handling
328
+ ];
329
+
330
+ if (infraPatterns.some(pattern => pattern.test(body))) {
331
+ return true;
332
+ }
333
+
334
+ // Call parent function check
335
+ return this.isInfrastructureFunction(method, methodName, body);
336
+ }
337
+
338
+ /**
339
+ * Count actual validation signals in code body using semantic analysis
340
+ */
341
+ countValidationSignals(body) {
342
+ let score = 0;
343
+ const patterns = [];
344
+
345
+ // Business validation patterns (weighted scoring)
346
+ const validationIndicators = [
347
+ // High weight - clear business validation
348
+ { pattern: /throw new.*ValidationError/gi, weight: 5, name: 'ValidationError' },
349
+ { pattern: /throw new.*BadRequest/gi, weight: 4, name: 'BadRequestError' },
350
+ { pattern: /throw new.*InvalidInput/gi, weight: 4, name: 'InvalidInput' },
351
+
352
+ // Medium weight - input validation
353
+ { pattern: /if\s*\(\s*!.*\)\s*{[^}]*throw/gi, weight: 3, name: 'conditional-validation' },
354
+ { pattern: /\.length\s*[<>]=?\s*\d+.*throw/gi, weight: 3, name: 'length-validation' },
355
+ { pattern: /typeof.*!==.*throw/gi, weight: 3, name: 'type-validation' },
356
+
357
+ // Framework validation
358
+ { pattern: /zod\.|joi\.|yup\./gi, weight: 4, name: 'validation-framework' },
359
+ { pattern: /class-validator/gi, weight: 4, name: 'class-validator' },
360
+
361
+ // Business rule validation
362
+ { pattern: /validate[A-Z][a-zA-Z]*\(/gi, weight: 3, name: 'validate-method' },
363
+ { pattern: /check[A-Z][a-zA-Z]*\(/gi, weight: 2, name: 'check-method' },
364
+ { pattern: /ensure[A-Z][a-zA-Z]*\(/gi, weight: 3, name: 'ensure-method' },
365
+
366
+ // Low weight - might be utility
367
+ { pattern: /\.test\(/gi, weight: 1, name: 'regex-test' },
368
+ { pattern: /\.match\(/gi, weight: 1, name: 'string-match' }
369
+ ];
370
+
371
+ validationIndicators.forEach(indicator => {
372
+ const matches = body.match(indicator.pattern);
373
+ if (matches) {
374
+ score += matches.length * indicator.weight;
375
+ patterns.push(`${indicator.name} (${matches.length})`);
376
+ }
377
+ });
378
+
379
+ // Penalty for infrastructure patterns (increased penalties)
380
+ const infrastructurePatterns = [
381
+ { pattern: /bcrypt|crypto|jwt/gi, penalty: -4 },
382
+ { pattern: /health|Health/gi, penalty: -6 },
383
+ { pattern: /\.hash\(|\.compare\(/gi, penalty: -3 },
384
+ { pattern: /HttpStatus\.|\.status\(/gi, penalty: -2 },
385
+ { pattern: /aws|s3|bucket/gi, penalty: -4 },
386
+ { pattern: /fileExist|checkExist/gi, penalty: -3 },
387
+ { pattern: /return\s+await\s+this\./gi, penalty: -2 }, // Delegation pattern
388
+ { pattern: /BadRequest.*S3/gi, penalty: -5 }, // S3 error handling
389
+ { pattern: /error.*message/gi, penalty: -1 } // Generic error handling
390
+ ];
391
+
392
+ infrastructurePatterns.forEach(infra => {
393
+ const matches = body.match(infra.pattern);
394
+ if (matches) {
395
+ score += matches.length * infra.penalty;
396
+ patterns.push(`infrastructure-penalty (${matches.length})`);
397
+ }
398
+ });
399
+
400
+ return { score: Math.max(0, score), patterns };
401
+ }
402
+
403
+ isValidationFunction(name) {
404
+ return this.validationPatterns.functionNames.some(pattern =>
405
+ name.toLowerCase().includes(pattern.toLowerCase())
406
+ );
407
+ }
408
+
409
+ isValidationError(text) {
410
+ return this.validationPatterns.errorTypes.some(errorType =>
411
+ text.includes(errorType)
412
+ );
413
+ }
414
+ }
415
+
416
+ module.exports = C040SymbolBasedAnalyzer;