File : DocsValidator.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
Doc reviewed 20211111
23
*/
24
/* ------------------------------------------------------------------------------------------------------------------------- */
25
26
import theLinkBuilder from './LinkBuilder.js';
27
28
/* ------------------------------------------------------------------------------------------------------------------------- */
29
/**
30
Validate the doc objects
31
*/
32
/* ------------------------------------------------------------------------------------------------------------------------- */
33
34
class DocsValidator {
35
36
    /**
37
    A counter for the errors
38
    @type {Number}
39
    */
40
41
    #errorsCounter;
42
43
    /**
44
    A counter for the warnings
45
    @type {Number}
46
    */
47
48
    #warningsCounter;
49
50
    /**
51
    A map to store the classes names
52
    @type {Map.<String>}
53
    */
54
55
    #classNames;
56
57
    /**
58
    The current class
59
    @type {String}
60
    */
61
62
    #currentClassDoc;
63
64
    /**
65
    The rules to apply
66
    @type {Object}
67
    */
68
69
    #rules = {
70
        classesRules : {
71
            duplicateClassName : {
72
                rule : classDoc => this.#classNames.get ( classDoc.name ),
73
                errorLevel : 'warning',
74
                ruleMessage : 'Duplicate class name',
75
                moreRule : classDoc => this.#classNames.set ( classDoc.name, classDoc.name )
76
            }
77
        },
78
        commonRules : {
79
            dontHaveDescription : {
80
                rule : doc => ! doc?.commentsDoc?.desc && 'set' !== doc.kind,
81
                errorLevel : 'error',
82
                ruleMessage : 'Missing description'
83
            },
84
            unknownType : {
85
                rule : doc => {
86
                    if ( ! doc?.commentsDoc ) {
87
                        return false;
88
                    }
89
                    let returnValue = false;
90
                    const types = [];
91
                    if ( doc?.commentsDoc?.type ) {
92
                        types.push ( doc.commentsDoc.type );
93
                    }
94
                    if ( doc?.commentsDoc?.returns?.type ) {
95
                        types.push ( doc.commentsDoc.returns.type );
96
                    }
97
                    if ( doc?.commentsDoc?.params ) {
98
                        doc.commentsDoc.params.forEach (
99
                            param => {
100
                                if ( param?.type ) {
101
                                    types.push ( param.type );
102
                                }
103
                            }
104
                        );
105
                    }
106
                    types.forEach (
107
                        type => {
108
                            type.split ( ' ' ).forEach (
109
                                word => {
110
                                    if (
111
                                        'or' !== word
112
                                        &&
113
                                        'of' !== word
114
                                        &&
115
                                        'null' !== word
116
                                        &&
117
                                        ! theLinkBuilder.isKnownType ( word )
118
                                    ) {
119
                                        returnValue = true;
120
                                    }
121
                                }
122
                            );
123
                        }
124
                    );
125
                    return returnValue;
126
                },
127
                errorLevel : 'warning',
128
                ruleMessage : 'Unknown type'
129
            }
130
        },
131
        methodsOrPropertiesRules : {
132
            constructorHaveReturn : {
133
                rule : methodOrPropertyDoc => 'constructor' === methodOrPropertyDoc.kind
134
                    &&
135
                    methodOrPropertyDoc?.commentsDoc?.returns,
136
                errorLevel : 'warning',
137
                ruleMessage : 'Constructor with @return tag'
138
            },
139
            getterDontHaveType : {
140
                rule : methodOrPropertyDoc => 'get' === methodOrPropertyDoc.kind
141
                    &&
142
                    ! methodOrPropertyDoc?.commentsDoc?.type,
143
                errorLevel : 'error',
144
                ruleMessage : 'Missing @type tag for getter'
145
            },
146
            getterHaveReturn : {
147
                rule : methodOrPropertyDoc => 'get' === methodOrPropertyDoc.kind
148
                    &&
149
                    methodOrPropertyDoc?.commentsDoc?.returns,
150
                errorLevel : 'warning',
151
                ruleMessage : 'Getter with @return tag'
152
            },
153
            parametersMismatch : {
154
                rule : methodOrPropertyDoc => {
155
                    if ( 'method' !== methodOrPropertyDoc.isA ) {
156
                        return false;
157
                    }
158
                    if ( 'set' === methodOrPropertyDoc.kind ) {
159
                        return false;
160
                    }
161
                    if ( methodOrPropertyDoc.params && ! methodOrPropertyDoc?.commentsDoc?.params ) {
162
                        return true;
163
                    }
164
                    if ( ! methodOrPropertyDoc.params && methodOrPropertyDoc?.commentsDoc?.params ) {
165
                        return true;
166
                    }
167
                    if ( ! methodOrPropertyDoc.params && ! methodOrPropertyDoc?.commentsDoc?.params ) {
168
                        return false;
169
                    }
170
171
                    const codeParams = Array.from ( methodOrPropertyDoc.params );
172
                    codeParams.sort ( ( first, second ) => first.localeCompare ( second ) );
173
174
                    const commentsParams = Array.from ( methodOrPropertyDoc.commentsDoc.params, first => first.name );
175
                    commentsParams.sort ( ( first, second ) => first.localeCompare ( second ) );
176
177
                    if ( codeParams.length !== commentsParams.length ) {
178
                        return true;
179
                    }
180
181
                    let returnValue = true;
182
                    for ( let paramCounter = 0; paramCounter < codeParams.length; paramCounter ++ ) {
183
184
                        /* eslint-disable-next-line no-bitwise */
185
                        returnValue &= codeParams [ paramCounter ] === commentsParams [ paramCounter ];
186
                    }
187
188
                    return ! returnValue;
189
                },
190
                errorLevel : 'error',
191
                ruleMessage : 'Mismatch between the @param tags and parameters in the code'
192
            },
193
            propertyDontHaveType : {
194
                rule : methodOrPropertyDoc => 'property' === methodOrPropertyDoc.isA
195
                    &&
196
                    ! methodOrPropertyDoc?.commentsDoc?.type,
197
                errorLevel : 'error',
198
                ruleMessage : 'Missing @type for property'
199
            },
200
            propertyHaveParam : {
201
                rule : methodOrPropertyDoc => 'property' === methodOrPropertyDoc.isA
202
                    &&
203
                    methodOrPropertyDoc?.commentsDoc?.params,
204
                errorLevel : 'warning',
205
                ruleMessage : 'Property with @param tag'
206
            },
207
            propertyHaveReturn : {
208
                rule : methodOrPropertyDoc => 'property' === methodOrPropertyDoc.isA
209
                    &&
210
                    methodOrPropertyDoc?.commentsDoc?.returns,
211
                errorLevel : 'warning',
212
                ruleMessage : 'Property with @return tag'
213
            },
214
            returnDontHaveDescription : {
215
                rule : methodOrPropertyDoc => methodOrPropertyDoc.commentsDoc
216
                    &&
217
                    methodOrPropertyDoc.commentsDoc.returns
218
                    &&
219
                    ! methodOrPropertyDoc.commentsDoc.returns.desc,
220
                errorLevel : 'error',
221
                ruleMessage : 'Missing description for @return tag'
222
            },
223
            returnDontHaveType : {
224
                rule : methodOrPropertyDoc => methodOrPropertyDoc.commentsDoc
225
                    &&
226
                    methodOrPropertyDoc.commentsDoc.returns
227
                    &&
228
                    ! methodOrPropertyDoc.commentsDoc.returns.type,
229
                errorLevel : 'error',
230
                ruleMessage : 'Missing type for @return tag'
231
            },
232
            setterHaveReturn : {
233
                rule : methodOrPropertyDoc => 'set' === methodOrPropertyDoc.kind && methodOrPropertyDoc?.commentsDoc?.returns,
234
                errorLevel : 'warning',
235
                ruleMessage : 'Setter with @return tag'
236
            },
237
            setterHaveType : {
238
                rule : methodOrPropertyDoc => 'set' === methodOrPropertyDoc.kind && methodOrPropertyDoc?.commentsDoc?.type,
239
                errorLevel : 'warning',
240
                ruleMessage : 'Setter with @type tag'
241
            },
242
            setterHaveGetterAndDoc : {
243
                rule : methodOrPropertyDoc => {
244
                    if ( 'set' !== methodOrPropertyDoc.kind ) {
245
                        return false;
246
                    }
247
                    const getter = this.#currentClassDoc.methodsAndProperties.find (
248
                        methodOrProperty => 'get' === methodOrProperty.kind &&
249
                            methodOrPropertyDoc.name === methodOrProperty.name &&
250
                            methodOrPropertyDoc.commentsDoc
251
                    );
252
                    if ( getter ) {
253
                        return true;
254
                    }
255
256
                    return false;
257
                },
258
                errorLevel : 'error',
259
                ruleMessage : 'Getter and Setter have documentation'
260
            },
261
            setterDontHaveGetterAndDesc : {
262
                rule : methodOrPropertyDoc => {
263
                    if ( 'set' !== methodOrPropertyDoc.kind ) {
264
                        return false;
265
                    }
266
                    const getter = this.#currentClassDoc.methodsAndProperties.find (
267
                        methodOrProperty => 'get' === methodOrProperty.kind &&
268
                            methodOrPropertyDoc.name === methodOrProperty.name
269
                    );
270
                    if ( ! getter && ! methodOrPropertyDoc?.commentsDoc?.desc ) {
271
                        return true;
272
                    }
273
274
                    return false;
275
                },
276
                errorLevel : 'error',
277
                ruleMessage : 'Setter don\'t have getter and don\'t have description'
278
            }
