Skip to content

Commit 05f1665

Browse files
author
nickpape-msft
authored
Merge pull request microsoft#45 from Microsoft/pgonzal/reset-api-extractor
Update snapshot of api-extractor source files
2 parents 96e7663 + 9dd2469 commit 05f1665

29 files changed

Lines changed: 3215 additions & 653 deletions

File tree

api-extractor/.npmignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Ignore everything by default
2+
**
3+
4+
# Use negative patterns to bring back the specific things we want to publish
5+
!/bin/**
6+
!/lib/**
7+
!/dist/**
8+
9+
# NOTE: These don't need to be specified, because NPM includes them automatically.
10+
#
11+
# package.json
12+
# README (and its variants)
13+
# CHANGELOG (and its variants)
14+
# LICENSE / LICENCE

api-extractor/gulpfile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
const build = require('@microsoft/node-library-build');
44

5+
build.typescript.taskConfig.typescript = require('typescript');
6+
57
build.initialize(require('gulp'));

api-extractor/src/DebugRun.ts

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@
44
import * as ts from 'typescript';
55
import Analyzer from './Analyzer';
66
import ApiFileGenerator from './generators/ApiFileGenerator';
7-
import TypeDocGenerator from './generators/TypeDocGenerator';
87
import ApiJsonGenerator from './generators/ApiJsonGenerator';
98
import { IDocItem } from './IDocItem';
109
import { IApiDefinitionReference } from './IApiDefinitionReference';
10+
import { IDocElement, IParam } from './IDocElement';
1111
import DocItemLoader from './DocItemLoader';
12+
import DocElementParser from './DocElementParser';
1213
import TestFileComparer from './TestFileComparer';
1314
import JsonFile from './JsonFile';
1415
import ApiStructuredType from './definitions/ApiStructuredType';
1516
import ApiDocumentation from './definitions/ApiDocumentation';
17+
import Tokenizer from './Tokenizer';
18+
19+
let docs: string = '{@link @microsoft/sp-core-library:Guid.equals | Guid equals}';
20+
let tokenizer: Tokenizer = new Tokenizer(docs, console.log);
21+
/* tslint:disable:no-unused-variable */
22+
const linkResult: IDocElement[] = DocElementParser.parse(tokenizer, console.log);
1623

1724
const analyzer: Analyzer = new Analyzer();
1825

@@ -25,20 +32,8 @@ class TestApiDocumentation extends ApiDocumentation {
2532
super(myDocumentedClass, analyzer.docItemLoader, (msg: string) => { return; });
2633
}
2734

28-
public tokenizeDocs(docs: string): string[] {
29-
return this._tokenizeDocs(docs);
30-
}
31-
32-
public parseDocsBlock(tokens: string[], startingIndex: number, tagName?: string): string {
33-
return this._parseDocsBlock(tokens, startingIndex, tagName);
34-
}
35-
36-
public parseDocsInline(token: string): string {
37-
return this._parseDocsInline(token);
38-
}
39-
40-
public parseApiReferenceExpression(apiDefinitionRef: string): IApiDefinitionReference {
41-
return this._parseApiReferenceExpression(apiDefinitionRef);
35+
public parseParam(_tokenizer: Tokenizer): IParam {
36+
return this._parseParam(_tokenizer);
4237
}
4338
}
4439

@@ -49,18 +44,15 @@ analyzer.analyze({
4944
moduleResolution: ts.ModuleResolutionKind.NodeJs,
5045
experimentalDecorators: true,
5146
jsx: ts.JsxEmit.React,
52-
rootDir: '../../spfx-core/sp-loader'
47+
rootDir: '../../spfx-core/sp-client-base'
5348
},
54-
entryPointFile: '../../spfx-core/sp-loader/src/index.ts', // local/bundles/platform-exports.ts',
55-
otherFiles: ['../../spfx-core/sp-loader/typings/tsd.d.ts']
49+
entryPointFile: '../../spfx-core/sp-client-base/src/index.ts', // local/bundles/platform-exports.ts',
50+
otherFiles: ['../../spfx-core/sp-client-base/typings/tsd.d.ts']
5651
});
5752

