1 | /* |
2 | Copyright - 2021 - wwwouaiebe - Contact: https://www.ouaie.be/ |
3 | |
4 | This program is free software; |
5 | you can redistribute it and/or modify it under the terms of the |
6 | GNU General Public License as published by the Free Software Foundation; |
7 | either version 3 of the License, or any later version. |
8 | |
9 | This program is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | GNU General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU General Public License |
15 | along with this program; if not, write to the Free Software |
16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
17 | */ |
18 | /* |
19 | Changes: |
20 | - v1.0.0: |
21 | - created |
22 | - v1.1.0: |
23 | - Issue โฏ3 : String.substr ( ) is deprecated... Replace... |
24 | Doc reviewed 20211111 |
25 | */ |
26 | /* ------------------------------------------------------------------------------------------------------------------------- */ |
27 | |
28 | import { TypeDescription, CommentsDoc } from './Docs.js'; |
29 | |
30 | /* ------------------------------------------------------------------------------------------------------------------------- */ |
31 | /** |
32 | Build a CommentsDoc object from the leading comments of a class, method, property or variable |
33 | */ |
34 | /* ------------------------------------------------------------------------------------------------------------------------- */ |
35 | |
36 | class CommentsDocBuilder { |
37 | |
38 | /** |
39 | The currently builded comments |
40 | @type {CommentsDoc} |
41 | */ |
42 | |
43 | #commentsDoc; |
44 | |
45 | /** |
46 | A RegExp to find the @desc, @classdesc, @sample,@type, @param, |
47 | @return, @returns, @ignore tags |
48 | @type {RegExp} |
49 | */ |
50 | |
51 | #tagRegExp; |
52 | |
53 | /** |
54 | A RegExp to find space or new line at the beginning of the string |
55 | @type {RegExp} |
56 | */ |
57 | |
58 | #beginSpaceNewlineRegExp; |
59 | |
60 | /** |
61 | A RegExp to find space or new line in the string |
62 | @type {RegExp} |
63 | */ |
64 | |
65 | #spaceNewlineRegExp; |
66 | |
67 | /** |
68 | A RegExp to find space or new line at the end of the string |
69 | @type {RegExp} |
70 | */ |
71 | |
72 | #endSpaceNewlineRegExp; |
73 | |
74 | /** |
75 | A RegExp to find multiple spaces |
76 | @type {RegExp} |
77 | */ |
78 | |
79 | #multipleSpacesRegExp; |
80 | |
81 | /** |
82 | A RegExp to find multiple spaces + newline +multiple spaces |
83 | @type {RegExp} |
84 | */ |
85 | |
86 | #spaceNewlineSpaceRegExp; |
87 | |
88 | /** |
89 | A RegExp to find a new line at the beginning of the string |
90 | @type {RegExp} |
91 | */ |
92 | |
93 | #beginNewLineRegExp; |
94 | |
95 | /** |
96 | A RegExp to find a type in the string ( a string starting with { and ending with } |
97 | @type {RegExp} |
98 | */ |
99 | |
100 | #typeRegExp; |
101 | |
102 | /** |
103 | A RegExp to find a name the string ( the first word with only chars and numbers |
104 | @type {RegExp} |
105 | */ |
106 | |
107 | #nameRegExp; |
108 | |
109 | /** |
110 | The constructor |
111 | */ |
112 | |
113 | constructor ( ) { |
114 | Object.freeze ( this ); |
115 | this.#tagRegExp = RegExp ( '@[a-z]*' ); |
116 | this.#beginSpaceNewlineRegExp = RegExp ( '^[ |\\n]' ); |
117 | this.#spaceNewlineRegExp = RegExp ( '[ |\\n]' ); |
118 | this.#endSpaceNewlineRegExp = RegExp ( '[ |\\n]$' ); |
119 | this.#multipleSpacesRegExp = RegExp ( '[ ]+', 'g' ); |
120 | this.#spaceNewlineSpaceRegExp = RegExp ( '[ ]*[\\n][ ]*', 'g' ); |
121 | this.#beginNewLineRegExp = RegExp ( '^\\n' ); |
122 | this.#typeRegExp = RegExp ( '{.*}' ); |
123 | this.#nameRegExp = RegExp ( '^[a-zA-Z0-9]*' ); |
124 | } |
125 | |
126 | /** |
127 | Set to uppercase the first letter of a text |
128 | @param {String} text The text to capitalize |
129 | @return {String} The capitalized text |
130 | */ |
131 | |
132 | #capitalizeFirstLetter ( text ) { |
133 | |
134 | switch ( text.toLowerCase ( ) ) { |
135 | case '' : |
136 | return text; |
137 | case 'null' : |
138 | return 'null'; |
139 | default : |
140 | return text [ 0 ].toUpperCase ( ) + text.substring ( 1 ); |
141 | } |
142 | } |
143 | |
144 | /** |
145 | Parse a type tag ( the value into {} for a type, param, return or returns tags. |
146 | Remove the { } < > ! and space chars from the type, replace the . char with ' of ', |
147 | replace the ? char with 'null or ', replace the | char with ' or ' and finally capitalize the first letter |
148 | of the types, so '{Number}' is parsed to 'Number', '{?String}' is parsed to 'null or String', |
149 | 'Array.<Number>' is parsed to 'Array of Number', {String|Number} is parsed to 'String or Number' |
150 | @param {String} type The type tag to parse |
151 | @return {String} The parsed type |
152 | */ |
153 | |
154 | #parseType ( type ) { |
155 | const tmpType = |
156 | type |
157 | .replaceAll ( '{', '' ) |
158 | .replaceAll ( '}', '' ) |
159 | .replaceAll ( ' ', '' ) |
160 | .replaceAll ( '.', ' of ' ) |
161 | .replaceAll ( '<', '' ) |
162 | .replaceAll ( '>', '' ) |
163 | .replaceAll ( '!', '' ) |
164 | .replaceAll ( '.', ' of ' ) |
165 | .replaceAll ( '?', 'null or ' ) |
166 | .replaceAll ( '|', ' or ' ); |
167 | if ( '' === tmpType ) { |
168 | return null; |
169 | } |
170 | |
171 | let returnValue = ''; |
172 | tmpType.trim ( ).split ( ' ' ) |
173 | .forEach ( |
174 | word => { |
175 | returnValue += |
176 | ( -1 === [ 'of', 'null', 'or' ].indexOf ( word ) ) |
177 | ? |
178 | this.#capitalizeFirstLetter ( word ) |
179 | : |
180 | word; |
181 | returnValue += ' '; |
182 | } |
183 | ); |
184 | |
185 | return returnValue.trimEnd ( ); |
186 | } |
187 | |
188 | /** |
189 | This method build a TypeDescription object from the contains of a comment tags |
190 | @param {String} commentTag The comment tag |
191 | @param {boolean} haveName A flag indicating that commentTag contains also a name to add in the TypeDescription |
192 | */ |
193 | |
194 | #getTypeDescription ( commentTag, haveName ) { |
195 | |
196 | const typeDescription = new TypeDescription ( ); |
197 | |
198 | // removing tag and spaces or newline. Spaces and newline must be in a separate replace! |
199 | let tmpCommentTag = |
200 | commentTag.replace ( this.#tagRegExp, '' ) |
201 | .replace ( this.#beginSpaceNewlineRegExp, '' ); |
202 | |
203 | // Searching type |
204 | const type = commentTag.match ( this.#typeRegExp ); |
205 | if ( type ) { |
206 | typeDescription.type = this.#parseType ( type [ 0 ] ); |
207 | |
208 | // removing type and spaces or newline |
209 | tmpCommentTag = |
210 | commentTag.substring ( commentTag.indexOf ( '}' ) + 1 ).replace ( this.#beginSpaceNewlineRegExp, '' ); |
211 | } |
212 | |
213 | // Searching name |
214 | if ( haveName ) { |
215 | if ( tmpCommentTag.match ( this.#nameRegExp ) ) { |
216 | typeDescription.name = tmpCommentTag.match ( this.#nameRegExp ) [ 0 ]; |
217 | typeDescription.name = typeDescription.name.replace ( this.#spaceNewlineRegExp, '' ); |
218 | if ( '' === typeDescription.name ) { |
219 | typeDescription.name = null; |
220 | } |
221 | |
222 | // removing name and spaces or newline |
223 | tmpCommentTag = tmpCommentTag.replace ( this.#nameRegExp, '' ).replace ( this.#beginSpaceNewlineRegExp, '' ); |
224 | } |
225 | } |
226 | |
227 | // Searching desscription |
228 | // removing space and newline at the end |
229 | tmpCommentTag = tmpCommentTag.replace ( this.#endSpaceNewlineRegExp, '' ); |
230 | if ( '' !== tmpCommentTag ) { |
231 | typeDescription.desc = this.#capitalizeFirstLetter ( tmpCommentTag ); |
232 | } |
233 | |
234 | return Object.freeze ( typeDescription ); |
235 | } |
236 | |
237 | /** |
238 | Parse a comment tag. A comment tag is a text starting at the beginning of a comment, just after the /** |
239 | or starting with a @ char and finishing just before the next @ char in the comment or just before the */ |
240 | @param {String} commentTag the comment tag to parse |
241 | */ |
242 | |
243 | #parseCommentTag ( commentTag ) { |
244 | |
245 | // no @ char at the beginning. It's a desc... |
246 | if ( ! commentTag.startsWith ( '@' ) ) { |
247 | this.#commentsDoc.desc = this.#capitalizeFirstLetter ( commentTag ); |
248 | return; |
249 | } |
250 | |
251 | // searching the @ tag |
252 | const tag = commentTag.match ( this.#tagRegExp ) [ 0 ]; |
253 | |
254 | switch ( tag ) { |
255 | case '@desc' : |
256 | case '@classdesc' : |
257 | this.#commentsDoc.desc = this.#capitalizeFirstLetter ( |
258 | commentTag.replace ( this.#tagRegExp, '' ).replace ( this.#endSpaceNewlineRegExp, '' ) |
259 | ); |
260 | break; |
261 | case '@sample' : |
262 | this.#commentsDoc.sample = |
263 | commentTag.replace ( this.#tagRegExp, '' ).replace ( this.#endSpaceNewlineRegExp, '' ); |
264 | break; |
265 | case '@type' : |
266 | { |
267 | const type = commentTag.match ( this.#typeRegExp ); |
268 | if ( type ) { |
269 | this.#commentsDoc.type = this.#parseType ( type [ 0 ] ); |
270 | } |
271 | } |
272 | break; |
273 | case '@param' : |
274 | this.#commentsDoc.params = ( this.#commentsDoc.params ?? [] ); |
275 | this.#commentsDoc.params.push ( this.#getTypeDescription ( commentTag, true ) ); |
276 | break; |
277 | case '@return' : |
278 | case '@returns' : |
279 | this.#commentsDoc.returns = this.#getTypeDescription ( commentTag, false ); |
280 | break; |
281 | case '@ignore' : |
282 | this.#commentsDoc.ignore = true; |
283 | break; |
284 | default : |
285 | break; |
286 | } |
287 | } |
288 | |
289 | /** |
290 | Parse a leading comment and extracts the @desc, @classdesc, @sample,@type, @param, |
291 | @return, @returns, @ignore tags |
292 | @param {String} leadingComment The comment to parse |
293 | */ |
294 | |
295 | #parseLeadingComment ( leadingComment ) { |
296 | |
297 | // replacing Windows and Mac EOL with Unix EOL, tab with spaces and @ with a strange text surely not used |
298 | // then spliting the comments at the strange text, so the comment is splitted, preserving the @ |
299 | leadingComment |
300 | .replaceAll ( '\r\n', '\n' ) // eol windows |
301 | .replaceAll ( '\r', '\n' ) // eol mac |
302 | .replaceAll ( '\t', ' ' ) // tab |
303 | .replaceAll ( this.#multipleSpacesRegExp, ' ' ) // multiple spaces |
304 | .replaceAll ( this.#spaceNewlineSpaceRegExp, '\n' ) // spaces + eol + spaces |
305 | .replaceAll ( '@', 'ยงยงยง@' ) // strange text |
306 | .replace ( this.#beginNewLineRegExp, '' ) // eol at the beginning |
307 | .split ( 'ยงยงยง' ) |
308 | .forEach ( |
309 | |
310 | // and parsing each result |
311 | commentTag => { this.#parseCommentTag ( commentTag ); } |
312 | ); |
313 | } |
314 | |
315 | /** |
316 | Build a CommentsDoc object from the leading comments found in the code before the class/method/properties/variable |
317 | @param {Array.<String>} leadingComments The leadingComments to use |
318 | @return {CommentsDoc} An object with the comments |
319 | */ |
320 | |
321 | build ( leadingComments ) { |
322 | |
323 | if ( ! leadingComments ) { |
324 | return null; |
325 | } |
326 | |
327 | // Filtering on comments starting with * |
328 | const docLeadingComments = leadingComments.filter ( leadingComment => '*' === leadingComment.value [ 0 ] ); |
329 | |
330 | if ( 0 === docLeadingComments.length ) { |
331 | return null; |
332 | } |
333 | |
334 | this.#commentsDoc = new CommentsDoc ( ); |
335 | docLeadingComments.forEach ( |
336 | docLeadingComment => this.#parseLeadingComment ( docLeadingComment.value.substring ( 1 ) ) |
337 | ); |
338 | return Object.freeze ( this.#commentsDoc ); |
339 | } |
340 | } |
341 | |
342 | export default CommentsDocBuilder; |
343 | |
344 | /* --- End of file --------------------------------------------------------------------------------------------------------- */ |
345 |