Files: c5c7a8ffadbd51aa28a5f3f096fe43f69405da44 / lib / detector.js
10262 bytesRaw
1 | /* eslint-disable operator-linebreak */ |
2 | /* eslint-disable prefer-const */ |
3 | |
4 | const common = require('../prelude/common.js'); |
5 | const generate = require('escodegen').generate; |
6 | const babelParser = require('@babel/parser'); |
7 | const parse = babelParser.parse; |
8 | |
9 | const ALIAS_AS_RELATIVE = common.ALIAS_AS_RELATIVE; |
10 | const ALIAS_AS_RESOLVABLE = common.ALIAS_AS_RESOLVABLE; |
11 | |
12 | function forge (pattern, was) { |
13 | return pattern.replace('{c1}', ', ') |
14 | .replace('{v1}', '"' + was.v1 + '"') |
15 | .replace('{c2}', was.v2 ? ', ' : '') |
16 | .replace('{v2}', was.v2 ? ('"' + was.v2 + '"') : '') |
17 | .replace('{c3}', was.v3 ? ' from ' : '') |
18 | .replace('{v3}', was.v3 ? was.v3 : ''); |
19 | } |
20 | |
21 | function valid2 (v2) { |
22 | return (v2 === undefined) || |
23 | (v2 === null) || |
24 | (v2 === 'must-exclude') || |
25 | (v2 === 'may-exclude'); |
26 | } |
27 | |
28 | function visitor_REQUIRE_RESOLVE (n) { // eslint-disable-line camelcase |
29 | const c = n.callee; |
30 | if (!c) return null; |
31 | const ci = (c.object |
32 | && c.object.type === 'Identifier' |
33 | && c.object.name === 'require' |
34 | && c.property |
35 | && c.property.type === 'Identifier' |
36 | && c.property.name === 'resolve'); |
37 | if (!ci) return null; |
38 | const f = (n.type === 'CallExpression' |
39 | && n.arguments |
40 | && n.arguments[0] |
41 | && n.arguments[0].type === 'Literal'); |
42 | if (!f) return null; |
43 | const m = (n.arguments[1] |
44 | && n.arguments[1].type === 'Literal'); |
45 | return { v1: n.arguments[0].value, |
46 | v2: m ? n.arguments[1].value : null }; |
47 | } |
48 | |
49 | function visitor_REQUIRE (n) { // eslint-disable-line camelcase |
50 | const c = n.callee; |
51 | if (!c) return null; |
52 | const ci = (c.type === 'Identifier' |
53 | && c.name === 'require'); |
54 | if (!ci) return null; |
55 | const f = (n.type === 'CallExpression' |
56 | && n.arguments |
57 | && n.arguments[0] |
58 | && n.arguments[0].type === 'Literal'); |
59 | if (!f) return null; |
60 | const m = (n.arguments[1] |
61 | && n.arguments[1].type === 'Literal'); |
62 | return { v1: n.arguments[0].value, |
63 | v2: m ? n.arguments[1].value : null }; |
64 | } |
65 | |
66 | function visitor_IMPORT (n) { // eslint-disable-line camelcase |
67 | const ni = (n.type === 'ImportDeclaration'); |
68 | if (!ni) return null; |
69 | const s = n.specifiers; |
70 | return { v1: n.source.value, |
71 | v3: reconstructSpecifiers(s) }; |
72 | } |
73 | |
74 | function visitor_PATH_JOIN (n) { // eslint-disable-line camelcase |
75 | const c = n.callee; |
76 | if (!c) return null; |
77 | const ci = (c.object |
78 | && c.object.type === 'Identifier' |
79 | && c.object.name === 'path' |
80 | && c.property |
81 | && c.property.type === 'Identifier' |
82 | && c.property.name === 'join'); |
83 | if (!ci) return null; |
84 | const dn = (n.arguments[0] |
85 | && n.arguments[0].type === 'Identifier' |
86 | && n.arguments[0].name === '__dirname'); |
87 | if (!dn) return null; |
88 | const f = (n.type === 'CallExpression' |
89 | && n.arguments |
90 | && n.arguments[1] |
91 | && n.arguments[1].type === 'Literal' |
92 | && n.arguments.length === 2); // TODO concate them |
93 | if (!f) return null; |
94 | return { v1: n.arguments[1].value }; |
95 | } |
96 | |
97 | module.exports.visitor_SUCCESSFUL = function (node, test) { // eslint-disable-line camelcase |
98 | let mustExclude, mayExclude, was; |
99 | |
100 | was = visitor_REQUIRE_RESOLVE(node); |
101 | if (was) { |
102 | if (test) return forge('require.resolve({v1}{c2}{v2})', was); |
103 | if (!valid2(was.v2)) return null; |
104 | mustExclude = (was.v2 === 'must-exclude'); |
105 | mayExclude = (was.v2 === 'may-exclude'); |
106 | return { alias: was.v1, |
107 | aliasType: ALIAS_AS_RESOLVABLE, |
108 | mustExclude: mustExclude, |
109 | mayExclude: mayExclude }; |
110 | } |
111 | |
112 | was = visitor_REQUIRE(node); |
113 | if (was) { |
114 | if (test) return forge('require({v1}{c2}{v2})', was); |
115 | if (!valid2(was.v2)) return null; |
116 | mustExclude = (was.v2 === 'must-exclude'); |
117 | mayExclude = (was.v2 === 'may-exclude'); |
118 | return { alias: was.v1, |
119 | aliasType: ALIAS_AS_RESOLVABLE, |
120 | mustExclude: mustExclude, |
121 | mayExclude: mayExclude }; |
122 | } |
123 | |
124 | was = visitor_IMPORT(node); |
125 | if (was) { |
126 | if (test) return forge('import {v3}{c3}{v1}', was); |
127 | return { alias: was.v1, |
128 | aliasType: ALIAS_AS_RESOLVABLE }; |
129 | } |
130 | |
131 | was = visitor_PATH_JOIN(node); |
132 | if (was) { |
133 | if (test) return forge('path.join(__dirname{c1}{v1})', was); |
134 | return { alias: was.v1, |
135 | aliasType: ALIAS_AS_RELATIVE, |
136 | mayExclude: false }; |
137 | } |
138 | |
139 | return null; |
140 | }; |
141 | |
142 | function visitor_NONLITERAL (n) { // eslint-disable-line camelcase |
143 | return (function () { |
144 | const c = n.callee; |
145 | if (!c) return null; |
146 | const ci = (c.object |
147 | && c.object.type === 'Identifier' |
148 | && c.object.name === 'require' |
149 | && c.property |
150 | && c.property.type === 'Identifier' |
151 | && c.property.name === 'resolve'); |
152 | if (!ci) return null; |
153 | const f = (n.type === 'CallExpression' |
154 | && n.arguments |
155 | && n.arguments[0] |
156 | && n.arguments[0].type !== 'Literal'); |
157 | if (!f) return null; |
158 | const m = n.arguments[1]; |
159 | if (!m) return { v1: reconstruct(n.arguments[0]) }; |
160 | const q = (n.arguments[1] |
161 | && n.arguments[1].type === 'Literal'); |
162 | if (!q) return null; |
163 | return { v1: reconstruct(n.arguments[0]), |
164 | v2: n.arguments[1].value }; |
165 | }()) || (function () { |
166 | const c = n.callee; |
167 | if (!c) return null; |
168 | const ci = (c.type === 'Identifier' |
169 | && c.name === 'require'); |
170 | if (!ci) return null; |
171 | const f = (n.type === 'CallExpression' |
172 | && n.arguments |
173 | && n.arguments[0] |
174 | && n.arguments[0].type !== 'Literal'); |
175 | if (!f) return null; |
176 | const m = n.arguments[1]; |
177 | if (!m) return { v1: reconstruct(n.arguments[0]) }; |
178 | const q = (n.arguments[1] |
179 | && n.arguments[1].type === 'Literal'); |
180 | if (!q) return null; |
181 | return { v1: reconstruct(n.arguments[0]), |
182 | v2: n.arguments[1].value }; |
183 | }()); |
184 | } |
185 | |
186 | module.exports.visitor_NONLITERAL = function (node) { // eslint-disable-line camelcase |
187 | let mustExclude, mayExclude, was; |
188 | |
189 | was = visitor_NONLITERAL(node); |
190 | if (was) { |
191 | if (!valid2(was.v2)) return null; |
192 | mustExclude = (was.v2 === 'must-exclude'); |
193 | mayExclude = (was.v2 === 'may-exclude'); |
194 | return { alias: was.v1, |
195 | mustExclude: mustExclude, |
196 | mayExclude: mayExclude }; |
197 | } |
198 | |
199 | return null; |
200 | }; |
201 | |
202 | function visitor_MALFORMED (n) { // eslint-disable-line camelcase |
203 | return (function () { |
204 | const c = n.callee; |
205 | if (!c) return null; |
206 | const ci = (c.object |
207 | && c.object.type === 'Identifier' |
208 | && c.object.name === 'require' |
209 | && c.property |
210 | && c.property.type === 'Identifier' |
211 | && c.property.name === 'resolve'); |
212 | if (!ci) return null; |
213 | const f = (n.type === 'CallExpression' |
214 | && n.arguments |
215 | && n.arguments[0]); |
216 | if (!f) return null; |
217 | return { v1: reconstruct(n.arguments[0]) }; |
218 | }()) || (function () { |
219 | const c = n.callee; |
220 | if (!c) return null; |
221 | const ci = (c.type === 'Identifier' |
222 | && c.name === 'require'); |
223 | if (!ci) return null; |
224 | const f = (n.type === 'CallExpression' |
225 | && n.arguments |
226 | && n.arguments[0]); |
227 | if (!f) return null; |
228 | return { v1: reconstruct(n.arguments[0]) }; |
229 | }()); |
230 | } |
231 | |
232 | module.exports.visitor_MALFORMED = function (node) { // eslint-disable-line camelcase |
233 | let was; |
234 | |
235 | was = visitor_MALFORMED(node); |
236 | if (was) return { alias: was.v1 }; |
237 | |
238 | return null; |
239 | }; |
240 | |
241 | function visitor_USESCWD (n) { // eslint-disable-line camelcase |
242 | const c = n.callee; |
243 | if (!c) return null; |
244 | const ci = (c.object |
245 | && c.object.type === 'Identifier' |
246 | && c.object.name === 'path' |
247 | && c.property |
248 | && c.property.type === 'Identifier' |
249 | && c.property.name === 'resolve'); |
250 | if (!ci) return null; |
251 | return { v1: n.arguments.map(reconstruct).join(', ') }; |
252 | } |
253 | |
254 | module.exports.visitor_USESCWD = function (node) { // eslint-disable-line camelcase |
255 | let was; |
256 | |
257 | was = visitor_USESCWD(node); |
258 | if (was) return { alias: was.v1 }; |
259 | |
260 | return null; |
261 | }; |
262 | |
263 | function reconstructSpecifiers (specs) { |
264 | if (!specs || !specs.length) return ''; |
265 | const defaults = []; |
266 | for (const spec of specs) { |
267 | if (spec.type === 'ImportDefaultSpecifier') { |
268 | defaults.push(spec.local.name); |
269 | } |
270 | } |
271 | const nonDefaults = []; |
272 | for (const spec of specs) { |
273 | if (spec.type === 'ImportSpecifier') { |
274 | if (spec.local.name === spec.imported.name) { |
275 | nonDefaults.push(spec.local.name); |
276 | } else { |
277 | nonDefaults.push( |
278 | spec.imported.name + ' as ' + spec.local.name |
279 | ); |
280 | } |
281 | } |
282 | } |
283 | if (nonDefaults.length) { |
284 | defaults.push('{ ' + nonDefaults.join(', ') + ' }'); |
285 | } |
286 | return defaults.join(', '); |
287 | } |
288 | |
289 | function reconstruct (node) { |
290 | let v = generate(node).replace(/\n/g, ''); |
291 | let v2; |
292 | while (true) { |
293 | v2 = v.replace(/\[ /g, '[') |
294 | .replace(/ \]/g, ']') |
295 | .replace(/ {2}/g, ' '); |
296 | if (v2 === v) break; |
297 | v = v2; |
298 | } |
299 | return v2; |
300 | } |
301 | |
302 | function traverse (ast, visitor) { |
303 | // modified esprima-walk to support |
304 | // visitor return value and "trying" flag |
305 | let stack = [ [ ast, false ] ]; |
306 | let i, j, key; |
307 | let len, item, node, trying, child; |
308 | for (i = 0; i < stack.length; i += 1) { |
309 | item = stack[i]; |
310 | node = item[0]; |
311 | if (node) { |
312 | trying = item[1] || (node.type === 'TryStatement'); |
313 | if (visitor(node, trying)) { |
314 | for (key in node) { |
315 | child = node[key]; |
316 | if (child instanceof Array) { |
317 | len = child.length; |
318 | for (j = 0; j < len; j += 1) { |
319 | stack.push([ child[j], trying ]); |
320 | } |
321 | } else |
322 | if (child && typeof child.type === 'string') { |
323 | stack.push([ child, trying ]); |
324 | } |
325 | } |
326 | } |
327 | } |
328 | } |
329 | } |
330 | |
331 | module.exports.parse = function (body) { |
332 | return parse(body, { |
333 | allowImportExportEverywhere: true, |
334 | allowReturnOutsideFunction: true, |
335 | ecmaVersion: 8, |
336 | plugins: [ 'estree', 'bigInt', 'classPrivateProperties', 'classProperties' ] |
337 | }); |
338 | }; |
339 | |
340 | module.exports.detect = function (body, visitor) { |
341 | const json = module.exports.parse(body); |
342 | if (!json) return; |
343 | traverse(json, visitor); |
344 | }; |
345 |
Built with git-ssb-web