Skip to content

Commit e11a20b

Browse files
authored
Merge pull request microsoft#1192 from Microsoft/ae-misc-doc-fixes
[api-extractor] Fix a couple documentation issues found during testing
2 parents 2e612a6 + 4a8b0b1 commit e11a20b

File tree

19 files changed

+240
-70
lines changed

19 files changed

+240
-70
lines changed

apps/api-documenter/src/cli/BaseAction.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22
// See LICENSE in the project root for license information.
33

44
import * as path from 'path';
5+
import * as tsdoc from '@microsoft/tsdoc';
6+
import * as colors from 'colors';
57

68
import {
79
CommandLineAction,
810
CommandLineStringParameter
911
} from '@microsoft/ts-command-line';
1012
import { FileSystem } from '@microsoft/node-core-library';
11-
import { ApiModel } from '@microsoft/api-extractor-model';
13+
import {
14+
ApiModel,
15+
ApiItem,
16+
ApiItemContainerMixin,
17+
ApiDocumentedItem,
18+
IResolveDeclarationReferenceResult
19+
} from '@microsoft/api-extractor-model';
1220

1321
export abstract class BaseAction extends CommandLineAction {
1422
protected inputFolder: string;
@@ -55,6 +63,66 @@ export abstract class BaseAction extends CommandLineAction {
5563
}
5664
}
5765

66+
this._applyInheritDoc(apiModel, apiModel);
67+
5868
return apiModel;
5969
}
70+
71+
// TODO: This is a temporary workaround. The long term plan is for API Extractor's DocCommentEnhancer
72+
// to apply all @inheritDoc tags before the .api.json file is written.
73+
// See DocCommentEnhancer._applyInheritDoc() for more info.
74+
private _applyInheritDoc(apiItem: ApiItem, apiModel: ApiModel): void {
75+
76+
if (apiItem instanceof ApiDocumentedItem) {
77+
if (apiItem.tsdocComment) {
78+
const inheritDocTag: tsdoc.DocInheritDocTag | undefined = apiItem.tsdocComment.inheritDocTag;
79+
80+
if (inheritDocTag && inheritDocTag.declarationReference) {
81+
// Attempt to resolve the declaration reference
82+
const result: IResolveDeclarationReferenceResult
83+
= apiModel.resolveDeclarationReference(inheritDocTag.declarationReference, apiItem);
84+
85+
if (result.errorMessage) {
86+
console.log(colors.yellow(`Warning: Unresolved @inheritDoc tag for ${apiItem.displayName}: `
87+
+ result.errorMessage));
88+
} else {
89+
if (result.resolvedApiItem instanceof ApiDocumentedItem
90+
&& result.resolvedApiItem.tsdocComment
91+
&& result.resolvedApiItem !== apiItem) {
92+
this._copyInheritedDocs(apiItem.tsdocComment, result.resolvedApiItem.tsdocComment);
93+
}
94+
}
95+
}
96+
97+
}
98+
}
99+
100+
// Recurse members
101+
if (ApiItemContainerMixin.isBaseClassOf(apiItem)) {
102+
for (const member of apiItem.members) {
103+
this._applyInheritDoc(member, apiModel);
104+
}
105+
}
106+
}
107+
108+
/**
109+
* Copy the content from `sourceDocComment` to `targetDocComment`.
110+
* This code is borrowed from DocCommentEnhancer as a temporary workaround.
111+
*/
112+
private _copyInheritedDocs(targetDocComment: tsdoc.DocComment, sourceDocComment: tsdoc.DocComment): void {
113+
targetDocComment.summarySection = sourceDocComment.summarySection;
114+
targetDocComment.remarksBlock = sourceDocComment.remarksBlock;
115+
116+
targetDocComment.params.clear();
117+
for (const param of sourceDocComment.params) {
118+
targetDocComment.params.add(param);
119+
}
120+
for (const typeParam of sourceDocComment.typeParams) {
121+
targetDocComment.typeParams.add(typeParam);
122+
}
123+
targetDocComment.returnsBlock = sourceDocComment.returnsBlock;
124+
125+
targetDocComment.inheritDocTag = undefined;
126+
}
127+
60128
}