5853
const apiFileGenerator: ApiFileGenerator = new ApiFileGenerator();
5954
apiFileGenerator.writeApiFile('./lib/DebugRun.api.ts', analyzer);
6055

61-
const typeDocGenerator: TypeDocGenerator = new TypeDocGenerator();
62-
typeDocGenerator.writeApiFile('./lib/DebugRun.typedoc.ts', analyzer);
63-
6456
const apiJsonGenerator: ApiJsonGenerator = new ApiJsonGenerator();
6557
apiJsonGenerator.writeJsonFile('./lib/DebugRun.json', analyzer);
6658

@@ -85,29 +77,35 @@ myDocumentedClass = analyzer.package.getSortedMemberItems()
8577
.filter(apiItem => apiItem.name === 'MyDocumentedClass')[0] as ApiStructuredType;
8678
const apiDoc: TestApiDocumentation = new TestApiDocumentation();
8779

80+
docs = '@param x - The height in {@link http://wikipedia.org/pixel_units}';
81+
tokenizer = new Tokenizer(docs, console.log);
82+
// ApiDocumentation gets the @param token before calling parseParam()
83+
tokenizer.getToken();
84+
apiDoc.parseParam(tokenizer);
85+
8886
/**
8987
* Put test cases here
9088
*/
9189
let apiReferenceExpr: string = '@microsoft/sp-core-library:Guid.equals';
9290
let actual: IApiDefinitionReference;
93-
actual = apiDoc.parseApiReferenceExpression(apiReferenceExpr);
91+
actual = ApiDocumentation.parseApiReferenceExpression(apiReferenceExpr, apiDoc.reportError);
9492

9593
apiReferenceExpr = '@microsoft/sp-core-library:Guid';
96-
actual = apiDoc.parseApiReferenceExpression(apiReferenceExpr);
94+
actual = ApiDocumentation.parseApiReferenceExpression(apiReferenceExpr, apiDoc.reportError);
9795

9896
apiReferenceExpr = 'sp-core-library:Guid';
99-
actual = apiDoc.parseApiReferenceExpression(apiReferenceExpr);
97+
actual = ApiDocumentation.parseApiReferenceExpression(apiReferenceExpr, apiDoc.reportError);
10098

10199
apiReferenceExpr = 'Guid.equals';
102-
actual = apiDoc.parseApiReferenceExpression(apiReferenceExpr);
100+
actual = ApiDocumentation.parseApiReferenceExpression(apiReferenceExpr, apiDoc.reportError);
103101

104102
apiReferenceExpr = 'Guid';
105-
actual = apiDoc.parseApiReferenceExpression(apiReferenceExpr);
103+
actual = ApiDocumentation.parseApiReferenceExpression(apiReferenceExpr, apiDoc.reportError);
106104

107105
// Should report error
108106
apiReferenceExpr = 'sp-core-library:Guid:equals';
109107
try {
110-
actual = apiDoc.parseApiReferenceExpression(apiReferenceExpr);
108+
actual = ApiDocumentation.parseApiReferenceExpression(apiReferenceExpr, apiDoc.reportError);
111109
} catch (error) {
112110
console.log(error);
113111
}
@@ -128,4 +126,4 @@ const apiDocItemNotInCache: IDocItem = docItemLoader.getItem(apiDefinitionRef);
128126
JsonFile.saveJsonFile('./lib/inheritedDoc-output.json', JSON.stringify(apiDocItemNotInCache));
129127
TestFileComparer.assertFileMatchesExpected('./lib/inheritedDoc-output.json', './testInputs/inheritedDoc-output.json');
130128
/* tslint:disable:no-unused-variable */
131-
const apiDocItemInCache: IDocItem = docItemLoader.getItem(apiDefinitionRef);
129+
const apiDocItemInCache: IDocItem = docItemLoader.getItem(apiDefinitionRef);
Lines changed: 149 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,169 @@
1-
import { IDocElement } from './IDocElement';
1+
import { IDocElement, IHrefLinkElement, ICodeLinkElement } from './IDocElement';
2+
import { IApiDefinitionReference } from './IApiDefinitionReference';
3+
import ApiDocumentation from './definitions/ApiDocumentation';
4+
import Token from './Token';
5+
import Tokenizer from './Tokenizer';
26

