File : CommentsDocBuilder.js

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