npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ts-doc-gen-md

v0.0.0

Published

Documentation generation library in markdown, for typescript projects.

Downloads

201

Readme

ts-doc-gen-md

Table of contents

Installation

npm install --save-dev ts-doc-gen-md

Description

Library that generates documentation in markdown for TypeScript projects. The created documentation is just the type signatures together with their respective raw JSDoc comments. For the documentation to be generated you have to provide the typings entry point of your project. Every type that is exported from there, together with the types it references, is resolved and documented. Imports and exports from node modules are documented as import and export statements, without further deeper resolution into the node module itself.

You can inject the markdown in the README.md file (take a look at md-in-place for that purpose) or create a separate markdown file. The markdown is made in such a way so that it is displayed properly on github. For the best results follow the best practices. The library will not work for the non covered cases.

Coverage

Code coverage is around 90%.

Best practices

  1. The internal API should depend on the public API, not the other way around.

    The following code snippet is an example of a typings entry point file that has its public API depend on the private API:

    export declare type iAmPublic = {
        prop1: boolean;
        prop2: Omit<iAmBad, "a">;
    };
    declare type iAmBad = {
        a: "a";
        b: "b";
    };

    The type iAmBad gets fully documented (both of its properties a and b are documented), while only the property b is needed for the public API. That is neither a fault or a lack of features by the documentation generation library. The public api being dependent on the private api, is to be blamed here. Here is how to inverse the dependency:

    export declare type iAmPublic = {
        prop1: boolean;
        prop2: iAmPublicAndNotPrivateDependent;
    };
    declare type iAmPublicAndNotPrivateDependent = { b: "b" };
    declare type iAmPrivateAndPublicDependent = {
        a: string;
    } & iAmPublicAndNotPrivateDependent;

    Now the generated documentation will not document property a.

    Similarly we can apply the same idea for other built in type functions (like : Pick, Parameters and ReturnType) that are usually used in way that makes the public API depend on the private API.

    Another similar example:

    export declare type iAmPublic = iAmBad["b"];
    declare type iAmBad = {
        a: "a";
        b: "b";
    };

    and here is how to achieve dependency inversion:

    export type iAmPublic = iAmPublicAndNotPrivateDependent;
    declare type iAmPublicAndNotPrivateDependent = "b";
    declare type iAmPrivateAndPublicDependent = {
        a: "a";
        b: iAmPublicAndNotPrivateDependent;
    };
  2. For projects that do not export many values from their entry point, it is advised to create a file called publicApi.ts, that will contain all the public api types. This file should be placed in the root of your source folder so that it is easily accessible.

  3. Keep the JSDoc comments concise.

  4. A part of the documentation has to be written in the README file and the JSDoc comments of your source code should have it as a context.

  5. Add a ruler in your code editor at a fixed column for all typescript files. This will aid you in creating JSDoc comments that do not overflow beyond that column.

  6. Functions with parameters that have initial values, do not get their initial values documented. Consider gathering all the parameters in an object and then using JSDoc comments with @default JSDoc tag to defined the initial value.

    • bad:
      export function foo(a: number = 1) {
          //some code
      }
    • good:
      export function foo(parameters: {
          /**
           * @default 1
           */
          a: number;
      }) {
          //initialize parameter `a` here
          //some code
      }
  7. It would be a good idea to avoid using classes, if possible. The sole reason is that the JSDoc comments of the implemented interfaces do not appear in the IDE intellisense. This forces you to write the JSDoc comments in the class, which makes your public API documentation be scattered throughout your project.

  8. If your public api exposes regular expressions values, they will appear as type RegExp in the generated documentation. For that reason make sure that you write descriptive enough JSDoc comments that will appear in the documentation and explain what the regular expression does.

Non covered cases

  1. Function overloading

  2. TypeScript namespaces

  3. TypeScript decorators

  4. TypeScript triple slash directives

  5. CommonJS import/exports

  6. imports and exports from node modules are documented as import and export statements, without further deeper resolution into the node module itself

  7. chain export statements or type references that need to be resolved for documentation and on resolution stumble upon delegation export statements from node modules

    In the current example since the library does not get into node modules when resolving exports, it is not possible to know whether there is an exported type from node-module with identifier A.

    // index.ts
    export { A } from "aFile.ts";
    
    // aFile.ts
    export * from "node-module";
    
    export declare const A = 1;

Documentation

The function that generates documentation has been converted to CLI using fn-to-cli. That means that you can generate documentation using the CLI or by importing the function from the entry point of this node module.

Use in terminal the following command:

npx ts-doc-gen-md --help

to get the CLI documentation:

CLI syntax:

  ts-doc-gen-md tsDocGenMd? [[--<option> | -<flag>] <value>]#

Description:

  Generates documentation in markdown, given the typings entry point file of a
  typescript project.

