1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | exports.extractInfoFromAst = extractInfoFromAst;
|
9 |
|
10 |
|
11 | const
|
12 | JsDoc = require( "./js-doc" ),
|
13 | Util = require( "./util" );
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | function extractInfoFromAst( ast ) {
|
22 | const output = {
|
23 | requires: [],
|
24 | exports: findExports( ast )
|
25 | };
|
26 | recursivelyExtractInfoFromAst( ast.program.body, output );
|
27 | return output;
|
28 | }
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | function recursivelyExtractInfoFromAst( ast, output ) {
|
37 | if ( !Array.isArray( ast ) ) return;
|
38 | ast.forEach( ( item ) => {
|
39 | if ( !item ) return;
|
40 | if ( item.type === 'CallExpression' ) {
|
41 | parseCallExpression( item, output );
|
42 | }
|
43 |
|
44 |
|
45 | Object.keys( item ).forEach( ( key ) => {
|
46 | const child = item[ key ];
|
47 | if ( Array.isArray( child ) ) {
|
48 | recursivelyExtractInfoFromAst( child, output );
|
49 | } else if ( typeof child === 'object' ) {
|
50 | recursivelyExtractInfoFromAst( [ child ], output );
|
51 | }
|
52 | } );
|
53 | } );
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | function parseCallExpression( item, output ) {
|
63 | const
|
64 | callee = item.callee,
|
65 | args = item.arguments;
|
66 | if ( !callee || !args ) return;
|
67 | if ( callee.name !== 'require' ) return;
|
68 | if ( args.length !== 1 ) return;
|
69 |
|
70 | const arg = args[ 0 ];
|
71 | if ( arg.type !== 'StringLiteral' ) return;
|
72 | Util.pushUnique( output.requires, arg.value );
|
73 | }
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | function findExports( ast ) {
|
83 | try {
|
84 | const
|
85 | publics = {},
|
86 | privates = {},
|
87 | body = ast.program.body[ 0 ].expression.arguments[ 1 ].body.body;
|
88 |
|
89 | body.forEach( function ( item ) {
|
90 | if ( parseExports( item, publics ) ) return;
|
91 | if ( parseModuleExports( item, publics ) ) return;
|
92 | parseFunctionDeclaration( item, privates );
|
93 | } );
|
94 |
|
95 | const output = [];
|
96 | Object.keys( publics ).forEach( function ( exportName ) {
|
97 | const
|
98 | exportValue = publics[ exportName ],
|
99 | target = privates[ exportValue.target ];
|
100 | if ( target ) {
|
101 | exportValue.comments = exportValue.comments || target.comments;
|
102 | exportValue.type = target.type;
|
103 | exportValue.params = target.params;
|
104 | }
|
105 | output.push( {
|
106 | id: exportName,
|
107 | type: exportValue.type,
|
108 | params: exportValue.params,
|
109 | comments: exportValue.comments
|
110 | } );
|
111 | } );
|
112 |
|
113 | return output;
|
114 | } catch ( ex ) {
|
115 | throw new Error( `${ex}\n...in module-analyser/findExports()` );
|
116 | }
|
117 | }
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | function parseExports( ast, publics ) {
|
125 | try {
|
126 | if ( ast.type !== "ExpressionStatement" ) return false;
|
127 | const expression = ast.expression;
|
128 | if ( expression.operator !== '=' ) return false;
|
129 |
|
130 | const left = expression.left;
|
131 | if ( left.type !== "MemberExpression" ) return false;
|
132 | const object = left.object;
|
133 | if ( object.type !== "Identifier" && object.name !== "exports" ) return false;
|
134 |
|
135 | const right = expression.right;
|
136 | if ( right.type !== "Identifier" ) return false;
|
137 |
|
138 | publics[ right.name ] = {
|
139 | type: "function",
|
140 | comments: parseComments( ast.leadingComments )
|
141 | };
|
142 | } catch ( ex ) {
|
143 | throw new Error( `${ex}\n...in module-analyser/parseExports(${JSON.stringify(ast)})` );
|
144 | }
|
145 | return true;
|
146 | }
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | function parseModuleExports( ast, publics ) {
|
154 | try {
|
155 | if ( ast.type !== "ExpressionStatement" ) return false;
|
156 | const expression = ast.expression;
|
157 | if ( expression.operator !== '=' ) return false;
|
158 |
|
159 | const left = expression.left;
|
160 | if ( left.type !== "MemberExpression" ) return false;
|
161 | const object = left.object;
|
162 | if ( object.type !== "Identifier" && object.name !== "exports" ) return false;
|
163 |
|
164 | const right = expression.right;
|
165 | if ( right.type !== "ObjectExpression" ) return false;
|
166 |
|
167 | const properties = right.properties;
|
168 | properties.forEach( function ( property ) {
|
169 | const name = property.key.name;
|
170 | publics[ name ] = {
|
171 | target: name,
|
172 | comments: parseComments( property.leadingComments )
|
173 | };
|
174 |
|
175 | } );
|
176 | } catch ( ex ) {
|
177 | throw new Error( `${ex}\n...in module-analyser/parseExports(${JSON.stringify(ast)})` );
|
178 | }
|
179 | return true;
|
180 | }
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | function parseFunctionDeclaration( ast, privates ) {
|
188 | if ( ast.type !== "FunctionDeclaration" ) return false;
|
189 | const name = ast.id.name;
|
190 |
|
191 | privates[ name ] = {
|
192 | type: "function",
|
193 | params: ast.params.map( mapParam ),
|
194 | comments: parseComments( ast.leadingComments )
|
195 | };
|
196 | return true;
|
197 | }
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | function parseComments( comments ) {
|
204 | if ( !Array.isArray( comments ) ) return "";
|
205 |
|
206 | let output = "";
|
207 | comments.forEach( function ( commentAst ) {
|
208 | const comment = commentAst.value;
|
209 | output += comment.trim().split( "\n" )
|
210 | .map( ( line ) => line.trim() )
|
211 | .map( ( line ) => removeLeadingStar( line ) )
|
212 | .join( "\n" )
|
213 | .trim();
|
214 | } );
|
215 | return JsDoc.parse( output );
|
216 | }
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | function removeLeadingStar( line ) {
|
223 | return line.charAt( 0 ) === '*' ? line.substr( 1 ) : line;
|
224 | }
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 | function mapParam( param ) {
|
231 | try {
|
232 | switch ( param.type ) {
|
233 | case "Identifier":
|
234 | return param.name;
|
235 | case "AssignmentPattern":
|
236 | return {
|
237 | name: param.left.name,
|
238 | value: param.right.value
|
239 | };
|
240 | default:
|
241 | return "?";
|
242 | }
|
243 | } catch ( ex ) {
|
244 | throw new Error( `${ex}\n...in module-analyser/mapParam(${JSON.stringify(param)})` );
|
245 | }
|
246 | }
|
247 |
|
\ | No newline at end of file |