UNPKG

8.43 kBJavaScriptView Raw
1/**
2 * On ne documente que les exports.
3 *
4 */
5
6var TreeWalker = require("./tree-walker");
7var UglifyJS = require("uglify-js");
8var Markdown = require("./tfw-md");
9var JSON = require("./tolojson");
10
11var declarations = {};
12var tw = new TreeWalker();
13var lastComments = undefined;
14var rxTag = /^[ \t]*@([a-zA-Z]+)[ \t]*/;
15var rxParam = /^(\{[^\}]*\})?[ \t]*([\w$_][\w$_0-9\.]*)[ \t-]*/;
16
17function comments(node) {
18 var com = node.start.comments_before;
19 if (!Array.isArray(com) || com.length == 0) return undefined;
20 com = stripComments( com[com.length - 1].value.trim() );
21 var lines = com.split("\n");
22 var tags = {$summary: "", $full: ""};
23 var tag = "$summary";
24 lines.forEach(
25 function(line) {
26 var m = line.match(rxTag);
27 var item;
28 if (m) {
29 line = line.substr(m[0].length);
30 tag = "$" + m[1].toLowerCase();
31 if (typeof tags[tag] === 'undefined') {
32 tags[tag] = [];
33 }
34 if (tag == '$param') {
35 m = line.match(rxParam);
36 item = {content: ""};
37 if (m) {
38 if (typeof m[1] === 'string') {
39 item.type = m[1].substr(1, m[1].length - 2).trim();
40 }
41 item.name = m[2].trim();
42 line = line.substr(m[0].length);
43 }
44 tags[tag].push(item);
45 } else {
46 tags[tag].push("");
47 }
48 }
49 if (tag == '$summary' && line.trim() == '') {
50 // Fin du summary, on passe en full description.
51 tags.$full = tags.$summary;
52 tag = '$full';
53 } else {
54 line += "\n";
55 item = tags[tag];
56 if (typeof item === 'string') {
57 tags[tag] += line;
58 }
59 else if (Array.isArray(item)) {
60 var arr = item;
61 item = item[arr.length - 1];
62 if (typeof item === 'string') {
63 arr[arr.length - 1] += line;
64 }
65 else if (typeof item.content === 'string') {
66 item.content += line;
67 }
68 }
69 }
70 }
71 );
72 if (Array.isArray(tags.$example)) {
73 // Préparer les exemples pour un highlight Javascript.
74 var i, example;
75 for (i = 0 ; i < tags.$example.length ; i++) {
76 example = tags.$example[i].trim();
77 tags.$example[i] = "```js\n" + example + "\n```";
78 }
79 }
80 var key, val;
81 for (key in tags) {
82 val = tags[key];
83 if (typeof val === 'string') {
84 tags[key] = Markdown.toHTML(val.trim());
85 }
86 else if (Array.isArray(val)) {
87 val.forEach(
88 function(itm, idx) {
89 if (typeof itm === 'string') {
90 val[idx] = Markdown.toHTML(itm.trim());
91 }
92 else if (typeof itm.content === 'string') {
93 itm.content = Markdown.toHTML(itm.content.trim());
94 }
95 }
96 );
97 }
98 }
99 return tags;
100}
101
102function getArgs(node) {
103 var args = [];
104 var argnames = node.argnames;
105 if (Array.isArray(argnames)) {
106 argnames.forEach(
107 function(arg) {
108 args.push(arg.name);
109 }
110 );
111 }
112 return args;
113}
114
115function getFunction(node) {
116 var name = node.name;
117 var com = comments(node);
118 if (typeof com === 'undefined' || com.length == 0) {
119 com = lastComments;
120 }
121 return {
122 TYPE: "Function",
123 name: name,
124 comments: com,
125 args: getArgs(node)
126 };
127}
128
129function parseFunction(node) {
130 var obj = getFunction(node.value);
131 declarations[node.name.name] = obj;
132}
133
134function parseMethod(node) {
135 var name = node.body.left.expression.expression.name;
136 var dec = declarations[name];
137 if (dec) {
138 dec.TYPE = "Class";
139 if (typeof dec.methods !== 'object') {
140 dec.methods = {};
141 }
142 var method = node.body.left.property;
143 dec.methods[method] = {
144 comments: comments(node),
145 args: getArgs(node.body.right)
146 };
147 }
148}
149
150function parseVar(tree) {
151 var actions = {
152 "[VarDef]value/[Function]": parseFunction
153 };
154 tree.definitions.forEach(
155 function(node) {
156 tw.action(node, actions);
157 }
158 );
159}
160
161function parseModuleExports(node) {
162 var exports = {
163 comments: comments(node) || lastComments
164 };
165 declarations.exports = exports;
166 var right = node.body.right;
167 if (right.TYPE == "SymbolRef") {
168 exports.value = declarations[right.name];
169 return;
170 }
171}
172
173function parseExportsAtt(node) {
174 if (typeof declarations.exports !== 'object'
175 || declarations.exports.TYPE != 'Object')
176 {
177 declarations.exports = {
178 TYPE: "Object",
179 attributes: {}
180 };
181 }
182 var name = node.body.left.property;
183 var exports = {
184 comments: comments(node),
185 value: undefined
186 };
187 declarations.exports.attributes[name] = exports;
188 var right = node.body.right;
189 if (right.TYPE == "SymbolRef") {
190 exports.value = declarations[right.name];
191 return;
192 }
193 if (right.TYPE == "Function") {
194 exports.value = {
195 TYPE: "Function",
196 args: getArgs(right)
197 };
198 }
199}
200
201function parseAttribute(node) {
202 var objName = node.body.left.expression.name;
203 var attName = node.body.left.property;
204 var f = node.body.right;
205 if (f.TYPE == 'Function') {
206 var dec = declarations[objName];
207 if (typeof dec.statics !== 'object') {
208 dec.statics = {};
209 }
210 dec.statics[attName] = getFunction(f);
211 if (typeof dec.statics[attName].comments !== 'object') {
212 var com = comments(node) || lastComments;
213 dec.statics[attName].comments = com;
214 }
215 }
216}
217
218/**
219 * Remove all leading stars if they appear on each line.
220 */
221exports.stripComments = stripComments;
222
223function stripComments(code) {
224 // Removing start of comment.
225 code = code.substr( 2 );
226 // If first line begins with a '*', add a space before to be like the other lines.
227 if( code.charAt(0) == '*' ) {
228 code = ' ' + code;
229 }
230 // If the first line is empty, remove it.
231 var match = /^[ \t]*\*+[ \t]*\n/.exec( code );
232 if( match ) {
233 code = code.substr( match[0].length );
234 }
235 // Removing end of comment.
236 var pos = code.lastIndexOf( '*/' );
237 if( pos > -1 ) {
238 code = code.substr( 0, pos );
239 }
240 // Split in lines.
241 var lines = code.split( '\n' );
242 // Regexp matching a non empty line where we have to remove the leading star.
243 var rxLine = /^ \*[ ]+[^ \n]/;
244 // Store the length of the common prefix to remove.
245 var prefixLength = 100;
246 lines.forEach(function (line) {
247 var m = rxLine.exec( line );
248 if( m ) {
249 prefixLength = Math.min( prefixLength, m[0].length - 1 );
250 }
251 });
252 // Lines after processing.
253 var result = [];
254 lines.forEach(function (line) {
255 var m = rxLine.exec( line );
256 if( m ) {
257 result.push( line.substr( prefixLength ) );
258 } else {
259 result.push( line );
260 }
261 });
262 return result.join( '\n' ).trimRight();
263};
264
265exports.parseDoc = function(code) {
266 declarations = {};
267 var tree = UglifyJS.parse(code);
268 var items = tree.body;
269 var actions = {
270 "[Var]definitions": parseVar,
271 "[SimpleStatement]body/": {
272 "[Assign]left/[Dot]expression/[Dot][property=prototype]": parseMethod,
273 "[Assign][operator==]left/": {
274 "[Dot][property=exports]expression/[SymbolRef][name=module]": parseModuleExports,
275 "[Dot]expression/[name=exports]": parseExportsAtt,
276 "[Dot]expression/[SymbolRef]": parseAttribute
277 }
278 }
279 };
280 items.forEach( function(node) {
281 lastComments = comments(node);
282 tw.action(node, actions);
283 } );
284 return {
285 exports: declarations.exports
286 };
287};