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: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ logs
npm-debug.log*

# Transpilation results
*.js
index.js
bin/*.js

# Dependency directories
node_modules/
Expand All @@ -25,3 +26,5 @@ node_modules/

### JetBrains ###
.idea/
yarn.lock
pnpm-lock.yaml
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file modified bin/ts-to-jsdoc
100644 → 100755
Empty file.
25 changes: 15 additions & 10 deletions bin/ts-to-jsdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 = `
Expand All @@ -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);
Expand All @@ -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);
}
}

Expand Down
51 changes: 36 additions & 15 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -262,18 +271,22 @@ 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
* @return Transpiled code (or the original source code if something went wrong)
*/
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: {
Expand All @@ -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`,
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -31,6 +34,7 @@
},
"files": [
"index.js",
"bin/ts-to-jsdoc"
"bin/ts-to-jsdoc",
"bin/ts-to-jsdoc.js"
]
}
5 changes: 4 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
},
"exclude": [
"test/"
]
}