-
Notifications
You must be signed in to change notification settings - Fork 604
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1265 from Vitalius1/FabricDocumenter
[api-documenter] Allow customization of table of contents
- Loading branch information
Showing
21 changed files
with
458 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | ||
// See LICENSE in the project root for license information. | ||
|
||
import * as path from 'path'; | ||
|
||
import { ApiDocumenterCommandLine } from './ApiDocumenterCommandLine'; | ||
import { BaseAction } from './BaseAction'; | ||
import { DocumenterConfig } from '../documenters/DocumenterConfig'; | ||
import { ExperimentalYamlDocumenter } from '../documenters/ExperimentalYamlDocumenter'; | ||
import { IConfigFile } from '../documenters/IConfigFile'; | ||
|
||
import { ApiModel } from '@microsoft/api-extractor-model'; | ||
import { FileSystem } from '@microsoft/node-core-library'; | ||
|
||
export class GenerateAction extends BaseAction { | ||
constructor(parser: ApiDocumenterCommandLine) { | ||
super({ | ||
actionName: 'generate', | ||
summary: 'EXPERIMENTAL', | ||
documentation: 'EXPERIMENTAL - This action is a prototype of a new config file driven mode of operation for' | ||
+ ' API Documenter. It is not ready for general usage yet. Its design may change in the future.' | ||
}); | ||
} | ||
|
||
protected onExecute(): Promise<void> { // override | ||
// Look for the config file under the current folder | ||
|
||
let configFilePath: string = path.join(process.cwd(), DocumenterConfig.FILENAME); | ||
|
||
// First try the current folder | ||
if (!FileSystem.exists(configFilePath)) { | ||
// Otherwise try the standard "config" subfolder | ||
configFilePath = path.join(process.cwd(), 'config', DocumenterConfig.FILENAME); | ||
if (!FileSystem.exists(configFilePath)) { | ||
throw new Error(`Unable to find ${DocumenterConfig.FILENAME} in the current folder or in a "config" subfolder`); | ||
} | ||
} | ||
|
||
const configFile: IConfigFile = DocumenterConfig.loadFile(configFilePath); | ||
|
||
const apiModel: ApiModel = this.buildApiModel(); | ||
|
||
const yamlDocumenter: ExperimentalYamlDocumenter = new ExperimentalYamlDocumenter(apiModel, configFile); | ||
yamlDocumenter.generateFiles(this.outputFolder); | ||
return Promise.resolve(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | ||
// See LICENSE in the project root for license information. | ||
|
||
import * as path from 'path'; | ||
import { JsonSchema, JsonFile } from '@microsoft/node-core-library'; | ||
import { IConfigFile } from './IConfigFile'; | ||
|
||
/** | ||
* Helper for loading the api-documenter.json file format. Later when the schema is more mature, | ||
* this class will be used to represent the validated and normalized configuration, whereas `IConfigFile` | ||
* represents the raw JSON file structure. | ||
*/ | ||
export class DocumenterConfig { | ||
/** | ||
* The JSON Schema for API Extractor config file (api-extractor.schema.json). | ||
*/ | ||
public static readonly jsonSchema: JsonSchema = JsonSchema.fromFile( | ||
path.join(__dirname, '..', 'schemas', 'api-documenter.schema.json')); | ||
|
||
/** | ||
* The config file name "api-extractor.json". | ||
*/ | ||
public static readonly FILENAME: string = 'api-documenter.json'; | ||
|
||
/** | ||
* Load and validate an api-documenter.json file. | ||
*/ | ||
public static loadFile(configFilePath: string): IConfigFile { | ||
const configFile: IConfigFile = JsonFile.loadAndValidate(configFilePath, DocumenterConfig.jsonSchema); | ||
|
||
return configFile; | ||
} | ||
} |
139 changes: 139 additions & 0 deletions
139
apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | ||
// See LICENSE in the project root for license information. | ||
|
||
import { PackageName } from '@microsoft/node-core-library'; | ||
import { DocComment, DocInlineTag } from '@microsoft/tsdoc'; | ||
import { ApiModel, ApiItem, ApiItemKind, ApiDocumentedItem } from '@microsoft/api-extractor-model'; | ||
|
||
import { IConfigFile, IConfigTableOfContents } from './IConfigFile'; | ||
import { IYamlTocItem, IYamlTocFile } from '../yaml/IYamlTocFile'; | ||
import { YamlDocumenter } from './YamlDocumenter'; | ||
|
||
/** | ||
* EXPERIMENTAL - This documenter is a prototype of a new config file driven mode of operation for | ||
* API Documenter. It is not ready for general usage yet. Its design may change in the future. | ||
*/ | ||
export class ExperimentalYamlDocumenter extends YamlDocumenter { | ||
private _config: IConfigTableOfContents; | ||
private _tocPointerMap: { [key: string]: IYamlTocItem }; | ||
private _catchAllPointer: IYamlTocItem; | ||
|
||
public constructor(apiModel: ApiModel, configFile: IConfigFile) { | ||
super(apiModel); | ||
this._config = configFile.tableOfContents!; | ||
|
||
this._tocPointerMap = {}; | ||
|
||
this._generateTocPointersMap(this._config.tocConfig); | ||
} | ||
|
||
/** @override */ | ||
protected buildYamlTocFile(apiItems: ReadonlyArray<ApiItem>): IYamlTocFile { | ||
this._buildTocItems2(apiItems); | ||
return this._config.tocConfig; | ||
} | ||
|
||
private _buildTocItems2(apiItems: ReadonlyArray<ApiItem>): IYamlTocItem[] { | ||
const tocItems: IYamlTocItem[] = []; | ||
for (const apiItem of apiItems) { | ||
let tocItem: IYamlTocItem; | ||
|
||
if (apiItem.kind === ApiItemKind.Namespace) { | ||
// Namespaces don't have nodes yet | ||
tocItem = { | ||
name: apiItem.displayName | ||
}; | ||
} else { | ||
if (this._shouldEmbed(apiItem.kind)) { | ||
// Don't generate table of contents items for embedded definitions | ||
continue; | ||
} | ||
|
||
if (apiItem.kind === ApiItemKind.Package) { | ||
tocItem = { | ||
name: PackageName.getUnscopedName(apiItem.displayName), | ||
uid: this._getUid(apiItem) | ||
}; | ||
} else { | ||
tocItem = { | ||
name: apiItem.displayName, | ||
uid: this._getUid(apiItem) | ||
}; | ||
// Filtering out the api-items as we build the tocItems array. | ||
if (apiItem instanceof ApiDocumentedItem) { | ||
const docInlineTag: DocInlineTag | undefined = | ||
(this._config && this._config.filterByInlineTag) | ||
? this._findInlineTagByName(this._config.filterByInlineTag, apiItem.tsdocComment) | ||
: undefined; | ||
|
||
const tagContent: string | undefined = | ||
docInlineTag && docInlineTag.tagContent && docInlineTag.tagContent.trim(); | ||
|
||
if (tagContent && this._tocPointerMap[tagContent]) { | ||
// null assertion used because when pointer map was created we checked for presence of empty `items` array | ||
this._tocPointerMap[tagContent].items!.push(tocItem); | ||
} else { | ||
if (this._catchAllPointer && this._catchAllPointer.items) { | ||
this._catchAllPointer.items.push(tocItem); | ||
} | ||
} | ||
} | ||
|
||
} | ||
} | ||
|
||
tocItems.push(tocItem); | ||
|
||
let children: ReadonlyArray<ApiItem>; | ||
if (apiItem.kind === ApiItemKind.Package) { | ||
// Skip over the entry point, since it's not part of the documentation hierarchy | ||
children = apiItem.members[0].members; | ||
} else { | ||
children = apiItem.members; | ||
} | ||
|
||
const childItems: IYamlTocItem[] = this._buildTocItems2(children); | ||
if (childItems.length > 0) { | ||
tocItem.items = childItems; | ||
} | ||
} | ||
return tocItems; | ||
} | ||
|
||
// Parses the tocConfig object to build a pointers map of nodes where we want to sort out the API items | ||
private _generateTocPointersMap(tocConfig: IYamlTocFile | IYamlTocItem): void { | ||
if (tocConfig.items) { | ||
for (const tocItem of tocConfig.items) { | ||
if (tocItem.items && tocItem.items.length > 0) { | ||
this._generateTocPointersMap(tocItem); | ||
} else { | ||
// check for presence of the `catchAllCategory` config option | ||
if (this._config && this._config.catchAllCategory && tocItem.name === this._config.catchAllCategory) { | ||
this._catchAllPointer = tocItem; | ||
} else { | ||
this._tocPointerMap[tocItem.name] = tocItem; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// This is a direct copy of a @docCategory inline tag finder in office-ui-fabric-react, | ||
// but is generic enough to be used for any inline tag | ||
private _findInlineTagByName(tagName: string, docComment: DocComment | undefined): DocInlineTag | undefined { | ||
if (docComment instanceof DocInlineTag) { | ||
if (docComment.tagName === tagName) { | ||
return docComment; | ||
} | ||
} | ||
if (docComment) { | ||
for (const childNode of docComment.getChildNodes()) { | ||
const result: DocInlineTag | undefined = this._findInlineTagByName(tagName, childNode as DocComment); | ||
if (result !== undefined) { | ||
return result; | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | ||
// See LICENSE in the project root for license information. | ||
|
||
import { IYamlTocFile } from '../yaml/IYamlTocFile'; | ||
|
||
/** | ||
* Typescript interface describing the config schema for toc.yml file format. | ||
*/ | ||
export interface IConfigTableOfContents { | ||
/** | ||
* Represents the tree structure describing the toc.file format. | ||
* Only the nodes that have an empty `items` array will be filled with API items | ||
* that are matched with the filters provided. Everything else will be placed under a catchAll category | ||
* that is highly recommended to be provided. | ||
*/ | ||
tocConfig: IYamlTocFile; | ||
|
||
/** | ||
* Optional category name that is recommended to include in the `tocConfig`, | ||
* along with one of the filters: `filterByApiItemName` or `filterByInlineTag`. | ||
* Any items that are not matched to the mentioned filters will be placed under this | ||
* catchAll category. If none provided the items will not be included in the final toc.yml file. | ||
*/ | ||
catchAllCategory?: string; | ||
|
||
/** | ||
* When loading more than one api.json files that might include the same API items, | ||
* toggle either to show duplicates or not. | ||
*/ | ||
noDuplicateEntries?: boolean; | ||
|
||
/** | ||
* Toggle either sorting of the API items should be made based on category name presence | ||
* in the API item's name. | ||
*/ | ||
filterByApiItemName?: boolean; | ||
|
||
/** | ||
* Filter that can be used to sort the API items according to an inline custom tag | ||
* that is present on them. | ||
*/ | ||
filterByInlineTag?: string; | ||
} | ||
|
||
/** | ||
* This interface represents the api-extractor.json file format. | ||
*/ | ||
export interface IConfigFile { | ||
/** {@inheritDoc IConfigTableOfContents} */ | ||
tableOfContents?: IConfigTableOfContents; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.