Skip to content

Commit 0799087

Browse files
CopilotMasonChow
andauthored
Fix token validation error by adding transformation layer for WebAssembly response (#22)
* Initial plan * Add test to reproduce source map parsing issue Co-authored-by: MasonChow <12403684+MasonChow@users.noreply.github.com> * Fix token validation error by adding transformation layer for WebAssembly response Co-authored-by: MasonChow <12403684+MasonChow@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MasonChow <12403684+MasonChow@users.noreply.github.com> Co-authored-by: MasonChow <masonchat@foxmail.com>
1 parent 8b3489f commit 0799087

File tree

3 files changed

+163
-1
lines changed

3 files changed

+163
-1
lines changed

src/external/docs/guide.xml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,35 @@ src/
5555
server.ts
5656
tests/
5757
client.test.ts
58+
issue-21-fix.test.ts
5859
memoryCacheManager.test.ts
60+
sourcemap-issue.test.ts
61+
sourcemap-local.test.ts
5962
README.md
6063
</directory_structure>
6164

6265
<files>
6366
This section contains the contents of the repository's files.
6467

68+
<file path="tests/issue-21-fix.test.ts">
69+
import { describe, it, expect } from 'vitest';
70+
import Parser from '../src/parser.js';
71+
import fs from 'fs/promises';
72+
import path from 'path';
73+
⋮----
74+
// Read the local source map file mentioned in the issue
75+
⋮----
76+
// Test the exact stack traces from the issue
77+
⋮----
78+
// Verify the token structure is correct
79+
⋮----
80+
// Verify sourceCode array contains SourceCode objects with correct structure
81+
⋮----
82+
// Use the local source map file for consistent results
83+
⋮----
84+
// For file:// URLs, we expect fetch to fail, which is OK
85+
</file>
86+
6587
<file path="src/types/source_map_parser_node.d.ts">
6688
export function init(): Promise<void>;
6789
export function generate_token_by_single_stack(
@@ -355,6 +377,12 @@ private validateToken(token: any): asserts token is Token
355377
⋮----
356378
// Validate each source code entry
357379
⋮----
380+
/**
381+
* Transforms the raw response from WebAssembly module to match the expected Token interface
382+
* @param rawToken - The raw token object from WebAssembly (uses snake_case)
383+
* @returns Transformed token object matching the Token interface (uses camelCase)
384+
*/
385+
private transformToken(rawToken: any): Token
358386
/**
359387
* Retrieves the source token for a given line and column in the source map.
360388
*
@@ -366,6 +394,8 @@ private validateToken(token: any): asserts token is Token
366394
*/
367395
public async getSourceToken(line: number, column: number, sourceMap: string): Promise<Token>
368396
⋮----
397+
// Transform the raw token to match the expected interface
398+
⋮----
369399
// Validate the token structure
370400
</file>
371401

@@ -401,6 +431,13 @@ cache.set('b', bufB, 'etag-b'); // should evict 'a'
401431
const bigBuf = Buffer.alloc(2 * 1024 * 1024, 1); // 2MB
402432
</file>
403433

434+
435+
<file path="tests/sourcemap-issue.test.ts">
436+
import { describe, it, expect } from 'vitest';
437+
import Parser from '../src/parser.js';
438+
// If it fails due to network issues, that's OK for now
439+
</file>
440+
404441
<file path="README.md">
405442
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/masonchow-source-map-parser-mcp-badge.png)](https://mseep.ai/app/masonchow-source-map-parser-mcp)
406443

src/parser.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,24 @@ class Parser {
260260
}
261261
}
262262

