diff --git a/.gitignore b/.gitignore index 054179e..f2c0263 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ logs npm-debug.log* # Transpilation results -*.js +index.js +bin/*.js # Dependency directories node_modules/ @@ -25,3 +26,5 @@ node_modules/ ### JetBrains ### .idea/ +yarn.lock +pnpm-lock.yaml diff --git a/README.md b/README.md index eec434b..bbfa847 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Options: -h --help Shows this. -o --out --output Directory to output transpiled JavaScript. [default: source path] -i --ignore File or directory paths to ignore when transpiling. + -f --force Overwrite existing output files. ``` ### Node.js diff --git a/bin/ts-to-jsdoc b/bin/ts-to-jsdoc old mode 100644 new mode 100755 diff --git a/bin/ts-to-jsdoc.ts b/bin/ts-to-jsdoc.ts index 5c9044d..21d0cbb 100644 --- a/bin/ts-to-jsdoc.ts +++ b/bin/ts-to-jsdoc.ts @@ -4,7 +4,7 @@ import path from "path"; import transpile from "../index"; const { - "--out": out, "--ignore": ignore, "--help": help, _, + "--out": out, "--ignore": ignore, "--force": force, "--help": help, _, } = arguments({ "--out": String, "-o": "--out", @@ -13,12 +13,15 @@ const { "--ignore": [String], "-i": "--ignore", + "--force": Boolean, + "-f": "--force", + "--help": Boolean, "-h": "--help", }); const args = { - out, ignore, help, _, + out, ignore, force, help, _, }; const helpMessage = ` @@ -28,7 +31,9 @@ Usage: Options: -h --help Shows this. -o --out --output Directory to output transpiled JavaScript. [default: source path] - -i --ignore File or directory paths to ignore when transpiling.`; + -i --ignore File or directory paths to ignore when transpiling. + -f --force Overwrite existing output files. +`; if (args.help || Object.keys(args).every((arg) => !args[arg]?.length)) { console.log(helpMessage); @@ -37,13 +42,13 @@ if (args.help || Object.keys(args).every((arg) => !args[arg]?.length)) { if (args.out) { args.out = makePathAbsolute(args.out); - if (!fs.existsSync(args.out)) { - console.error(error(`Output directory ${args.out} does not exist.`)); - process.exit(1); - } - if (!fs.lstatSync(args.out).isDirectory()) { - console.error(error(`Output directory ${args.out} is not a directory.`)); - process.exit(1); + if (fs.existsSync(args.out)) { + if (!args.force) { + console.error(error(`Output directory exists: ${args.out}`)); + process.exit(1); + } + } else { + fs.mkdirSync(args.out); } } diff --git a/index.ts b/index.ts index 6811e47..c2b5ab0 100644 --- a/index.ts +++ b/index.ts @@ -65,7 +65,9 @@ function sanitizeType(str: string): string | null { return str; } -/** Generate @param documentation from function parameters */ +/** + * Generate @param documentation from function parameters, storing it in functionNode + */ function generateParameterDocumentation(functionNode: FunctionLikeDeclaration): void { const params = functionNode.getParameters(); for (const param of params) { @@ -78,23 +80,28 @@ function generateParameterDocumentation(functionNode: FunctionLikeDeclaration): // @ts-ignore .find((tag) => tag.compilerNode.name?.getText() === param.getName()); - const paramName = param.compilerNode.name?.getText(); + const paramNameRaw = param.compilerNode.name?.getText(); + // Skip parameter names if they are present in the type as an object literal + // e.g. destructuring; { a }: { a: string } + const paramName = paramNameRaw.match(/[{},]/) ? "" : ` ${paramNameRaw}`; if (paramTag) { - // Replace tag with one that contains typing info + // Replace tag with one that contains type info const comment = paramTag.getComment(); const tagName = paramTag.getTagName(); - paramTag.replaceWithText(`@${tagName} {${parameterType}} ${paramName} ${comment}`); + paramTag.replaceWithText(`@${tagName} {${parameterType}}${paramName} ${comment}`); } else { jsDoc.addTag({ tagName: "param", - text: `{${parameterType}} ${paramName}`, + text: `{${parameterType}}${paramName}`, }); } } } -/** Generate @returns documentation from function return type */ +/** + * Generate @returns documentation from function return type, storing it in functionNode + */ function generateReturnTypeDocumentation(functionNode: FunctionLikeDeclaration): void { const functionReturnType = sanitizeType(functionNode.getReturnType()?.getText()); const jsDoc = getJsDocOrCreate(functionNode); @@ -119,7 +126,9 @@ function generateReturnTypeDocumentation(functionNode: FunctionLikeDeclaration): } } -/** Generate documentation for function */ +/** + * Generate documentation for a function, storing it in functionNode + */ function generateFunctionDocumentation(functionNode: FunctionLikeDeclaration): void { generateParameterDocumentation(functionNode); generateReturnTypeDocumentation(functionNode); @@ -161,14 +170,14 @@ function generateClassBaseDocumentation(classNode: ClassDeclaration) { } } -/** Generate documentation for class members in general; whether property or method */ +/** Generate documentation for class members in general; either property or method */ function generateClassMemberDocumentation(classMemberNode: ClassMemberNode): void { generateModifierDocumentation(classMemberNode); Node.isObjectProperty(classMemberNode) && generateInitializerDocumentation(classMemberNode); Node.isMethodDeclaration(classMemberNode) && generateFunctionDocumentation(classMemberNode); } -/** Generate documentation for a class -- itself and its members */ +/** Generate documentation for a class — itself and its members */ function generateClassDocumentation(classNode: ClassDeclaration): void { generateClassBaseDocumentation(classNode); classNode.getMembers().forEach(generateClassMemberDocumentation); @@ -262,7 +271,7 @@ function generateInterfaceDocumentation(interfaceNode: InterfaceDeclaration): st /** * Transpile. * @param src Source code to transpile - * @param filename Filename to use internally when transpiling (can be path or just a name) + * @param [filename=input.ts] Filename to use internally when transpiling (can be a path or a name) * @param [compilerOptions={}] Options for the compiler. * See https://www.typescriptlang.org/tsconfig#compilerOptions * @param [debug=false] Whether to log errors @@ -270,10 +279,14 @@ function generateInterfaceDocumentation(interfaceNode: InterfaceDeclaration): st */ function transpile( src: string, - filename: string, + filename = "input.ts", compilerOptions: object = {}, debug = false, ): string { + // Useless variable to prevent comments from getting removed when code contains just + // typedefs/interfaces, which get transpiled to nothing but comments + const protectCommentsHeader = "const __tsToJsdoc_protectCommentsHeader = 1;\n"; + try { const project = new Project({ compilerOptions: { @@ -283,9 +296,7 @@ function transpile( }, }); - // Useless variable to prevent comments from getting removed when code contains just - // typedefs/interfaces, which get transpiled to nothing but comments - const code = `const __fakeValue = null;\n\n${src}`; + const code = protectCommentsHeader + src; // ts-morph throws a fit if the path already exists const sourceFile = project.createSourceFile( `${path.basename(filename, ".ts")}.ts-to-jsdoc.ts`, @@ -302,8 +313,18 @@ function transpile( sourceFile.getFunctions().forEach(generateFunctionDocumentation); - const result = project.emitToMemory()?.getFiles()?.[0]?.text; + let result = project.emitToMemory()?.getFiles()?.[0]?.text; if (result) { + if (!result.startsWith(protectCommentsHeader)) { + throw new Error( + "Internal error: generated header is missing in output\n\n" + + `protectCommentsHeader: ${JSON.stringify(protectCommentsHeader)}\n` + + `Output: ${ + JSON.stringify(`${result.slice(protectCommentsHeader.length + 100)} ...`) + }`, + ); + } + result = result.slice(protectCommentsHeader.length); return `${result}\n\n${typedefs}\n\n${interfaces}`; } } catch (e) { diff --git a/package-lock.json b/package-lock.json index 00e28fa..7814ecc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ts-to-jsdoc", - "version": "1.1.2", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ts-to-jsdoc", - "version": "1.1.2", + "version": "1.2.0", "license": "MIT", "dependencies": { "arg": "^5.0.1", diff --git a/package.json b/package.json index 74093ca..3564d62 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "name": "ts-to-jsdoc", - "version": "1.1.2", + "version": "1.2.0", "description": "Transpile TypeScript code to fully compatible JavaScript + JSDoc comments.", "main": "index.js", "bin": "./bin/ts-to-jsdoc", "repository": "github:futurGH/ts-to-jsdoc", + "scripts": { + "build": "tsc --build" + }, "keywords": [ "doc", "docs", @@ -31,6 +34,7 @@ }, "files": [ "index.js", - "bin/ts-to-jsdoc" + "bin/ts-to-jsdoc", + "bin/ts-to-jsdoc.js" ] } diff --git a/tsconfig.json b/tsconfig.json index e1d4209..6dcbbd4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,8 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true - } + }, + "exclude": [ + "test/" + ] }