diff --git a/src/utils.ts b/src/utils.ts index b11e975..96255dc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -74,14 +74,41 @@ function formatType(type: string, options?: Options): string { try { const TYPE_START = "type name = "; - let pretty = format(`${TYPE_START}${type}`, { + let pretty = type; + + // Rest parameter types start with "...". This is supported by TS and JSDoc + // but it's implemented in a weird way in TS. TS will only acknowledge the + // "..." if the function parameter is a rest parameter. In this case, TS + // will interpret `...T` as `T[]`. But we can't just convert "..." to arrays + // because of @callback types. In @callback types `...T` and `T[]` are not + // equivalent, so we have to support "..." as is. + // + // This formatting itself is rather simple. If `...T` is detected, it will + // be replaced with `T[]` and formatted. At the end, the outer array will + // be removed and "..." will be added again. + // + // As a consequence, union types will get an additional pair of parentheses + // (e.g. `...A|B` -> `...(A|B)`). This is technically unnecessary but it + // makes the operator precedence very clear. + // + // https://www.typescriptlang.org/docs/handbook/functions.html#rest-parameters + let rest = false; + if (pretty.startsWith("...")) { + rest = true; + pretty = `(${pretty.slice(3)})[]`; + } + + pretty = format(`${TYPE_START}${pretty}`, { ...options, parser: "typescript", }); pretty = pretty.slice(TYPE_START.length); - pretty = pretty.replace(/[;\n]*$/g, ""); + if (rest) { + pretty = "..." + pretty.replace(/\[\s*\]$/, ""); + } + return pretty; } catch (error) { // console.log("jsdoc-parser", error); diff --git a/tests/__snapshots__/main.test.js.snap b/tests/__snapshots__/main.test.js.snap index b08e07d..8474da0 100644 --- a/tests/__snapshots__/main.test.js.snap +++ b/tests/__snapshots__/main.test.js.snap @@ -37,6 +37,17 @@ exports[`Empty comment 1`] = ` " `; +exports[`Format rest parameters properly 1`] = ` +"/** + * @param {...any} arg1 + * @param {...number} arg2 + * @param {...(string | number)} arg3 + * @param {...(string | number)} arg4 This is equivalent to arg3 + */ +function a() {} +" +`; + exports[`Hyphen at the start of description 1`] = ` "/** * Assign the project to an employee. diff --git a/tests/main.test.js b/tests/main.test.js index 620dd87..4193827 100755 --- a/tests/main.test.js +++ b/tests/main.test.js @@ -427,3 +427,18 @@ test("Non-jsdoc comment", () => { expect(result).toMatchSnapshot(); }); + +test("Format rest parameters properly", () => { + const result = subject(` + /** + * @param {... *} arg1 + * @param {... number} arg2 + * @param {... (string|number)} arg3 + * @param {... string|number} arg4 This is equivalent to arg3 + * + */ + function a(){} + `); + + expect(result).toMatchSnapshot(); +});