diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 00000000..3255b143 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +service_name: travis-ci diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4d9e87f5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +@types/* linguist-generated=true +dist/* linguist-generated=true +**/__snapshots__/* linguist-generated=true +**/examples/* linguist-generated=true diff --git a/packages/ts-docs-gen/examples/simple/docs/api/exported-const-variables.md b/packages/ts-docs-gen/examples/simple/docs/api/exported-const-variables.md new file mode 100644 index 00000000..7fe3fe2f --- /dev/null +++ b/packages/ts-docs-gen/examples/simple/docs/api/exported-const-variables.md @@ -0,0 +1,33 @@ +# exported-const-variables + +## Kintamasis + +First const variable in the exported items. + +```ts +const Kintamasis: "Hello World!"; +``` + +### Type + +"Hello World!" + +## Kintamasis2 + +```ts +const Kintamasis2: "World, Hello!"; +``` + +### Type + +"World, Hello!" + +## Kintamasis3 + +```ts +const Kintamasis3: "Not imported!"; +``` + +### Type + +"Not imported!" diff --git a/packages/ts-docs-gen/examples/simple/docs/api/exported-functions.md b/packages/ts-docs-gen/examples/simple/docs/api/exported-functions.md new file mode 100644 index 00000000..6b8e6e49 --- /dev/null +++ b/packages/ts-docs-gen/examples/simple/docs/api/exported-functions.md @@ -0,0 +1,5 @@ +# exported-functions + +## function: Foo + +## function: Bar diff --git a/packages/ts-docs-gen/examples/simple/docs/api/index.md b/packages/ts-docs-gen/examples/simple/docs/api/index.md new file mode 100644 index 00000000..a0f98148 --- /dev/null +++ b/packages/ts-docs-gen/examples/simple/docs/api/index.md @@ -0,0 +1,28 @@ +[ClassDeclaration-0]: index.md#class-world +[ClassDeclaration-1]: index.md#class-earth +[FunctionDeclaration-0]: exported-functions.md#function-foo +# index + +## class: World + +## class: Earth + +## Hello + +```typescript +const Hello: World & Earth; +``` + +### Type + +[World][ClassDeclaration-0] & [Earth][ClassDeclaration-1] + +## FooFunc + +```typescript +const FooFunc: () => string; +``` + +### Type + +[Foo][FunctionDeclaration-0] diff --git a/packages/ts-docs-gen/examples/simple/exported-const-variables.ts b/packages/ts-docs-gen/examples/simple/exported-const-variables.ts index 140d0134..42aca37f 100644 --- a/packages/ts-docs-gen/examples/simple/exported-const-variables.ts +++ b/packages/ts-docs-gen/examples/simple/exported-const-variables.ts @@ -1,3 +1,6 @@ +/** + * First const variable in the exported items. + */ export const Kintamasis = "Hello World!"; -export const Kintamasis2 = "Hello World!"; +export const Kintamasis2 = "World, Hello!"; export const Kintamasis3 = "Not imported!"; diff --git a/packages/ts-docs-gen/examples/simple/index.ts b/packages/ts-docs-gen/examples/simple/index.ts index fc335410..abb00c30 100644 --- a/packages/ts-docs-gen/examples/simple/index.ts +++ b/packages/ts-docs-gen/examples/simple/index.ts @@ -1,6 +1,13 @@ // tslint:disable -// import { MyInterface } from "./my-types"; +import { Foo } from "./exported-functions"; + +export class World { } +export class Earth { } + +export declare const Hello: World & Earth; + +export const FooFunc = Foo; // export function Foo(): string { // return "foo"; @@ -91,9 +98,9 @@ // Type: TType; // } -export async function GetFoo(): Promise { - return; -} +// export async function GetFoo(): Promise { +// return; +// } // export interface Bar extends Foo, Boo { // OtherStuff: string[]; diff --git a/packages/ts-docs-gen/src/abstractions/api-item-plugin-base.ts b/packages/ts-docs-gen/src/abstractions/api-item-plugin-base.ts index eafbe0e2..734aa81a 100644 --- a/packages/ts-docs-gen/src/abstractions/api-item-plugin-base.ts +++ b/packages/ts-docs-gen/src/abstractions/api-item-plugin-base.ts @@ -2,8 +2,9 @@ import { Contracts } from "ts-extractor"; import { RenderItemOutputDto } from "../contracts/render-item-output-dto"; import { SupportedApiItemKindType, ApiItemKindsAdditional } from "../contracts/supported-api-item-kind-type"; +import { PluginData } from "../contracts/plugin-data"; -export abstract class ApiItemPluginBase { +export abstract class ApiItemPluginBase { // TODO: Clarify naming. protected get SupportKind(): typeof Contracts.ApiItemKinds & typeof ApiItemKindsAdditional { return Object.assign(Contracts.ApiItemKinds, ApiItemKindsAdditional); @@ -15,5 +16,5 @@ export abstract class ApiItemPluginBase { return true; } - public abstract Render(item: Contracts.ApiItemDto, getItem: (itemId: string) => RenderItemOutputDto): RenderItemOutputDto; + public abstract Render(data: PluginData): RenderItemOutputDto; } diff --git a/packages/ts-docs-gen/src/abstractions/file-manager-base.ts b/packages/ts-docs-gen/src/abstractions/file-manager-base.ts new file mode 100644 index 00000000..95931b4e --- /dev/null +++ b/packages/ts-docs-gen/src/abstractions/file-manager-base.ts @@ -0,0 +1,9 @@ +import { Contracts } from "ts-extractor"; +import { RenderItemOutputDto } from "../contracts/render-item-output-dto"; +import { FileOutputDto } from "../contracts/file-output-dto"; + +export abstract class FileManagerBaseBase { + public abstract AddItem(entryFile: Contracts.ApiSourceFileDto, item: RenderItemOutputDto, referenceId: string): void; + + public abstract ToFilesOutput(): FileOutputDto[]; +} diff --git a/packages/ts-docs-gen/src/abstractions/printer-base.ts b/packages/ts-docs-gen/src/abstractions/printer-base.ts deleted file mode 100644 index 34e57074..00000000 --- a/packages/ts-docs-gen/src/abstractions/printer-base.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { RenderedDto } from "../contracts/rendered-dto"; - -export abstract class PrinterBase { - public abstract EntryFileCrawler(data: RenderedDto): any; -} diff --git a/packages/ts-docs-gen/src/builders/generator-configuration-builder.ts b/packages/ts-docs-gen/src/builders/generator-configuration-builder.ts index 182ea74f..5098e2c0 100644 --- a/packages/ts-docs-gen/src/builders/generator-configuration-builder.ts +++ b/packages/ts-docs-gen/src/builders/generator-configuration-builder.ts @@ -6,6 +6,7 @@ import { ApiItemPluginBase } from "../abstractions/api-item-plugin-base"; import { GeneratorConfiguration, WorkingGeneratorConfiguration } from "../contracts/generator-configuration"; import { PluginRegistry } from "../registries/plugin-registry"; +import { DefaultPlugins } from "../default-plugins"; // TODO: Add method to read compiler options from tsconfig. export class GeneratorConfigurationBuilder { @@ -53,6 +54,11 @@ export class GeneratorConfigurationBuilder { public async Build(entryFiles: string[]): Promise { // Register all plugins. const pluginManager = new PluginRegistry(); + // Register default plugins + for (const item of DefaultPlugins) { + pluginManager.Register(item); + } + if (this.configuration.Plugins != null) { // TODO: Register default plugins. // Registering given plugins. diff --git a/packages/ts-docs-gen/src/contracts/file-output-dto.ts b/packages/ts-docs-gen/src/contracts/file-output-dto.ts new file mode 100644 index 00000000..6fde6c5d --- /dev/null +++ b/packages/ts-docs-gen/src/contracts/file-output-dto.ts @@ -0,0 +1,4 @@ +export interface FileOutputDto { + FileLocation: string; + Output: string[]; +} diff --git a/packages/ts-docs-gen/src/contracts/plugin-data.ts b/packages/ts-docs-gen/src/contracts/plugin-data.ts new file mode 100644 index 00000000..6d9845c8 --- /dev/null +++ b/packages/ts-docs-gen/src/contracts/plugin-data.ts @@ -0,0 +1,9 @@ +import { ReferenceTuple } from "./reference-tuple"; +import { RenderItemOutputDto } from "./render-item-output-dto"; +import { Contracts } from "ts-extractor"; + +export interface PluginData { + Reference: ReferenceTuple; + ApiItem: TKind; + GetItem(entryFile: Contracts.ApiSourceFileDto, reference: ReferenceTuple): RenderItemOutputDto; +} diff --git a/packages/ts-docs-gen/src/contracts/reference-tuple.ts b/packages/ts-docs-gen/src/contracts/reference-tuple.ts new file mode 100644 index 00000000..857d01a2 --- /dev/null +++ b/packages/ts-docs-gen/src/contracts/reference-tuple.ts @@ -0,0 +1,4 @@ +/** + * [referenceId, alias] + */ +export type ReferenceTuple = [string, string]; diff --git a/packages/ts-docs-gen/src/contracts/render-item-output-dto.ts b/packages/ts-docs-gen/src/contracts/render-item-output-dto.ts index 26919c09..ba62e2d1 100644 --- a/packages/ts-docs-gen/src/contracts/render-item-output-dto.ts +++ b/packages/ts-docs-gen/src/contracts/render-item-output-dto.ts @@ -2,6 +2,10 @@ import { Contracts } from "ts-extractor"; export interface RenderItemOutputDto { References: string[]; + /** + * Heading is used for navigation in documentation. It should be the same in the render output. + */ + Heading: string; RenderOutput: string[]; ApiItem: Contracts.ApiItemDto; } diff --git a/packages/ts-docs-gen/src/debug.ts b/packages/ts-docs-gen/src/debug.ts index a6a1ac62..0392b129 100644 --- a/packages/ts-docs-gen/src/debug.ts +++ b/packages/ts-docs-gen/src/debug.ts @@ -6,15 +6,17 @@ import { Generator } from "./generator"; async function Main(): Promise { const projectDirectory = path.join(process.cwd(), "./examples/simple/"); - const entryFiles = ["./index.ts", "./exported-const-variables.ts", "./exported-functions.ts"]; + // const entryFiles = ["./index.ts", "./exported-const-variables.ts", "./exported-functions.ts"]; + const entryFiles = ["./index.ts", "./exported-functions.ts"]; const configPromise = new GeneratorConfigurationBuilder(projectDirectory).Build(entryFiles); const config = await configPromise; const generator = new Generator(config); - generator.PrintToFiles(); + // tslint:disable-next-line:no-console + console.log(generator.OutputData); // tslint:disable-next-line:no-debugger - debugger; + await generator.WriteToFiles(); } Main(); diff --git a/packages/ts-docs-gen/src/default-plugins.ts b/packages/ts-docs-gen/src/default-plugins.ts new file mode 100644 index 00000000..b6d52aea --- /dev/null +++ b/packages/ts-docs-gen/src/default-plugins.ts @@ -0,0 +1,5 @@ +import { ApiVariablePlugin } from "./plugins/api-variable-plugin"; + +export const DefaultPlugins = [ + new ApiVariablePlugin() +]; diff --git a/packages/ts-docs-gen/src/extractor-helpers.ts b/packages/ts-docs-gen/src/extractor-helpers.ts new file mode 100644 index 00000000..7b9d7af4 --- /dev/null +++ b/packages/ts-docs-gen/src/extractor-helpers.ts @@ -0,0 +1,47 @@ +import { Contracts, ExtractDto } from "ts-extractor"; +import { ReferenceTuple } from "./contracts/reference-tuple"; + +export namespace ExtractorHelpers { + export function GetReferenceTuples( + extractedData: ExtractDto, + entryFile: Contracts.ApiSourceFileDto, + itemsReference: Contracts.ApiItemReferenceTuple + ): ReferenceTuple[] { + let list: ReferenceTuple[] = []; + + for (const [alias, references] of itemsReference) { + for (const referenceId of references) { + // Check if item is ExportSpecifier or ExportDeclaration. + const apiItem = extractedData.Registry[referenceId]; + + switch (apiItem.ApiKind) { + case Contracts.ApiItemKinds.Export: { + const referenceTuples = GetReferenceTuples(extractedData, entryFile, apiItem.Members); + list = list.concat(referenceTuples); + break; + } + case Contracts.ApiItemKinds.ExportSpecifier: { + if (apiItem.ApiItems == null) { + console.warn(`ApiItems are missing in "${apiItem.Name}"?`); + break; + } + const referenceTuples = GetReferenceTuples(extractedData, entryFile, [[apiItem.Name, apiItem.ApiItems]]); + list = list.concat(referenceTuples); + break; + } + default: { + list.push([referenceId, alias]); + } + } + } + } + + return list; + } + + export function ApiVariableToString(item: Contracts.ApiVariableDto, alias?: string): string { + const name = alias != null ? alias : item.Name; + + return `${item.VariableDeclarationType} ${name}: ${item.Type.Text};`; + } +} diff --git a/packages/ts-docs-gen/src/file-manager.ts b/packages/ts-docs-gen/src/file-manager.ts new file mode 100644 index 00000000..40892827 --- /dev/null +++ b/packages/ts-docs-gen/src/file-manager.ts @@ -0,0 +1,81 @@ +import { Contracts } from "ts-extractor"; +import { MarkdownGenerator } from "@simplrjs/markdown"; +import * as path from "path"; + +import { FileManagerBaseBase } from "./abstractions/file-manager-base"; +import { RenderItemOutputDto } from "./contracts/render-item-output-dto"; +import { FileOutputDto } from "./contracts/file-output-dto"; +import { Helpers } from "./utils/helpers"; + +interface OutputData { + RenderOutput: string[]; +} + +type RenderedItem = Array; + +export class FileManager extends FileManagerBaseBase { + private filesList: Map = new Map(); + private referenceToFile: Map = new Map(); + + private fileHeader(entryFile: Contracts.ApiSourceFileDto): OutputData { + const heading = path.basename(entryFile.Name, path.extname(entryFile.Name)); + + const output: string[] = [ + MarkdownGenerator.header(`${heading}`, 1) + ]; + + return { + RenderOutput: output + }; + } + + private getDefaultEntryFile(entryFile: Contracts.ApiSourceFileDto): RenderedItem { + return [ + this.fileHeader(entryFile) + ]; + } + + private renderItemIsItemOutputDto(item: RenderItemOutputDto | OutputData): item is RenderItemOutputDto { + return (item as RenderItemOutputDto).ApiItem != null; + } + + public AddItem(entryFile: Contracts.ApiSourceFileDto, item: RenderItemOutputDto, referenceId: string): void { + const fileName = path.basename(entryFile.Name, path.extname(entryFile.Name)) + ".md"; + const items = this.filesList.get(fileName) || this.getDefaultEntryFile(entryFile); + items.push(item); + + // Add reference link. + this.referenceToFile.set(referenceId, `${fileName}#${Helpers.HeadingToAnchor(item.Heading)}`); + + this.filesList.set(fileName, items); + } + + public ToFilesOutput(): FileOutputDto[] { + const output: FileOutputDto[] = []; + + for (const [fileLocation, items] of this.filesList) { + // Link definitions to file location. + const references: string[] = []; + for (const item of items) { + if (this.renderItemIsItemOutputDto(item)) { + item.References + .forEach(referenceId => + references.push( + MarkdownGenerator.linkDefinition(referenceId, this.referenceToFile.get(referenceId) || "#__error") + ) + ); + } + } + + output.push({ + FileLocation: fileLocation, + Output: [ + ...references, + ...Helpers.Flatten(items.map(x => [x.RenderOutput, ""])) + ] + }); + } + + return output; + } +} diff --git a/packages/ts-docs-gen/src/generator-helpers.ts b/packages/ts-docs-gen/src/generator-helpers.ts new file mode 100644 index 00000000..7f15fd6b --- /dev/null +++ b/packages/ts-docs-gen/src/generator-helpers.ts @@ -0,0 +1,66 @@ +import { Contracts } from "ts-extractor"; +import { MarkdownGenerator } from "@simplrjs/markdown"; + +export namespace GeneratorHelpers { + export interface TypeToStringDto { + References: string[]; + Text: string; + } + + export function TypeDtoToMarkdownString(type: Contracts.TypeDto): TypeToStringDto { + let references: string[] = []; + let text: string = ""; + + switch (type.ApiTypeKind) { + case Contracts.TypeKinds.Union: + case Contracts.TypeKinds.Intersection: { + const symbol = type.ApiTypeKind === Contracts.TypeKinds.Union ? "|" : "&"; + + type.Types + .map(TypeDtoToMarkdownString) + .forEach(typeItem => { + references = references.concat(typeItem.References); + + if (text === "") { + text = typeItem.Text; + } else { + text += ` ${symbol} ${typeItem.Text}`; + } + }); + break; + } + case Contracts.TypeKinds.Reference: { + references.push(type.ReferenceId); + + // Link to definition + text = `${MarkdownGenerator.link(type.Name || "???", type.ReferenceId, true)}`; + + // Generics + if (type.Generics != null) { + const generics = type.Generics.map(TypeDtoToMarkdownString); + references = references.concat(...generics.map(x => x.References)); + + text += `<${generics.map(x => x.Text).join(", ")}>`; + } + break; + } + case Contracts.TypeKinds.Basic: + default: { + text = type.Name || type.Text; + + // Generics + if (type.Name != null && type.Generics != null) { + const generics = type.Generics.map(TypeDtoToMarkdownString); + references = references.concat(...generics.map(x => x.References)); + + text += `<${generics.map(x => x.Text).join(", ")}>`; + } + } + } + + return { + References: references, + Text: text + }; + } +} diff --git a/packages/ts-docs-gen/src/generator.ts b/packages/ts-docs-gen/src/generator.ts index b2fec265..8d089c27 100644 --- a/packages/ts-docs-gen/src/generator.ts +++ b/packages/ts-docs-gen/src/generator.ts @@ -1,141 +1,95 @@ import { Contracts } from "ts-extractor"; import * as path from "path"; +import * as fs from "fs-extra"; import { GeneratorConfiguration } from "./contracts/generator-configuration"; +import { FileManager } from "./file-manager"; import { RenderItemOutputDto } from "./contracts/render-item-output-dto"; -import { RenderedDto } from "./contracts/rendered-dto"; - +import { ReferenceTuple } from "./contracts/reference-tuple"; import { ApiDefaultPlugin } from "./plugins/api-default-plugin"; -/** - * TODO: Aliasias like - * ```ts - * import { Contracts as ExtractorContracts } from "ts-extractor"; - * ``` - */ +import { ExtractorHelpers } from "./extractor-helpers"; +import { FileOutputDto } from "./contracts/file-output-dto"; + export class Generator { - constructor(private configuration: GeneratorConfiguration) { } + constructor(private configuration: GeneratorConfiguration) { + this.fileManager = new FileManager(); + const { ExtractedData } = this.configuration; - private renderedItems: Map = new Map(); - private renderedData: RenderedDto | undefined; + for (const entryFile of this.configuration.ExtractedData.EntryFiles) { + const referenceTuples = ExtractorHelpers.GetReferenceTuples(ExtractedData, entryFile, entryFile.Members); - private renderApiItem(apiItem: Contracts.ApiItemDto): RenderItemOutputDto { - const plugins = this.configuration.PluginManager.GetPluginsByKind(apiItem.ApiKind); + for (const reference of referenceTuples) { + const [referenceId] = reference; - for (const plugin of plugins) { - if (plugin.CheckApiItem(apiItem)) { - return plugin.Render(apiItem, this.getRenderedItemById); + const renderedItem = this.getRenderedItemByReference(entryFile, reference); + this.fileManager.AddItem(entryFile, renderedItem, referenceId); } } - const defaultPlugin = new ApiDefaultPlugin(); - return defaultPlugin.Render(apiItem, this.getRenderedItemById); + this.outputData = this.fileManager.ToFilesOutput(); } - // TODO: Check for infinity loop. - private getRenderedItemById = (itemId: string): RenderItemOutputDto => { - if (!this.renderedItems.has(itemId)) { - const { Registry } = this.configuration.ExtractedData; - const renderedData = this.renderApiItem(Registry[itemId]); - this.renderedItems.set(itemId, renderedData); - - return renderedData; - } + private renderedItems: Map = new Map(); + private fileManager: FileManager; + private outputData: FileOutputDto[]; - return this.renderedItems.get(itemId)!; + public get OutputData(): ReadonlyArray { + return this.outputData; } - private onRenderData(): RenderedDto { - const { Registry, EntryFiles } = this.configuration.ExtractedData; - - for (const [itemKey] of Object.entries(Registry)) { - if (!this.renderedItems.has(itemKey)) { - this.getRenderedItemById(itemKey); + public async WriteToFiles(): Promise { + for (const item of this.outputData) { + const fullLocation = path.join(this.configuration.OutputDirectory, "api", item.FileLocation); + + try { + // Ensure output directory + await fs.ensureDir(path.dirname(fullLocation)); + // Output file + await fs.writeFile(fullLocation, item.Output.join("\n")); + } catch (error) { + console.error(error); } } - - return { - EntryFiles: EntryFiles, - RenderedItems: this.renderedItems - }; } - public GetRenderedData(): RenderedDto { - let data: RenderedDto | undefined = this.renderedData; - if (data == null) { - data = this.onRenderData(); - } + private getRenderedItemByReference = (entryFile: Contracts.ApiSourceFileDto, reference: ReferenceTuple): RenderItemOutputDto => { + const [referenceId] = reference; + const renderedItem = this.renderedItems.get(reference); - return data; - } + if (renderedItem == null) { + const { Registry } = this.configuration.ExtractedData; + const renderedData = this.renderApiItem(reference, entryFile, Registry[referenceId]); + this.renderedItems.set(reference, renderedData); - public PrintToFiles(): void { - // ===================================== - // - // Preparing files we want to write / output / fill. - // P.S. move this into separate file. - // - // ===================================== - interface PrinterFileData { - /** - * Relative file location to `OutDir` path. - */ - Location: string; - Items: RenderItemOutputDto[]; + return renderedData; } - const data = this.GetRenderedData(); - const list: PrinterFileData[] = []; - - for (const entryFile of data.EntryFiles) { - const printerFile: PrinterFileData = { - Location: path.basename(entryFile.Name) + ".md", - Items: this.getItems(data, entryFile, entryFile.Members) - }; - - list.push(printerFile); - } + return renderedItem; } - private getItems( - data: RenderedDto, + private renderApiItem( + reference: ReferenceTuple, entryFile: Contracts.ApiSourceFileDto, - itemsReference: Contracts.ApiItemReferenceTuple - ): RenderItemOutputDto[] { - let items: RenderItemOutputDto[] = []; - - for (const [, references] of itemsReference) { - for (const reference of references) { - // Check if item is ExportSpecifier or ExportDeclaration. - const apiItem = this.configuration.ExtractedData.Registry[reference]; - - switch (apiItem.ApiKind) { - case Contracts.ApiItemKinds.Export: { - const exporterItems = this.getItems(data, entryFile, apiItem.Members); - items = [...items, ...exporterItems]; - break; - } - case Contracts.ApiItemKinds.ExportSpecifier: { - if (apiItem.ApiItems == null) { - console.warn(`ApiItems are missing in "${apiItem.Name}"?`); - break; - } - const exporterItems = this.getItems(data, entryFile, [[apiItem.Name, apiItem.ApiItems]]); - items = [...items, ...exporterItems]; - break; - } - default: { - const renderedItem = data.RenderedItems.get(reference); - if (renderedItem != null) { - items.push(renderedItem); - } else { - console.warn(`Reference "${reference}" is missing in ${entryFile.Name}?`); - } - } - } + apiItem: Contracts.ApiItemDto + ): RenderItemOutputDto { + const plugins = this.configuration.PluginManager.GetPluginsByKind(apiItem.ApiKind); + + for (const plugin of plugins) { + if (plugin.CheckApiItem(apiItem)) { + return plugin.Render({ + Reference: reference, + ApiItem: apiItem, + GetItem: this.getRenderedItemByReference + }); } } - return items; + const defaultPlugin = new ApiDefaultPlugin(); + return defaultPlugin.Render({ + Reference: reference, + ApiItem: apiItem, + GetItem: this.getRenderedItemByReference + }); } } diff --git a/packages/ts-docs-gen/src/plugins/api-default-plugin.ts b/packages/ts-docs-gen/src/plugins/api-default-plugin.ts index 3939f1ba..0aa5c753 100644 --- a/packages/ts-docs-gen/src/plugins/api-default-plugin.ts +++ b/packages/ts-docs-gen/src/plugins/api-default-plugin.ts @@ -1,22 +1,25 @@ -import { Contracts } from "ts-extractor"; import { MarkdownGenerator } from "@simplrjs/markdown"; import { ApiItemPluginBase } from "../abstractions/api-item-plugin-base"; import { SupportedApiItemKindType } from "../contracts/supported-api-item-kind-type"; import { RenderItemOutputDto } from "../contracts/render-item-output-dto"; +import { PluginData } from "../contracts/plugin-data"; export class ApiDefaultPlugin extends ApiItemPluginBase { public SupportedApiItemsKinds(): SupportedApiItemKindType[] { return [this.SupportKind.Any]; } - public Render(item: Contracts.ApiItemDto, getItem: (itemId: string) => RenderItemOutputDto): RenderItemOutputDto { + public Render(data: PluginData): RenderItemOutputDto { + const [, alias] = data.Reference; + const heading = `${data.ApiItem.ApiKind}: ${alias}`; const output: string[] = [ - MarkdownGenerator.header(`${item.Name}: ${item.ApiKind.toUpperCase()}`, 2) + MarkdownGenerator.header(heading, 2) ]; return { - ApiItem: item, + Heading: heading, + ApiItem: data.ApiItem, References: [], RenderOutput: output }; diff --git a/packages/ts-docs-gen/src/plugins/api-variable-plugin.ts b/packages/ts-docs-gen/src/plugins/api-variable-plugin.ts new file mode 100644 index 00000000..05765cc5 --- /dev/null +++ b/packages/ts-docs-gen/src/plugins/api-variable-plugin.ts @@ -0,0 +1,50 @@ +import { Contracts } from "ts-extractor"; +import { MarkdownGenerator } from "@simplrjs/markdown"; + +import { ApiItemPluginBase } from "../abstractions/api-item-plugin-base"; +import { SupportedApiItemKindType } from "../contracts/supported-api-item-kind-type"; +import { RenderItemOutputDto } from "../contracts/render-item-output-dto"; +import { PluginData } from "../contracts/plugin-data"; +import { ExtractorHelpers } from "../extractor-helpers"; +import { GeneratorHelpers } from "../generator-helpers"; + +export class ApiVariablePlugin extends ApiItemPluginBase { + public SupportedApiItemsKinds(): SupportedApiItemKindType[] { + return [this.SupportKind.Variable]; + } + + public Render(data: PluginData): RenderItemOutputDto { + const [, alias] = data.Reference; + let references: string[] = []; + const heading = alias; + + let documentationComment: string[] = []; + if (data.ApiItem.Metadata.DocumentationComment.length > 0) { + documentationComment = [ + ...data.ApiItem.Metadata.DocumentationComment.map(x => x.text), + "" + ]; + } + + const typeStringDto = GeneratorHelpers.TypeDtoToMarkdownString(data.ApiItem.Type); + references = references.concat(typeStringDto.References); + + const output: string[] = [ + MarkdownGenerator.header(heading, 2), + "", + ...documentationComment, + ...MarkdownGenerator.code(ExtractorHelpers.ApiVariableToString(data.ApiItem), { lang: "typescript" }), + "", + MarkdownGenerator.header("Type", 3), + "", + typeStringDto.Text + ]; + + return { + Heading: heading, + ApiItem: data.ApiItem, + References: references, + RenderOutput: output + }; + } +} diff --git a/packages/ts-docs-gen/src/printers/default-printer.ts b/packages/ts-docs-gen/src/printers/default-printer.ts deleted file mode 100644 index b5b0109f..00000000 --- a/packages/ts-docs-gen/src/printers/default-printer.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PrinterBase } from "../abstractions/printer-base"; -import { RenderedDto } from "../contracts/rendered-dto"; - -export class DefaultPrinter extends PrinterBase { - public EntryFileCrawler(data: RenderedDto): void { - throw new Error("Method not implemented."); - } - -} diff --git a/packages/ts-docs-gen/src/utils/helpers.ts b/packages/ts-docs-gen/src/utils/helpers.ts new file mode 100644 index 00000000..adbe288f --- /dev/null +++ b/packages/ts-docs-gen/src/utils/helpers.ts @@ -0,0 +1,11 @@ +export namespace Helpers { + export function Flatten(arr: any[][]): any[] { + return arr.reduce((flat, toFlatten) => + flat.concat(Array.isArray(toFlatten) ? Flatten(toFlatten) : toFlatten), []); + } + + // TODO: Move this to @simplrjs/markdown package. + export function HeadingToAnchor(heading: string): string { + return heading.trim().toLowerCase().replace(/[^\w\- ]+/g, "").replace(/\s/g, "-").replace(/\-+$/, ""); + } +} diff --git a/packages/ts-docs-gen/tests/cases/__tests__/__snapshots__/simple-project-1.test.ts.snap b/packages/ts-docs-gen/tests/cases/__tests__/__snapshots__/simple-project-1.test.ts.snap new file mode 100644 index 00000000..1b490680 --- /dev/null +++ b/packages/ts-docs-gen/tests/cases/__tests__/__snapshots__/simple-project-1.test.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`index 1`] = ` +Array [ + Object { + "FileLocation": "index.md", + "Output": Array [ + "# index", + "", + "## class: Foo", + "", + ], + }, +] +`; diff --git a/packages/ts-docs-gen/tests/cases/__tests__/__snapshots__/simple-project-2.test.ts.snap b/packages/ts-docs-gen/tests/cases/__tests__/__snapshots__/simple-project-2.test.ts.snap new file mode 100644 index 00000000..387ccdc0 --- /dev/null +++ b/packages/ts-docs-gen/tests/cases/__tests__/__snapshots__/simple-project-2.test.ts.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`index 1`] = ` +Array [ + Object { + "FileLocation": "index.md", + "Output": Array [ + "# index", + "", + "## class: FooStart", + "", + ], + }, + Object { + "FileLocation": "foo.md", + "Output": Array [ + "# foo", + "", + "## class: Foo", + "", + ], + }, +] +`; diff --git a/packages/ts-docs-gen/tests/cases/simple-project-1/test-config.json b/packages/ts-docs-gen/tests/cases/simple-project-1/test-config.json index f4bef8b7..a058bc3b 100644 --- a/packages/ts-docs-gen/tests/cases/simple-project-1/test-config.json +++ b/packages/ts-docs-gen/tests/cases/simple-project-1/test-config.json @@ -1,7 +1,5 @@ { "EntryFiles": [ - "./index.ts", - "./index2.ts", - "./index3.ts" + "./index.ts" ] } diff --git a/packages/ts-docs-gen/tests/cases/simple-project-2/test-config.json b/packages/ts-docs-gen/tests/cases/simple-project-2/test-config.json index a058bc3b..182bf979 100644 --- a/packages/ts-docs-gen/tests/cases/simple-project-2/test-config.json +++ b/packages/ts-docs-gen/tests/cases/simple-project-2/test-config.json @@ -1,5 +1,6 @@ { "EntryFiles": [ - "./index.ts" + "./index.ts", + "./foo.ts" ] } diff --git a/packages/ts-docs-gen/tests/scripts/tests-generator.ts b/packages/ts-docs-gen/tests/scripts/tests-generator.ts index 2da09185..d9b33dfb 100644 --- a/packages/ts-docs-gen/tests/scripts/tests-generator.ts +++ b/packages/ts-docs-gen/tests/scripts/tests-generator.ts @@ -32,13 +32,24 @@ export async function TestsGenerator(dirName: string, cwd: string): Promise {`, + `test("${name}", async done => {`, Tab(1) + `const projectDirectory = "${projectDirectory}";`, Tab(1) + `const entryFiles = ${JSON.stringify(testConfig.EntryFiles)};`, "", - Tab(1) + `expect(true).toBe(true);`, - `});`, + Tab(1) + "try {", + Tab(2) + "const configuration = await new GeneratorConfigurationBuilder(projectDirectory)", + Tab(3) + ".Build(entryFiles);", + "", + Tab(2) + "const generator = new Generator(configuration);", + "", + Tab(2) + "expect(generator.OutputData).toMatchSnapshot();", + Tab(2) + "done();", + Tab(1) + "} catch (error) {", + Tab(2) + "done.fail(error);", + Tab(1) + "}", + "});", "" ].join(os.EOL);