Skip to content

Commit 27cb8c7

Browse files
committed
fix type self-reference problem
1 parent e4f2bf9 commit 27cb8c7

8 files changed

Lines changed: 95 additions & 27 deletions

File tree

ts-parser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "abcoder-ts-parser",
3-
"version": "0.0.13",
3+
"version": "0.0.20",
44
"description": "TypeScript AST parser for UNIAST specification",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

ts-parser/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const program = new Command();
1111
program
1212
.name('abcoder-ts-parser')
1313
.description('TypeScript AST parser for UNIAST specification')
14-
.version('0.0.13');
14+
.version('0.0.20');
1515

1616
program
1717
.command('parse')

ts-parser/src/parser/FunctionParser.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -705,12 +705,12 @@ export class FunctionParser {
705705
const defEndOffset = decls[0].getEnd();
706706
const key = `${resolvedSymbol.moduleName}?${resolvedSymbol.packagePath}#${resolvedSymbol.name}`;
707707

708-
// Check if this is not a self-reference within the same function
708+
// Check if this is a self-reference (type reference within its own definition)
709709
const isSelfReference = (
710710
resolvedSymbol.moduleName === moduleName &&
711711
this.getPkgPath(resolvedSymbol.packagePath || packagePath) === packagePath &&
712-
defEndOffset <= node.getEnd() &&
713-
defStartOffset >= node.getStart()
712+
defStartOffset <= resolvedSymbol.startOffset &&
713+
resolvedSymbol.endOffset <= defEndOffset
714714
);
715715

716716
if (!visited.has(key) && !isSelfReference) {
@@ -775,11 +775,12 @@ export class FunctionParser {
775775
EndOffset: resolvedSymbol.endOffset
776776
};
777777

778+
// Check if this is a self-reference (type reference within its own definition)
778779
if (
779780
dep.ModPath === moduleName &&
780781
dep.PkgPath === packagePath &&
781-
defEndOffset <= node.getEnd() &&
782-
defStartOffset >= node.getStart()
782+
defStartOffset <= resolvedSymbol.startOffset &&
783+
resolvedSymbol.endOffset <= defEndOffset
783784
) {
784785
continue;
785786
}

ts-parser/src/parser/PackageParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class PackageParser {
2929
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3030
const vars: Record<string, any> = {};
3131

32-
for (const sourceFile of sourceFiles) {
32+
for (const sourceFile of sourceFiles) {
3333
// Parse functions
3434
const fileFunctions = this.functionParser.parseFunctions(sourceFile, moduleName, packagePath);
3535
Object.assign(functions, fileFunctions);

ts-parser/src/parser/TypeParser.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -313,19 +313,12 @@ export class TypeParser {
313313
if (resolvedSymbol && !resolvedSymbol.isExternal) {
314314
const decls = resolvedRealSymbol?.getDeclarations() || [];
315315
if (decls.length > 0) {
316-
const defStartOffset = decls[0].getStart();
317-
const defEndOffset = decls[0].getEnd();
318316
const key = `${resolvedSymbol.moduleName}?${resolvedSymbol.packagePath}#${resolvedSymbol.name}`;
319317

320-
// Check if this is not a self-reference
321-
const isSelfReference = (
322-
resolvedSymbol.moduleName === moduleName &&
323-
this.getPkgPath(resolvedSymbol.packagePath || packagePath) === packagePath &&
324-
defStartOffset <= resolvedSymbol.startOffset &&
325-
resolvedSymbol.endOffset <= defEndOffset
326-
);
318+
// Check if this is a self-reference: the type reference is within its own definition
319+
const isSelfRef = typeNode.getAncestors().some(ancestor => ancestor === decls[0]);
327320

328-
if (!visited.has(key) && !isSelfReference) {
321+
if (!visited.has(key) && !isSelfRef) {
329322
visited.add(key);
330323
dependencies.push({
331324
ModPath: resolvedSymbol.moduleName || moduleName,
@@ -380,8 +373,11 @@ export class TypeParser {
380373
continue;
381374
}
382375

383-
const defStartOffset = decls[0].getStart();
384-
const defEndOffset = decls[0].getEnd();
376+
// Check if this is a self-reference: the type reference is within its own definition
377+
// If typeRef's ancestors include decls[0], it's a self-reference
378+
const isSelfRef = typeRef.getAncestors().some(ancestor => ancestor === decls[0]);
379+
380+
if (isSelfRef) continue;
385381

386382
visited.add(key);
387383
const dep: Dependency = {
@@ -394,13 +390,6 @@ export class TypeParser {
394390
EndOffset: resolvedSymbol.endOffset
395391
};
396392

397-
// Skip self-references
398-
if (
399-
dep.ModPath === moduleName &&
400-
dep.PkgPath === packagePath &&
401-
defStartOffset <= resolvedSymbol.startOffset &&
402-
resolvedSymbol.endOffset <= defEndOffset
403-
) continue;
404393
dependencies.push(dep);
405394
}
406395

ts-parser/src/parser/test/FunctionParser.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,5 +749,39 @@ describe('FunctionParser', () => {
749749

750750
cleanup();
751751
});
752+
753+
it('should filter out self-referencing recursive types', () => {
754+
const { project, sourceFile, cleanup } = createTestProject(`
755+
export type TreeNode = {
756+
value: string;
757+
children: TreeNode[];
758+
};
759+
760+
export function processTree(node: TreeNode): void {
761+
console.log(node.value);
762+
}
763+
`);
764+
765+
const parser = new FunctionParser(project, process.cwd());
766+
let pkgPathAbsFile: string = sourceFile.getFilePath();
767+
pkgPathAbsFile = pkgPathAbsFile.split('/').slice(0, -1).join('/');
768+
const pkgPath = path.relative(process.cwd(), pkgPathAbsFile);
769+
770+
const functions = parser.parseFunctions(sourceFile, 'parser-tests', pkgPath);
771+
772+
const processTree = expectToBeDefined(functions['processTree']);
773+
774+
// Should have TreeNode in Types array
775+
expect(processTree.Types).toBeDefined();
776+
const typeNames = expectToBeDefined(processTree.Types).map(dep => dep.Name);
777+
778+
expect(typeNames).toContain('TreeNode');
779+
780+
// TreeNode should only appear once (the self-reference in TreeNode definition should be filtered)
781+
const treeNodeCount = typeNames.filter(name => name === 'TreeNode').length;
782+
expect(treeNodeCount).toBe(1);
783+
784+
cleanup();
785+
});
752786
});
753787
});

ts-parser/src/parser/test/TypeParser.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,5 +671,38 @@ describe('TypeParser', () => {
671671

672672
cleanup();
673673
});
674+
675+
it('should filter out self-referencing recursive types in InlineStruct', () => {
676+
const { project, sourceFile, cleanup } = createTestProject(`
677+
export type TreeNode = {
678+
value: string;
679+
children: TreeNode[];
680+
};
681+
682+
export type LinkedListNode = {
683+
data: number;
684+
next: LinkedListNode | null;
685+
};
686+
`);
687+
688+
const parser = new TypeParser(process.cwd());
689+
let pkgPathAbsFile: string = sourceFile.getFilePath();
690+
pkgPathAbsFile = pkgPathAbsFile.split('/').slice(0, -1).join('/');
691+
const pkgPath = path.relative(process.cwd(), pkgPathAbsFile);
692+
693+
const types = parser.parseTypes(sourceFile, 'parser-tests', pkgPath);
694+
695+
// TreeNode should not include itself in InlineStruct (self-reference should be filtered)
696+
const treeNode = expectToBeDefined(types['TreeNode']);
697+
const treeNodeInlineNames = (treeNode.InlineStruct || []).map(dep => dep.Name);
698+
expect(treeNodeInlineNames).not.toContain('TreeNode');
699+
700+
// LinkedListNode should not include itself in InlineStruct
701+
const linkedListNode = expectToBeDefined(types['LinkedListNode']);
702+
const linkedListInlineNames = (linkedListNode.InlineStruct || []).map(dep => dep.Name);
703+
expect(linkedListInlineNames).not.toContain('LinkedListNode');
704+
705+
cleanup();
706+
});
674707
});
675708
});

ts-parser/test-repo/src/test-export-default.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,14 @@ export default foo;
99
export const bar = () => {
1010
console.log('baz')
1111
}
12+
13+
export type Status = 'normal' | 'abnormal'
14+
15+
export type ServerStatus = {
16+
code: number;
17+
status: Status;
18+
}
19+
20+
export const flipStatus = (s: Status): Status => {
21+
return s === 'normal' ? 'abnormal' : 'normal';
22+
}

0 commit comments

Comments
 (0)