-
Notifications
You must be signed in to change notification settings - Fork 604
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
[api-documenter] Allow customization of table of contents #1265
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
ef91702
Setup testing
Vitalius1 9f4a5e4
Generate schema function
Vitalius1 78f59df
setup the config
Vitalius1 8d0fdba
Pipe config file all the way to YamlDocumenter
Vitalius1 e64c063
Make some changes to how to load configs.
Vitalius1 3e2ddb5
Bring generic changes to api-documenter
Vitalius1 b37f5fa
Change a nested if statement
Vitalius1 15388bd
Add some types to the new logic
Vitalius1 252268e
Change file
Vitalius1 4fa3a4e
Change the test
Vitalius1 48180b9
Adjust debugger
Vitalius1 5de428b
Move IYamlTocConfigSchema into an IConfigFile interface with a corres…
octogonz ce0f679
Add an api-documenter-template.json file that in the future could be …
octogonz 336a230
Extract the experimental functionality into an ExperimentYamlDocument…
octogonz 34339cd
Add a new command-line action "api-documenter generate"
octogonz bd3e7d7
Merge branch 'master' into FabricDocumenter
iclanton c65db70
Sort imports and remove the debugger file
Vitalius1 5dacc8a
Merge remote-tracking branch 'origin/FabricDocumenter' into FabricDoc…
Vitalius1 6bbaa35
Remove unnecessary override
Vitalius1 de5a492
More feedback addressed
Vitalius1 7218cff
Manually remove some blank lines
Vitalius1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | ||
// See LICENSE in the project root for license information. | ||
|
||
// tslint:disable:member-ordering | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @octogonz - did you ever update this rule? |
||
import * as path from 'path'; | ||
|
||
import yaml = require('js-yaml'); | ||
|
@@ -184,6 +186,15 @@ export class YamlDocumenter { | |
* Write the table of contents | ||
*/ | ||
private _writeTocFile(apiItems: ReadonlyArray<ApiItem>): void { | ||
const tocFile: IYamlTocFile = this.buildYamlTocFile(apiItems); | ||
|
||
const tocFilePath: string = path.join(this._outputFolder, 'toc.yml'); | ||
console.log('Writing ' + tocFilePath); | ||
this._writeYamlFile(tocFile, tocFilePath, '', undefined); | ||
} | ||
|
||
/** @virtual */ | ||
protected buildYamlTocFile(apiItems: ReadonlyArray<ApiItem>): IYamlTocFile { | ||
const tocFile: IYamlTocFile = { | ||
items: [ ] | ||
}; | ||
|
@@ -192,10 +203,7 @@ export class YamlDocumenter { | |
tocFile.items.push(rootItem); | ||
|
||
rootItem.items!.push(...this._buildTocItems(apiItems)); | ||
|
||
const tocFilePath: string = path.join(this._outputFolder, 'toc.yml'); | ||
console.log('Writing ' + tocFilePath); | ||
this._writeYamlFile(tocFile, tocFilePath, '', undefined); | ||
return tocFile; | ||
} | ||
|
||
private _buildTocItems(apiItems: ReadonlyArray<ApiItem>): IYamlTocItem[] { | ||
|
@@ -245,7 +253,7 @@ export class YamlDocumenter { | |
return tocItems; | ||
} | ||
|
||
private _shouldEmbed(apiItemKind: ApiItemKind): boolean { | ||
protected _shouldEmbed(apiItemKind: ApiItemKind): boolean { | ||
switch (apiItemKind) { | ||
case ApiItemKind.Class: | ||
case ApiItemKind.Package: | ||
|
@@ -511,7 +519,7 @@ export class YamlDocumenter { | |
* Calculate the DocFX "uid" for the ApiItem | ||
* Example: node-core-library.JsonFile.load | ||
*/ | ||
private _getUid(apiItem: ApiItem): string { | ||
protected _getUid(apiItem: ApiItem): string { | ||
let result: string = ''; | ||
for (const hierarchyItem of apiItem.getHierarchy()) { | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a weird verb. Maybe "experimental-yaml" or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See this comment. We discussed it with @octogonz that we might wanna move to a using this generic
generate
action along with a config file that will command which documenter to be used.