Skip to content

Commit 5c080d4

Browse files
authored
Merge pull request microsoft#114 from Microsoft/dagaeta/improvements-to-support-harmless-cycles
ApiItem initialization was split into 3 different categories
2 parents 0ca9ddc + 69fb212 commit 5c080d4

File tree

13 files changed

+351
-186
lines changed

13 files changed

+351
-186
lines changed

api-extractor/src/DocElementParser.ts

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { ITextElement, IDocElement, IHrefLinkElement, ICodeLinkElement, ISeeDocElement } from './IDocElement';
22
import ApiDefinitionReference from './ApiDefinitionReference';
3+
import ApiDocumentation from './definitions/ApiDocumentation';
4+
import { ApiItemKind } from './definitions/ApiItem';
35
import Token, { TokenType } from './Token';
46
import Tokenizer from './Tokenizer';
7+
import ResolvedApiItem from './ResolvedApiItem';
58

69
export default class DocElementParser {
710

@@ -57,7 +60,7 @@ export default class DocElementParser {
5760
return {kind: 'textDocElement', value: text} as ITextElement;
5861
}
5962

60-
public static parse(tokenizer: Tokenizer, reportError: (message: string) => void): IDocElement[] {
63+
public static parse(documentation: ApiDocumentation, tokenizer: Tokenizer): IDocElement[] {
6164
const docElements: IDocElement[] = [];
6265
let parsing: boolean = true;
6366
let token: Token;
@@ -75,7 +78,7 @@ export default class DocElementParser {
7578
tokenizer.getToken();
7679
docElements.push({
7780
kind: 'seeDocElement',
78-
seeElements: this.parse(tokenizer, reportError)
81+
seeElements: this.parse(documentation, tokenizer)
7982
} as ISeeDocElement);
8083
break;
8184
default:
@@ -84,10 +87,22 @@ export default class DocElementParser {
8487
}
8588
} else if (token.type === TokenType.Inline) {
8689
switch (token.tag) {
90+
case '@inheritdoc':
91+
tokenizer.getToken();
92+
if (docElements.length > 0 || documentation.summary.length > 0) {
93+
documentation.reportError('Cannot provide summary in JsDoc if @inheritdoc tag is given');
94+
}
95+
documentation.incompleteInheritdocs.push(token);
96+
documentation.isDocInherited = true;
97+
break;
8798
case '@link' :
88-
const linkDocElement: ICodeLinkElement | IHrefLinkElement = this.parseLinkTag(token, reportError);
99+
const linkDocElement: ICodeLinkElement | IHrefLinkElement = this.parseLinkTag(documentation, token);
89100
if (linkDocElement) {
101+
// Push to docElements to retain position in the documentation
90102
docElements.push(linkDocElement);
103+
if (linkDocElement.referenceType === 'code') {
104+
documentation.incompleteLinks.push(linkDocElement);
105+
}
91106
}
92107
tokenizer.getToken(); // get the link token
93108
break;
@@ -99,7 +114,7 @@ export default class DocElementParser {
99114
docElements.push({kind: 'textDocElement', value: token.text} as ITextElement);
100115
tokenizer.getToken();
101116
} else {
102-
reportError(`Unidentifiable Token ${token.type} ${token.tag} ${token.text}`);
117+
documentation.reportError(`Unidentifiable Token ${token.type} ${token.tag} ${token.text}`);
103118
}
104119
}
105120
return docElements;
@@ -119,10 +134,9 @@ export default class DocElementParser {
119134
* \{@link @microsoft/sp-core-library:Guid.newGuid | new Guid Object \}
120135
* \{@link @microsoft/sp-core-library:Guid.newGuid \}
121136
*/
122-
public static parseLinkTag(tokenItem: Token,
123-
reportError: (message: string) => void): IHrefLinkElement | ICodeLinkElement {
137+
public static parseLinkTag(documentation: ApiDocumentation, tokenItem: Token): IHrefLinkElement | ICodeLinkElement {
124138
if (!tokenItem.text) {
125-
reportError('Invalid @link inline token, a url or API definition reference must be given');
139+
documentation.reportError('Invalid @link inline token, a url or API definition reference must be given');
126140
return;
127141
}
128142

@@ -134,7 +148,7 @@ export default class DocElementParser {
134148
}
135149
});
136150
if (pipeSplitContent.length > 2) {
137-
reportError('Invalid @link parameters, at most one pipe character allowed.');
151+
documentation.reportError('Invalid @link parameters, at most one pipe character allowed.');
138152
return;
139153
}
140154

@@ -145,7 +159,7 @@ export default class DocElementParser {
145159

146160
// Make sure only a single url is given
147161
if (urlContent.length > 1 && urlContent[1] !== '' ) {
148-
reportError('Invalid @link parameter, url must be a single string.');
162+
documentation.reportError('Invalid @link parameter, url must be a single string.');
149163
return;
150164
}
151165

@@ -160,7 +174,7 @@ export default class DocElementParser {
160174
// we are processing an API definition reference
161175
const apiDefitionRef: ApiDefinitionReference = ApiDefinitionReference.createFromString(
162176
pipeSplitContent[0],
163-
reportError
177+
documentation.reportError
164178
);
165179

166180
// Once we can locate local API definitions, an error should be reported here if not found.
@@ -181,7 +195,7 @@ export default class DocElementParser {
181195
if (linkDocElement && pipeSplitContent.length > 1) {
182196
const displayTextParts: string[] = pipeSplitContent[1].match(this._wordRegEx);
183197
if (displayTextParts && displayTextParts[0].length !== pipeSplitContent[1].length) {
184-
reportError('Display name in @link token may only contain alphabetic characters.');
198+
documentation.reportError('Display name in @link token may only contain alphabetic characters.');
185199
return;
186200
}
187201
// Full match is valid text
@@ -190,4 +204,87 @@ export default class DocElementParser {
190204

191205
return linkDocElement;
192206
}
207+
208+
/**
209+
* This method parses the semantic information in an \@inheritdoc JSDoc tag and sets
210+
* all the relevant documenation properties from the inherited doc onto the documenation
211+
* of the current api item.
212+
*
213+
* The format for the \@inheritdoc tag is {\@inheritdoc scopeName/packageName:exportName.memberName}.
214+
* For more information on the format see IInheritdocRef.
215+
*/
216+
public static parseInheritDoc(documentation: ApiDocumentation, token: Token): void {
217+
218+
// Check to make sure the API definition reference is at most one string
219+
const tokenChunks: string[] = token.text.split(' ');
220+
if (tokenChunks.length > 1) {
221+
documentation.reportError('Too many parameters for @inheritdoc inline tag.' +
222+
'The format should be {@inheritdoc scopeName/packageName:exportName}. Extra parameters are ignored');
223+
return;
224+
}
225+
226+
// Create the IApiDefinitionReference object
227+
// Deconstruct the API reference expression 'scopeName/packageName:exportName.memberName'
228+
const apiDefinitionRef: ApiDefinitionReference = ApiDefinitionReference.createFromString(
229+
token.text,
230+
documentation.reportError
231+
);
232+
// if API reference expression is formatted incorrectly then apiDefinitionRef will be undefined
233+
if (!apiDefinitionRef) {
234+
documentation.reportError('Incorrecty formatted API definition reference');
235+
return;
236+
}
237+
238+
// Atempt to locate the apiDefinitionRef
239+
const resolvedApiItem: ResolvedApiItem = documentation.referenceResolver.resolve(
240+
apiDefinitionRef,
241+
documentation.extractor.package,
242+
documentation.reportError
243+
);
244+
245+
// If no resolvedApiItem found then nothing to inherit
246+
// But for the time being set the summary to a text object
247+
if (!resolvedApiItem) {
248+
const textDocItem: IDocElement = {
249+
kind: 'textDocElement',
250+
value: `See documentation for ${tokenChunks[0]}`
251+
} as ITextElement;
252+
documentation.summary = [textDocItem];
253+
return;
254+
}
255+
256+
// We are going to copy the resolvedApiItem's documentation
257+
// We must make sure it's documentation can be completed,
258+
// if we cannot, an error will be reported viathe documentation error handler.
259+
// This will only be the case our resolvedApiItem was created from a local
260+
// ApiItem. Resolutions from JSON will have an undefined 'apiItem' property.
261+
// Example: a circular reference will report an error.
262+
if (resolvedApiItem.apiItem) {
263+
resolvedApiItem.apiItem.completeInitialization();
264+
}
265+
266+
// inheritdoc found, copy over IDocBase properties
267+
documentation.summary = resolvedApiItem.summary;
268+
documentation.remarks = resolvedApiItem.remarks;
269+
270+
// Copy over detailed properties if neccessary
271+
// Add additional cases if needed
272+
switch (resolvedApiItem.kind) {
273+
case ApiItemKind.Function:
274+
documentation.parameters = resolvedApiItem.params;
275+
documentation.returnsMessage = resolvedApiItem.returnsMessage;
276+
break;
277+
case ApiItemKind.Method:
278+
documentation.parameters = resolvedApiItem.params;
279+
documentation.returnsMessage = resolvedApiItem.returnsMessage;
280+
break;
281+
}
282+
283+
// Check if inheritdoc is depreacted
284+
// We need to check if this documentation has a deprecated message
285+
// but it may not appear until after this token.
286+
if (resolvedApiItem.deprecatedMessage) {
287+
documentation.isDocInheritedDeprecated = true;
288+
}
289+
}
193290
}

api-extractor/src/DocItemLoader.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ export interface IParsedScopeName {
2626
}
2727

2828
/**
29-
* A loader for locating the IDocItem associated with a given project and API item.
30-
* The DocItem loader utilizes the json files generated by the API-Extractor ApiJsonGenerator.
29+
* A loader for locating the IDocItem associated with a given project and API item, or
30+
* for locating an ApiItem locally.
31+
* No processing on the IDocItem orApiItem should be done in this class, this class is only
32+
* concerned with communicating state.
3133
* The IDocItem can then be used to enforce correct API usage, like enforcing internal.
32-
* To use to DocItemLoader: provide a projectFolder to construct a instance of the DocItemLoader,
34+
* To use DocItemLoader: provide a projectFolder to construct a instance of the DocItemLoader,
3335
* then use DocItemLoader.getItem to retrieve the IDocItem of a particular API item.
3436
*/
3537
export default class DocItemLoader {
@@ -70,7 +72,9 @@ export default class DocItemLoader {
7072

7173
/**
7274
* Resolution of API definition references in the scenario that the reference given indicates
73-
* that we should search within the current ApiPackage to resolve.
75+
* that we should search within the current ApiPackage to resolve.
76+
* No processing on the ApiItem should be done here, this class is only concerned
77+
* with communicating state.
7478
*/
7579
public resolveLocalReferences(apiDefinitionRef: ApiDefinitionReference,
7680
apiPackage: ApiPackage,
@@ -103,7 +107,6 @@ export default class DocItemLoader {
103107
return undefined;
104108
}
105109

106-
apiItem.tryResolveReferences();
107110
return ResolvedApiItem.createFromApiItem(apiItem);
108111
}
109112

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.tryResolveReferences(); // creates ApiDocumentation
99+
this.package.completeInitialization(); // creates ApiDocumentation
100100
}
101101

102102
/**

api-extractor/src/ResolvedApiItem.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,30 @@ export default class ResolvedApiItem {
1313
public summary: IDocElement[];
1414
public remarks: IDocElement[];
1515
public deprecatedMessage: IDocElement[];
16+
public apiTag: ApiTag;
1617
public isBeta: boolean;
1718
public params: {[name: string]: IParam};
1819
public returnsMessage: IDocElement[];
20+
/**
21+
* This property will either be an ApiItem or undefined.
22+
*/
23+
public apiItem: ApiItem;
24+
1925
/**
2026
* A function to abstract the construction of a ResolvedApiItem instance
2127
* from an ApiItem.
2228
*/
2329
public static createFromApiItem(apiItem: ApiItem): ResolvedApiItem {
24-
const canResolveRefs: boolean = apiItem.tryResolveReferences();
25-
if (!canResolveRefs) {
26-
return undefined;
27-
}
28-
2930
return new ResolvedApiItem(
3031
apiItem.kind,
3132
apiItem.documentation.summary,
3233
apiItem.documentation.remarks,
3334
apiItem.documentation.deprecatedMessage,
3435
apiItem.documentation.apiTag === ApiTag.Beta,
3536
apiItem.documentation.parameters,
36-
apiItem.documentation.returnsMessage
37+
apiItem.documentation.returnsMessage,
38+
apiItem.documentation.apiTag,
39+
apiItem
3740
);
3841
}
3942

@@ -64,7 +67,9 @@ export default class ResolvedApiItem {
6467
docItem.deprecatedMessage,
6568
docItem.isBeta,
6669
parameters,
67-
returnsMessage
70+
returnsMessage,
71+
ApiTag.Public,
72+
undefined
6873
);
6974
}
7075

@@ -75,12 +80,16 @@ export default class ResolvedApiItem {
7580
deprecatedMessage: IDocElement[],
7681
isBeta: boolean,
7782
params: {[name: string]: IParam},
78-
returnsMessage: IDocElement[]) {
83+
returnsMessage: IDocElement[],
84+
apiTag: ApiTag,
85+
apiItem: ApiItem) {
7986
this.kind = kind;
8087
this.summary = summary;
8188
this.remarks = remarks;
8289
this.isBeta = isBeta;
8390
this.params = params;
8491
this.returnsMessage = returnsMessage;
92+
this.apiTag = apiTag;
93+
this.apiItem = apiItem;
8594
}
8695
}

0 commit comments

Comments
 (0)