Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
`docs/design/mixed-ios-and-react-native-bridging.md`.

### Fixed
- **TypeScript interface members now produce `references` edges to their
annotated types (#432).** Types that appeared only in an interface's
property or method signatures — e.g. `value?: Partial<IPage>` or
`fetchPage(arg: IPage): IOrderField` — were being silently dropped at
extraction time, so `codegraph_impact`/`codegraph_callers` on the named
type missed every consumer that imported it just to use it in an
interface shape. The walker now extracts type annotations from both
`property_signature` and `method_signature` nodes inside class-like
parents (interfaces, classes), and the resolver wires the resulting
references the same way it wires field and parameter types elsewhere.
- **Git worktrees no longer silently borrow another tree's index (#155).**
When a worktree is nested inside the main checkout — exactly what agent
tools that place worktrees under gitignored paths like
Expand Down
32 changes: 32 additions & 0 deletions __tests__/extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,38 @@ export interface User {
});
});

it('should extract type references from interface property signatures', () => {
const code = `
import type { IPage } from '../PromoterList';
import type { IOrderField } from '../types';

interface Hprops {
value?: Partial<IPage> & Partial<IOrderField>;
}
`;
const result = extractFromSource('HeaderFilter.ts', code);

const refs = result.unresolvedReferences.filter((r) => r.referenceKind === 'references');
expect(refs.some((r) => r.referenceName === 'IPage')).toBe(true);
expect(refs.some((r) => r.referenceName === 'IOrderField')).toBe(true);
});

it('should extract type references from interface method signatures', () => {
const code = `
import type { IPage } from '../PromoterList';
import type { IOrderField } from '../types';

interface MethodForm {
fetchPage(arg: IPage): IOrderField;
}
`;
const result = extractFromSource('MethodForm.ts', code);

const refs = result.unresolvedReferences.filter((r) => r.referenceKind === 'references');
expect(refs.some((r) => r.referenceName === 'IPage')).toBe(true);
expect(refs.some((r) => r.referenceName === 'IOrderField')).toBe(true);
});

it('should track function calls', () => {
const code = `
function main() {
Expand Down
16 changes: 16 additions & 0 deletions src/extraction/tree-sitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,22 @@ export class TreeSitterExtractor {
else if (nodeType === 'impl_item') {
this.extractRustImplItem(node);
}
// TypeScript interface members: property_signature (`foo: T`, `foo?: T`)
// and method_signature (`foo(arg: A): R`) both carry type annotations the
// interface walker would otherwise drop. Extract them as `references`
// edges from the interface so resolvers can wire callers/impact for
// types that only appear in interface members.
else if (
(nodeType === 'property_signature' || nodeType === 'method_signature') &&
this.isInsideClassLikeNode() &&
this.TYPE_ANNOTATION_LANGUAGES.has(this.language)
) {
const parentId = this.nodeStack[this.nodeStack.length - 1];
if (parentId) {
this.extractTypeAnnotations(node, parentId);
}
// don't skipChildren — nested signatures still need traversal
}

// Visit children (unless the extract method already visited them)
if (!skipChildren) {
Expand Down