File : DocBuilder.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 ♯1 : Improve colorization of sources files...
24
    - v1.2.0:
25
        - Issue ♯4 : Add an option to avoid the HTML files generation
26
Doc reviewed 20211111
27
*/
28
/* ------------------------------------------------------------------------------------------------------------------------- */
29
30
import process from 'process';
31
import fs from 'fs';
32
import { parse } from '@babel/parser';
33
import traverse from '@babel/traverse';
34
35
import ClassDocBuilder from './ClassDocBuilder.js';
36
import VariableDocBuilder from './VariableDocBuilder.js';
37
import theConfig from './Config.js';
38
import SourceHtmlBuilder from './SourceHtmlBuilder.js';
39
import ClassHtmlBuilder from './ClassHtmlBuilder.js';
40
import VariablesHtmlBuilder from './VariablesHtmlBuilder.js';
41
import theLinkBuilder from './LinkBuilder.js';
42
import DocsValidator from './DocsValidator.js';
43
import IndexHtmlBuilder from './IndexHtmlBuilder.js';
44
import TagData from './TagData.js';
45
46
/* ------------------------------------------------------------------------------------------------------------------------- */
47
/**
48
Build the complete documentation: generate AST from the source files, then extracting doc objects from AST
49
and finally buid HTML pages from the doc objects.
50
*/
51
/* ------------------------------------------------------------------------------------------------------------------------- */
52
53
class DocBuilder {
54
55
    /**
56
    A VariableDocBuilder object used by the class
57
    @type {VariableDocBuilder}
58
    */
59
60
    #variableDocBuilder;
61
62
    /**
63
    A ClassDocBuilder object used by the class
64
    @type {ClassDocBuilder}
65
    */
66
67
    #classDocBuilder;
68
69
    /**
70
    The generated ClassDoc objects
71
    @type {Array.<ClassDoc>}
72
    */
73
74
    #classesDocs = [];
75
76
    /**
77
    The generated VariableDoc objects
78
    @type {Array.<VariableDoc>}
79
    */
80
81
    #variablesDocs = [];
82
83
    /**
84
    The options for the babel/parser
85
    @type {Object}
86
    */
87
88
    #parserOptions = {
89
        allowAwaitOutsideFunction : true,
90
        allowImportExportEverywhere : true,
91
        allowReturnOutsideFunction : true,
92
        allowSuperOutsideMethod : true,
93
        plugins : [
94
            [ 'decorators', {
95
                decoratorsBeforeExport : true
96
            } ],
97
            'doExpressions',
98
            'exportDefaultFrom',
99
            'functionBind',
100
            'importMeta',
101
            [ 'pipelineOperator', {
102
                proposal : 'fsharp'
103
            } ],
104
            'throwExpressions'
105
        ],
106
        ranges : true,
107
        sourceType : 'module'
108
    };
109
110
    /**
111
    A Map with all the TagData of all the source files ordered by SourceFileName
112
    @type {Map.<Array.<TagData>>}
113
    */
114
115
    #tagsDataMap;
116
117
    /**
118
    The constructor
119
    */
120
121
    constructor ( ) {
122
        Object.freeze ( this );
123
        this.#classDocBuilder = new ClassDocBuilder ( );
124
        this.#variableDocBuilder = new VariableDocBuilder ( );
125
    }
126
127
    /**
128
    Build all the docs for a file
129
    @param {Object} ast The root
130
    [ast node](https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md) given by the babel/parser
131
    @param {String} sourceFileName The source file name, including relative path since theConfig.srcDir
132
    */
133
134
    #buildDocs ( ast, sourceFileName ) {
135
        ast.program.body.forEach (
136
            astNode => {
137
                switch ( astNode.type ) {
138
                case 'ClassDeclaration' :
139
                    {
140
                        const classDoc = this.#classDocBuilder.build ( astNode, sourceFileName );
141
                        if ( ! classDoc?.commentsDoc?.ignore ) {
142
                            this.#classesDocs.push ( classDoc );
143
                        }
144
                    }
145
                    break;
146
                case 'VariableDeclaration' :
147
                    {
148
                        const variableDoc = this.#variableDocBuilder.build ( astNode, sourceFileName );
149
                        if ( ! variableDoc?.commentsDoc?.ignore ) {
150
                            this.#variablesDocs.push ( variableDoc );
151
                        }
152
                    }
153
                    break;
154
                default :
155
                    break;
156
                }
157
            }
158
        );
159
    }
160
161
    /**
162
    Traverse the ast created by the Babel parser and extract the TagData objects for the
163
    template literals, string literals, regexp literals and comments
164
    @param {Object} ast The root
165
    [ast node](https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md) given by the babel/parser
166
    @param {String} sourceFileName The source file name, including relative path since theConfig.srcDir
167
    */
168
169
    #traverseAst ( ast, sourceFileName ) {
170
171
        const tagsData = [];
172
173
        /*
174
        A helper function for extracting the comments TagData
175
        Yes, I know... I don't like functions, but traverse will not know this
176
        */