279
280
        }
281
    };
282
283
    /**
284
    Display an error or warning on the screen
285
    @param {Object} rule The rule that have generated the error or warning
286
    @param {VariableDoc|ClassDoc|MethodOrPropertyDoc} doc The Doc object for witch the error is generated
287
    */
288
289
    #logFault ( rule, doc ) {
290
        let color = '';
291
        if ( 'warning' === rule.errorLevel ) {
292
            this.#warningsCounter ++;
293
            color = '\x1b[96m';
294
        }
295
        else {
296
            this.#errorsCounter ++;
297
            color = '\x1b[31m';
298
        }
299
        const className = this?.#currentClassDoc?.name ? this.#currentClassDoc.name + '.' : '';
300
        const methodPrefix = doc.private ? '#' : '';
301
        console.error (
302
            `\t${color}${rule.errorLevel}\x1b[0m '${rule.ruleMessage}' for ` +
303
            `${className + methodPrefix + doc.name} in file ` +
304
            `${color}${doc.file}\x1b[0m at line ${color}${doc.line}\x1b[0m)`
305
        );
306
    }
307
308
    /**
309
    Apply a rule on a Doc object
310
    @param {Object} rule The rule that have tobe applied on the Doc Object
311
    @param {VariableDoc|ClassDoc|MethodOrPropertyDoc} doc The Doc object to validate
312
    */
