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