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
37export 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