File size: 13,200 Bytes
ec4aa90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
"""
Code Validator - Validates generated code for common issues.
Catches problems before they reach the sandbox execution phase.
"""

import re
import logging
from typing import Dict, List, Tuple

logger = logging.getLogger(__name__)


class CodeValidator:
    """Validates generated code for common issues and inconsistencies."""
    
    @staticmethod
    def validate_typescript_module_system(source_code: str) -> Tuple[bool, List[str]]:
        """
        Validate that TypeScript code is compatible with Jest/ts-jest (CommonJS).
        
        Args:
            source_code: TypeScript source code
        
        Returns:
            (is_valid, list_of_issues)
        """
        issues = []
        
        # Check for ES module-only features that break Jest/ts-jest
        if 'import.meta' in source_code:
            issues.append(
                "Code uses 'import.meta' which requires ES modules. "
                "Jest/ts-jest uses CommonJS. Remove import.meta usage."
            )
        
        if re.search(r'\btop-level\s+await\b', source_code) or re.search(r'^await\s+', source_code, re.MULTILINE):
            issues.append(
                "Code uses top-level await which requires ES modules. "
                "Jest/ts-jest uses CommonJS. Wrap in async function."
            )
        
        # Check for CLI execution patterns that shouldn't be in library code
        if 'process.argv[1]' in source_code or 'if (require.main === module)' in source_code:
            issues.append(
                "Code includes CLI execution logic. "
                "Library code should not include main execution blocks."
            )
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_typescript_exports(source_code: str, test_code: str) -> Tuple[bool, List[str]]:
        """
        Validate that all TypeScript types/enums/interfaces imported in tests are exported in source.
        
        Args:
            source_code: TypeScript source code
            test_code: TypeScript test code
        
        Returns:
            (is_valid, list_of_issues)
        """
        issues = []
        
        # Extract imports from test code
        import_pattern = r'import\s+\{([^}]+)\}\s+from\s+["\']\./'
        test_imports = re.findall(import_pattern, test_code)
        
        if not test_imports:
            return True, []
        
        # Get all imported names
        imported_names = set()
        for import_group in test_imports:
            names = [name.strip() for name in import_group.split(',')]
            imported_names.update(names)
        
        # Check if each imported name is exported in source
        for name in imported_names:
            # Check for export function/class/enum/interface/type
            export_patterns = [
                rf'export\s+(function|class|enum|interface|type)\s+{name}\b',
                rf'export\s+\{{\s*[^}}]*\b{name}\b[^}}]*\}}',
                rf'export\s+const\s+{name}\s*=',
            ]
            
            is_exported = any(re.search(pattern, source_code) for pattern in export_patterns)
            
            if not is_exported:
                # Check if it's declared but not exported
                declaration_patterns = [
                    rf'\b(function|class|enum|interface|type)\s+{name}\b',
                    rf'\bconst\s+{name}\s*=',
                ]
                is_declared = any(re.search(pattern, source_code) for pattern in declaration_patterns)
                
                if is_declared:
                    issues.append(
                        f"'{name}' is declared in source but not exported. "
                        f"Add 'export' keyword before the declaration."
                    )
                else:
                    issues.append(
                        f"'{name}' is imported in tests but not found in source code."
                    )
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_javascript_exports(source_code: str, test_code: str) -> Tuple[bool, List[str]]:
        """
        Validate that all JavaScript functions/classes imported in tests are exported in source.
        
        Args:
            source_code: JavaScript source code
            test_code: JavaScript test code
        
        Returns:
            (is_valid, list_of_issues)
        """
        issues = []
        
        # Extract imports from test code (ES6 imports)
        import_pattern = r'import\s+\{([^}]+)\}\s+from\s+["\']\./'
        test_imports = re.findall(import_pattern, test_code)
        
        if not test_imports:
            return True, []
        
        # Get all imported names
        imported_names = set()
        for import_group in test_imports:
            names = [name.strip() for name in import_group.split(',')]
            imported_names.update(names)
        
        # Check if each imported name is exported in source
        for name in imported_names:
            # Check for various export patterns
            export_patterns = [
                rf'export\s+(function|class|const|let|var)\s+{name}\b',
                rf'export\s+\{{\s*[^}}]*\b{name}\b[^}}]*\}}',
                rf'module\.exports\s*=\s*\{{[^}}]*\b{name}\b[^}}]*\}}',
                rf'exports\.{name}\s*=',
            ]
            
            is_exported = any(re.search(pattern, source_code) for pattern in export_patterns)
            
            if not is_exported:
                issues.append(
                    f"'{name}' is imported in tests but not exported in source. "
                    f"Add it to the export statement."
                )
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_python_imports(source_code: str, test_code: str) -> Tuple[bool, List[str]]:
        """
        Validate that all Python functions/classes imported in tests exist in source.
        
        Args:
            source_code: Python source code
            test_code: Python test code
        
        Returns:
            (is_valid, list_of_issues)
        """
        issues = []
        
        # Extract imports from test code
        import_patterns = [
            r'from\s+\w+\s+import\s+([^#\n]+)',
            r'import\s+(\w+)',
        ]
        
        imported_names = set()
        for pattern in import_patterns:
            matches = re.findall(pattern, test_code)
            for match in matches:
                names = [name.strip() for name in match.split(',')]
                imported_names.update(names)
        
        # Check if each imported name is defined in source
        for name in imported_names:
            # Check for function/class definitions
            definition_patterns = [
                rf'^def\s+{name}\s*\(',
                rf'^class\s+{name}\b',
                rf'^{name}\s*=',
            ]
            
            is_defined = any(re.search(pattern, source_code, re.MULTILINE) for pattern in definition_patterns)
            
            if not is_defined:
                issues.append(
                    f"'{name}' is imported in tests but not defined in source code."
                )
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_code(source_code: str, test_code: str, language: str) -> Tuple[bool, List[str]]:
        """
        Validate code based on language.
        
        Args:
            source_code: Source code
            test_code: Test code
            language: Programming language
        
        Returns:
            (is_valid, list_of_issues)
        """
        language = language.lower()
        all_issues = []
        
        if language == 'typescript':
            # Check module system compatibility
            is_valid_module, module_issues = CodeValidator.validate_typescript_module_system(source_code)
            all_issues.extend(module_issues)
            
            # Check exports
            is_valid_exports, export_issues = CodeValidator.validate_typescript_exports(source_code, test_code)
            all_issues.extend(export_issues)
            
            return len(all_issues) == 0, all_issues
        elif language == 'javascript':
            return CodeValidator.validate_javascript_exports(source_code, test_code)
        elif language == 'python':
            return CodeValidator.validate_python_imports(source_code, test_code)
        else:
            # No validation for other languages yet
            return True, []
    
    @staticmethod
    def auto_fix_typescript_module_system(source_code: str) -> str:
        """
        Remove ES module-only features that break Jest/ts-jest.
        
        Args:
            source_code: TypeScript source code
        
        Returns:
            Fixed source code
        """
        fixed_code = source_code
        
        # Remove import.meta usage and related code
        if 'import.meta' in fixed_code:
            # Remove the entire CLI execution block that uses import.meta
            # Pattern: from import statement to the end of the if block
            pattern = r'\n// Modern ES module.*?\n.*?import.*?from [\'"]url[\'"];.*?\n.*?import.*?from [\'"]path[\'"];.*?\n\nconst __filename.*?import\.meta\.url\);.*?\n.*?if \(process\.argv\[1\].*?\{.*?\n.*?\n.*?\n\}'
            fixed_code = re.sub(pattern, '', fixed_code, flags=re.DOTALL)
            
            # Fallback: remove just the import.meta line
            if 'import.meta' in fixed_code:
                fixed_code = re.sub(r'.*import\.meta.*\n', '', fixed_code)
            
            logger.info("Auto-fixed: Removed import.meta usage")
        
        # Remove CLI execution patterns
        if 'process.argv[1]' in fixed_code:
            # Remove if (process.argv[1] === __filename) blocks
            pattern = r'\nif \(process\.argv\[1\].*?\{[^}]*\}'
            fixed_code = re.sub(pattern, '', fixed_code, flags=re.DOTALL)
            logger.info("Auto-fixed: Removed CLI execution block")
        
        return fixed_code
    
    @staticmethod
    def auto_fix_typescript_exports(source_code: str, missing_exports: List[str]) -> str:
        """
        Automatically add export keywords to TypeScript declarations.
        
        Args:
            source_code: TypeScript source code
            missing_exports: List of names that need to be exported
        
        Returns:
            Fixed source code
        """
        fixed_code = source_code
        
        for name in missing_exports:
            # Try to add export keyword before declaration
            patterns = [
                (rf'(\n)(enum\s+{name}\b)', r'\1export \2'),
                (rf'(\n)(interface\s+{name}\b)', r'\1export \2'),
                (rf'(\n)(type\s+{name}\b)', r'\1export \2'),
                (rf'(\n)(class\s+{name}\b)', r'\1export \2'),
                (rf'(\n)(function\s+{name}\b)', r'\1export \2'),
                (rf'(\n)(const\s+{name}\s*=)', r'\1export \2'),
            ]
            
            for pattern, replacement in patterns:
                new_code = re.sub(pattern, replacement, fixed_code)
                if new_code != fixed_code:
                    logger.info(f"Auto-fixed: Added 'export' to '{name}'")
                    fixed_code = new_code
                    break
        
        return fixed_code