313
314
    #validateDoc ( rule, doc ) {
315
        if ( rule.rule ( doc ) ) {
316
            this.#logFault ( rule, doc );
317
        }
318
        if ( rule.moreRule ) {
319
            rule.moreRule ( doc );
320
        }
321
    }
322
323
    /**
324
    Validate a VariableDoc object
325
    @param {VariableDoc} variableDoc The Doc object to validate
326
    */
327
328
    #validateVariableDoc ( variableDoc ) {
329
        for ( const rule in this.#rules.commonRules ) {
330
            this.#validateDoc ( this.#rules.commonRules [ rule ], variableDoc );
331
        }
332
    }
333
334
    /**
335
    Validate a MethodOrPropertyDoc object
336
    @param {MethodOrPropertyDoc} methodOrPropertyDoc The Doc object to validate
337
    */
338
339
    #validateMethodOrPropertyDoc ( methodOrPropertyDoc ) {
340
        for ( const rule in this.#rules.commonRules ) {
341
            this.#validateDoc ( this.#rules.commonRules [ rule ], methodOrPropertyDoc );
342
        }
343
        for ( const rule in this.#rules.methodsOrPropertiesRules ) {
344
            this.#validateDoc ( this.#rules.methodsOrPropertiesRules [ rule ], methodOrPropertyDoc );
345
        }
346
    }
347
348
    /**
349
    Validate a ClassDoc object
350
    @param {ClassDoc} classDoc The Doc object to validate
351
    */
352
353
    #validateClassDoc ( classDoc ) {
354
        this.#currentClassDoc = classDoc;
355
        for ( const rule in this.#rules.commonRules ) {
356
            this.#validateDoc ( this.#rules.commonRules [ rule ], classDoc );
357
        }
358
        for ( const rule in this.#rules.classesRules ) {
359
            this.#validateDoc ( this.#rules.classesRules [ rule ], classDoc );
360
        }
361
        if ( classDoc.methodsAndProperties ) {
362
            classDoc.methodsAndProperties.forEach (
363
                methodOrPropertyDoc => this.#validateMethodOrPropertyDoc ( methodOrPropertyDoc )
364
            );
365
        }
366
        this.#currentClassDoc = null;
367
    }
368
369
    /**
370
    The constructor
371
    */
372
373
    constructor ( ) {
374
        Object.freeze ( this );
375
        this.#classNames = new Map ( );
376
    }
377
378
    /**
379
    Validate the documentation
380
    @param {Array.<ClassDoc>} classesDocs The classes Doc objects found in the documentation
381
    @param {Array.<VariableDoc>} variablesDocs The variable Doc objects found in the documentation
382
    */
383
384
    validate ( classesDocs, variablesDocs ) {
385
        this.#classNames.clear ( );
386
        this.#errorsCounter = 0;
387
        this.#warningsCounter = 0;
388
        this.#currentClassDoc = null;
389
390
        classesDocs?.forEach ( classDoc => this.#validateClassDoc ( classDoc ) );
391
        variablesDocs?.forEach ( variableDoc => this.#validateVariableDoc ( variableDoc ) );
392
393
        console.error ( `\n\t${this.#errorsCounter} errors found` );
394
        console.error ( `\n\t${this.#warningsCounter} warnings found` );
395
    }
396
397
}
398
399
export default DocsValidator;
400
401
/* --- End of file --------------------------------------------------------------------------------------------------------- */
402