Skip to content

Commit 8a276c1

Browse files
authored
Fix extractDocs for re-exported (aliased) symbols (#55)
## Summary - `extractDocs` fails for symbols that are re-exported through barrel files (e.g. `@reduxjs/toolkit:createSlice`, `@mui/material:Button`), even though `getSourceLocation` works fine for the same references. - The root cause is that re-exported symbols have `SymbolFlags.Alias` and no direct `valueDeclaration`. The existing declaration-type checks in `extractDocs` don't account for this, causing the symbol to fall through to `findSymbolInJavaScriptFile` which fails on `.d.ts` files. - Fix: resolve aliases via `checker.getAliasedSymbol()` before checking the declaration type. This recovers the original declaration and its full documentation. This affects most of the npm ecosystem — any package using barrel re-exports (`export { X } from './X'`). ## Test plan - Added test fixtures (`reexport-source.ts` + `reexport-barrel.ts`) with a barrel file that re-exports functions, classes, enums, and interfaces - 6 new tests covering all re-exported symbol types and the error path - Verified the 5 positive tests fail without the fix and pass with it - Full test suite passes (134 tests)
1 parent f6035b9 commit 8a276c1

File tree

4 files changed

+112
-3
lines changed

4 files changed

+112
-3
lines changed

src/resolution/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,10 @@ export async function extractDocs(modulePath: string): Promise<ExtractResult> {
212212
const exportSymbol = exports.find((exp: ts.Symbol) => exp.getName() === symbol);
213213

214214
if (exportSymbol) {
215-
// Check if the export symbol has a meaningful valueDeclaration
216-
// OR if it's a type-only symbol like interface/type alias/enum
217-
if (
215+
// Resolve re-exported (aliased) symbols to their original declaration
216+
if (exportSymbol.flags & ts.SymbolFlags.Alias) {
217+
targetSymbol = checker.getAliasedSymbol(exportSymbol);
218+
} else if (
218219
exportSymbol.valueDeclaration &&
219220
(ts.isFunctionDeclaration(exportSymbol.valueDeclaration) ||
220221
ts.isClassDeclaration(exportSymbol.valueDeclaration) ||

test/fixtures/reexport-barrel.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Barrel file that re-exports symbols from reexport-source.
3+
* Used to test that extractDocs resolves aliased (re-exported) symbols.
4+
*/
5+
export { add, Counter, Status } from './reexport-source';
6+
export type { Result } from './reexport-source';

test/fixtures/reexport-source.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Adds two numbers together.
3+
* @param a - First number
4+
* @param b - Second number
5+
* @returns The sum of a and b
6+
*/
7+
export function add(a: number, b: number): number {
8+
return a + b;
9+
}
10+
11+
/**
12+
* A simple counter class for testing re-exports.
13+
*/
14+
export class Counter {
15+
private value: number;
16+
17+
constructor(initial = 0) {
18+
this.value = initial;
19+
}
20+
21+
/** Increment and return the new value. */
22+
increment(): number {
23+
return ++this.value;
24+
}
25+
26+
/** Get the current value. */
27+
get current(): number {
28+
return this.value;
29+
}
30+
}
31+
32+
/**
33+
* Status codes for operations.
34+
*/
35+
export enum Status {
36+
Ok = 'ok',
37+
Error = 'error',
38+
Pending = 'pending',
39+
}
40+
41+
/**
42+
* Shape of a result object.
43+
*/
44+
export interface Result {
45+
status: Status;
46+
data: unknown;
47+
}

test/resolution.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,61 @@ describe('TypeScript Extraction', () => {
287287
});
288288
});
289289

290+
describe('re-exported (aliased) symbols', () => {
291+
it('should extract docs for a re-exported function', async () => {
292+
const docs = (await extractDocs('./test/fixtures/reexport-barrel:add')) as SymbolInfo;
293+
294+
expect(isExtractError(docs)).toBe(false);
295+
expect(docs.name).toBe('add');
296+
expect(docs.kind).toBe('function');
297+
expect(docs.documentation).toContain('Adds two numbers');
298+
});
299+
300+
it('should extract docs for a re-exported class', async () => {
301+
const docs = (await extractDocs('./test/fixtures/reexport-barrel:Counter')) as SymbolInfo;
302+
303+
expect(isExtractError(docs)).toBe(false);
304+
expect(docs.name).toBe('Counter');
305+
expect(docs.kind).toBe('class');
306+
expect(docs.documentation).toContain('simple counter class');
307+
});
308+
309+
it('should extract docs for a re-exported class instance member', async () => {
310+
const docs = (await extractDocs(
311+
'./test/fixtures/reexport-barrel:Counter#increment',
312+
)) as SymbolInfo;
313+
314+
expect(isExtractError(docs)).toBe(false);
315+
expect(docs.name).toBe('Counter#increment');
316+
expect(docs.kind).toBe('method');
317+
});
318+
319+
it('should extract docs for a re-exported enum', async () => {
320+
const docs = (await extractDocs('./test/fixtures/reexport-barrel:Status')) as SymbolInfo;
321+
322+
expect(isExtractError(docs)).toBe(false);
323+
expect(docs.name).toBe('Status');
324+
expect(docs.kind).toBe('enum');
325+
});
326+
327+
it('should extract docs for a re-exported interface', async () => {
328+
const docs = (await extractDocs('./test/fixtures/reexport-barrel:Result')) as SymbolInfo;
329+
330+
expect(isExtractError(docs)).toBe(false);
331+
expect(docs.name).toBe('Result');
332+
expect(docs.kind).toBe('interface');
333+
});
334+
335+
it('should return SYMBOL_NOT_FOUND for non-existent re-export', async () => {
336+
const docs = (await extractDocs(
337+
'./test/fixtures/reexport-barrel:NonExistent',
338+
)) as ExtractError;
339+
340+
expect(isExtractError(docs)).toBe(true);
341+
expect(docs.error.code).toBe('SYMBOL_NOT_FOUND');
342+
});
343+
});
344+
290345
describe('getSourceLocation', () => {
291346
it('should resolve built-in module paths', async () => {
292347
const sourcePath = await getSourceLocation('typescript');

0 commit comments

Comments
 (0)