@davidwells/extract-deps 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,539 @@
1
+ const { test } = require('uvu')
2
+ const assert = require('uvu/assert')
3
+ const {
4
+ getCjsExports,
5
+ getModuleExportsAssignment,
6
+ getModuleExportsProperty,
7
+ getExportsProperty
8
+ } = require('../src/get-cjs-exports.js')
9
+
10
+ test('should extract module.exports = {} assignments', () => {
11
+ const code = `
12
+ module.exports = function hello() {}
13
+ module.exports = class MyClass {}
14
+ module.exports = { foo: 'bar' }
15
+ module.exports = [1, 2, 3]
16
+ module.exports = 'hello'
17
+ module.exports = 42
18
+ module.exports = () => {}
19
+ `
20
+
21
+ const foundExports = getModuleExportsAssignment(code)
22
+ // console.log('foundExports', foundExports)
23
+
24
+ assert.ok(foundExports.length === 7, 'Should find 7 module.exports assignments')
25
+
26
+ // Check function export
27
+ const functionExport = foundExports.find(exp => exp.exportDefinitionName === 'hello')
28
+ assert.ok(functionExport, 'Should find function export')
29
+ assert.equal(functionExport.exportType, 'default')
30
+ assert.equal(functionExport.exportValue, 'function hello() {}')
31
+
32
+ // Check class export
33
+ const classExport = foundExports.find(exp => exp.exportDefinitionName === 'MyClass')
34
+ assert.ok(classExport, 'Should find class export')
35
+ assert.equal(classExport.exportType, 'default')
36
+ assert.equal(classExport.exportValue, 'class MyClass {}')
37
+
38
+ // Check object literal
39
+ const objectExport = foundExports.find(exp => exp.exportDefinitionName === 'literal')
40
+ assert.ok(objectExport, 'Should find object literal export')
41
+ assert.equal(objectExport.exportValue, "{ foo: 'bar' }")
42
+
43
+ // Check array literal
44
+ const arrayExport = foundExports.find(exp => exp.exportValue === '[1, 2, 3]')
45
+ assert.ok(arrayExport, 'Should find array literal export')
46
+ assert.equal(arrayExport.exportName, '')
47
+
48
+ // Check string literal
49
+ const stringExport = foundExports.find(exp => exp.exportValue === "'hello'")
50
+ assert.ok(stringExport, 'Should find string literal export')
51
+ assert.equal(stringExport.exportName, "")
52
+
53
+ // Check number literal
54
+ const numberExport = foundExports.find(exp => exp.exportValue === '42')
55
+ assert.ok(numberExport, 'Should find number literal export')
56
+ assert.equal(numberExport.exportName, '')
57
+
58
+ // Check arrow function
59
+ const arrowExport = foundExports.find(exp => exp.exportValue === '() => {}')
60
+ assert.ok(arrowExport, 'Should find arrow function export')
61
+ assert.equal(arrowExport.exportName, '')
62
+
63
+ foundExports.forEach(exp => {
64
+ assert.equal(exp.exportType, 'default')
65
+ assert.ok(exp.isExport)
66
+ })
67
+ })
68
+
69
+ test('should extract module.exports.property assignments', () => {
70
+ const code = `
71
+ module.exports.hello = async event => {
72
+ console.log('lodash', lodash)
73
+ return {
74
+ statusCode: 200,
75
+ body: JSON.stringify({ message: 'Hello' })
76
+ }
77
+ }
78
+ module.exports.world = function() { return 'world' }
79
+ module.exports.MyClass = class MyClass {}
80
+ module.exports.config = { foo: 'bar' }
81
+ module.exports.items = [1, 2, 3]
82
+ `
83
+
84
+ const foundExports = getModuleExportsProperty(code)
85
+
86
+ assert.ok(foundExports.length === 5, 'Should find 5 module.exports property assignments')
87
+
88
+ const exportNames = foundExports.map(exp => exp.exportName)
89
+ assert.ok(exportNames.includes('hello'), 'Should find hello export')
90
+ assert.ok(exportNames.includes('world'), 'Should find world export')
91
+ assert.ok(exportNames.includes('MyClass'), 'Should find MyClass export')
92
+ assert.ok(exportNames.includes('config'), 'Should find config export')
93
+ assert.ok(exportNames.includes('items'), 'Should find items export')
94
+
95
+ foundExports.forEach(exp => {
96
+ assert.equal(exp.exportType, 'named')
97
+ assert.ok(exp.isExport)
98
+ })
99
+ })
100
+
101
+ test('should extract exports.property assignments', () => {
102
+ const code = `
103
+ exports.hello = function() {}
104
+ exports.world = () => {}
105
+ exports.MyClass = class MyClass {}
106
+ exports.config = { foo: 'bar' }
107
+ exports.items = [1, 2, 3]
108
+ exports.name = 'test'
109
+ `
110
+
111
+ const foundExports = getExportsProperty(code)
112
+
113
+ assert.ok(foundExports.length === 6, 'Should find 6 exports property assignments')
114
+
115
+ const exportNames = foundExports.map(exp => exp.exportName)
116
+ assert.ok(exportNames.includes('hello'), 'Should find hello export')
117
+ assert.ok(exportNames.includes('world'), 'Should find world export')
118
+ assert.ok(exportNames.includes('MyClass'), 'Should find MyClass export')
119
+ assert.ok(exportNames.includes('config'), 'Should find config export')
120
+ assert.ok(exportNames.includes('items'), 'Should find items export')
121
+ assert.ok(exportNames.includes('name'), 'Should find name export')
122
+
123
+ foundExports.forEach(exp => {
124
+ assert.equal(exp.exportType, 'named')
125
+ assert.ok(exp.isExport)
126
+ })
127
+ })
128
+
129
+ test('should combine all CJS export types in getCjsExports', () => {
130
+ const code = `
131
+ // module.exports assignment
132
+ module.exports = {
133
+ hello: function() {},
134
+ world: 'test'
135
+ }
136
+
137
+ // module.exports properties
138
+ module.exports.foo = function() {}
139
+ module.exports.bar = 'bar'
140
+
141
+ // exports properties
142
+ exports.baz = function() {}
143
+ exports.qux = 'qux'
144
+ `
145
+
146
+ const foundExports = getCjsExports(code)
147
+
148
+ assert.ok(foundExports.length === 5, 'Should find 5 total exports')
149
+
150
+ // Check sorting by index
151
+ for (let i = 1; i < foundExports.length; i++) {
152
+ assert.ok(foundExports[i].index >= foundExports[i-1].index, 'Exports should be sorted by index')
153
+ }
154
+ })
155
+
156
+ test('should handle empty code', () => {
157
+ const foundExports = getCjsExports('')
158
+
159
+ assert.ok(foundExports.length === 0, 'Should return empty array for empty code')
160
+ })
161
+
162
+ test('should handle code with no exports', () => {
163
+ const code = `
164
+ function myFunction() {}
165
+ const myVariable = 'value'
166
+ const hello = require('./hello')
167
+ `
168
+
169
+ const foundExports = getCjsExports(code)
170
+
171
+ assert.ok(foundExports.length === 0, 'Should return empty array for code with no exports')
172
+ })
173
+
174
+ test('should handle complex arrow functions', () => {
175
+ const code = `
176
+ module.exports.hello = async event => {
177
+ console.log('lodash', lodash)
178
+ return {
179
+ statusCode: 200,
180
+ body: JSON.stringify(
181
+ {
182
+ message: 'Go Serverless v1.0! Your function executed successfully!',
183
+ input: event,
184
+ },
185
+ null,
186
+ 2
187
+ ),
188
+ }
189
+ }
190
+ `
191
+
192
+ const foundExports = getCjsExports(code)
193
+
194
+ assert.ok(foundExports.length === 1, 'Should find 1 export')
195
+ assert.equal(foundExports[0].exportName, 'hello', 'Should find hello export')
196
+ assert.equal(foundExports[0].exportType, 'named', 'Should be named type')
197
+ })
198
+
199
+ test('should handle module.exports with object shorthand', () => {
200
+ const code = `
201
+ const hello = function() {}
202
+ const world = 'test'
203
+ module.exports = { hello, world }
204
+ `
205
+
206
+ const foundExports = getCjsExports(code)
207
+
208
+ assert.ok(foundExports.length === 1, 'Should find 1 module.exports assignment')
209
+ assert.equal(foundExports[0].exportType, 'default')
210
+ assert.equal(foundExports[0].exportName, '')
211
+ assert.equal(foundExports[0].exportValue, '{ hello, world }')
212
+
213
+ // Check definitions
214
+ assert.ok(foundExports[0].definitions, 'Should have definitions')
215
+ assert.ok(foundExports[0].definitions.expectsDestructuring, 'Should expect destructuring')
216
+ assert.equal(foundExports[0].definitions.destructuredParameters.length, 2, 'Should have 2 properties')
217
+ assert.equal(foundExports[0].definitions.destructuredParameters[0].name, 'hello')
218
+ assert.equal(foundExports[0].definitions.destructuredParameters[1].name, 'world')
219
+ })
220
+
221
+ test('should handle SINGLELINE module.exports with object property references', () => {
222
+ const code = `
223
+ const barRef = function() { return 'bar' }
224
+ const fooValue = 'foo'
225
+ module.exports = { bar: barRef, foo: fooValue }
226
+ `
227
+
228
+ const foundExports = getCjsExports(code)
229
+
230
+ assert.ok(foundExports.length === 1, 'Should find 1 module.exports assignment')
231
+ assert.equal(foundExports[0].exportType, 'default')
232
+ assert.equal(foundExports[0].exportName, '')
233
+ assert.ok(foundExports[0].exportValue.includes('bar: barRef'), 'Should include bar property')
234
+ assert.ok(foundExports[0].exportValue.includes('foo: fooValue'), 'Should include foo property')
235
+
236
+ // Check definitions
237
+ assert.ok(foundExports[0].definitions, 'Should have definitions')
238
+ assert.ok(foundExports[0].definitions.expectsDestructuring, 'Should expect destructuring')
239
+ assert.equal(foundExports[0].definitions.destructuredParameters.length, 2, 'Should have 2 properties')
240
+
241
+ // Check bar property
242
+ const barParam = foundExports[0].definitions.destructuredParameters[0]
243
+ assert.equal(barParam.name, 'bar', 'First property name should be bar')
244
+ assert.equal(barParam.definition, 'barRef', 'First property alias should be barRef')
245
+
246
+ // Check foo property
247
+ const fooParam = foundExports[0].definitions.destructuredParameters[1]
248
+ assert.equal(fooParam.name, 'foo', 'Second property name should be foo')
249
+ assert.equal(fooParam.definition, 'fooValue', 'Second property alias should be fooValue')
250
+ })
251
+
252
+ test('should handle MULTILINE module.exports with object property references', () => {
253
+ const code = `
254
+ const barRef = function() { return 'bar' }
255
+ const fooValue = 'foo'
256
+ module.exports = {
257
+ bar: barRef,
258
+ foo: fooValue
259
+ }
260
+ `
261
+
262
+ const foundExports = getCjsExports(code)
263
+
264
+ assert.ok(foundExports.length === 1, 'Should find 1 module.exports assignment')
265
+ assert.equal(foundExports[0].exportType, 'default')
266
+ assert.equal(foundExports[0].exportName, '')
267
+ assert.ok(foundExports[0].exportValue.includes('bar: barRef'), 'Should include bar property')
268
+ assert.ok(foundExports[0].exportValue.includes('foo: fooValue'), 'Should include foo property')
269
+
270
+ // Check definitions
271
+ assert.ok(foundExports[0].definitions, 'Should have definitions')
272
+ assert.ok(foundExports[0].definitions.expectsDestructuring, 'Should expect destructuring')
273
+ assert.equal(foundExports[0].definitions.destructuredParameters.length, 2, 'Should have 2 properties')
274
+
275
+ // Check bar property
276
+ const barParam = foundExports[0].definitions.destructuredParameters[0]
277
+ assert.equal(barParam.name, 'bar', 'First property name should be bar')
278
+ assert.equal(barParam.definition, 'barRef', 'First property alias should be barRef')
279
+
280
+ // Check foo property
281
+ const fooParam = foundExports[0].definitions.destructuredParameters[1]
282
+ assert.equal(fooParam.name, 'foo', 'Second property name should be foo')
283
+ assert.equal(fooParam.definition, 'fooValue', 'Second property alias should be fooValue')
284
+ })
285
+
286
+ test('should handle nested objects in module.exports', () => {
287
+ const code = `
288
+ module.exports = {
289
+ handlers: {
290
+ hello: function() {},
291
+ world: function() {}
292
+ },
293
+ config: {
294
+ foo: 'bar'
295
+ }
296
+ }
297
+ `
298
+
299
+ const foundExports = getCjsExports(code)
300
+
301
+ assert.ok(foundExports.length === 1, 'Should find 1 module.exports assignment')
302
+ assert.equal(foundExports[0].exportType, 'default')
303
+ assert.equal(foundExports[0].definitions.destructuredParameters.length, 2, 'Should have 2 top-level keys')
304
+ assert.equal(foundExports[0].definitions.destructuredParameters[0].name, 'handlers')
305
+ assert.equal(foundExports[0].definitions.destructuredParameters[1].name, 'config')
306
+ })
307
+
308
+ test('should handle module.exports assignment with additional properties', () => {
309
+ const code = `
310
+ module.exports = function main() {}
311
+ module.exports.foo = function() {}
312
+ module.exports.bar = function() {}
313
+ `
314
+
315
+ const foundExports = getCjsExports(code)
316
+
317
+ assert.ok(foundExports.length === 3, 'Should find 3 exports')
318
+
319
+ // Check main function assignment
320
+ const mainExport = foundExports.find(exp => exp.exportName === '')
321
+ assert.ok(mainExport, 'Should find main function export')
322
+ assert.equal(mainExport.exportType, 'default')
323
+ assert.equal(mainExport.exportValue, 'function main() {}')
324
+
325
+ // Check foo property
326
+ const fooExport = foundExports.find(exp => exp.exportName === 'foo')
327
+ assert.ok(fooExport, 'Should find foo property export')
328
+ assert.equal(fooExport.exportType, 'named')
329
+
330
+ // Check bar property
331
+ const barExport = foundExports.find(exp => exp.exportName === 'bar')
332
+ assert.ok(barExport, 'Should find bar property export')
333
+ assert.equal(barExport.exportType, 'named')
334
+
335
+ // Check sorting by index
336
+ assert.ok(foundExports[0].index < foundExports[1].index, 'Exports should be sorted by index')
337
+ assert.ok(foundExports[1].index < foundExports[2].index, 'Exports should be sorted by index')
338
+ })
339
+
340
+ test('should not match comments', () => {
341
+ const code = `
342
+ // module.exports.hello = function() {}
343
+ /* module.exports.world = function() {} */
344
+ const real = 'actual code'
345
+ module.exports.real = real
346
+ `
347
+
348
+ const foundExports = getCjsExports(code)
349
+
350
+ assert.ok(foundExports.length === 1, 'Should only find 1 real export, not commented ones')
351
+ assert.equal(foundExports[0].exportName, 'real', 'Should find real export')
352
+ })
353
+
354
+ test('should handle module.exports in strings', () => {
355
+ const code = `
356
+ const str = "module.exports.fake = 'test'"
357
+ const str2 = 'module.exports.fake2 = "test"'
358
+ module.exports.real = function() {}
359
+ `
360
+
361
+ const foundExports = getCjsExports(code)
362
+
363
+ // Note: Basic regex may catch strings - this test documents current behavior
364
+ // We may need to improve string detection if this becomes an issue
365
+ assert.ok(foundExports.length >= 1, 'Should find at least the real export')
366
+ const realExport = foundExports.find(exp => exp.exportName === 'real')
367
+ assert.ok(realExport, 'Should find real export')
368
+ })
369
+
370
+ test('should handle string literal property names', () => {
371
+ const code = `
372
+ exports['hello-world'] = function() {}
373
+ module.exports['my-config'] = { port: 8080 }
374
+ `
375
+
376
+ const foundExports = getCjsExports(code)
377
+
378
+ assert.ok(foundExports.length === 2, 'Should find 2 exports with string literal property names')
379
+
380
+ const helloWorldExport = foundExports.find(exp => exp.exportName === 'hello-world')
381
+ assert.ok(helloWorldExport, 'Should find hello-world export')
382
+ assert.equal(helloWorldExport.exportType, 'named')
383
+
384
+ const configExport = foundExports.find(exp => exp.exportName === 'my-config')
385
+ assert.ok(configExport, 'Should find my-config export')
386
+ assert.equal(configExport.exportType, 'named')
387
+ })
388
+
389
+ test('should handle variable property names', () => {
390
+ const code = `
391
+ const myExportName = 'myFunction'
392
+ exports[myExportName] = () => { /* ... */ }
393
+ `
394
+
395
+ const foundExports = getCjsExports(code)
396
+
397
+ // This may not work with simple regex - documenting expected behavior
398
+ assert.ok(foundExports.length >= 0, 'Variable property names may not be detectable with regex')
399
+ })
400
+
401
+ test('should handle chained assignment', () => {
402
+ const code = `
403
+ exports = module.exports = createRouter()
404
+ `
405
+
406
+ const foundExports = getCjsExports(code)
407
+
408
+ assert.ok(foundExports.length === 1, 'Should find chained assignment')
409
+ assert.equal(foundExports[0].exportType, 'default')
410
+ assert.equal(foundExports[0].exportValue, 'createRouter()')
411
+ })
412
+
413
+ test('should handle Object.assign to exports', () => {
414
+ const code = `
415
+ const utils = {
416
+ isString() {},
417
+ isNumber() {}
418
+ }
419
+ const constants = {
420
+ MAX_RETRIES: 5
421
+ }
422
+
423
+ Object.assign(exports, utils, constants)
424
+ `
425
+
426
+ const foundExports = getCjsExports(code)
427
+
428
+ assert.ok(foundExports.length === 1, 'Should find Object.assign to exports')
429
+ assert.equal(foundExports[0].exportType, 'object-assign')
430
+ assert.equal(foundExports[0].exportName, 'Object.assign')
431
+ assert.equal(foundExports[0].exportValue, 'Object.assign(exports, utils, constants)')
432
+ })
433
+
434
+ test('should handle module.exports with Object.assign', () => {
435
+ const code = `
436
+ const utils = {
437
+ isString() {},
438
+ isNumber() {}
439
+ }
440
+
441
+ module.exports = Object.assign({}, utils, { someOtherExport: 'foo' })
442
+ `
443
+
444
+ const foundExports = getCjsExports(code)
445
+
446
+ assert.ok(foundExports.length === 1, 'Should find module.exports assignment')
447
+ assert.equal(foundExports[0].exportType, 'object-assign')
448
+ assert.equal(foundExports[0].exportName, '')
449
+ assert.ok(foundExports[0].exportValue.includes('Object.assign'), 'Should include Object.assign call')
450
+ })
451
+
452
+ test('should handle MULTILINE module.exports with Object.assign', () => {
453
+ const code = `
454
+ const utils = {
455
+ isString() {},
456
+ isNumber() {}
457
+ }
458
+
459
+ module.exports = Object.assign(
460
+ {},
461
+ utils,
462
+ {
463
+ someOtherExport: 'foo'
464
+ })
465
+ `
466
+
467
+ const foundExports = getCjsExports(code)
468
+
469
+ assert.ok(foundExports.length === 1, 'Should find module.exports assignment')
470
+ assert.equal(foundExports[0].exportType, 'object-assign')
471
+ assert.equal(foundExports[0].exportName, '')
472
+ assert.ok(foundExports[0].exportValue.includes('Object.assign'), 'Should include Object.assign call')
473
+ assert.ok(foundExports[0].exportValue.includes('someOtherExport'), 'Should include complete multiline call')
474
+ })
475
+
476
+ test('should handle object spread in module.exports', () => {
477
+ const code = `
478
+ const baseConfig = { port: 3000, host: 'localhost' }
479
+ const specificFuncs = {
480
+ start() {},
481
+ stop() {}
482
+ }
483
+
484
+ module.exports = {
485
+ ...baseConfig,
486
+ ...specificFuncs,
487
+ environment: 'development'
488
+ }
489
+ `
490
+
491
+ const foundExports = getCjsExports(code)
492
+
493
+ assert.ok(foundExports.length === 1, 'Should find module.exports assignment')
494
+ assert.equal(foundExports[0].exportType, 'object-spread')
495
+ assert.equal(foundExports[0].exportName, '')
496
+ assert.ok(foundExports[0].exportValue.includes('...baseConfig'), 'Should include spread operator')
497
+ assert.ok(foundExports[0].exportValue.includes('...specificFuncs'), 'Should include spread operator')
498
+ })
499
+
500
+ test('should ignore comment conflicting values', () => {
501
+ const code = `
502
+ const baseConfig = { port: 3000, host: 'localhost' }
503
+ const specificFuncs = {
504
+ start() {},
505
+ stop() {}
506
+ }
507
+
508
+ module.exports = {
509
+ // ...baseConfig,
510
+ /* ...specificFuncs */
511
+ baseConfig,
512
+ specificFuncs,
513
+ environment: 'development'
514
+ }
515
+ `
516
+
517
+ const foundExports = getCjsExports(code)
518
+
519
+
520
+ // console.log('foundExports', foundExports)
521
+ // console.log('foundExports', foundExports[0].definitions)
522
+
523
+ assert.ok(foundExports.length === 1, 'Should find module.exports assignment')
524
+ assert.equal(foundExports[0].exportType, 'default')
525
+ assert.equal(foundExports[0].exportName, '')
526
+
527
+ // Comments should be preserved in exportValue
528
+ assert.ok(foundExports[0].exportValue.includes('// ...baseConfig'), 'Should preserve comments in exportValue')
529
+ assert.ok(foundExports[0].exportValue.includes('baseConfig'), 'Should include baseConfig')
530
+ assert.ok(foundExports[0].exportValue.includes('specificFuncs'), 'Should include specificFuncs')
531
+
532
+ // Comments should NOT appear in keys array
533
+ assert.equal(foundExports[0].keys.length, 3, 'Should have 3 keys (no comment lines)')
534
+ assert.ok(foundExports[0].keys.includes('baseConfig'), 'Keys should include baseConfig')
535
+ assert.ok(foundExports[0].keys.includes('specificFuncs'), 'Keys should include specificFuncs')
536
+ assert.ok(foundExports[0].keys.includes('environment'), 'Keys should include environment')
537
+ })
538
+
539
+ test.run()