@loxia-labs/loxia-autopilot-one 1.0.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.
- package/LICENSE +267 -0
- package/README.md +509 -0
- package/bin/cli.js +117 -0
- package/package.json +94 -0
- package/scripts/install-scanners.js +236 -0
- package/src/analyzers/CSSAnalyzer.js +297 -0
- package/src/analyzers/ConfigValidator.js +690 -0
- package/src/analyzers/ESLintAnalyzer.js +320 -0
- package/src/analyzers/JavaScriptAnalyzer.js +261 -0
- package/src/analyzers/PrettierFormatter.js +247 -0
- package/src/analyzers/PythonAnalyzer.js +266 -0
- package/src/analyzers/SecurityAnalyzer.js +729 -0
- package/src/analyzers/TypeScriptAnalyzer.js +247 -0
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
- package/src/analyzers/codeCloneDetector/detector.js +203 -0
- package/src/analyzers/codeCloneDetector/index.js +160 -0
- package/src/analyzers/codeCloneDetector/parser.js +199 -0
- package/src/analyzers/codeCloneDetector/reporter.js +148 -0
- package/src/analyzers/codeCloneDetector/scanner.js +59 -0
- package/src/core/agentPool.js +1474 -0
- package/src/core/agentScheduler.js +2147 -0
- package/src/core/contextManager.js +709 -0
- package/src/core/messageProcessor.js +732 -0
- package/src/core/orchestrator.js +548 -0
- package/src/core/stateManager.js +877 -0
- package/src/index.js +631 -0
- package/src/interfaces/cli.js +549 -0
- package/src/interfaces/webServer.js +2162 -0
- package/src/modules/fileExplorer/controller.js +280 -0
- package/src/modules/fileExplorer/index.js +37 -0
- package/src/modules/fileExplorer/middleware.js +92 -0
- package/src/modules/fileExplorer/routes.js +125 -0
- package/src/modules/fileExplorer/types.js +44 -0
- package/src/services/aiService.js +1232 -0
- package/src/services/apiKeyManager.js +164 -0
- package/src/services/benchmarkService.js +366 -0
- package/src/services/budgetService.js +539 -0
- package/src/services/contextInjectionService.js +247 -0
- package/src/services/conversationCompactionService.js +637 -0
- package/src/services/errorHandler.js +810 -0
- package/src/services/fileAttachmentService.js +544 -0
- package/src/services/modelRouterService.js +366 -0
- package/src/services/modelsService.js +322 -0
- package/src/services/qualityInspector.js +796 -0
- package/src/services/tokenCountingService.js +536 -0
- package/src/tools/agentCommunicationTool.js +1344 -0
- package/src/tools/agentDelayTool.js +485 -0
- package/src/tools/asyncToolManager.js +604 -0
- package/src/tools/baseTool.js +800 -0
- package/src/tools/browserTool.js +920 -0
- package/src/tools/cloneDetectionTool.js +621 -0
- package/src/tools/dependencyResolverTool.js +1215 -0
- package/src/tools/fileContentReplaceTool.js +875 -0
- package/src/tools/fileSystemTool.js +1107 -0
- package/src/tools/fileTreeTool.js +853 -0
- package/src/tools/imageTool.js +901 -0
- package/src/tools/importAnalyzerTool.js +1060 -0
- package/src/tools/jobDoneTool.js +248 -0
- package/src/tools/seekTool.js +956 -0
- package/src/tools/staticAnalysisTool.js +1778 -0
- package/src/tools/taskManagerTool.js +2873 -0
- package/src/tools/terminalTool.js +2304 -0
- package/src/tools/webTool.js +1430 -0
- package/src/types/agent.js +519 -0
- package/src/types/contextReference.js +972 -0
- package/src/types/conversation.js +730 -0
- package/src/types/toolCommand.js +747 -0
- package/src/utilities/attachmentValidator.js +292 -0
- package/src/utilities/configManager.js +582 -0
- package/src/utilities/constants.js +722 -0
- package/src/utilities/directoryAccessManager.js +535 -0
- package/src/utilities/fileProcessor.js +307 -0
- package/src/utilities/logger.js +436 -0
- package/src/utilities/tagParser.js +1246 -0
- package/src/utilities/toolConstants.js +317 -0
- package/web-ui/build/index.html +15 -0
- package/web-ui/build/logo.png +0 -0
- package/web-ui/build/logo2.png +0 -0
- package/web-ui/build/static/index-CjkkcnFA.js +344 -0
- package/web-ui/build/static/index-Dy2bYbOa.css +1 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLintAnalyzer - JavaScript/TypeScript code style analysis using ESLint
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Analyze code style and quality issues
|
|
6
|
+
* - Detect best practice violations
|
|
7
|
+
* - Support auto-fix for fixable issues
|
|
8
|
+
* - Integration with popular ESLint configurations
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ESLint } from 'eslint';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
import { STATIC_ANALYSIS } from '../utilities/constants.js';
|
|
15
|
+
|
|
16
|
+
class ESLintAnalyzer {
|
|
17
|
+
constructor(logger = null) {
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
this.eslintCache = new Map();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Analyze code with ESLint
|
|
24
|
+
* @param {string} filePath - Path to file
|
|
25
|
+
* @param {string} content - File content
|
|
26
|
+
* @param {Object} options - Analysis options
|
|
27
|
+
* @returns {Promise<Array>} Array of diagnostics
|
|
28
|
+
*/
|
|
29
|
+
async analyze(filePath, content, options = {}) {
|
|
30
|
+
try {
|
|
31
|
+
const eslint = await this.getESLintInstance(options);
|
|
32
|
+
|
|
33
|
+
// Analyze the code
|
|
34
|
+
const results = await eslint.lintText(content, {
|
|
35
|
+
filePath,
|
|
36
|
+
warnIgnored: false
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Format results
|
|
40
|
+
const diagnostics = [];
|
|
41
|
+
|
|
42
|
+
if (results && results.length > 0) {
|
|
43
|
+
const result = results[0];
|
|
44
|
+
|
|
45
|
+
for (const message of result.messages) {
|
|
46
|
+
diagnostics.push(this.formatMessage(message, filePath));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.logger?.debug('ESLint analysis completed', {
|
|
51
|
+
file: filePath,
|
|
52
|
+
totalDiagnostics: diagnostics.length,
|
|
53
|
+
errors: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR).length,
|
|
54
|
+
warnings: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING).length
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return diagnostics;
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
this.logger?.error('ESLint analysis failed', {
|
|
61
|
+
file: filePath,
|
|
62
|
+
error: error.message
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Return empty array on error to allow TypeScript analyzer to continue
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Auto-fix code issues
|
|
72
|
+
* @param {string} filePath - Path to file
|
|
73
|
+
* @param {string} content - File content
|
|
74
|
+
* @param {Object} options - Fix options
|
|
75
|
+
* @returns {Promise<Object>} Fixed content and changes
|
|
76
|
+
*/
|
|
77
|
+
async fix(filePath, content, options = {}) {
|
|
78
|
+
try {
|
|
79
|
+
const eslint = await this.getESLintInstance({
|
|
80
|
+
...options,
|
|
81
|
+
fix: true // Enable auto-fix
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Fix the code
|
|
85
|
+
const results = await eslint.lintText(content, {
|
|
86
|
+
filePath,
|
|
87
|
+
warnIgnored: false
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (results && results.length > 0) {
|
|
91
|
+
const result = results[0];
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
fixed: result.output !== undefined,
|
|
95
|
+
content: result.output || content,
|
|
96
|
+
fixedCount: result.fixableErrorCount + result.fixableWarningCount,
|
|
97
|
+
remainingErrors: result.errorCount - result.fixableErrorCount,
|
|
98
|
+
remainingWarnings: result.warningCount - result.fixableWarningCount,
|
|
99
|
+
changes: result.output ? this.describeChanges(content, result.output) : []
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
fixed: false,
|
|
105
|
+
content,
|
|
106
|
+
fixedCount: 0,
|
|
107
|
+
remainingErrors: 0,
|
|
108
|
+
remainingWarnings: 0,
|
|
109
|
+
changes: []
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.logger?.error('ESLint fix failed', {
|
|
114
|
+
file: filePath,
|
|
115
|
+
error: error.message
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
throw new Error(`ESLint fix failed: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get or create ESLint instance
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
async getESLintInstance(options = {}) {
|
|
127
|
+
const {
|
|
128
|
+
workingDir,
|
|
129
|
+
fix = false,
|
|
130
|
+
framework
|
|
131
|
+
} = options;
|
|
132
|
+
|
|
133
|
+
// Create ESLint instance with configuration
|
|
134
|
+
const eslintConfig = await this.getESLintConfig(workingDir, framework);
|
|
135
|
+
|
|
136
|
+
const eslint = new ESLint({
|
|
137
|
+
fix,
|
|
138
|
+
useEslintrc: true, // Use .eslintrc if available
|
|
139
|
+
overrideConfig: eslintConfig,
|
|
140
|
+
errorOnUnmatchedPattern: false
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return eslint;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get ESLint configuration
|
|
148
|
+
* @private
|
|
149
|
+
*/
|
|
150
|
+
async getESLintConfig(workingDir, framework) {
|
|
151
|
+
// Base configuration
|
|
152
|
+
const config = {
|
|
153
|
+
env: {
|
|
154
|
+
browser: true,
|
|
155
|
+
es2021: true,
|
|
156
|
+
node: true
|
|
157
|
+
},
|
|
158
|
+
parserOptions: {
|
|
159
|
+
ecmaVersion: 'latest',
|
|
160
|
+
sourceType: 'module'
|
|
161
|
+
},
|
|
162
|
+
rules: {
|
|
163
|
+
// Common rules
|
|
164
|
+
'no-unused-vars': 'warn',
|
|
165
|
+
'no-undef': 'error',
|
|
166
|
+
'no-console': 'off',
|
|
167
|
+
'semi': ['warn', 'always'],
|
|
168
|
+
'quotes': ['warn', 'single', { avoidEscape: true }]
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Framework-specific configurations
|
|
173
|
+
if (framework === 'react') {
|
|
174
|
+
config.extends = ['eslint:recommended'];
|
|
175
|
+
config.parserOptions.ecmaFeatures = { jsx: true };
|
|
176
|
+
config.settings = {
|
|
177
|
+
react: {
|
|
178
|
+
version: 'detect'
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
} else if (framework === 'vue') {
|
|
182
|
+
config.extends = ['eslint:recommended'];
|
|
183
|
+
} else {
|
|
184
|
+
config.extends = ['eslint:recommended'];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Try to detect and use project's ESLint config
|
|
188
|
+
if (workingDir) {
|
|
189
|
+
const configFiles = [
|
|
190
|
+
'.eslintrc.js',
|
|
191
|
+
'.eslintrc.cjs',
|
|
192
|
+
'.eslintrc.json',
|
|
193
|
+
'eslint.config.js'
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
for (const configFile of configFiles) {
|
|
197
|
+
try {
|
|
198
|
+
const configPath = path.join(workingDir, configFile);
|
|
199
|
+
await fs.access(configPath);
|
|
200
|
+
this.logger?.debug('Found ESLint config', { configFile });
|
|
201
|
+
// If config exists, let ESLint use it via useEslintrc
|
|
202
|
+
return {}; // Return empty config to let ESLint use the file
|
|
203
|
+
} catch {
|
|
204
|
+
// Config file doesn't exist, continue
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return config;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Format ESLint message to standard diagnostic format
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
formatMessage(message, filePath) {
|
|
217
|
+
return {
|
|
218
|
+
file: filePath,
|
|
219
|
+
line: message.line || 1,
|
|
220
|
+
column: message.column || 1,
|
|
221
|
+
endLine: message.endLine,
|
|
222
|
+
endColumn: message.endColumn,
|
|
223
|
+
severity: message.severity === 2
|
|
224
|
+
? STATIC_ANALYSIS.SEVERITY.ERROR
|
|
225
|
+
: STATIC_ANALYSIS.SEVERITY.WARNING,
|
|
226
|
+
rule: message.ruleId || 'eslint',
|
|
227
|
+
message: message.message,
|
|
228
|
+
category: this.categorizeRule(message.ruleId),
|
|
229
|
+
fixable: message.fix !== undefined,
|
|
230
|
+
source: 'eslint'
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Categorize ESLint rule into error category
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
categorizeRule(ruleId) {
|
|
239
|
+
if (!ruleId) return STATIC_ANALYSIS.CATEGORY.STYLE;
|
|
240
|
+
|
|
241
|
+
// Security rules
|
|
242
|
+
if (ruleId.includes('security') ||
|
|
243
|
+
ruleId.includes('xss') ||
|
|
244
|
+
ruleId === 'no-eval' ||
|
|
245
|
+
ruleId === 'no-implied-eval') {
|
|
246
|
+
return STATIC_ANALYSIS.CATEGORY.SECURITY;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Performance rules
|
|
250
|
+
if (ruleId.includes('performance') ||
|
|
251
|
+
ruleId === 'no-await-in-loop' ||
|
|
252
|
+
ruleId === 'prefer-promise-reject-errors') {
|
|
253
|
+
return STATIC_ANALYSIS.CATEGORY.PERFORMANCE;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Import rules
|
|
257
|
+
if (ruleId.includes('import') ||
|
|
258
|
+
ruleId === 'no-undef') {
|
|
259
|
+
return STATIC_ANALYSIS.CATEGORY.IMPORT;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Best practice rules
|
|
263
|
+
if (ruleId.includes('best-practices') ||
|
|
264
|
+
ruleId === 'no-unused-vars' ||
|
|
265
|
+
ruleId === 'no-unreachable' ||
|
|
266
|
+
ruleId === 'no-var') {
|
|
267
|
+
return STATIC_ANALYSIS.CATEGORY.BEST_PRACTICE;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Default to style
|
|
271
|
+
return STATIC_ANALYSIS.CATEGORY.STYLE;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Describe changes made by auto-fix
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
describeChanges(original, fixed) {
|
|
279
|
+
const changes = [];
|
|
280
|
+
const originalLines = original.split('\n');
|
|
281
|
+
const fixedLines = fixed.split('\n');
|
|
282
|
+
|
|
283
|
+
// Simple diff - compare line by line
|
|
284
|
+
const maxLines = Math.max(originalLines.length, fixedLines.length);
|
|
285
|
+
|
|
286
|
+
for (let i = 0; i < maxLines; i++) {
|
|
287
|
+
const origLine = originalLines[i] || '';
|
|
288
|
+
const fixedLine = fixedLines[i] || '';
|
|
289
|
+
|
|
290
|
+
if (origLine !== fixedLine) {
|
|
291
|
+
changes.push({
|
|
292
|
+
line: i + 1,
|
|
293
|
+
type: origLine && fixedLine ? 'modified' : (origLine ? 'removed' : 'added'),
|
|
294
|
+
original: origLine,
|
|
295
|
+
fixed: fixedLine
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return changes;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get supported file extensions
|
|
305
|
+
* @returns {Array<string>} Array of supported extensions
|
|
306
|
+
*/
|
|
307
|
+
getSupportedExtensions() {
|
|
308
|
+
return ['.js', '.jsx', '.mjs', '.cjs'];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if auto-fix is supported
|
|
313
|
+
* @returns {boolean} True if auto-fix is supported
|
|
314
|
+
*/
|
|
315
|
+
supportsAutoFix() {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export default ESLintAnalyzer;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScriptAnalyzer - Analyze JavaScript files for errors using TypeScript Compiler API
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Analyze JavaScript files for syntax, type, and semantic errors
|
|
6
|
+
* - Use TypeScript Compiler API with allowJs for JavaScript analysis
|
|
7
|
+
* - Provide detailed error information with line numbers
|
|
8
|
+
* - Support JSX syntax
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import ts from 'typescript';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { STATIC_ANALYSIS } from '../utilities/constants.js';
|
|
14
|
+
|
|
15
|
+
class JavaScriptAnalyzer {
|
|
16
|
+
constructor(logger = null) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.compilerOptions = {
|
|
19
|
+
allowJs: true,
|
|
20
|
+
checkJs: true,
|
|
21
|
+
noEmit: true,
|
|
22
|
+
jsx: ts.JsxEmit.React,
|
|
23
|
+
target: ts.ScriptTarget.Latest,
|
|
24
|
+
module: ts.ModuleKind.ESNext,
|
|
25
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
26
|
+
esModuleInterop: true,
|
|
27
|
+
skipLibCheck: true,
|
|
28
|
+
strict: false, // Don't be too strict for JavaScript
|
|
29
|
+
noImplicitAny: false // Allow implicit any in JavaScript
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Analyze JavaScript file for errors
|
|
35
|
+
* @param {string} filePath - Path to file
|
|
36
|
+
* @param {string} content - File content
|
|
37
|
+
* @param {Object} options - Analysis options
|
|
38
|
+
* @returns {Promise<Array>} Array of diagnostic errors
|
|
39
|
+
*/
|
|
40
|
+
async analyze(filePath, content, options = {}) {
|
|
41
|
+
try {
|
|
42
|
+
const diagnostics = [];
|
|
43
|
+
|
|
44
|
+
// Create in-memory source file
|
|
45
|
+
const sourceFile = ts.createSourceFile(
|
|
46
|
+
filePath,
|
|
47
|
+
content,
|
|
48
|
+
ts.ScriptTarget.Latest,
|
|
49
|
+
true // setParentNodes
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Get syntactic diagnostics (syntax errors)
|
|
53
|
+
const syntacticDiagnostics = this.getSyntacticDiagnostics(sourceFile);
|
|
54
|
+
diagnostics.push(...syntacticDiagnostics);
|
|
55
|
+
|
|
56
|
+
// Get semantic diagnostics (type errors, undefined variables, etc.)
|
|
57
|
+
// For JavaScript, semantic analysis is limited but still useful
|
|
58
|
+
const semanticDiagnostics = await this.getSemanticDiagnostics(filePath, content);
|
|
59
|
+
diagnostics.push(...semanticDiagnostics);
|
|
60
|
+
|
|
61
|
+
this.logger?.debug('JavaScript analysis completed', {
|
|
62
|
+
file: filePath,
|
|
63
|
+
totalDiagnostics: diagnostics.length,
|
|
64
|
+
errors: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR).length,
|
|
65
|
+
warnings: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING).length
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return diagnostics;
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.logger?.error('JavaScript analysis failed', {
|
|
72
|
+
file: filePath,
|
|
73
|
+
error: error.message
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return [{
|
|
77
|
+
file: filePath,
|
|
78
|
+
line: 0,
|
|
79
|
+
column: 0,
|
|
80
|
+
severity: STATIC_ANALYSIS.SEVERITY.ERROR,
|
|
81
|
+
rule: 'analyzer-error',
|
|
82
|
+
message: `Analysis failed: ${error.message}`,
|
|
83
|
+
category: STATIC_ANALYSIS.CATEGORY.SYNTAX
|
|
84
|
+
}];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get syntactic diagnostics (syntax errors)
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
getSyntacticDiagnostics(sourceFile) {
|
|
93
|
+
const diagnostics = [];
|
|
94
|
+
|
|
95
|
+
// Walk the AST to find syntax errors
|
|
96
|
+
const visit = (node) => {
|
|
97
|
+
// Check for syntax errors
|
|
98
|
+
if (node.kind === ts.SyntaxKind.Unknown) {
|
|
99
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
100
|
+
diagnostics.push({
|
|
101
|
+
file: sourceFile.fileName,
|
|
102
|
+
line: position.line + 1, // 1-indexed
|
|
103
|
+
column: position.character + 1,
|
|
104
|
+
severity: STATIC_ANALYSIS.SEVERITY.ERROR,
|
|
105
|
+
rule: 'syntax-error',
|
|
106
|
+
message: 'Syntax error',
|
|
107
|
+
category: STATIC_ANALYSIS.CATEGORY.SYNTAX,
|
|
108
|
+
fixable: false
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ts.forEachChild(node, visit);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
visit(sourceFile);
|
|
116
|
+
|
|
117
|
+
// Also check for parsing errors
|
|
118
|
+
if (sourceFile.parseDiagnostics && sourceFile.parseDiagnostics.length > 0) {
|
|
119
|
+
for (const diagnostic of sourceFile.parseDiagnostics) {
|
|
120
|
+
diagnostics.push(this.formatDiagnostic(diagnostic, sourceFile));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return diagnostics;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get semantic diagnostics (type errors, undefined variables)
|
|
129
|
+
* @private
|
|
130
|
+
*/
|
|
131
|
+
async getSemanticDiagnostics(filePath, content) {
|
|
132
|
+
const diagnostics = [];
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
// Create a minimal program for semantic analysis
|
|
136
|
+
const sourceFile = ts.createSourceFile(
|
|
137
|
+
filePath,
|
|
138
|
+
content,
|
|
139
|
+
ts.ScriptTarget.Latest,
|
|
140
|
+
true
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Create a compiler host that provides files from memory
|
|
144
|
+
const host = {
|
|
145
|
+
getSourceFile: (fileName) => {
|
|
146
|
+
if (fileName === filePath) {
|
|
147
|
+
return sourceFile;
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
},
|
|
151
|
+
getDefaultLibFileName: () => 'lib.d.ts',
|
|
152
|
+
writeFile: () => {},
|
|
153
|
+
getCurrentDirectory: () => path.dirname(filePath),
|
|
154
|
+
getDirectories: () => [],
|
|
155
|
+
fileExists: (fileName) => fileName === filePath,
|
|
156
|
+
readFile: (fileName) => {
|
|
157
|
+
if (fileName === filePath) {
|
|
158
|
+
return content;
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
},
|
|
162
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
163
|
+
useCaseSensitiveFileNames: () => true,
|
|
164
|
+
getNewLine: () => '\n'
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Create program
|
|
168
|
+
const program = ts.createProgram({
|
|
169
|
+
rootNames: [filePath],
|
|
170
|
+
options: this.compilerOptions,
|
|
171
|
+
host
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Get semantic diagnostics
|
|
175
|
+
const tsDiagnostics = program.getSemanticDiagnostics(sourceFile);
|
|
176
|
+
|
|
177
|
+
for (const diagnostic of tsDiagnostics) {
|
|
178
|
+
diagnostics.push(this.formatDiagnostic(diagnostic, sourceFile));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
} catch (error) {
|
|
182
|
+
this.logger?.debug('Semantic analysis skipped', {
|
|
183
|
+
file: filePath,
|
|
184
|
+
reason: error.message
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return diagnostics;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Format TypeScript diagnostic to standard error format
|
|
193
|
+
* @private
|
|
194
|
+
*/
|
|
195
|
+
formatDiagnostic(diagnostic, sourceFile) {
|
|
196
|
+
let line = 0;
|
|
197
|
+
let column = 0;
|
|
198
|
+
|
|
199
|
+
if (diagnostic.file && diagnostic.start !== undefined) {
|
|
200
|
+
const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
201
|
+
line = position.line + 1; // 1-indexed
|
|
202
|
+
column = position.character + 1;
|
|
203
|
+
} else if (sourceFile && diagnostic.start !== undefined) {
|
|
204
|
+
const position = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
|
|
205
|
+
line = position.line + 1;
|
|
206
|
+
column = position.character + 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
210
|
+
|
|
211
|
+
// Determine severity
|
|
212
|
+
let severity;
|
|
213
|
+
switch (diagnostic.category) {
|
|
214
|
+
case ts.DiagnosticCategory.Error:
|
|
215
|
+
severity = STATIC_ANALYSIS.SEVERITY.ERROR;
|
|
216
|
+
break;
|
|
217
|
+
case ts.DiagnosticCategory.Warning:
|
|
218
|
+
severity = STATIC_ANALYSIS.SEVERITY.WARNING;
|
|
219
|
+
break;
|
|
220
|
+
case ts.DiagnosticCategory.Message:
|
|
221
|
+
case ts.DiagnosticCategory.Suggestion:
|
|
222
|
+
severity = STATIC_ANALYSIS.SEVERITY.INFO;
|
|
223
|
+
break;
|
|
224
|
+
default:
|
|
225
|
+
severity = STATIC_ANALYSIS.SEVERITY.ERROR;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Determine category
|
|
229
|
+
let category = STATIC_ANALYSIS.CATEGORY.SYNTAX;
|
|
230
|
+
const code = diagnostic.code;
|
|
231
|
+
|
|
232
|
+
// Categorize based on error code
|
|
233
|
+
if (code >= 2000 && code < 3000) {
|
|
234
|
+
category = STATIC_ANALYSIS.CATEGORY.TYPE;
|
|
235
|
+
} else if (code >= 1000 && code < 2000) {
|
|
236
|
+
category = STATIC_ANALYSIS.CATEGORY.SYNTAX;
|
|
237
|
+
} else if (code >= 2300 && code < 2400) {
|
|
238
|
+
category = STATIC_ANALYSIS.CATEGORY.IMPORT;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check if message indicates import error
|
|
242
|
+
if (message.toLowerCase().includes('cannot find module') ||
|
|
243
|
+
message.toLowerCase().includes('import')) {
|
|
244
|
+
category = STATIC_ANALYSIS.CATEGORY.IMPORT;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
file: diagnostic.file?.fileName || sourceFile?.fileName || 'unknown',
|
|
249
|
+
line,
|
|
250
|
+
column,
|
|
251
|
+
severity,
|
|
252
|
+
rule: `TS${code}`,
|
|
253
|
+
message,
|
|
254
|
+
category,
|
|
255
|
+
fixable: false, // TypeScript doesn't provide fix information easily
|
|
256
|
+
code: diagnostic.code
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default JavaScriptAnalyzer;
|