177
178
        function addCommentTags ( comment ) {
179
            let currentLine = comment.loc.start.line;
180
            tagsData.push ( new TagData ( currentLine, comment.loc.start.column, '' ) );
181
            while ( currentLine !== comment.loc.end.line ) {
182
                tagsData.push ( new TagData ( currentLine, null, '' ) );
183
                currentLine ++;
184
                tagsData.push ( new TagData ( currentLine, 0, '' ) );
185
            }
186
            tagsData.push ( new TagData ( comment.loc.end.line, comment.loc.end.column, '' ) );
187
        }
188
189
        traverse (
190
            ast,
191
            {
192
                enter ( path ) {
193
                    switch ( path.node.type ) {
194
                    case 'TemplateLiteral' :
195
                    case 'RegExpLiteral' :
196
                    case 'StringLiteral' :
197
                        tagsData.push (
198
                            new TagData (
199
                                path.node.loc.start.line,
200
                                path.node.loc.start.column,
201
                                ' + '"' +
202
                                    path.node.type.toLowerCase ( ).replaceAll ( /literal/g, '-literal' ) + '">'
203
                            )
204
                        );
205
                        tagsData.push ( new TagData ( path.node.loc.end.line, path.node.loc.end.column, '' ) );
206
                        break;
207
                    default :
208
                        break;
209
                    }
210
211
                    if ( path.node.leadingComments ) {
212
                        path.node.leadingComments.forEach ( addCommentTags );
213
                    }
214
                    if ( path.node.trailingComments ) {
215
                        path.node.trailingComments.forEach ( addCommentTags );
216
                    }
217
                }
218
            }
219
        );
220
        this.#tagsDataMap.set ( sourceFileName, tagsData );
221
    }
222
223
    /**
224
    Build all the docs for the app and then build all the html files
225
    @param {Array.<String>} sourceFilesList The source files names, including relative path since theConfig.srcDir
226
    */
227
228
    buildFiles ( sourceFilesList ) {
229
        let ast = null;
230
        this.#tagsDataMap = new Map ( );
231
        sourceFilesList.forEach (
232
            sourceFileName => {
233
                try {
234
235
                    // Reading the source
236
                    const fileContent = fs.readFileSync ( theConfig.srcDir + sourceFileName, 'utf8' );
237
                    ast = parse ( fileContent, this.#parserOptions );
238
                }
239
                catch ( err ) {
240
                    console.error ( err );
241
242
                    /*
243
                    console.error (
244
                        `\n\t\x1b[31mError\x1b[0m parsing file \x1b[31m${sourceFileName}\x1b[0m` +
245
                        ` at line ${err.loc.line} column ${err.loc.column} : \n\t\t${err.message}\n`
246
                    );
247
                    */
248
249
                    process.exit ( 1 );
250
                }
251
252
                if ( ! theConfig.noSourcesColor ) {
253
                    this.#traverseAst ( ast, sourceFileName );
254
                }
255
256
                // buiding docs for the source
257
                this.#buildDocs ( ast, sourceFileName );
258
259
                // buiding the links for the source
260
                const htmlFileName = sourceFileName.replace ( '.js', 'js.html' );
261
                theLinkBuilder.setSourceLink ( sourceFileName, htmlFileName );
262
            }
263
        );
264
265
        // Saving links for classes and variables
266
        this.#classesDocs.forEach ( classDoc => theLinkBuilder.setClassLink ( classDoc ) );
267
        this.#variablesDocs.forEach ( variableDoc => theLinkBuilder.setVariableLink ( variableDoc ) );
268
269
        // Validation
270
        if ( theConfig.validate ) {
271
            const docsValidator = new DocsValidator ( );
272
            docsValidator.validate ( this.#classesDocs, this.#variablesDocs );
273
        }
274
275
        if ( ! theConfig.noFiles ) {
276
277
            // Building classes html files
278
            const classHtmlBuilder = new ClassHtmlBuilder ( );
279
            this.#classesDocs.forEach ( classDoc => classHtmlBuilder.build ( classDoc ) );
280
281
            console. error ( `\n\tCreated ${classHtmlBuilder.classesCounter} class files` );
282
283
            // Building sources html files
284
            const sourceHtmlBuilder = new SourceHtmlBuilder ( );
285
            sourceFilesList.forEach (
286
                sourceFileName => {
287
                    const fileContent = fs.readFileSync ( theConfig.srcDir + sourceFileName, 'utf8' );
288
                    sourceHtmlBuilder.build ( fileContent, sourceFileName, this.#tagsDataMap.get ( sourceFileName ) );
289
                }
290
            );
291
292
            console.error ( `\n\tCreated ${sourceHtmlBuilder.sourcesCounter} source files` );
293
294
            // Building the variables html file
295
            new VariablesHtmlBuilder ( ).build ( this.#variablesDocs );
296
297
            // Building the index.html file
298
            new IndexHtmlBuilder ( ).build ( );
299
        }
300
    }
301
}
302
303
export default DocBuilder;
304
305
/* --- End of file --------------------------------------------------------------------------------------------------------- */
306