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

[api-documenter] Support linked types for markdown output #1849

Merged
merged 25 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
89f9ab8
Make jest work
Feiyang1 Apr 27, 2020
7fa8d3f
debug settup
Feiyang1 Apr 27, 2020
94cee00
plumbing for rending types as links
Feiyang1 Apr 28, 2020
2f1c6d0
function parameter types are links now
Feiyang1 Apr 28, 2020
3d49789
Added apiItemsByCanonicalReference map
Feiyang1 Apr 28, 2020
363d013
revert previous implementation
Feiyang1 Apr 28, 2020
39ef0e1
use the canonical type name
Feiyang1 Apr 29, 2020
01f29d4
link for function parameter types and return types
Feiyang1 Apr 29, 2020
160913d
link for interface property types
Feiyang1 Apr 29, 2020
005cbb8
get ready for PR
Feiyang1 Apr 29, 2020
4398277
recover launch.json
Feiyang1 Apr 29, 2020
bedc168
format
Feiyang1 Apr 29, 2020
1422d1a
format
Feiyang1 Apr 29, 2020
90d70bb
run rush change
Feiyang1 Apr 29, 2020
5fdd891
Regenerate api-documenter-test test output
octogonz May 2, 2020
6f8e54e
Add a test case for hyperlinked function parameters and return value
octogonz May 2, 2020
93b481c
Handle composite type link properly
Feiyang1 May 2, 2020
eaf47bb
Update test output
Feiyang1 May 2, 2020
ee6769f
remove an empty line to make linter happy
Feiyang1 May 2, 2020
0b37ac5
Render hyperlinks using DocPlainText instead of DocCodeSpan
octogonz May 3, 2020
5edbfaf
Fix "TODO" item and add some missing API docs for Excerpt.ts
octogonz May 3, 2020
3535fa6
Merge branch 'type-link-pr' into type-link-pr-proposal
Feiyang1 May 4, 2020
8e6a8dd
Add a safeguard for a case where an incompatible version of the `@mic…
octogonz May 6, 2020
d9b7204
Update change files
octogonz May 6, 2020
c1893e5
Merge branch 'type-link-pr' into type-link-pr-proposal
octogonz May 6, 2020
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
161 changes: 108 additions & 53 deletions apps/api-documenter/src/documenters/MarkdownDocumenter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// 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';
/* eslint max-lines: "off" */

import * as path from 'path';
import {
PackageName,
FileSystem,
Expand Down Expand Up @@ -39,7 +40,9 @@ import {
ApiParameterListMixin,
ApiReturnTypeMixin,
ApiDeclaredItem,
ApiNamespace
ApiNamespace,
ExcerptTokenKind,
IResolveDeclarationReferenceResult
} from '@microsoft/api-extractor-model';

import { CustomDocNodes } from '../nodes/CustomDocNodeKind';
Expand Down Expand Up @@ -103,7 +106,7 @@ export class MarkdownDocumenter {
this._writeApiItemPage(this._apiModel);

if (this._pluginLoader.markdownDocumenterFeature) {
this._pluginLoader.markdownDocumenterFeature.onFinished({ });
this._pluginLoader.markdownDocumenterFeature.onFinished({});
}
}

Expand Down Expand Up @@ -162,7 +165,7 @@ export class MarkdownDocumenter {
}

if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) {
if (apiItem.releaseTag === ReleaseTag.Beta) {
if (apiItem.releaseTag === ReleaseTag.Beta) {
this._writeBetaWarning(output);
}
}
Expand Down Expand Up @@ -196,7 +199,7 @@ export class MarkdownDocumenter {
if (apiItem.excerpt.text.length > 0) {
output.appendNode(
new DocParagraph({ configuration }, [
new DocEmphasisSpan({ configuration, bold: true}, [
new DocEmphasisSpan({ configuration, bold: true }, [
new DocPlainText({ configuration, text: 'Signature:' })
])
])
Expand Down Expand Up @@ -257,7 +260,7 @@ export class MarkdownDocumenter {
}

if (appendRemarks) {
this._writeRemarksSection(output, apiItem);
this._writeRemarksSection(output, apiItem);
}

const filename: string = path.join(this._outputFolder, this._getFilenameForApiItem(apiItem));
Expand Down Expand Up @@ -346,8 +349,9 @@ export class MarkdownDocumenter {
private _writeModelTable(output: DocSection, apiModel: ApiModel): void {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

const packagesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Package', 'Description' ]
const packagesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Package', 'Description']
});

for (const apiMember of apiModel.members) {
Expand Down Expand Up @@ -377,32 +381,39 @@ export class MarkdownDocumenter {
private _writePackageOrNamespaceTables(output: DocSection, apiContainer: ApiPackage | ApiNamespace): void {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

const classesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Class', 'Description' ]
const classesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Class', 'Description']
});

const enumerationsTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Enumeration', 'Description' ]
const enumerationsTable: DocTable = new DocTable({
configuration,
headerTitles: ['Enumeration', 'Description']
});

const functionsTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Function', 'Description' ]
const functionsTable: DocTable = new DocTable({
configuration,
headerTitles: ['Function', 'Description']
});

const interfacesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Interface', 'Description' ]
const interfacesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Interface', 'Description']
});

const namespacesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Namespace', 'Description' ]
const namespacesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Namespace', 'Description']
});

const variablesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Variable', 'Description' ]
const variablesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Variable', 'Description']
});

const typeAliasesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Type Alias', 'Description' ]
const typeAliasesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Type Alias', 'Description']
});

const apiMembers: ReadonlyArray<ApiItem> = apiContainer.kind === ApiItemKind.Package ?
Expand Down Expand Up @@ -495,20 +506,24 @@ export class MarkdownDocumenter {
private _writeClassTables(output: DocSection, apiClass: ApiClass): void {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

const eventsTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Property', 'Modifiers', 'Type', 'Description' ]
const eventsTable: DocTable = new DocTable({
configuration,
headerTitles: ['Property', 'Modifiers', 'Type', 'Description']
});

const constructorsTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Constructor', 'Modifiers', 'Description' ]
const constructorsTable: DocTable = new DocTable({
configuration,
headerTitles: ['Constructor', 'Modifiers', 'Description']
});

const propertiesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Property', 'Modifiers', 'Type', 'Description' ]
const propertiesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Property', 'Modifiers', 'Type', 'Description']
});

const methodsTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Method', 'Modifiers', 'Description' ]
const methodsTable: DocTable = new DocTable({
configuration,
headerTitles: ['Method', 'Modifiers', 'Description']
});

for (const apiMember of apiClass.members) {
Expand Down Expand Up @@ -594,8 +609,9 @@ export class MarkdownDocumenter {
private _writeEnumTables(output: DocSection, apiEnum: ApiEnum): void {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

const enumMembersTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Member', 'Value', 'Description' ]
const enumMembersTable: DocTable = new DocTable({
configuration,
headerTitles: ['Member', 'Value', 'Description']
});

for (const apiEnumMember of apiEnum.members) {
Expand Down Expand Up @@ -631,16 +647,19 @@ export class MarkdownDocumenter {
private _writeInterfaceTables(output: DocSection, apiClass: ApiInterface): void {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

const eventsTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Property', 'Type', 'Description' ]
const eventsTable: DocTable = new DocTable({
configuration,
headerTitles: ['Property', 'Type', 'Description']
});

const propertiesTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Property', 'Type', 'Description' ]
const propertiesTable: DocTable = new DocTable({
configuration,
headerTitles: ['Property', 'Type', 'Description']
});

const methodsTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Method', 'Description' ]
const methodsTable: DocTable = new DocTable({
configuration,
headerTitles: ['Method', 'Description']
});

for (const apiMember of apiClass.members) {
Expand Down Expand Up @@ -707,29 +726,27 @@ export class MarkdownDocumenter {
private _writeParameterTables(output: DocSection, apiParameterListMixin: ApiParameterListMixin): void {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

const parametersTable: DocTable = new DocTable({ configuration,
headerTitles: [ 'Parameter', 'Type', 'Description' ]
const parametersTable: DocTable = new DocTable({
configuration,
headerTitles: ['Parameter', 'Type', 'Description']
});

for (const apiParameter of apiParameterListMixin.parameters) {
const parameterDescription: DocSection = new DocSection({ configuration } );
const parameterDescription: DocSection = new DocSection({ configuration });
if (apiParameter.tsdocParamBlock) {
this._appendSection(parameterDescription, apiParameter.tsdocParamBlock.content);
}

parametersTable.addRow(
new DocTableRow({ configuration }, [
new DocTableCell({configuration}, [
new DocTableCell({ configuration }, [
new DocParagraph({ configuration }, [
new DocPlainText({ configuration, text: apiParameter.name })
])
]),
new DocTableCell({configuration}, [
new DocParagraph({ configuration }, [
new DocCodeSpan({ configuration, code: apiParameter.parameterTypeExcerpt.text })
])
new DocTableCell({ configuration }, [
this._createParagraphForTypeExcerpt(apiParameter.parameterTypeExcerpt)
]),
new DocTableCell({configuration}, parameterDescription.nodes)
new DocTableCell({ configuration }, parameterDescription.nodes)
])
);
}
Expand All @@ -743,16 +760,14 @@ export class MarkdownDocumenter {
const returnTypeExcerpt: Excerpt = apiParameterListMixin.returnTypeExcerpt;
output.appendNode(
new DocParagraph({ configuration }, [
new DocEmphasisSpan({ configuration, bold: true}, [
new DocEmphasisSpan({ configuration, bold: true }, [
new DocPlainText({ configuration, text: 'Returns:' })
])
])
);

output.appendNode(
new DocParagraph({ configuration }, [
new DocCodeSpan({ configuration, code: returnTypeExcerpt.text.trim() || '(not declared)' })
])
this._createParagraphForTypeExcerpt(returnTypeExcerpt)
);

if (apiParameterListMixin instanceof ApiDocumentedItem) {
Expand All @@ -763,6 +778,44 @@ export class MarkdownDocumenter {
}
}

private _createParagraphForTypeExcerpt(excerpt: Excerpt): DocParagraph {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

const paragraph: DocParagraph = new DocParagraph({ configuration });

if (!excerpt.text.trim()) {
paragraph.appendNode(new DocPlainText({ configuration, text: '(not declared)' }));
} else {
for (const token of excerpt.spannedTokens) {
// Markdown doesn't provide a standardized syntax for hyperlinks inside code spans, so we will render
// the type expression as DocPlainText. Instead of creating multiple DocParagraphs, we can simply
// discard any newlines and let the renderer do normal word-wrapping.
const unwrappedTokenText: string = token.text.replace(/[\r\n]+/g, ' ');

// If it's hyperlinkable, then append a DocLinkTag
if (token.kind === ExcerptTokenKind.Reference && token.canonicalReference) {
const apiItemResult: IResolveDeclarationReferenceResult = this._apiModel.resolveDeclarationReference(
token.canonicalReference, undefined);

if (apiItemResult.resolvedApiItem) {
paragraph.appendNode(new DocLinkTag({
configuration,
tagName: '@link',
linkText: unwrappedTokenText,
urlDestination: this._getLinkFilenameForApiItem(apiItemResult.resolvedApiItem)
}));
continue;
}
}

// Otherwise append non-hyperlinked text
paragraph.appendNode(new DocPlainText({ configuration, text: unwrappedTokenText }));
}
}

return paragraph;
}

private _createTitleCell(apiItem: ApiItem): DocTableCell {
const configuration: TSDocConfiguration = this._tsdocConfiguration;

Expand Down Expand Up @@ -830,7 +883,9 @@ export class MarkdownDocumenter {
const section: DocSection = new DocSection({ configuration });

if (apiItem instanceof ApiPropertyItem) {
section.appendNodeInParagraph(new DocCodeSpan({ configuration, code: apiItem.propertyTypeExcerpt.text }));
section.appendNode(
this._createParagraphForTypeExcerpt(apiItem.propertyTypeExcerpt)
);
}

return new DocTableCell({ configuration }, section.nodes);
Expand Down
4 changes: 1 addition & 3 deletions apps/api-documenter/src/documenters/YamlDocumenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,9 +776,7 @@ export class YamlDocumenter {
}

private _renderType(contextUid: DeclarationReference, typeExcerpt: Excerpt): string {
const excerptTokens: ExcerptToken[] = typeExcerpt.tokens.slice(
typeExcerpt.tokenRange.startIndex,
typeExcerpt.tokenRange.endIndex);
const excerptTokens: ExcerptToken[] = [...typeExcerpt.spannedTokens]; // copy the read-only array

if (excerptTokens.length === 0) {
return '';
Expand Down
Loading