Non required options:

  -i --input                 : string                  = "./dist/index.d.ts"  Path to the typings entry point file.
  -o --output                : string                  = undefined            Path to save the generated documentation. If no path is provided then no output file is created.
     --sort                  : boolean                 = false                Display in alphabetic order the documentation.
     --prefixHref            : string                  = ""                   Prefix for all the hyperlinks of the generated documentation.
     --headingStartingNumber : 2 | 3 | 4 | 5 | 6       = 3                    Number that specifies the starting heading number of the generated documentation.
     --format                : (src: string) => string = (src) => src         Used to format the code.

Motivation

I was looking to automate the documentation generation process for my TypeScript projects, and I realized that I had to create my own documentation generation library. I made that decision after I checked the available choices: typedoc, api-extractor, which back then (December of 2020), I found:

  1. difficult to be used
  2. opinionated
  3. slow at adopting features
  4. missing basic features
  5. producing documentation that is unfamiliar looking to the developer

Acknowledgements

You might find interesting

  • typedoc, documentation generation library for TypeScript projects
  • api-extractor, documentation generation library for TypeScript projects

FAQs

That is because, it will not make it possible to properly hyperlink to type references, since they will point to a collapsed details tag.

No. I consider that over-engineering. Markdown is enough for generating documentation even for complex libraries.

That is likely because I am not experienced enough with the typescript node module abstract syntax tree parser.

The d.ts files simplify complicated cases that appear in the .ts files. For example there is no need for me to do type inference and resolve destructured exports.

Contributing

I am open to suggestions/pull request to improve this program.

You will find the following commands useful:

  • Clones the github repository of this project:

    git clone https://github.com/lillallol/ts-doc-gen-md
  • Installs the node modules (nothing will work without them):

    npm install
  • Tests the source code:

    npm run test
  • Lints the source folder using typescript and eslint:

    npm run lint
  • Builds the typescript code from the ./src folder to javascript code in ./dist:

    npm run build-ts
  • Creates the CLI executable of this program in ./bin/bin.js:

    npm run build-bin

    Make sure that the ./dist exists when you execute this command, otherwise it will not work.

  • Injects in place the generated toc and imported files to README.md:

    npm run build-md
  • Checks the project for spelling mistakes:

    npm run spell-check

    Take a look at the related configuration ./cspell.json.

  • Checks ./src for dead typescript files:

    npm run dead-files

    Take a look at the related configuration ./unimportedrc.json.

Terminology

Here I define the terminology used in the source code, and give simple examples when necessary.

chain export statement

export * from "./some/where";
export * as x from "./some/where";
export { A, b as B, default as C, default } from "./some/where";
export { A, b as B, c as default };
export default foo;

delegation export statement

export * from "./some/where";

namespace export statement

export * as x from "./some/where";

named export path full

export { A, b as B, default as C, default } from "./some/where";

named export path less

export { A, b as B, c as default };

default export chain

export default foo;

export statement non chain

export function foo() {}
export class A {
    constructor() {}
}
export const a = 1,
    b = 2;
export interface I {}
export type obj = { a: number; b: string };
export enum Country {
    Germany,
    Sweden,
    USA,
}

Even without the export keyword, the previous statements are still referred as export statement non chain.

export default function foo() {}
export default class A {
    constructor() {}
}
export default const a = 1;
export default interface I {

}

export chain leaf statement

These are the statements that were located while resolving the public api but can not be resolved more deeply, i.e. : non chain export statements, chain exports and imports from node modules.

export statement

Non chain, and export export statements.

resolution index

Given the following statement :

export const a: number = 1, b: string = "1";
//           0,             1

or :

import D, { A, b as B, default as C } from "./some/where";
//     0,   1,      2,            3

the resolution index defines at which one of the values we are interested in.

statement index

Given a file, the statement index corresponds to the index of the statement we are interested in. For example :

"hello world"; //statement index 0
export * from "./some/where"; //statement index 1

function foo() {} //statement index 2

entry point file

The entry point .d.ts file of the project to be documented.

resolve export statement

A chain export statement corresponds to a set of non chain export statements. Resolving an export statement means, finding this set.

documentation node

For each non chain export statements that is made public from the entry point file, a documentation node is created with all the necessary information needed to create a human readable documentation.

documentation reference node

Some statements that correspond to documentation nodes, reference other statements in their type signature. For each referenced statement, a documentation reference node is created.

How it works

The typescript abstract syntax tree (ast) parser from the typescript node module is used to parse the typings entry point file. Export statements are located. Those that are chain exports get resolved to non chain exports. After that the abstract syntax tree of each statement is iterated. The iteration serves two purposes:

  • create the documentation string of the statement
  • locate the referenced types, resolve them to their non chain export statements and recursively apply to them the same steps

Help needed

The chain export to non chain export resolution was coded from scratch and it is also used to resolve the statements of type references. It was really tedious to code that. If someone knows any similar built in functionality in the typescript node module, then feel free to inform me. Take into account that this functionality also resolves the namespaces and the exposed name to the public api.

Changelog

0.0.0

First publish.

License

MIT