UNPKG

3.6 kBJavaScriptView Raw
1const _ = require('lodash');
2const t = require('@babel/types');
3const parse = require('../parse');
4const walkComments = require('../extractors/comments');
5const walkExported = require('../extractors/exported');
6const util = require('util');
7const debuglog = util.debuglog('documentation');
8const findTarget = require('../infer/finders').findTarget;
9const { parseToAst } = require('./parse_to_ast');
10
11/**
12 * Left-pad a string so that it can be sorted lexicographically. We sort
13 * comments to keep them in order.
14 * @param {string} str the string
15 * @param {number} width the width to pad to
16 * @returns {string} a padded string with the correct width
17 * @private
18 */
19function leftPad(str, width) {
20 str = str.toString();
21 while (str.length < width) {
22 str = '0' + str;
23 }
24 return str;
25}
26
27/**
28 * Receives a module-dep item,
29 * reads the file, parses the JavaScript, and parses the JSDoc.
30 *
31 * @param {Object} data a chunk of data provided by module-deps
32 * @param {Object} config config
33 * @returns {Array<Object>} an array of parsed comments
34 */
35function parseJavaScript(data, config) {
36 const visited = new Set();
37 const commentsByNode = new Map();
38
39 const ast = parseToAst(data.source, data.file);
40 const addComment = _addComment.bind(null, visited, commentsByNode);
41
42 return _.flatMap(
43 config.documentExported
44 ? [walkExported]
45 : [
46 walkComments.bind(null, 'leadingComments', true),
47 walkComments.bind(null, 'innerComments', false),
48 walkComments.bind(null, 'trailingComments', false)
49 ],
50 fn => fn(ast, data, addComment)
51 ).filter(comment => comment && !comment.lends);
52}
53
54function _addComment(
55 visited,
56 commentsByNode,
57 data,
58 commentValue,
59 commentLoc,
60 path,
61 nodeLoc,
62 includeContext
63) {
64 // Avoid visiting the same comment twice as a leading
65 // and trailing node
66 const key =
67 data.file + ':' + commentLoc.start.line + ':' + commentLoc.start.column;
68 if (!visited.has(key)) {
69 visited.add(key);
70
71 const context /* : {
72 loc: Object,
73 file: string,
74 sortKey: string,
75 ast?: Object,
76 code?: string
77 }*/ = {
78 loc: nodeLoc,
79 file: data.file,
80 sortKey: data.sortKey + ' ' + leftPad(nodeLoc.start.line, 8)
81 };
82
83 if (includeContext) {
84 // This is non-enumerable so that it doesn't get stringified in
85 // output; e.g. by the documentation binary.
86 Object.defineProperty(context, 'ast', {
87 configurable: true,
88 enumerable: false,
89 value: path
90 });
91
92 if (path.parentPath && path.parentPath.node) {
93 const parentNode = path.parentPath.node;
94 context.code = data.source.substring(parentNode.start, parentNode.end);
95 }
96 }
97 const comment = parse(commentValue, commentLoc, context);
98 if (includeContext) {
99 commentsByNode.set((findTarget(path) || path).node, comment);
100
101 if (t.isClassMethod(path) && path.node.kind === 'constructor') {
102 // #689
103 if (
104 comment.tags.some(
105 tag => tag.title !== 'param' && tag.title !== 'hideconstructor'
106 )
107 ) {
108 debuglog(
109 'A constructor was documented explicitly: document along with the class instead'
110 );
111 }
112
113 const parentComment = commentsByNode.get(
114 path.parentPath.parentPath.node
115 );
116 if (parentComment) {
117 parentComment.constructorComment = comment;
118 return;
119 }
120 if (comment.hideconstructor) {
121 return;
122 }
123 }
124 }
125 return comment;
126 }
127}
128
129module.exports = parseJavaScript;