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, 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