Skip to content

Commit eebf150

Browse files
committed
Added local reference resolution for Api-Extractor
1 parent 925fe25 commit eebf150

File tree

12 files changed

+432
-57
lines changed

12 files changed

+432
-57
lines changed

api-extractor/src/DocItemLoader.ts

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as fsx from 'fs-extra';
22
import * as os from 'os';
33
import * as path from 'path';
4-
import { IDocItem, IDocPackage, IDocClass } from './IDocItem';
4+
import { IDocItem, IDocPackage, IDocClass, IDocMember } from './IDocItem';
55
import { IApiDefinitionReference } from './IApiDefinitionReference';
6+
import ApiItem from './definitions/ApiItem';
7+
import ApiPackage from './definitions/ApiPackage';
8+
import ResolvedApiItem from './ResolvedApiItem';
69
import JsonFile from './JsonFile';
710

811
/**
@@ -31,7 +34,6 @@ export interface IParsedScopeName {
3134
export default class DocItemLoader {
3235
private _cache: Map<string, IDocPackage>;
3336
private _projectFolder: string; // Root directory to check for node modules
34-
private _errorHandler: (message: string) => void;
3537

3638
/**
3739
* The projectFolder is the top-level folder containing package.json for a project
@@ -49,36 +51,87 @@ export default class DocItemLoader {
4951
/**
5052
* {@inheritdoc ApiDocumentation.IReferenceResolver}
5153
*/
52-
public resolve(apiDefinitionRef: IApiDefinitionReference, reportError: (message: string) => void): IDocItem {
53-
if (!apiDefinitionRef) {
54-
reportError('Expected reference within {@inheritdoc} tag');
55-
return undefined;
54+
public resolve(apiDefinitionRef: IApiDefinitionReference,
55+
apiPackage: ApiPackage,
56+
reportError: (message: string) => void): ResolvedApiItem {
57+
58+
const packageName: string = path.dirname(apiPackage.name).split('/').pop();
59+
60+
// Resolution for local references
61+
if (packageName === apiDefinitionRef.packageName || !apiDefinitionRef.packageName) {
62+
return this.resolveLocalReference(apiDefinitionRef, apiPackage, reportError);
5663
}
57-
// Try to load the package given the provided packageName into the cache
64+
65+
// Resolution for references in JSON files
5866
const docPackage: IDocPackage = this.getPackage(apiDefinitionRef, reportError);
5967

6068
// Check if package was not found
6169
if (!docPackage) {
70+
reportError('API definition was not found in JSON files');
71+
return undefined;
72+
}
73+
74+
return this.resolveJsonReference(apiDefinitionRef, docPackage, reportError);
75+
}
76+
77+
/**
78+
*
79+
*/
80+
public resolveLocalReference(apiDefinitionRef: IApiDefinitionReference,
81+
apiPackage: ApiPackage,
82+
reportError: (message: string) => void): ResolvedApiItem {
83+
84+
const apiItem: ApiItem = apiPackage.getMemberItem(apiDefinitionRef.exportName);
85+
if (apiItem) {
86+
let resolvedApiItem: ResolvedApiItem = ResolvedApiItem.createFromApiItem(apiItem);
87+
88+
// If memberName exists then check for the existense of the name
89+
if (apiDefinitionRef.memberName) {
90+
if (apiDefinitionRef.memberName in resolvedApiItem.members) {
91+
92+
// We are certain the return type will be ApiItem
93+
const member: ApiItem | IDocMember = resolvedApiItem.members[apiDefinitionRef.memberName];
94+
if (member && member instanceof ApiItem) {
95+
member.canResolveReferences();
96+
resolvedApiItem = ResolvedApiItem.createFromApiItem(member);
97+
}
98+
} else {
99+
// member name was not found, apiDefinitionRef is invalid
100+
reportError(`\"${apiDefinitionRef.memberName}\",` +
101+
` not found as member of \"${apiDefinitionRef.exportName}\"`);
102+
return undefined;
103+
}
104+
}
105+
return resolvedApiItem;
106+
} else {
107+
reportError(`Unable to resolve ApiItem: "${apiDefinitionRef.exportName}"`);
62108
return undefined;
63109
}
110+
}
111+
112+
public resolveJsonReference(apiDefinitionRef: IApiDefinitionReference,
113+
docPackage: IDocPackage,
114+
reportError: (message: string) => void): ResolvedApiItem {
64115

65116
if (apiDefinitionRef.exportName in docPackage.exports) {
66117
let docItem: IDocItem = docPackage.exports[apiDefinitionRef.exportName];
67118

68119
// If memberName exists then check for the existense of the name
69120
if (apiDefinitionRef.memberName) {
70-
if ( apiDefinitionRef.memberName in (docItem as IDocClass).members) {
121+
if (apiDefinitionRef.memberName in (docItem as IDocClass).members) {
71122
docItem = (docItem as IDocClass).members[apiDefinitionRef.memberName];
72123
} else {
73124
// member name was not found, apiDefinitionRef is invalid
125+
reportError('API member name was not found on the export item');
74126
return undefined;
75127
}
76128
}
77129

78130
// Correct doc item was found
79-
return docItem;
131+
return ResolvedApiItem.createFromJson(docItem);
80132
} else {
81133
// Not found
134+
reportError('API export item was not found in the package');
82135
return undefined;
83136
}
84137
}
@@ -103,11 +156,6 @@ export default class DocItemLoader {
103156
return this._cache.get(cachePackageName);
104157
}
105158

106-
if (!apiDefinitionRef.packageName) {
107-
// Local export resolution is currently not supported yet
108-
return;
109-
}
110-
111159
// Doesn't exist in cache, attempt to load the json file
112160
const packageJsonFilePath: string = path.join(
113161
this._projectFolder,

api-extractor/src/Extractor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export default class Extractor {
9696
}
9797

9898
this.package = new ApiPackage(this, rootFile); // construct members
99-
this.package.resolveReferences(); // creates ApiDocumentation
99+
this.package.canResolveReferences(); // creates ApiDocumentation
100100
}
101101

102102
/**

api-extractor/src/IDocItem.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export interface IDocReturnValue {
8080
*/
8181
export interface IDocBase {
8282
/**
83-
* kind of DocItem. Ex: 'class', 'Enum', 'Function'
83+
* kind of DocItem. Ex: 'class', 'Enum', 'Function', etc.
8484
*/
8585
kind: string;
8686
isBeta: boolean;
@@ -93,6 +93,11 @@ export interface IDocBase {
9393
* A property of a TypeScript class or interface
9494
*/
9595
export interface IDocProperty extends IDocBase {
96+
97+
/**
98+
* {@inheritdoc IDocBase.kind}
99+
*/
100+
kind: 'IDocProperty';
96101
/**
97102
* For an interface member, whether it is optional
98103
*/
@@ -118,6 +123,10 @@ export interface IDocProperty extends IDocBase {
118123
* A member function of a typescript class or interface.
119124
*/
120125
export interface IDocMethod extends IDocBase {
126+
/**
127+
* {@inheritdoc IDocBase.kind}
128+
*/
129+
kind: 'IDocMethod';
121130
/**
122131
* a text summary of the method definition
123132
*/
@@ -154,6 +163,10 @@ export interface IDocMethod extends IDocBase {
154163
* A Typescript function.
155164
*/
156165
export interface IDocFunction extends IDocBase {
166+
/**
167+
* {@inheritdoc IDocBase.kind}
168+
*/
169+
kind: 'IDocFunction';
157170
/**
158171
* parameters of the function
159172
*/
@@ -170,6 +183,10 @@ export interface IDocFunction extends IDocBase {
170183
*
171184
*/
172185
export interface IDocClass extends IDocBase {
186+
/**
187+
* {@inheritdoc IDocBase.kind}
188+
*/
189+
kind: 'IDocClass';
173190
/**
174191
* Can be a combination of methods and/or properties
175192
*/
@@ -195,13 +212,22 @@ export interface IDocClass extends IDocBase {
195212
* IDocEnum represents an exported enum.
196213
*/
197214
export interface IDocEnum extends IDocBase {
215+
/**
216+
* {@inheritdoc IDocBase.kind}
217+
*/
218+
kind: 'IDocEnum';
219+
198220
values: IDocEnumValue[];
199221
}
200222

201223
/**
202224
* IDocInterface represents an exported interface.
203225
*/
204226
export interface IDocInterface extends IDocBase {
227+
/**
228+
* {@inheritdoc IDocBase.kind}
229+
*/
230+
kind: 'IDocInterface';
205231
/**
206232
* A mapping from the name of a member API to its IDocMember
207233
*/
@@ -229,10 +255,10 @@ export interface IDocInterface extends IDocBase {
229255
* classes, interfaces, enums, functions.
230256
*/
231257
export interface IDocPackage {
232-
/**
233-
* Always should be 'package'
258+
/**
259+
* {@inheritdoc IDocBase.kind}
234260
*/
235-
kind: string;
261+
kind: 'IDocPackage';
236262

237263
/**
238264
* IDocItems of exported API items
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import ApiItem, { ApiItemKind } from './definitions/ApiItem';
2+
import ApiItemContainer from './definitions/ApiItemContainer';
3+
import { ApiTag } from './definitions/ApiDocumentation';
4+
import { IDocElement, IParam } from './IDocElement';
5+
import { IDocItem, IDocFunction, IDocMember } from './IDocItem';
6+
7+
/**
8+
* A class to abstract away the difference between an item from our public API that could be
9+
* represented by either an ApiItem or an IDocItem that is retrieved from a JSON file.
10+
*/
11+
export default class ResolvedApiItem {
12+
public kind: ApiItemKind;
13+
public summary: IDocElement[];
14+
public remarks: IDocElement[];
15+
public deprecatedMessage: IDocElement[];
16+
public apiTag: ApiTag;
17+
public params: {[name: string]: IParam};
18+
public returnsMessage: IDocElement[];
19+
public members: { [name: string]: ApiItem | IDocMember};
20+
21+
/**
22+
*
23+
*/
24+
public static createFromApiItem(apiItem: ApiItem): ResolvedApiItem {
25+
const canResolveRefs: boolean = apiItem.canResolveReferences();
26+
if (!canResolveRefs) {
27+
return undefined;
28+
}
29+
30+
const members: { [name: string]: ApiItem} = {};
31+
if (apiItem instanceof ApiItemContainer) {
32+
apiItem.memberItems.forEach(value => {
33+
members[value.name] = value;
34+
});
35+
}
36+
return new ResolvedApiItem(
37+
apiItem.kind,
38+
apiItem.documentation.summary,
39+
apiItem.documentation.remarks,
40+
apiItem.documentation.deprecatedMessage,
41+
apiItem.documentation.apiTag,
42+
apiItem.documentation.parameters,
43+
apiItem.documentation.returnsMessage,
44+
members
45+
);
46+
}
47+
48+
/**
49+
*
50+
*/
51+
public static createFromJson(docItem: IDocItem): ResolvedApiItem {
52+
let parameters: {[name: string]: IParam} = undefined;
53+
let returnsMessage: IDocElement[] = undefined;
54+
let members: { [name: string]: IDocMember} = undefined;
55+
switch (docItem.kind) {
56+
case 'IDocFunction':
57+
parameters = docItem.parameters;
58+
returnsMessage = docItem.returnValue.description;
59+
break;
60+
case 'IDocMethod':
61+
parameters = docItem.parameters;
62+
returnsMessage = docItem.returnValue.description;
63+
break;
64+
case 'IDocClass':
65+
members = docItem.members;
66+
break;
67+
default:
68+
break;
69+
}
70+
71+
return new ResolvedApiItem(
72+
ApiItemKind[docItem.kind],
73+
docItem.summary,
74+
docItem.remarks,
75+
docItem.deprecatedMessage,
76+
undefined,
77+
parameters,
78+
returnsMessage,
79+
members
80+
81+
);
82+
}
83+
84+
constructor(
85+
kind: ApiItemKind,
86+
summary: IDocElement[],
87+
remarks: IDocElement[],
88+
deprecatedMessage: IDocElement[],
89+
apiTag?: ApiTag,
90+
params?: {[name: string]: IParam},
91+
returnsMessage?: IDocElement[],
92+
members?: { [name: string]: ApiItem | IDocMember} ) {
93+
this.kind = kind;
94+
this.summary = summary;
95+
this.remarks = remarks;
96+
this.apiTag = apiTag;
97+
this.params = params;
98+
this.returnsMessage = returnsMessage;
99+
this.members = members;
100+
}
101+
}

0 commit comments

Comments
 (0)