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
5 changes: 5 additions & 0 deletions .changeset/funky-monkeys-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"docflow": patch
---

resolve absolute import paths in extracted type signatures
19 changes: 16 additions & 3 deletions packages/cli/src/core/parser/source/extract-signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,21 @@ import {
FunctionDeclaration,
TypeAliasDeclaration,
EnumDeclaration,
TypeFormatFlags,
} from "ts-morph";

/**
* Prevents `typeof import("/absolute/path").Foo` for symbols not directly imported in the current scope.
* `UseAliasDefinedOutsideCurrentScope` resolves out-of-scope symbols by their alias name instead.
*
* @see {@link https://github.com/dsherret/ts-morph/issues/453}
*/
const TYPE_FORMAT_FLAGS =
TypeFormatFlags.NoTruncation |
TypeFormatFlags.WriteTypeArgumentsOfSignature |
TypeFormatFlags.UseTypeOfFunction |
TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;

export function extractSignature(declaration: Node): string | undefined {
if (Node.isFunctionDeclaration(declaration)) {
return formatFunctionSignature(declaration);
Expand Down Expand Up @@ -51,7 +64,7 @@ function formatFunctionSignature(node: FunctionDeclaration): string {
.getParameters()
.map(p => p.getText())
.join(", ");
const returnType = node.getReturnType().getText(node);
const returnType = node.getReturnType().getText(node, TYPE_FORMAT_FLAGS);

return `function ${name}${typeParams ? `<${typeParams}>` : ""}(${params}): ${returnType};`;
}
Expand All @@ -75,7 +88,7 @@ function formatArrowFunctionSignature(node: VariableDeclaration): string {
.getParameters()
.map(p => p.getText())
.join(", ");
const returnType = initializer.getReturnType().getText(node);
const returnType = initializer.getReturnType().getText(node, TYPE_FORMAT_FLAGS);

return `${declarationKind} ${name}: ${typeParams ? `<${typeParams}>` : ""}(${params}) => ${returnType};`;
}
Expand All @@ -85,7 +98,7 @@ function formatVariableSignature(node: VariableDeclaration): string {
const name = node.getName();
const variableStatement = node.getVariableStatement();
const declarationKind = variableStatement?.getDeclarationKind() ?? "const";
const type = node.getType().getText(node);
const type = node.getType().getText(node, TYPE_FORMAT_FLAGS);

return `${declarationKind} ${name}: ${type};`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,22 @@ describe("extractSignature", () => {
expect(signature).toContain("/** The database URL. */");
});

it("should not include absolute file paths in nested object variable types", async () => {
const tsConfigPath = getTsConfigPath(workspace.root, "packages/core");
const project = getTsProject(tsConfigPath);

const toolbarFile = project.getSourceFiles().find(sf => sf.getFilePath().includes("toolbar.ts"));

const toolbar = toolbarFile?.getVariableDeclaration("Toolbar");
expect(toolbar).toBeDefined();

const signature = extractSignature(toolbar!);
expect(signature).not.toMatch(/import\("[^"]*\/[^"]*"\)/);
expect(signature).toContain("ToolbarMenu");
expect(signature).toContain("CopyAction");
expect(signature).toContain("PasteAction");
});

it("should not include absolute file paths in return types", async () => {
const tsConfigPath = getTsConfigPath(workspace.root, "packages/core");
const project = getTsProject(tsConfigPath);
Expand Down
49 changes: 48 additions & 1 deletion packages/cli/src/tests/utils/package-creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export function getDefaultConfig(): UserConfig {

export * from './math.js';
export * from './string.js';
export * from './classes.js';`
export * from './classes.js';
export * from './toolbar.js';`
);

await workspace.write(
Expand Down Expand Up @@ -149,6 +150,52 @@ export function toLower(str: string): string {
export const greet = (name: string) => \`Hello, \${name}!\`;`
);

// Nested object with barrel-exported typeof references (DevTool pattern)
// ToolbarMenu is directly imported → in scope → resolved by name
// Actions is imported as barrel object, CopyAction/PasteAction inside are NOT directly imported
await workspace.write(
"packages/core/src/toolbar/copy-action.ts",
`export class CopyAction {
execute(): void {}
}`
);

await workspace.write(
"packages/core/src/toolbar/paste-action.ts",
`export class PasteAction {
execute(): void {}
}`
);

await workspace.write(
"packages/core/src/toolbar/actions.ts",
`import { CopyAction } from './copy-action';
import { PasteAction } from './paste-action';

export const Actions = {
Copy: CopyAction,
Paste: PasteAction,
};`
);

await workspace.write(
"packages/core/src/toolbar/toolbar-menu.ts",
`export class ToolbarMenu {
open(): void {}
}`
);

await workspace.write(
"packages/core/src/toolbar.ts",
`import { ToolbarMenu } from './toolbar/toolbar-menu';
import { Actions } from './toolbar/actions';

export const Toolbar = {
Menu: ToolbarMenu,
Actions,
};`
);

await workspace.write(
"packages/core/src/classes.ts",
`import { UserConfig } from '@libs/types';
Expand Down
Loading