Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: JSON doc generation #533

Merged
merged 27 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ Will produce a file called `API.md` with the api reference for this module.
As a library:

```ts
import { Documentation } from 'jsii-docgen';
import { Documentation, Language } from 'jsii-docgen';

const docs = await Documentation.forLocalPackage('.', { language: 'ts' });
const markdown = docs.render().render(); // returns a markdown string
const docs = await Documentation.forProject('.');
const markdown = await docs.toMarkdown({ language: Language.TYPESCRIPT }).render(); // returns a markdown string

const json = await docs.toJson({ language: Language.TYPESCRIPT }).render(); // returns a JSON object
```

Note that you can pass in either `ts` or `python` as the language, as opposed to the CLI, which only produces a TypeScript reference.
Curreently jsii-docgen supports generating documentation in the following languages:

- TypeScript (`typescript`)
- Python (`python`)
- Java (`java`)
- C# (`csharp` or `dotnet`)

## Contributions

Expand Down
12 changes: 8 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import { Documentation } from './index';
export async function main() {
const args = yargs
.usage('Usage: $0')
.option('output', { type: 'string', alias: 'o', required: false, desc: 'Output filename (defaults to API.md)' })
.option('output', { type: 'string', alias: 'o', required: false, desc: 'Output filename (defaults to API.md if format is markdown, and API.json if format is JSON)' })
.option('format', { alias: 'f', default: 'md', choices: ['md', 'json'], desc: 'Output format, markdown or json' })
.option('language', { alias: 'l', default: 'typescript', choices: Language.values().map(x => x.toString()), desc: 'Output language' })
.example('$0', 'Generate documentation for the current module as a single file (auto-resolves node depedencies)')
.argv;

const language = Language.fromString(args.language);
const docs = await Documentation.forProject(process.cwd());
const output = args.output ?? 'API.md';
const markdown = await docs.render({ readme: false, language });
fs.writeFileSync(output, markdown.render());
const options = { readme: false, language };
const fileSuffix = args.format === 'md' ? 'md' : 'json';
const output = args.output ?? `API.${fileSuffix}`;

const content = await (args.format === 'md' ? docs.toMarkdown(options) : docs.toJson(options));
fs.writeFileSync(output, content.render());
}

main().catch(e => {
Expand Down
10 changes: 10 additions & 0 deletions src/docgen/render/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Type-safe Json renderer.
*/
export class Json<T> {
constructor(public readonly content: T) {};

public render(replacer?: any, space?: string | number): string {
return JSON.stringify(this.content, replacer, space);
}
}
51 changes: 18 additions & 33 deletions src/docgen/render/markdown.ts → src/docgen/render/markdown-doc.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,4 @@
import * as reflect from 'jsii-reflect';

const sanitize = (input: string): string => {
return input
.toLowerCase()
.replace(/[^a-zA-Z0-9 ]/g, '')
.replace(/ /g, '-');
};

export const anchorForId = (id: string): string => {
return sanitize(id);
};
import { DocsSchema } from '../schema';

/**
* Options for defining a markdown header.
Expand Down Expand Up @@ -64,11 +53,11 @@ export interface MarkdownOptions {
/**
* Markdown element.
*/
export class Markdown {
export class MarkdownDocument {
/**
* An empty markdown element.
*/
public static readonly EMPTY = new Markdown();
public static readonly EMPTY = new MarkdownDocument();

/**
* Sanitize markdown reserved characters from external input.
Expand All @@ -90,15 +79,16 @@ export class Markdown {
}

public static pre(text: string): string {
return `\`${text}\``;
// using <code> instead of backticks since this allows links
return `<code>${text}</code>`;
}

public static italic(text: string) {
return `*${text}*`;
}

private readonly _lines = new Array<string>();
private readonly _sections = new Array<Markdown>();
private readonly _sections = new Array<MarkdownDocument>();

private readonly id?: string;
private readonly header?: string;
Expand All @@ -109,25 +99,24 @@ export class Markdown {
}

/**
* Render a `jsii-reflect.Docs` element into the markdown.
* Render a docs element into the markdown.
*/
public docs(docs: reflect.Docs) {
public docs(docs: DocsSchema) {
if (docs.summary) {
this.lines(Markdown.sanitize(docs.summary));
this.lines(MarkdownDocument.sanitize(docs.summary));
this.lines('');
}
if (docs.remarks) {
this.lines(Markdown.sanitize(docs.remarks));
this.lines(MarkdownDocument.sanitize(docs.remarks));
this.lines('');
}

if (docs.docs.see) {
this.quote(docs.docs.see);
if (docs.see) {
this.quote(docs.see);
}

const customLink = docs.customTag('link');
if (customLink) {
this.quote(`[${customLink}](${customLink})`);
if (docs.see) {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
this.quote(`[${docs.see}](${docs.see})`);
}
}

Expand Down Expand Up @@ -166,7 +155,7 @@ export class Markdown {
this.lines('');
}

public section(section: Markdown) {
public section(section: MarkdownDocument) {
this._sections.push(section);
}

Expand All @@ -180,20 +169,16 @@ export class Markdown {

const content: string[] = [];
if (this.header) {
const anchor = anchorForId(this.id ?? '');
const heading = `${'#'.repeat(headerSize)} ${this.header}`;

// This is nasty, i'm aware.
// Its just an escape hatch so that produce working links by default, but also support producing the links that construct-hub currently relies on.
// This will be gone soon.
// Note though that cross links (i.e links dependencies will not work yet regardless)
// temporary hack to avoid breaking Construct Hub
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
const headerSpan = !!process.env.HEADER_SPAN;
if (headerSpan) {
content.push(
`${heading} <span data-heading-title="${this.header}" data-heading-id="${anchor}"></span>`,
`${heading} <span data-heading-title="${this.options.header?.title}" data-heading-id="${this.id}"></span>`,
);
} else {
content.push(`${heading} <a name="${this.id}" id="${anchor}"></a>`);
content.push(`${heading} <a name="${this.options.header?.title}" id="${this.id}"></a>`);
}
content.push('');
}
Expand Down
Loading