263+
/**
264+
* Transforms the raw response from WebAssembly module to match the expected Token interface
265+
* @param rawToken - The raw token object from WebAssembly (uses snake_case)
266+
* @returns Transformed token object matching the Token interface (uses camelCase)
267+
*/
268+
private transformToken(rawToken: any): Token {
269+
return {
270+
line: rawToken.line,
271+
column: rawToken.column,
272+
src: rawToken.src,
273+
sourceCode: rawToken.source_code?.map((sourceCode: any) => ({
274+
line: sourceCode.line,
275+
isStackLine: sourceCode.is_stack_line,
276+
raw: sourceCode.raw
277+
})) || []
278+
};
279+
}
280+
263281
/**
264282
* Retrieves the source token for a given line and column in the source map.
265283
*
@@ -274,7 +292,10 @@ class Parser {
274292

275293
try {
276294
const res = sourceMapParser.generate_token_by_single_stack(line, column, sourceMap, this.contextOffsetLine);
277-
const token = JSON.parse(res);
295+
const rawToken = JSON.parse(res);
296+
297+
// Transform the raw token to match the expected interface
298+
const token = this.transformToken(rawToken);
278299

279300
// Validate the token structure
280301
this.validateToken(token);

tests/issue-21-fix.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { describe, it, expect } from 'vitest';
2+
import Parser from '../src/parser.js';
3+
import fs from 'fs/promises';
4+
import path from 'path';
5+
6+
describe('Issue #21 - Token validation fix', () => {
7+
it('should correctly parse multiple stack traces from the issue report', async () => {
8+
const parser = new Parser({ contextOffsetLine: 1 });
9+
10+
// Read the local source map file mentioned in the issue
11+
const sourceMapPath = path.join(process.cwd(), 'example', 'special', 'foo.js.map');
12+
const sourceMapContent = await fs.readFile(sourceMapPath, 'utf-8');
13+
14+
// Test the exact stack traces from the issue
15+
const testCases = [
16+
{ line: 49, column: 34832, description: 'Main error location' },
17+
{ line: 48, column: 83322, description: 'ka function call' },
18+
{ line: 48, column: 98013, description: 'Vs function call (1)' },
19+
{ line: 48, column: 97897, description: 'Et function call (1)' },
20+
{ line: 48, column: 98749, description: 'Vs function call (2)' },
21+
{ line: 48, column: 97897, description: 'Et function call (2)' },
22+
{ line: 48, column: 98059, description: 'Vs function call (3)' },
23+
{ line: 48, column: 110550, description: 'sv function call' },
24+
{ line: 48, column: 107925, description: 'Anonymous call' },
25+
{ line: 25, column: 1635, description: 'MessagePort.Ot call' }
26+
];
27+
28+
console.log('Testing all stack traces from the original issue...');
29+
30+
for (const testCase of testCases) {
31+
try {
32+
const token = await parser.getSourceToken(testCase.line, testCase.column, sourceMapContent);
33+
34+
// Verify the token structure is correct
35+
expect(token).toBeDefined();
36+
expect(typeof token.line).toBe('number');
37+
expect(typeof token.column).toBe('number');
38+
expect(typeof token.src).toBe('string');
39+
expect(Array.isArray(token.sourceCode)).toBe(true);
40+
41+
// Verify sourceCode array contains SourceCode objects with correct structure
42+
for (const sourceCode of token.sourceCode) {
43+
expect(typeof sourceCode.line).toBe('number');
44+
expect(typeof sourceCode.isStackLine).toBe('boolean');
45+
expect(typeof sourceCode.raw).toBe('string');
46+
}
47+
48+
console.log(`✅ ${testCase.description} (${testCase.line}:${testCase.column}) - Success`);
49+
console.log(` → Mapped to: ${token.src}:${token.line}:${token.column}`);
50+
51+
} catch (error) {
52+
console.error(`❌ ${testCase.description} (${testCase.line}:${testCase.column}) - Failed:`, error.message);
53+
throw error;
54+
}
55+
}
56+
57+
console.log('🎉 All test cases passed! The issue has been resolved.');
58+
});
59+
60+
it('should handle batch parsing correctly', async () => {
61+
const parser = new Parser({ contextOffsetLine: 1 });
62+
63+
// Use the local source map file for consistent results
64+
const sourceMapPath = path.join(process.cwd(), 'example', 'special', 'foo.js.map');
65+
const sourceMapUrl = `file://${sourceMapPath}`;
66+
67+
const stacks = [
68+
{
69+
line: 49,
70+
column: 34832,
71+
sourceMapUrl: sourceMapUrl
72+
},
73+
{
74+
line: 48,
75+
column: 83322,
76+
sourceMapUrl: sourceMapUrl
77+
}
78+
];
79+
80+
try {
81+
const result = await parser.batchParseStack(stacks);
82+
83+
expect(result).toBeDefined();
84+
expect(Array.isArray(result)).toBe(true);
85+
expect(result.length).toBe(2);
86+
87+
for (const res of result) {
88+
if (res.success) {
89+
expect(res.token).toBeDefined();
90+
expect(Array.isArray(res.token.sourceCode)).toBe(true);
91+
console.log(`✅ Batch parsing success: ${res.token.src}:${res.token.line}:${res.token.column}`);
92+
} else {
93+
console.log(`ℹ️ Batch parsing failed (expected for file:// URLs):`, res.error?.message);
94+
// For file:// URLs, we expect fetch to fail, which is OK
95+
expect(res.error).toBeDefined();
96+
}
97+
}
98+
99+
} catch (error) {
100+
console.error('Batch parsing test error:', error);
101+
throw error;
102+
}
103+
});
104+
});

0 commit comments

Comments
 (0)