def validate_and_fix_code(source_code: str, test_code: str, language: str) -> Tuple[str, bool, List[str]]:
    """
    Validate code and attempt to auto-fix common issues.
    
    Args:
        source_code: Source code
        test_code: Test code
        language: Programming language
    
    Returns:
        (fixed_source_code, is_valid, list_of_remaining_issues)
    """
    validator = CodeValidator()
    is_valid, issues = validator.validate_code(source_code, test_code, language)
    
    if not is_valid and language.lower() == 'typescript':
        fixed_code = source_code
        
        # Auto-fix module system issues (import.meta, etc.)
        module_issues = [issue for issue in issues if 'import.meta' in issue or 'top-level await' in issue or 'CLI execution' in issue]
        if module_issues:
            logger.info(f"Attempting to auto-fix {len(module_issues)} module system issues")
            fixed_code = validator.auto_fix_typescript_module_system(fixed_code)
        
        # Auto-fix export issues
        missing_names = []
        for issue in issues:
            # Extract name from issue message
            match = re.search(r"'(\w+)'", issue)
            if match and "not exported" in issue:
                missing_names.append(match.group(1))
        
        if missing_names:
            logger.info(f"Attempting to auto-fix {len(missing_names)} export issues")
            fixed_code = validator.auto_fix_typescript_exports(fixed_code, missing_names)
        
        # Re-validate if we made any fixes
        if fixed_code != source_code:
            is_valid, issues = validator.validate_code(fixed_code, test_code, language)
            return fixed_code, is_valid, issues
    
    return source_code, is_valid, issues