apps/api-documenter/src/markdown/CustomMarkdownEmitter.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,12 @@ export class CustomMarkdownEmitter extends MarkdownEmitter {
177177
context.writer.write(encodedLinkText);
178178
context.writer.write(`](${filename!})`);
179179
} else {
180-
console.log(colors.red('WARNING: Unable to determine link text'));
180+
console.log(colors.yellow('WARNING: Unable to determine link text'));
181181
}
182182
}
183183
} else if (result.errorMessage) {
184-
console.log(colors.red('WARNING: Unable to resolve reference: ' + result.errorMessage));
184+
console.log(colors.yellow(`WARNING: Unable to resolve reference "${docLinkTag.codeDestination!.emitAsTsdoc()}": `
185+
+ result.errorMessage));
185186
}
186187
}
187188

apps/api-extractor/src/collector/Collector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ export class Collector {
526526

527527
this.messageRouter.addAnalyzerIssue(
528528
ExtractorMessageId.MissingReleaseTag,
529-
`"${entity.nameForEmit}" is exported by the package, but it is missing `
529+
`"${entity.astEntity.localName}" is exported by the package, but it is missing `
530530
+ `a release tag (@alpha, @beta, @public, or @internal)`,
531531
astSymbol
532532
);

apps/api-extractor/src/enhancers/DocCommentEnhancer.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import { Collector } from '../collector/Collector';
88
import { AstSymbol } from '../analyzer/AstSymbol';
99
import { AstDeclaration } from '../analyzer/AstDeclaration';
1010
import { DeclarationMetadata } from '../collector/DeclarationMetadata';
11-
import { AedocDefinitions } from '@microsoft/api-extractor-model';
11+
import { AedocDefinitions, ReleaseTag } from '@microsoft/api-extractor-model';
1212
import { ExtractorMessageId } from '../api/ExtractorMessageId';
1313
import { VisitorState } from '../collector/VisitorState';
1414
import { ResolverFailure } from '../analyzer/AstReferenceResolver';
15+
import { SymbolMetadata } from '../collector/SymbolMetadata';
1516

1617
export class DocCommentEnhancer {
1718
private readonly _collector: Collector;
@@ -54,7 +55,7 @@ export class DocCommentEnhancer {
5455
metadata.docCommentEnhancerVisitorState = VisitorState.Visiting;
5556

5657
if (metadata.tsdocComment && metadata.tsdocComment.inheritDocTag) {
57-
this._analyzeInheritDoc(astDeclaration, metadata.tsdocComment, metadata.tsdocComment.inheritDocTag);
58+
this._applyInheritDoc(astDeclaration, metadata.tsdocComment, metadata.tsdocComment.inheritDocTag);
5859
}
5960

6061
this._analyzeNeedsDocumentation(astDeclaration, metadata);
@@ -72,6 +73,9 @@ export class DocCommentEnhancer {
7273
// will auto-generate one.
7374
metadata.needsDocumentation = false;
7475

76+
// The class that contains this constructor
77+
const classDeclaration: AstDeclaration = astDeclaration.parent!;
78+
7579
const configuration: tsdoc.TSDocConfiguration = AedocDefinitions.tsdocConfiguration;
7680

7781
if (!metadata.tsdocComment) {
@@ -83,12 +87,49 @@ export class DocCommentEnhancer {
8387
new tsdoc.DocPlainText({ configuration, text: 'Constructs a new instance of the ' }),
8488
new tsdoc.DocCodeSpan({
8589
configuration,
86-
code: astDeclaration.astSymbol.parentAstSymbol!.localName
90+
code: classDeclaration.astSymbol.localName
8791
}),
8892
new tsdoc.DocPlainText({ configuration, text: ' class' })
8993
]);
9094
}
9195

96+
const symbolMetadata: SymbolMetadata = this._collector.fetchMetadata(astDeclaration.astSymbol);
97+
98+
if (symbolMetadata.releaseTag === ReleaseTag.Internal) {
99+
// If the constructor is marked as internal, then add a boilerplate notice for the containing class
100+
const classMetadata: DeclarationMetadata = this._collector.fetchMetadata(classDeclaration);
101+
102+
if (!classMetadata.tsdocComment) {
103+
classMetadata.tsdocComment = new tsdoc.DocComment({ configuration });
104+
}
105+
106+
if (classMetadata.tsdocComment.remarksBlock === undefined) {
107+
classMetadata.tsdocComment.remarksBlock = new tsdoc.DocBlock({
108+
configuration,
109+
blockTag: new tsdoc.DocBlockTag({
110+
configuration,
111+
tagName: tsdoc.StandardTags.remarks.tagName
112+
})
113+
});
114+
}
115+
116+
classMetadata.tsdocComment.remarksBlock.content.appendNode(
117+
new tsdoc.DocParagraph({ configuration }, [
118+
new tsdoc.DocPlainText({
119+
configuration,
120+
text: `The constructor for this class is marked as internal. Third-party code should not`
121+
+ ` call the constructor directly or create subclasses that extend the `
122+
}),
123+
new tsdoc.DocCodeSpan({
124+
configuration,
125+
code: classDeclaration.astSymbol.localName
126+
}),
127+
new tsdoc.DocPlainText({ configuration, text: ' class.' })
128+
])
129+
);
130+
131+
}
132+
92133
} else if (metadata.tsdocComment) {
93134
// Require the summary to contain at least 10 non-spacing characters
94135
metadata.needsDocumentation = !tsdoc.PlainTextEmitter.hasAnyTextContent(
@@ -109,9 +150,9 @@ export class DocCommentEnhancer {
109150
if (node instanceof tsdoc.DocLinkTag) {
110151
if (node.codeDestination) {
111152

112-
// Is it referring to the working package? If so, we don't do any link validation, because
153+
// Is it referring to the working package? If not, we don't do any link validation, because
113154
// AstReferenceResolver doesn't support it yet (but ModelReferenceResolver does of course).
114-
// TODO: We need to come back and fix this.
155+
// Tracked by: https://github.com/Microsoft/web-build-tools/issues/1195
115156
if (node.codeDestination.packageName === undefined
116157
|| node.codeDestination.packageName === this._collector.workingPackage.name) {
117158

@@ -135,7 +176,7 @@ export class DocCommentEnhancer {
135176
/**
136177
* Follow an `{@inheritDoc ___}` reference and copy the content that we find in the referenced comment.
137178
*/
138-
private _analyzeInheritDoc(astDeclaration: AstDeclaration, docComment: tsdoc.DocComment,
179+
private _applyInheritDoc(astDeclaration: AstDeclaration, docComment: tsdoc.DocComment,
139180
inheritDocTag: tsdoc.DocInheritDocTag): void {
140181

141182
if (!inheritDocTag.declarationReference) {
@@ -145,6 +186,16 @@ export class DocCommentEnhancer {
145186
return;
146187
}
147188

189+
// Is it referring to the working package?
190+
if (!(inheritDocTag.declarationReference.packageName === undefined
191+
|| inheritDocTag.declarationReference.packageName === this._collector.workingPackage.name)) {
192+
193+
// It's referencing an external package, so skip this inheritDoc tag, since AstReferenceResolver doesn't
194+
// support it yet. As a workaround, this tag will get handled later by api-documenter.
195+
// Tracked by: https://github.com/Microsoft/web-build-tools/issues/1195
196+
return;
197+
}
198+
148199
const referencedAstDeclaration: AstDeclaration | ResolverFailure = this._collector.astReferenceResolver
149200
.resolve(inheritDocTag.declarationReference);
150201

build-tests/api-documenter-test/etc/api-documenter-test.api.json

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
{
4141
"kind": "Class",
4242
"canonicalReference": "(DocClass1:class)",
43-
"docComment": "/**\n * This is an example class.\n *\n * @remarks\n *\n * These are some remarks.\n *\n * @defaultValue\n *\n * a default value for this function\n *\n * @public\n */\n",
43+
"docComment": "/**\n * This is an example class.\n *\n * @remarks\n *\n * These are some remarks.\n *\n * The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `DocClass1` class.\n *\n * @defaultValue\n *\n * a default value for this function\n *\n * @public\n */\n",
4444
"excerptTokens": [
4545
{
4646
"kind": "Content",
@@ -86,45 +86,6 @@
8686
"releaseTag": "Public",
8787
"name": "DocClass1",
8888
"members": [
89-
{
90-
"kind": "Constructor",
91-
"canonicalReference": "(:constructor,instance,0)",
92-
"docComment": "/**\n * The class constructor\n */\n",
93-
"excerptTokens": [
94-
{
95-
"kind": "Content",
96-
"text": "constructor("
97-
},
98-
{
99-
"kind": "Reference",
100-
"text": "name"
101-
},
102-
{
103-
"kind": "Content",
104-
"text": ": "
105-
},
106-
{
107-
"kind": "Content",
108-
"text": "string"
109-
},
110-
{
111-
"kind": "Content",
112-
"text": ");"
113-
}
114-
],
115-
"isStatic": false,
116-
"releaseTag": "Public",
117-
"overloadIndex": 0,
118-
"parameters": [
119-
{
120-
"parameterName": "name",
121-
"parameterTypeTokenRange": {
122-
"startIndex": 3,
123-
"endIndex": 4
124-
}
125-
}
126-
]
127-
},
12889
{
12990
"kind": "Method",
13091
"canonicalReference": "(deprecatedExample:instance,0)",

build-tests/api-documenter-test/etc/api-documenter-test.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class DocBaseClass {
1313

1414
// @public
1515
export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInterface2 {
16+
// @internal
1617
constructor(name: string);
1718
// @deprecated (undocumented)
1819
deprecatedExample(): void;

build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@ export declare class DocClass1 extends DocBaseClass implements IDocInterface1, I
3838
3939
These are some remarks.
4040
41+
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `DocClass1` class.
42+

build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
items:
33
- uid: api-documenter-test.DocClass1
44
summary: This is an example class.
5-
remarks: These are some remarks.
5+
remarks: >-
6+
These are some remarks.
7+
8+
9+
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or
10+
create subclasses that extend the `DocClass1` class.
611
name: DocClass1
712
fullName: DocClass1
813
langs:
@@ -15,7 +20,6 @@ items:
1520
- api-documenter-test.IDocInterface2
1621
package: api-documenter-test
1722
children:
18-
- api-documenter-test.DocClass1.(constructor)
1923
- api-documenter-test.DocClass1.deprecatedExample
2024
- api-documenter-test.DocClass1.exampleFunction
2125
- api-documenter-test.DocClass1.exampleFunction_1
@@ -25,20 +29,6 @@ items:
2529
- api-documenter-test.DocClass1.regularProperty
2630
- api-documenter-test.DocClass1.sumWithExample
2731
- api-documenter-test.DocClass1.tableExample
28-
- uid: api-documenter-test.DocClass1.(constructor)
29-
summary: The class constructor
30-
name: (constructor)(name)
31-
fullName: (constructor)(name)
32-
langs:
33-
- typeScript
34-
type: constructor
35-
syntax:
36-
content: 'constructor(name: string);'
37-
parameters:
38-
- id: name
39-
description: ''
40-
type:
41-
- string
4232
- uid: api-documenter-test.DocClass1.deprecatedExample
4333
deprecated:
4434
content: Use `otherThing()` instead.

build-tests/api-documenter-test/src/DocClass1.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ export interface IDocInterface4 {
9898
*/
9999
export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInterface2 {
100100
/**
101-
* The class constructor
101+
* An internal class constructor.
102+
* @internal
102103
*/
103104
public constructor(name: string) {
104105
super();

build-tests/api-extractor-scenarios/etc/test-outputs/docReferences/api-extractor-scenarios.api.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,41 @@
187187
}
188188
]
189189
},
190+
{
191+
"kind": "Function",
192+
"canonicalReference": "(succeedForNow:0)",
193+
"docComment": "/**\n * @privateRemarks\n *\n * succeedForNow() should fail due to a broken link, but it's ignored until we fix this issue: https://github.com/Microsoft/web-build-tools/issues/1195\n * {@inheritDoc nonexistent-package#MyNamespace.MyClass.nonExistentMethod}\n *\n * @public\n */\n",
194+
"excerptTokens": [
195+
{
196+
"kind": "Content",
197+
"text": "export declare function "
198+
},
199+
{
200+
"kind": "Reference",
201+
"text": "succeedForNow"
202+
},
203+
{
204+
"kind": "Content",
205+
"text": "(): "
206+
},
207+
{
208+
"kind": "Content",
209+
"text": "void"
210+
},
211+
{
212+
"kind": "Content",
213+
"text": ";"
214+
}
215+
],
216+
"returnTypeTokenRange": {
217+
"startIndex": 3,
218+
"endIndex": 4
219+
},
220+
"releaseTag": "Public",
221+
"overloadIndex": 0,
222+
"parameters": [],
223+
"name": "succeedForNow"
224+
},
190225
{
191226
"kind": "Function",
192227
"canonicalReference": "(testSimple:0)",

0 commit comments

Comments
 (0)