onbuzz 3.6.1 → 3.6.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.
- package/package.json +1 -1
- package/src/__test-utils__/fixtures/malformedJson.js +31 -0
- package/src/__test-utils__/globalSetup.js +9 -0
- package/src/__test-utils__/globalTeardown.js +12 -0
- package/src/__test-utils__/mockFactories.js +101 -0
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -0
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -0
- package/src/analyzers/__tests__/ESLintAnalyzer.test.js +271 -0
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -0
- package/src/analyzers/__tests__/PrettierFormatter.test.js +197 -0
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +208 -0
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -0
- package/src/analyzers/__tests__/SparrowAnalyzer.test.js +270 -0
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -0
- package/src/core/__tests__/agentPool.test.js +601 -0
- package/src/core/__tests__/agentScheduler.test.js +576 -0
- package/src/core/__tests__/contextManager.test.js +252 -0
- package/src/core/__tests__/flowExecutor.test.js +262 -0
- package/src/core/__tests__/messageProcessor.test.js +627 -0
- package/src/core/__tests__/orchestrator.test.js +257 -0
- package/src/core/__tests__/stateManager.test.js +375 -0
- package/src/core/agentPool.js +11 -1
- package/src/index.js +25 -9
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +3 -5
- package/src/services/__tests__/agentActivityService.test.js +319 -0
- package/src/services/__tests__/apiKeyManager.test.js +206 -0
- package/src/services/__tests__/benchmarkService.test.js +184 -0
- package/src/services/__tests__/budgetService.test.js +211 -0
- package/src/services/__tests__/contextInjectionService.test.js +205 -0
- package/src/services/__tests__/conversationCompactionService.test.js +280 -0
- package/src/services/__tests__/credentialVault.test.js +469 -0
- package/src/services/__tests__/errorHandler.test.js +314 -0
- package/src/services/__tests__/fileAttachmentService.test.js +278 -0
- package/src/services/__tests__/flowContextService.test.js +199 -0
- package/src/services/__tests__/memoryService.test.js +450 -0
- package/src/services/__tests__/modelRouterService.test.js +388 -0
- package/src/services/__tests__/modelsService.test.js +261 -0
- package/src/services/__tests__/portRegistry.test.js +123 -0
- package/src/services/__tests__/projectDetector.test.js +34 -0
- package/src/services/__tests__/promptService.test.js +242 -0
- package/src/services/__tests__/qualityInspector.test.js +97 -0
- package/src/services/__tests__/scheduleService.test.js +308 -0
- package/src/services/__tests__/serviceRegistry.test.js +74 -0
- package/src/services/__tests__/skillsService.test.js +402 -0
- package/src/services/__tests__/tokenCountingService.test.js +48 -0
- package/src/tools/__tests__/agentCommunicationTool.test.js +500 -0
- package/src/tools/__tests__/agentDelayTool.test.js +342 -0
- package/src/tools/__tests__/asyncToolManager.test.js +344 -0
- package/src/tools/__tests__/baseTool.test.js +420 -0
- package/src/tools/__tests__/codeMapTool.test.js +348 -0
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -0
- package/src/tools/__tests__/fileTreeTool.test.js +274 -0
- package/src/tools/__tests__/filesystemTool.test.js +717 -0
- package/src/tools/__tests__/helpTool.test.js +204 -0
- package/src/tools/__tests__/jobDoneTool.test.js +296 -0
- package/src/tools/__tests__/memoryTool.test.js +297 -0
- package/src/tools/__tests__/seekTool.test.js +282 -0
- package/src/tools/__tests__/skillsTool.test.js +226 -0
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -0
- package/src/tools/__tests__/taskManagerTool.test.js +725 -0
- package/src/tools/__tests__/terminalTool.test.js +384 -0
- package/src/tools/__tests__/userPromptTool.test.js +297 -0
- package/src/tools/__tests__/webTool.e2e.test.js +25 -11
- package/src/tools/webTool.js +6 -12
- package/src/types/__tests__/agent.test.js +499 -0
- package/src/types/__tests__/contextReference.test.js +606 -0
- package/src/types/__tests__/conversation.test.js +555 -0
- package/src/types/__tests__/toolCommand.test.js +584 -0
- package/src/types/contextReference.js +1 -1
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -0
- package/src/utilities/__tests__/configManager.test.js +397 -0
- package/src/utilities/__tests__/constants.test.js +49 -0
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -0
- package/src/utilities/__tests__/fileProcessor.test.js +104 -0
- package/src/utilities/__tests__/jsonRepair.test.js +104 -0
- package/src/utilities/__tests__/logger.test.js +129 -0
- package/src/utilities/__tests__/platformUtils.test.js +87 -0
- package/src/utilities/__tests__/structuredFileValidator.test.js +263 -0
- package/src/utilities/__tests__/tagParser.test.js +887 -0
- package/src/utilities/__tests__/toolConstants.test.js +94 -0
- package/src/utilities/tagParser.js +2 -2
- package/src/tools/browserTool.js +0 -897
- package/src/utilities/platformUtils.test.js +0 -98
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock fs/promises
|
|
5
|
+
const mockAccess = jest.fn();
|
|
6
|
+
const mockStat = jest.fn();
|
|
7
|
+
jest.unstable_mockModule('fs/promises', () => ({
|
|
8
|
+
default: { access: mockAccess, stat: mockStat },
|
|
9
|
+
access: mockAccess,
|
|
10
|
+
stat: mockStat
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Mock sparrow-sast
|
|
14
|
+
const mockScan = jest.fn();
|
|
15
|
+
const mockGetRegistry = jest.fn();
|
|
16
|
+
const mockGetAllAnalyzers = jest.fn();
|
|
17
|
+
jest.unstable_mockModule('sparrow-sast', () => ({
|
|
18
|
+
default: {
|
|
19
|
+
scan: mockScan,
|
|
20
|
+
getRegistry: mockGetRegistry,
|
|
21
|
+
getAllAnalyzers: mockGetAllAnalyzers,
|
|
22
|
+
Language: {
|
|
23
|
+
Python: 'python',
|
|
24
|
+
JavaScript: 'javascript',
|
|
25
|
+
TypeScript: 'typescript',
|
|
26
|
+
Go: 'go',
|
|
27
|
+
Java: 'java',
|
|
28
|
+
Ruby: 'ruby',
|
|
29
|
+
Rust: 'rust',
|
|
30
|
+
PHP: 'php',
|
|
31
|
+
CSharp: 'csharp',
|
|
32
|
+
Bash: 'bash',
|
|
33
|
+
HTML: 'html',
|
|
34
|
+
CSS: 'css'
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
scan: mockScan,
|
|
38
|
+
getRegistry: mockGetRegistry,
|
|
39
|
+
getAllAnalyzers: mockGetAllAnalyzers,
|
|
40
|
+
Language: {
|
|
41
|
+
Python: 'python',
|
|
42
|
+
JavaScript: 'javascript',
|
|
43
|
+
TypeScript: 'typescript'
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const { default: SparrowAnalyzer } = await import('../SparrowAnalyzer.js');
|
|
48
|
+
|
|
49
|
+
describe('SparrowAnalyzer', () => {
|
|
50
|
+
let analyzer;
|
|
51
|
+
let logger;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
logger = createMockLogger();
|
|
55
|
+
analyzer = new SparrowAnalyzer(logger);
|
|
56
|
+
analyzer.initialized = false;
|
|
57
|
+
analyzer.sparrow = null;
|
|
58
|
+
jest.clearAllMocks();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ── Constructor ──
|
|
62
|
+
test('constructor initializes with defaults', () => {
|
|
63
|
+
expect(analyzer.logger).toBe(logger);
|
|
64
|
+
expect(analyzer.sparrow).toBeNull();
|
|
65
|
+
expect(analyzer.initialized).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── getSupportedLanguages ──
|
|
69
|
+
test('getSupportedLanguages returns all supported languages', () => {
|
|
70
|
+
const langs = analyzer.getSupportedLanguages();
|
|
71
|
+
expect(langs).toContain('python');
|
|
72
|
+
expect(langs).toContain('javascript');
|
|
73
|
+
expect(langs).toContain('typescript');
|
|
74
|
+
expect(langs).toContain('go');
|
|
75
|
+
expect(langs.length).toBe(12);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ── getSupportedExtensions ──
|
|
79
|
+
test('getSupportedExtensions returns file extensions', () => {
|
|
80
|
+
const exts = analyzer.getSupportedExtensions();
|
|
81
|
+
expect(exts).toContain('.py');
|
|
82
|
+
expect(exts).toContain('.js');
|
|
83
|
+
expect(exts).toContain('.ts');
|
|
84
|
+
expect(exts).toContain('.go');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ── isSupported ──
|
|
88
|
+
test('isSupported returns true for supported files', () => {
|
|
89
|
+
expect(analyzer.isSupported('test.py')).toBe(true);
|
|
90
|
+
expect(analyzer.isSupported('test.js')).toBe(true);
|
|
91
|
+
expect(analyzer.isSupported('test.ts')).toBe(true);
|
|
92
|
+
expect(analyzer.isSupported('test.go')).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('isSupported returns false for unsupported files', () => {
|
|
96
|
+
expect(analyzer.isSupported('test.txt')).toBe(false);
|
|
97
|
+
expect(analyzer.isSupported('test.md')).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ── ensureInitialized ──
|
|
101
|
+
test('ensureInitialized loads sparrow module', async () => {
|
|
102
|
+
await analyzer.ensureInitialized();
|
|
103
|
+
expect(analyzer.initialized).toBe(true);
|
|
104
|
+
expect(analyzer.sparrow).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('ensureInitialized skips if already initialized', async () => {
|
|
108
|
+
analyzer.initialized = true;
|
|
109
|
+
analyzer.sparrow = { scan: jest.fn() };
|
|
110
|
+
await analyzer.ensureInitialized();
|
|
111
|
+
// No error thrown
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ── transformIssue ──
|
|
115
|
+
test('transformIssue converts sparrow issue to standard format', () => {
|
|
116
|
+
const issue = {
|
|
117
|
+
id: 'SQL_INJECTION',
|
|
118
|
+
filepath: '/project/test.py',
|
|
119
|
+
range: { start: { row: 4, column: 0 }, end: { row: 4, column: 20 } },
|
|
120
|
+
severity: 'error',
|
|
121
|
+
category: 'security',
|
|
122
|
+
message: 'Possible SQL injection'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const result = analyzer.transformIssue(issue, '/project/test.py');
|
|
126
|
+
expect(result.id).toBe('SQL_INJECTION');
|
|
127
|
+
expect(result.line).toBe(5); // 0-based to 1-based
|
|
128
|
+
expect(result.column).toBe(1);
|
|
129
|
+
expect(result.endLine).toBe(5);
|
|
130
|
+
expect(result.severity).toBe('error');
|
|
131
|
+
expect(result.source).toBe('sparrow');
|
|
132
|
+
expect(result.fixable).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('transformIssue handles missing range', () => {
|
|
136
|
+
const issue = {
|
|
137
|
+
id: 'TEST',
|
|
138
|
+
filepath: '/project/test.py',
|
|
139
|
+
message: 'Test issue'
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = analyzer.transformIssue(issue, '/project/test.py');
|
|
143
|
+
expect(result.line).toBe(1);
|
|
144
|
+
expect(result.column).toBe(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ── groupByCategory ──
|
|
148
|
+
test('groupByCategory groups issues correctly', () => {
|
|
149
|
+
const issues = [
|
|
150
|
+
{ category: 'security' },
|
|
151
|
+
{ category: 'security' },
|
|
152
|
+
{ category: 'performance' },
|
|
153
|
+
{ category: undefined }
|
|
154
|
+
];
|
|
155
|
+
const groups = analyzer.groupByCategory(issues);
|
|
156
|
+
expect(groups.security).toBe(2);
|
|
157
|
+
expect(groups.performance).toBe(1);
|
|
158
|
+
expect(groups.other).toBe(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── groupByLanguage ──
|
|
162
|
+
test('groupByLanguage groups file results by language', () => {
|
|
163
|
+
const fileResults = [
|
|
164
|
+
{ file: 'test.py', issues: [] },
|
|
165
|
+
{ file: 'app.js', issues: [] },
|
|
166
|
+
{ file: 'util.js', issues: [] },
|
|
167
|
+
{ file: 'main.go', issues: [] }
|
|
168
|
+
];
|
|
169
|
+
const groups = analyzer.groupByLanguage(fileResults);
|
|
170
|
+
expect(groups.python).toBe(1);
|
|
171
|
+
expect(groups.javascript).toBe(2);
|
|
172
|
+
expect(groups.go).toBe(1);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('groupByLanguage maps unknown extensions to other', () => {
|
|
176
|
+
const fileResults = [{ file: 'readme.txt', issues: [] }];
|
|
177
|
+
const groups = analyzer.groupByLanguage(fileResults);
|
|
178
|
+
expect(groups.other).toBe(1);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ── scanFile ──
|
|
182
|
+
test('scanFile returns skipped result for unsupported file', async () => {
|
|
183
|
+
mockAccess.mockResolvedValue(undefined);
|
|
184
|
+
|
|
185
|
+
const result = await analyzer.scanFile('/project/readme.txt');
|
|
186
|
+
expect(result.success).toBe(true);
|
|
187
|
+
expect(result.skipped).toBe(true);
|
|
188
|
+
expect(result.issues).toEqual([]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('scanFile returns issues for supported file', async () => {
|
|
192
|
+
mockAccess.mockResolvedValue(undefined);
|
|
193
|
+
mockScan.mockResolvedValue([
|
|
194
|
+
{ id: 'XSS', filepath: '/project/test.js', message: 'XSS vulnerability', severity: 'error', category: 'security' }
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
const result = await analyzer.scanFile('/project/test.js');
|
|
198
|
+
expect(result.success).toBe(true);
|
|
199
|
+
expect(result.issues.length).toBe(1);
|
|
200
|
+
expect(result.summary.total).toBe(1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('scanFile handles scan errors', async () => {
|
|
204
|
+
mockAccess.mockRejectedValue(new Error('File not found'));
|
|
205
|
+
|
|
206
|
+
const result = await analyzer.scanFile('/project/test.js');
|
|
207
|
+
expect(result.success).toBe(false);
|
|
208
|
+
expect(result.error).toBeDefined();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ── scanProject ──
|
|
212
|
+
test('scanProject scans directory and returns grouped results', async () => {
|
|
213
|
+
mockStat.mockResolvedValue({ isDirectory: () => true });
|
|
214
|
+
mockScan.mockResolvedValue([
|
|
215
|
+
{ id: 'XSS', filepath: '/project/app.js', message: 'XSS', severity: 'error', category: 'security' },
|
|
216
|
+
{ id: 'SQLI', filepath: '/project/db.py', message: 'SQL injection', severity: 'critical', category: 'security' }
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
const result = await analyzer.scanProject('/project');
|
|
220
|
+
expect(result.success).toBe(true);
|
|
221
|
+
expect(result.isDirectory).toBe(true);
|
|
222
|
+
expect(result.files.length).toBe(2);
|
|
223
|
+
expect(result.summary.totalFiles).toBe(2);
|
|
224
|
+
expect(result.summary.totalIssues).toBe(2);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('scanProject handles scan errors', async () => {
|
|
228
|
+
mockStat.mockRejectedValue(new Error('Path not found'));
|
|
229
|
+
|
|
230
|
+
const result = await analyzer.scanProject('/nonexistent');
|
|
231
|
+
expect(result.success).toBe(false);
|
|
232
|
+
expect(result.error).toBeDefined();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('scanProject applies language filter', async () => {
|
|
236
|
+
mockStat.mockResolvedValue({ isDirectory: () => true });
|
|
237
|
+
mockScan.mockResolvedValue([]);
|
|
238
|
+
|
|
239
|
+
const result = await analyzer.scanProject('/project', { languages: ['python', 'javascript'] });
|
|
240
|
+
expect(result.success).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ── getCheckersInfo ──
|
|
244
|
+
test('getCheckersInfo returns analyzer info', async () => {
|
|
245
|
+
mockGetRegistry.mockReturnValue({});
|
|
246
|
+
mockGetAllAnalyzers.mockReturnValue([
|
|
247
|
+
{ name: 'sql-injection', language: 'python', category: 'security', severity: 'error' }
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const info = await analyzer.getCheckersInfo();
|
|
251
|
+
expect(info.length).toBe(1);
|
|
252
|
+
expect(info[0].name).toBe('sql-injection');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('getCheckersInfo returns empty on error', async () => {
|
|
256
|
+
mockGetRegistry.mockReturnValue(null);
|
|
257
|
+
|
|
258
|
+
const info = await analyzer.getCheckersInfo();
|
|
259
|
+
expect(info).toEqual([]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('getCheckersInfo handles missing getRegistry', async () => {
|
|
263
|
+
// sparrow without getRegistry
|
|
264
|
+
analyzer.sparrow = {};
|
|
265
|
+
analyzer.initialized = true;
|
|
266
|
+
|
|
267
|
+
const info = await analyzer.getCheckersInfo();
|
|
268
|
+
expect(info).toEqual([]);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
import TypeScriptAnalyzer from '../TypeScriptAnalyzer.js';
|
|
4
|
+
|
|
5
|
+
describe('TypeScriptAnalyzer', () => {
|
|
6
|
+
let analyzer;
|
|
7
|
+
let logger;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
logger = createMockLogger();
|
|
11
|
+
analyzer = new TypeScriptAnalyzer(logger);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// ── Constructor ──
|
|
15
|
+
test('constructor sets logger and compiler options', () => {
|
|
16
|
+
expect(analyzer.logger).toBe(logger);
|
|
17
|
+
expect(analyzer.compilerOptions).toBeDefined();
|
|
18
|
+
expect(analyzer.compilerOptions.noEmit).toBe(true);
|
|
19
|
+
expect(analyzer.compilerOptions.strict).toBe(true);
|
|
20
|
+
expect(analyzer.compilerOptions.allowJs).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('constructor works without logger', () => {
|
|
24
|
+
const a = new TypeScriptAnalyzer();
|
|
25
|
+
expect(a.logger).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// ── analyze ──
|
|
29
|
+
test('analyze returns empty array for valid TypeScript', async () => {
|
|
30
|
+
const content = 'const x: number = 42;\n';
|
|
31
|
+
const result = await analyzer.analyze('test.ts', content);
|
|
32
|
+
expect(Array.isArray(result)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('analyze returns diagnostics for syntax errors', async () => {
|
|
36
|
+
const content = 'const x: number = ;\nfunction( { {{\n';
|
|
37
|
+
const result = await analyzer.analyze('broken.ts', content);
|
|
38
|
+
expect(result.length).toBeGreaterThan(0);
|
|
39
|
+
expect(result[0]).toHaveProperty('severity');
|
|
40
|
+
expect(result[0]).toHaveProperty('message');
|
|
41
|
+
expect(result[0]).toHaveProperty('file');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('analyze returns diagnostics with proper rule format', async () => {
|
|
45
|
+
const content = 'const x: number = "string";\n';
|
|
46
|
+
const result = await analyzer.analyze('types.ts', content);
|
|
47
|
+
// Should find type error or at least return an array
|
|
48
|
+
expect(Array.isArray(result)).toBe(true);
|
|
49
|
+
for (const diag of result) {
|
|
50
|
+
expect(diag.rule).toMatch(/^TS\d+$/);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('analyze returns error diagnostic when analysis throws', async () => {
|
|
55
|
+
// Force an error by corrupting the analyzer
|
|
56
|
+
const badAnalyzer = new TypeScriptAnalyzer(logger);
|
|
57
|
+
badAnalyzer.compilerOptions = null; // Will cause createProgram to fail
|
|
58
|
+
|
|
59
|
+
const result = await badAnalyzer.analyze('test.ts', 'const x = 1;');
|
|
60
|
+
// Either returns normally or returns an error diagnostic
|
|
61
|
+
expect(Array.isArray(result)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('analyze handles empty content', async () => {
|
|
65
|
+
const result = await analyzer.analyze('empty.ts', '');
|
|
66
|
+
expect(Array.isArray(result)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('analyze handles TSX content', async () => {
|
|
70
|
+
const content = 'const el = <div>hello</div>;\n';
|
|
71
|
+
const result = await analyzer.analyze('component.tsx', content);
|
|
72
|
+
expect(Array.isArray(result)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ── getSyntacticDiagnostics ──
|
|
76
|
+
test('getSyntacticDiagnostics returns empty for valid source', async () => {
|
|
77
|
+
const ts = (await import('typescript')).default;
|
|
78
|
+
const sourceFile = ts.createSourceFile('test.ts', 'const x = 1;', ts.ScriptTarget.Latest, true);
|
|
79
|
+
const result = analyzer.getSyntacticDiagnostics(sourceFile);
|
|
80
|
+
expect(Array.isArray(result)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ── getSemanticDiagnostics ──
|
|
84
|
+
test('getSemanticDiagnostics returns array for valid content', async () => {
|
|
85
|
+
const result = await analyzer.getSemanticDiagnostics('test.ts', 'const x: number = 1;');
|
|
86
|
+
expect(Array.isArray(result)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('getSemanticDiagnostics detects type errors', async () => {
|
|
90
|
+
const content = 'const x: number = "hello";';
|
|
91
|
+
const result = await analyzer.getSemanticDiagnostics('types.ts', content);
|
|
92
|
+
expect(Array.isArray(result)).toBe(true);
|
|
93
|
+
// Should have at least one type error
|
|
94
|
+
if (result.length > 0) {
|
|
95
|
+
expect(result[0]).toHaveProperty('severity');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ── formatDiagnostic ──
|
|
100
|
+
test('formatDiagnostic handles diagnostic with file and position', async () => {
|
|
101
|
+
const ts = (await import('typescript')).default;
|
|
102
|
+
const sourceFile = ts.createSourceFile('test.ts', 'const x = 1;\nconst y = 2;', ts.ScriptTarget.Latest, true);
|
|
103
|
+
|
|
104
|
+
const diagnostic = {
|
|
105
|
+
file: sourceFile,
|
|
106
|
+
start: 14, // position in source
|
|
107
|
+
category: ts.DiagnosticCategory.Error,
|
|
108
|
+
messageText: 'Test error message',
|
|
109
|
+
code: 2322
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const result = analyzer.formatDiagnostic(diagnostic, sourceFile);
|
|
113
|
+
expect(result.line).toBeGreaterThan(0);
|
|
114
|
+
expect(result.column).toBeGreaterThan(0);
|
|
115
|
+
expect(result.severity).toBe('error');
|
|
116
|
+
expect(result.rule).toBe('TS2322');
|
|
117
|
+
expect(result.message).toBe('Test error message');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('formatDiagnostic handles diagnostic without file', async () => {
|
|
121
|
+
const ts = (await import('typescript')).default;
|
|
122
|
+
const sourceFile = ts.createSourceFile('test.ts', 'const x = 1;', ts.ScriptTarget.Latest, true);
|
|
123
|
+
|
|
124
|
+
const diagnostic = {
|
|
125
|
+
start: 0,
|
|
126
|
+
category: ts.DiagnosticCategory.Warning,
|
|
127
|
+
messageText: 'Warning message',
|
|
128
|
+
code: 1000
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const result = analyzer.formatDiagnostic(diagnostic, sourceFile);
|
|
132
|
+
expect(result.severity).toBe('warning');
|
|
133
|
+
expect(result.category).toBe('syntax'); // code 1000 is in syntax range
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('formatDiagnostic handles suggestion category', async () => {
|
|
137
|
+
const ts = (await import('typescript')).default;
|
|
138
|
+
|
|
139
|
+
const diagnostic = {
|
|
140
|
+
category: ts.DiagnosticCategory.Suggestion,
|
|
141
|
+
messageText: 'Suggestion',
|
|
142
|
+
code: 9999
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const result = analyzer.formatDiagnostic(diagnostic, null);
|
|
146
|
+
expect(result.severity).toBe('info');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('formatDiagnostic categorizes import errors by message', async () => {
|
|
150
|
+
const ts = (await import('typescript')).default;
|
|
151
|
+
|
|
152
|
+
const diagnostic = {
|
|
153
|
+
category: ts.DiagnosticCategory.Error,
|
|
154
|
+
messageText: 'Cannot find module "foo"',
|
|
155
|
+
code: 5000
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const result = analyzer.formatDiagnostic(diagnostic, null);
|
|
159
|
+
expect(result.category).toBe('import');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('formatDiagnostic categorizes type errors by code range', async () => {
|
|
163
|
+
const ts = (await import('typescript')).default;
|
|
164
|
+
|
|
165
|
+
const diagnostic = {
|
|
166
|
+
category: ts.DiagnosticCategory.Error,
|
|
167
|
+
messageText: 'Some error about assignment',
|
|
168
|
+
code: 2500
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const result = analyzer.formatDiagnostic(diagnostic, null);
|
|
172
|
+
expect(result.category).toBe('type');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('formatDiagnostic categorizes type errors by message content', async () => {
|
|
176
|
+
const ts = (await import('typescript')).default;
|
|
177
|
+
|
|
178
|
+
const diagnostic = {
|
|
179
|
+
category: ts.DiagnosticCategory.Error,
|
|
180
|
+
messageText: 'Property of type X',
|
|
181
|
+
code: 5000
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const result = analyzer.formatDiagnostic(diagnostic, null);
|
|
185
|
+
expect(result.category).toBe('type');
|
|
186
|
+
});
|
|
187
|
+
});
|