37
export default class DocElementParser {
48

9+
/**
10+
* Matches only strings that contain characters for words.
11+
* Any non word characters or spaces, will be present in the third entry in the match results
12+
* if they exist.
13+
*/
14+
private static _wordRegEx: RegExp = /^([\w\s]*)/;
15+
16+
/**
17+
* Matches a href reference. This is used to get an idea whether a given reference is for an href
18+
* or an API definition reference.
19+
*
20+
* For example, the following would be matched:
21+
* 'http://'
22+
* 'https://'
23+
*
24+
* The following would not be matched:
25+
* '@microsoft/sp-core-library:Guid.newGuid'
26+
* 'Guid.newGuid'
27+
* 'Guid'
28+
*/
29+
private static _hrefRegEx: RegExp = /^[a-z]+:\/\//;
30+
531
public static makeTextElement(text: string): IDocElement {
32+
if (!text) {
33+
return;
34+
}
635
return {kind: 'textDocElement', value: text};
736
}
837

9-
public static parse(tokenStream: string[]): IDocElement[] {
10-
const summaryDocElements: IDocElement[] = [];
38+
public static parse(tokenizer: Tokenizer, reportError: (message: string) => void): IDocElement[] {
39+
const docElements: IDocElement[] = [];
1140
let parsing: boolean = true;
12-
let token: string;
41+
let token: Token;
1342

1443
while (parsing) {
15-
token = this._peek(tokenStream);
44+
token = tokenizer.peekToken();
1645
if (!token) {
1746
parsing = false; // end of stream
18-
} else if (token === '@see') {
19-
tokenStream.shift();
20-
summaryDocElements.push({
21-
kind: 'seeDocElement',
22-
seeElements: this.parse(tokenStream)
23-
});
24-
} else if (token.substring(0, 6) === '{@link') {
25-
// ? Raise error if there is no space after link ?
26-
summaryDocElements.push({
27-
kind: 'linkDocElement',
28-
targetUrl: token.substring(7, token.length - 1).trim()
29-
});
30-
tokenStream.shift(); // pop the link token
31-
} else if (this._isTextSymbol(token)) {
32-
summaryDocElements.push({kind: 'textDocElement', value: token});
33-
tokenStream.shift();
47+
break;
48+
}
49+
50+
if (token.type === 'Tag') {
51+
switch (token.tag) {
52+
case '@see':
53+
tokenizer.getToken();
54+
docElements.push({
55+
kind: 'seeDocElement',
56+
seeElements: this.parse(tokenizer, reportError)
57+
});
58+
break;
59+
default:
60+
parsing = false; // end of summary tokens
61+
break;
62+
}
63+
} else if (token.type === 'Inline') {
64+
switch (token.tag) {
65+
case '@link' :
66+
const linkDocElement: ICodeLinkElement | IHrefLinkElement = this.parseLinkTag(token, reportError);
67+
if (linkDocElement) {
68+
docElements.push(linkDocElement);
69+
}
70+
tokenizer.getToken(); // get the link token
71+
break;
72+
default:
73+
parsing = false;
74+
break;
75+
}
76+
} else if (token.type === 'Text') {
77+
docElements.push({kind: 'textDocElement', value: token.text});
78+
tokenizer.getToken();
3479
} else {
35-
parsing = false; // end of summary tokens
80+
reportError(`Unidentifiable Token ${token.type} ${token.tag} ${token.text}`);
3681
}
3782
}
38-
return summaryDocElements;
83+
return docElements;
3984
}
4085

41-
private static _isTextSymbol(token: string): boolean {
42-
// any non '@' || '{' char is treated as text
43-
return token.charAt(0) !== '@' && token.charAt(0) !== '{';
44-
}
86+
/**
87+
* This method parses the semantic information in an \@link JSDoc tag, creates and returns a
88+
* linkDocElement with the corresponding information. If the corresponding inline tag \@link is
89+
* not formatted correctly an error will be reported.
90+
*
91+
* The format for the \@link tag is {\@link url or API defintion reference | display text}, where
92+
* the '|' is only needed if the optional display text is given.
93+
*
94+
* Examples:
95+
* \{@link http://microsoft.com | microsoft home \}
96+
* \{@link http://microsoft.com \}
97+
* \{@link @microsoft/sp-core-library:Guid.newGuid | new Guid Object \}
98+
* \{@link @microsoft/sp-core-library:Guid.newGuid \}
99+
*/
100+
public static parseLinkTag(tokenItem: Token,
101+
reportError: (message: string) => void): IHrefLinkElement | ICodeLinkElement {
102+
if (!tokenItem.text) {
103+
reportError('Invalid @link inline token, a url or API definition reference must be given');
104+
return;
105+
}
106+
107+
// Make sure there are no extra pipes
108+
let pipeSplitContent: string[] = tokenItem.text.split('|');
109+
pipeSplitContent = pipeSplitContent.map( value => {
110+
if (value) {
111+
return value.trim();
112+
}
113+
});
114+
if (pipeSplitContent.length > 2) {
115+
reportError('Invalid @link parameters, at most pipe character allowed.');
116+
return;
117+
}
118+
119+
// Try to guess if the tokenContent is a link or API definition reference
120+
let linkDocElement: ICodeLinkElement | IHrefLinkElement;
121+
if (tokenItem.text.match(this._hrefRegEx)) {
122+
const urlContent: string[] = pipeSplitContent[0].split(' ');
123+
124+
// Make sure only a single url is given
125+
if (urlContent.length > 1 && urlContent[1] !== '' ) {
126+
reportError('Invalid @link parameter, url must be a single string.');
127+
return;
128+
}
129+
130+
linkDocElement = {
131+
kind: 'linkDocElement',
132+
referenceType: 'href',
133+
targetUrl: urlContent[0],
134+
value: ''
135+
};
136+
137+
} else {
138+
// we are processing an API definition reference
139+
const apiDefitionRef: IApiDefinitionReference = ApiDocumentation.parseApiReferenceExpression(
140+
pipeSplitContent[0], reportError);
141+
142+
// Once we can locate local API definitions, an error should be reported here if not found.
143+
if (apiDefitionRef) {
144+
145+
linkDocElement = {
146+
kind: 'linkDocElement',
147+
referenceType: 'code',
148+
scopeName: apiDefitionRef.scopeName,
149+
packageName: apiDefitionRef.packageName,
150+
exportName: apiDefitionRef.exportName,
151+
memberName: apiDefitionRef.memberName
152+
};
153+
}
154+
}
155+
156+
// If a display name is given, ensure it only contains characters for words.
157+
if (linkDocElement && pipeSplitContent.length > 1) {
158+
const displayTextParts: string[] = pipeSplitContent[1].match(this._wordRegEx);
159+
if (displayTextParts && displayTextParts[0].length !== pipeSplitContent[1].length) {
160+
reportError('Display name in @link token may only contain alphabetic characters.');
161+
return;
162+
}
163+
// Full match is valid text
164+
linkDocElement.value = displayTextParts[0].trim();
165+
}
45166

46-
private static _peek(tokenStream: string[]): string {
47-
return tokenStream.length === 0 ? undefined : tokenStream[0];
167+
return linkDocElement;
48168
}
49169
}

0 commit comments

Comments
 (0)