From 4688bfb1a14d06f2d0bf794722a56e85608d735a Mon Sep 17 00:00:00 2001 From: chungyo Date: Tue, 8 Sep 2026 13:18:15 +0900 Subject: [PATCH] fix(check): report missing docs for destructured parameters --- .changeset/fix-destructured-param-docs.md | 5 ++ .../validate/validator/function-validator.ts | 2 +- .../commands/check/function-validator.spec.ts | 66 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-destructured-param-docs.md diff --git a/.changeset/fix-destructured-param-docs.md b/.changeset/fix-destructured-param-docs.md new file mode 100644 index 0000000..72f3ce7 --- /dev/null +++ b/.changeset/fix-destructured-param-docs.md @@ -0,0 +1,5 @@ +--- +"docflow": patch +--- + +Report missing `@param` documentation for destructured parameters without a JSDoc root name. diff --git a/packages/cli/src/commands/check/validate/validator/function-validator.ts b/packages/cli/src/commands/check/validate/validator/function-validator.ts index 8ff75ea..34a91a3 100644 --- a/packages/cli/src/commands/check/validate/validator/function-validator.ts +++ b/packages/cli/src/commands/check/validate/validator/function-validator.ts @@ -131,7 +131,7 @@ export class FunctionValidator extends Validator { const { value: rootName } = jsDocRootsForDestructured.next(); if (rootName == null) { - return []; + return [entry.name]; } return [rootName, ...entry.paths.map(path => `${rootName}.${path}`)]; diff --git a/packages/cli/src/tests/commands/check/function-validator.spec.ts b/packages/cli/src/tests/commands/check/function-validator.spec.ts index 5bfc387..8530ca9 100644 --- a/packages/cli/src/tests/commands/check/function-validator.spec.ts +++ b/packages/cli/src/tests/commands/check/function-validator.spec.ts @@ -272,6 +272,72 @@ describe("FunctionValidator", () => { }); describe("destructured param validation", () => { + it("should detect missing documentation for a destructured parameter without a virtual root", () => { + const sourceFile = createTSSourceFile(` + /** + * @public + * @returns The greeting + */ + export function greet({ name }: { name: string }): string { + return name; + } + `); + + const fn = sourceFile.getFunctions()[0]; + assert(fn != null, "Expected function"); + + const validator = new FunctionValidator(fn, parseJSDocFromNode(fn)); + const result = validator.validate(); + + expect(result.isValid).toBe(false); + expect(result.errors).toEqual([{ type: "missing_param", target: "{ name }" }]); + }); + + it("should detect an undocumented destructured parameter after a documented one", () => { + const sourceFile = createTSSourceFile(` + /** + * @public + * @param first - The first options + * @param first.name - The name + * @returns The greeting + */ + export function greet({ name }: { name: string }, { greeting }: { greeting: string }): string { + return greeting + name; + } + `); + + const fn = sourceFile.getFunctions()[0]; + assert(fn != null, "Expected function"); + + const validator = new FunctionValidator(fn, parseJSDocFromNode(fn)); + const result = validator.validate(); + + expect(result.isValid).toBe(false); + expect(result.errors).toEqual([{ type: "missing_param", target: "{ greeting }" }]); + }); + + it("should not use a named parameter's documentation as a destructured root", () => { + const sourceFile = createTSSourceFile(` + /** + * @public + * @param prefix - The greeting prefix + * @returns The greeting + */ + export function greet(prefix: string, { name }: { name: string }): string { + return prefix + name; + } + `); + + const fn = sourceFile.getFunctions()[0]; + assert(fn != null, "Expected function"); + + const validator = new FunctionValidator(fn, parseJSDocFromNode(fn)); + const result = validator.validate(); + + expect(result.isValid).toBe(false); + expect(result.errors).toEqual([{ type: "missing_param", target: "{ name }" }]); + }); + it("should return valid when destructured params are documented with virtual root name", () => { const sourceFile = createTSSourceFile(` interface Options {