diff --git a/.eslintignore b/.eslintignore index 1665521..14623e3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ node_modules/ +prettier-plugin-fake/ lib/ tests/files diff --git a/package.json b/package.json index 4cb8302..92cdd9b 100755 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "prettier-plugin-jsdoc", "version": "0.3.17", "description": "", + "workspaces": { + "prettier-plugin-fake": "./prettier-plugin-fake" + }, "main": "lib/index.js", "scripts": { "prepare": "tsc", @@ -79,4 +82,4 @@ "engines": { "node": ">=12.0.0" } -} +} \ No newline at end of file diff --git a/prettier-plugin-fake/index.js b/prettier-plugin-fake/index.js new file mode 100644 index 0000000..d91830f --- /dev/null +++ b/prettier-plugin-fake/index.js @@ -0,0 +1,29 @@ +const { parsers: typescriptParsers } = require("prettier/parser-typescript"); + +/** + * + * @param {*} text + * @param {import("prettier/index").Options} options + * @returns + */ +const preprocess = (text, options) => { + if ( + options.plugins.find((plugin) => plugin.name === "prettier-plugin-fake") + ) { + return `//prettier-plugin-fake\n${text}`; + } + return text; +}; + +exports.parsers = { + typescript: { + ...typescriptParsers.typescript, + preprocess: typescriptParsers.typescript.preprocess + ? (text, options) => + preprocess( + typescriptParsers.typescript.preprocess(text, options), + options, + ) + : preprocess, + }, +}; diff --git a/prettier-plugin-fake/package.json b/prettier-plugin-fake/package.json new file mode 100644 index 0000000..98ce78c --- /dev/null +++ b/prettier-plugin-fake/package.json @@ -0,0 +1,8 @@ +{ + "name": "prettier-plugin-fake", + "version": "1.1.1", + "main": "index.js", + "files": [ + "index.js" + ] +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index dd3d8f8..b4a4603 100755 --- a/src/index.ts +++ b/src/index.ts @@ -95,32 +95,89 @@ const parsers = { // JS - Babel get babel() { const parser = parserBabel.parsers.babel; - return { ...parser, parse: getParser(parser.parse) }; + return mergeParsers(parser, "babel-babel"); }, get "babel-flow"() { const parser = parserBabel.parsers["babel-flow"]; - return { ...parser, parse: getParser(parser.parse) }; + return mergeParsers(parser, "babel-flow"); }, get "babel-ts"() { const parser = parserBabel.parsers["babel-ts"]; - return { ...parser, parse: getParser(parser.parse) }; + return mergeParsers(parser, "babel-ts"); }, // JS - Flow get flow() { const parser = parserFlow.parsers.flow; - return { ...parser, parse: getParser(parser.parse) }; + return mergeParsers(parser, "flow"); }, // JS - TypeScript - get typescript() { + get typescript(): prettier.Parser { const parser = parserTypescript.parsers.typescript; - return { ...parser, parse: getParser(parser.parse) }; + + return mergeParsers(parser, "typescript"); // require("./parser-typescript").parsers.typescript; }, get "jsdoc-parser"() { // Backward compatible, don't use this in new version since 1.0.0 const parser = parserBabel.parsers["babel-ts"]; - return { ...parser, parse: getParser(parser.parse) }; + + return mergeParsers(parser, "babel-ts"); }, }; +function mergeParsers(originalParser: prettier.Parser, parserName: string) { + let pluginParse = originalParser.parse; + + const jsDocParse = getParser(pluginParse) as any; + const jsDocPreprocess = (text: string, options: prettier.ParserOptions) => { + const tsPlugin = options.plugins.find((plugin) => { + return ( + typeof plugin === "object" && + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + plugin.name && + plugin.parsers && + // eslint-disable-next-line no-prototype-builtins + plugin.parsers.hasOwnProperty(parserName) + ); + }) as prettier.Plugin | undefined; + + if ( + !tsPlugin || // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + tsPlugin.name === "prettier-plugin-jsdoc" || + tsPlugin.parsers?.hasOwnProperty("jsdoc-parser") + ) { + return originalParser.preprocess + ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + originalParser.preprocess(text, options) + : text; + } + + const tsPluginParser = tsPlugin.parsers?.typescript || originalParser; + + pluginParse = tsPluginParser.parse || pluginParse; + + const preprocess = tsPluginParser.preprocess || originalParser.preprocess; + + Object.assign(parser, { + ...parser, + ...tsPluginParser, + preprocess: jsDocPreprocess, + parse: jsDocParse, + }); + + return preprocess ? preprocess(text, options) : text; + }; + + const parser = { + ...originalParser, + preprocess: jsDocPreprocess, + parse: jsDocParse, + }; + + return parser; +} + export { languages, options, parsers, defaultOptions }; diff --git a/src/utils.ts b/src/utils.ts index 73f972f..39c9dce 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -103,6 +103,8 @@ function formatType(type: string, options?: Options): string { pretty = format(`${TYPE_START}${pretty}`, { ...options, parser: "typescript", + plugins: [], + filepath: "file.ts", }); pretty = pretty.slice(TYPE_START.length); pretty = pretty.replace(/[;\n]*$/g, ""); diff --git a/tests/__snapshots__/compatibleWithPlugins.test.ts.snap b/tests/__snapshots__/compatibleWithPlugins.test.ts.snap new file mode 100644 index 0000000..ac9f2fd --- /dev/null +++ b/tests/__snapshots__/compatibleWithPlugins.test.ts.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should convert to single line if necessary 1`] = ` +"//prettier-plugin-fake +/** Single line description */ +" +`; + +exports[`Should convert to single line if necessary 2`] = ` +"//prettier-plugin-fake +/** Single line description */ +" +`; + +exports[`Should convert to single line if necessary 3`] = ` +"//prettier-plugin-fake +/** + * Single line description + * + * @returns {Boolean} Always true + */ +" +`; + +exports[`Should format jsDoc default values 1`] = ` +"//prettier-plugin-fake + +/** + * @param {String} [arg1=\\"defaultTest\\"] Foo. Default is \`\\"defaultTest\\"\` + * @param {number} [arg2=123] The width of the rectangle. Default is \`123\` + * @param {number} [arg3=123] Default is \`123\` + * @param {number} [arg4=Foo.bar.baz] Default is \`Foo.bar.baz\` + * @param {number | string} [arg5=123] Something. Default is \`123\` + */ +" +`; + +exports[`Should format jsDoc default values 2`] = ` +"//prettier-plugin-fake +//prettier-plugin-fake + +/** + * @param {String} [arg1=\\"defaultTest\\"] Foo. Default is \`\\"defaultTest\\"\` + * @param {number} [arg2=123] The width of the rectangle. Default is \`123\` + * @param {number} [arg3=123] Default is \`123\` + * @param {number} [arg4=Foo.bar.baz] Default is \`Foo.bar.baz\` + * @param {number | string} [arg5=123] Something. Default is \`123\` + */ +" +`; + +exports[`Should format regular jsDoc 1`] = ` +"//prettier-plugin-fake + +import b from \\"b\\"; +import { k } from \\"k\\"; +import a from \\"a\\"; + +/** + * Function example description that was wrapped by hand so it have more then + * one line and don't end with a dot REPEATED TWO TIMES BECAUSE IT WAS EASIER to + * copy function example description that was wrapped by hand so it have more + * then one line. + * + * @async + * @private + * @memberof test + * @example + * //prettier-plugin-fake + * var one = 5; + * var two = 10; + * + * if (one > 2) { + * two += one; + * } + * + * @param {String | Number} text - Some text description that is very long and + * needs to be wrapped + * @param {String} [defaultValue=\\"defaultTest\\"] TODO. Default is \`\\"defaultTest\\"\` + * @param {Number | Null} [optionalNumber] + * @returns {Boolean} Description for @returns with s + * @undefiendTag + * @undefiendTag {number} name des + */ +const testFunction = (text, defaultValue, optionalNumber) => true; +" +`; + +exports[`Should format regular jsDoc 2`] = ` +"//prettier-plugin-fake +//prettier-plugin-fake + +import b from \\"b\\"; +import { k } from \\"k\\"; +import a from \\"a\\"; + +/** + * Function example description that was wrapped by hand so it have more then + * one line and don't end with a dot REPEATED TWO TIMES BECAUSE IT WAS EASIER to + * copy function example description that was wrapped by hand so it have more + * then one line. + * + * @async + * @private + * @memberof test + * @example + * //prettier-plugin-fake + * //prettier-plugin-fake + * var one = 5; + * var two = 10; + * + * if (one > 2) { + * two += one; + * } + * + * @param {String | Number} text - Some text description that is very long and + * needs to be wrapped + * @param {String} [defaultValue=\\"defaultTest\\"] TODO. Default is \`\\"defaultTest\\"\` + * @param {Number | Null} [optionalNumber] + * @returns {Boolean} Description for @returns with s + * @undefiendTag + * @undefiendTag {number} name des + */ +const testFunction = (text, defaultValue, optionalNumber) => true; +" +`; diff --git a/tests/compatibleWithPlugins.test.ts b/tests/compatibleWithPlugins.test.ts new file mode 100755 index 0000000..cac422f --- /dev/null +++ b/tests/compatibleWithPlugins.test.ts @@ -0,0 +1,78 @@ +import prettier from "prettier"; +import { AllOptions } from "../src/types"; + +function subject(code: string, options: Partial = {}) { + return prettier.format(code, { + parser: "typescript", + plugins: ["prettier-plugin-fake", "prettier-plugin-jsdoc"], + jsdocSpaces: 1, + ...options, + } as AllOptions); +} + +test("Should format regular jsDoc", () => { + const result = subject(` + import b from "b" + import {k} from "k" + import a from "a" + +/** +* function example description that was wrapped by hand +* so it have more then one line and don't end with a dot +* REPEATED TWO TIMES BECAUSE IT WAS EASIER to copy +* function example description that was wrapped by hand +* so it have more then one line. +* @return {Boolean} Description for @returns with s +* @param {String|Number} text - some text description that is very long and needs to be wrapped +* @param {String} [defaultValue="defaultTest"] TODO +* @arg {Number|Null} [optionalNumber] +* @private +*@memberof test +@async +* @examples +* var one = 5 +* var two = 10 +* +* if(one > 2) { two += one } +* @undefiendTag${" "} +* @undefiendTag {number} name des +*/ +const testFunction = (text, defaultValue, optionalNumber) => true +`); + + expect(result).toMatchSnapshot(); + expect(subject(result)).toMatchSnapshot(); +}); + +test("Should format jsDoc default values", () => { + const result = subject(` +/** +* @param {String} [arg1="defaultTest"] foo +* @param {number} [arg2=123] the width of the rectangle +* @param {number} [arg3= 123 ] +* @param {number} [arg4= Foo.bar.baz ] +* @param {number|string} [arg5=123] Something. Default is \`"wrong"\` +*/ +`); + + expect(result).toMatchSnapshot(); + expect(subject(result)).toMatchSnapshot(); +}); + +test("Should convert to single line if necessary", () => { + const Result1 = subject(`/** single line description*/`); + const Result2 = subject(`/** + * single line description + * @example + */`); + + const Result3 = subject(`/** + * single line description + * @return {Boolean} Always true + * @example + */`); + + expect(Result1).toMatchSnapshot(); + expect(Result2).toMatchSnapshot(); + expect(Result3).toMatchSnapshot(); +});