@davidwells/extract-deps 0.0.1

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.
Files changed (59) hide show
  1. package/README.md +211 -0
  2. package/_usage.js +175 -0
  3. package/examples/get-exports.js +31 -0
  4. package/package.json +27 -0
  5. package/src/detect-syntax.js +70 -0
  6. package/src/get-exports.js +299 -0
  7. package/src/get-imports.js +165 -0
  8. package/src/get-requires.js +104 -0
  9. package/src/index.js +90 -0
  10. package/src/utils.js +64 -0
  11. package/tests/detect-syntax.test.js +280 -0
  12. package/tests/get-exports.test.js +346 -0
  13. package/tests/get-imports.test.js +395 -0
  14. package/tests/get-requires.test.js +201 -0
  15. package/tests/index.test.js +202 -0
  16. package/tests/test-utils.js +30 -0
  17. package/tsconfig.json +19 -0
  18. package/types/detect-syntax.d.ts +39 -0
  19. package/types/detect-syntax.d.ts.map +1 -0
  20. package/types/extract-deps/_usage.d.ts +20 -0
  21. package/types/extract-deps/_usage.d.ts.map +1 -0
  22. package/types/extract-deps/examples/get-exports.d.ts +2 -0
  23. package/types/extract-deps/examples/get-exports.d.ts.map +1 -0
  24. package/types/extract-deps/src/detect-syntax.d.ts +39 -0
  25. package/types/extract-deps/src/detect-syntax.d.ts.map +1 -0
  26. package/types/extract-deps/src/get-exports.d.ts +52 -0
  27. package/types/extract-deps/src/get-exports.d.ts.map +1 -0
  28. package/types/extract-deps/src/get-imports.d.ts +53 -0
  29. package/types/extract-deps/src/get-imports.d.ts.map +1 -0
  30. package/types/extract-deps/src/get-requires.d.ts +34 -0
  31. package/types/extract-deps/src/get-requires.d.ts.map +1 -0
  32. package/types/extract-deps/src/index.d.ts +39 -0
  33. package/types/extract-deps/src/index.d.ts.map +1 -0
  34. package/types/extract-deps/src/utils.d.ts +4 -0
  35. package/types/extract-deps/src/utils.d.ts.map +1 -0
  36. package/types/extract-deps/tests/detect-syntax.test.d.ts +2 -0
  37. package/types/extract-deps/tests/detect-syntax.test.d.ts.map +1 -0
  38. package/types/extract-deps/tests/get-exports.test.d.ts +2 -0
  39. package/types/extract-deps/tests/get-exports.test.d.ts.map +1 -0
  40. package/types/extract-deps/tests/get-imports.test.d.ts +2 -0
  41. package/types/extract-deps/tests/get-imports.test.d.ts.map +1 -0
  42. package/types/extract-deps/tests/get-requires.test.d.ts +2 -0
  43. package/types/extract-deps/tests/get-requires.test.d.ts.map +1 -0
  44. package/types/extract-deps/tests/index.test.d.ts +18 -0
  45. package/types/extract-deps/tests/index.test.d.ts.map +1 -0
  46. package/types/extract-deps/tests/test-utils.d.ts +9 -0
  47. package/types/extract-deps/tests/test-utils.d.ts.map +1 -0
  48. package/types/get-exports.d.ts +52 -0
  49. package/types/get-exports.d.ts.map +1 -0
  50. package/types/get-imports.d.ts +53 -0
  51. package/types/get-imports.d.ts.map +1 -0
  52. package/types/get-requires.d.ts +34 -0
  53. package/types/get-requires.d.ts.map +1 -0
  54. package/types/index.d.ts +39 -0
  55. package/types/index.d.ts.map +1 -0
  56. package/types/packages-list/index.d.ts +138 -0
  57. package/types/packages-list/index.d.ts.map +1 -0
  58. package/types/utils.d.ts +4 -0
  59. package/types/utils.d.ts.map +1 -0
package/src/utils.js ADDED
@@ -0,0 +1,64 @@
1
+ const { inspectParameters, getParametersNames } = require('inspect-parameters-declaration')
2
+
3
+ function isRelativeImport(importLocation) {
4
+ return !!importLocation.match(/^\./)
5
+ }
6
+
7
+ function isRelativeImportWithQuotes(importLocation) {
8
+ // TODO harden for "${" or " + 'var' + "
9
+ return !!importLocation.match(/[`"']\./)
10
+ && importLocation.indexOf('${') === -1 // No template literals
11
+ && importLocation.indexOf(' + ') === -1 // No string concatenation
12
+ }
13
+
14
+ function getNames(names) {
15
+ // console.log('names', names)
16
+ // Safety check for undefined or null names
17
+ if (!names || names.trim() === '') {
18
+ return null
19
+ }
20
+ const results = inspectParameters(names).map((item) => {
21
+ item.name = item.parameter
22
+ if (item.destructuredParameters && item.destructuredParameters.length > 0) {
23
+ item.destructuredParameters = item.destructuredParameters.map((details) => {
24
+ if (details.parameter.indexOf(':') > -1) {
25
+ const [ name, alias ] = details.parameter.replace(/^{/, '').replace(/}$/, '').split(':')
26
+ details.name = name.trim()
27
+ details.alias = alias.trim()
28
+ delete details.parameter
29
+ return details
30
+ }
31
+ if (details.declaration.indexOf(' as ') > -1) {
32
+ const [ parameter, alias ] = details.declaration.split(' as ')
33
+ details.name = parameter.trim()
34
+ details.alias = alias.trim()
35
+ delete details.parameter
36
+ return details
37
+ }
38
+ details.name = details.parameter
39
+ delete details.parameter
40
+ return details
41
+ })
42
+ }
43
+
44
+ if (item.name.indexOf('{') === -1 && item.parameter.indexOf(' as ') > -1) {
45
+ const [ parameter, alias ] = item.parameter.split(' as ')
46
+ item.name = parameter.trim()
47
+ item.alias = alias.trim()
48
+ }
49
+
50
+ delete item.parameter
51
+
52
+ return item
53
+ })
54
+
55
+ // Return the first (and only) result instead of the array
56
+ return results.length > 0 ? results[0] : null
57
+ }
58
+
59
+
60
+ module.exports = {
61
+ isRelativeImport,
62
+ isRelativeImportWithQuotes,
63
+ getNames
64
+ }
@@ -0,0 +1,280 @@
1
+ const { test } = require('uvu')
2
+ const assert = require('uvu/assert')
3
+ const { hasESMSyntax, hasCJSSyntax, detectSyntax } = require('../src/detect-syntax.js')
4
+
5
+ // Test data for ESM syntax
6
+ const ESM_EXAMPLES = [
7
+ 'import foo from "bar"',
8
+ 'import { foo } from "bar"',
9
+ 'import * as foo from "bar"',
10
+ 'import foo, { bar } from "baz"',
11
+ 'export default foo',
12
+ 'export { foo }',
13
+ 'export * from "bar"',
14
+ 'export const foo = "bar"',
15
+ 'export function foo() {}',
16
+ 'export class Foo {}',
17
+ 'export type Foo = string',
18
+ 'export async function foo() {}',
19
+ 'import.meta.url',
20
+ 'import.meta.resolve("foo")'
21
+ ]
22
+
23
+ // Test data for CJS syntax
24
+ const CJS_EXAMPLES = [
25
+ 'module.exports = foo',
26
+ 'module.exports.foo = bar',
27
+ 'exports.foo = bar',
28
+ 'require("foo")',
29
+ 'const foo = require("bar")',
30
+ 'global.foo = bar',
31
+ 'global.console.log("test")'
32
+ ]
33
+
34
+ // Test data for mixed syntax
35
+ const MIXED_EXAMPLES = [
36
+ `import foo from "bar"
37
+ module.exports = foo`,
38
+ `const foo = require("bar")
39
+ export default foo`,
40
+ `export { foo }
41
+ const bar = require("baz")`
42
+ ]
43
+
44
+ // Test data for no syntax
45
+ const NO_SYNTAX_EXAMPLES = [
46
+ 'const foo = "bar"',
47
+ 'function test() { return true }',
48
+ 'class Test {}',
49
+ 'console.log("hello")',
50
+ 'const obj = { foo: "bar" }',
51
+ 'if (true) { console.log("test") }'
52
+ ]
53
+
54
+ // Test data with comments
55
+ const COMMENTED_EXAMPLES = [
56
+ '// import foo from "bar"',
57
+ '/* export default foo */',
58
+ 'const foo = "bar" // module.exports = foo',
59
+ '/* require("foo") */ const bar = "baz"'
60
+ ]
61
+
62
+ // Test data with comments followed by actual syntax
63
+ const COMMENTED_WITH_SYNTAX_EXAMPLES = [
64
+ `// This is a comment
65
+ import foo from "bar"`,
66
+ `/* Multi-line comment
67
+ about exports */
68
+ module.exports = foo`
69
+ ]
70
+
71
+ test('hasESMSyntax should detect ESM syntax correctly', () => {
72
+ ESM_EXAMPLES.forEach((code, index) => {
73
+ assert.ok(hasESMSyntax(code), `ESM example ${index + 1} should be detected: "${code}"`)
74
+ })
75
+ })
76
+
77
+ test('hasESMSyntax should not detect non-ESM syntax', () => {
78
+ CJS_EXAMPLES.forEach((code, index) => {
79
+ assert.not(hasESMSyntax(code), `CJS example ${index + 1} should not be detected as ESM: "${code}"`)
80
+ })
81
+
82
+ NO_SYNTAX_EXAMPLES.forEach((code, index) => {
83
+ assert.not(hasESMSyntax(code), `No syntax example ${index + 1} should not be detected as ESM: "${code}"`)
84
+ })
85
+ })
86
+
87
+ test('hasCJSSyntax should detect CJS syntax correctly', () => {
88
+ CJS_EXAMPLES.forEach((code, index) => {
89
+ assert.ok(hasCJSSyntax(code), `CJS example ${index + 1} should be detected: "${code}"`)
90
+ })
91
+ })
92
+
93
+ test('hasCJSSyntax should not detect non-CJS syntax', () => {
94
+ ESM_EXAMPLES.forEach((code, index) => {
95
+ assert.not(hasCJSSyntax(code), `ESM example ${index + 1} should not be detected as CJS: "${code}"`)
96
+ })
97
+
98
+ NO_SYNTAX_EXAMPLES.forEach((code, index) => {
99
+ assert.not(hasCJSSyntax(code), `No syntax example ${index + 1} should not be detected as CJS: "${code}"`)
100
+ })
101
+ })
102
+
103
+ test('detectSyntax should correctly identify ESM-only code', () => {
104
+ ESM_EXAMPLES.forEach((code, index) => {
105
+ const result = detectSyntax(code)
106
+ assert.ok(result.hasESM, `ESM example ${index + 1} should have hasESM=true: "${code}"`)
107
+ assert.not(result.hasCJS, `ESM example ${index + 1} should have hasCJS=false: "${code}"`)
108
+ assert.not(result.isMixed, `ESM example ${index + 1} should have isMixed=false: "${code}"`)
109
+ })
110
+ })
111
+
112
+ test('detectSyntax should correctly identify CJS-only code', () => {
113
+ CJS_EXAMPLES.forEach((code, index) => {
114
+ const result = detectSyntax(code)
115
+ assert.not(result.hasESM, `CJS example ${index + 1} should have hasESM=false: "${code}"`)
116
+ assert.ok(result.hasCJS, `CJS example ${index + 1} should have hasCJS=true: "${code}"`)
117
+ assert.not(result.isMixed, `CJS example ${index + 1} should have isMixed=false: "${code}"`)
118
+ })
119
+ })
120
+
121
+ test('detectSyntax should correctly identify mixed syntax code', () => {
122
+ MIXED_EXAMPLES.forEach((code, index) => {
123
+ const result = detectSyntax(code)
124
+ /*
125
+ console.info(code);
126
+ console.log(result)
127
+ /** */
128
+ assert.ok(result.hasESM, `Mixed example ${index + 1} should have hasESM=true: "${code}"`)
129
+ assert.ok(result.hasCJS, `Mixed example ${index + 1} should have hasCJS=true: "${code}"`)
130
+ assert.ok(result.isMixed, `Mixed example ${index + 1} should have isMixed=true: "${code}"`)
131
+ })
132
+ })
133
+
134
+ test('detectSyntax should correctly identify code with no syntax', () => {
135
+ NO_SYNTAX_EXAMPLES.forEach((code, index) => {
136
+ const result = detectSyntax(code)
137
+ assert.not(result.hasESM, `No syntax example ${index + 1} should have hasESM=false: "${code}"`)
138
+ assert.not(result.hasCJS, `No syntax example ${index + 1} should have hasCJS=false: "${code}"`)
139
+ assert.not(result.isMixed, `No syntax example ${index + 1} should have isMixed=false: "${code}"`)
140
+ })
141
+ })
142
+
143
+ test('should handle comments correctly (comments are always stripped)', () => {
144
+ COMMENTED_EXAMPLES.forEach((code, index) => {
145
+ const result = detectSyntax(code)
146
+ // Comments are always stripped to avoid false positives from commented syntax
147
+ assert.not(result.hasESM, `Commented example ${index + 1} should not detect commented ESM: "${code}"`)
148
+ assert.not(result.hasCJS, `Commented example ${index + 1} should not detect commented CJS: "${code}"`)
149
+ })
150
+ })
151
+
152
+ test('should detect actual syntax even when comments are present', () => {
153
+ COMMENTED_WITH_SYNTAX_EXAMPLES.forEach((code, index) => {
154
+ const result = detectSyntax(code)
155
+ // After stripping comments, actual syntax should be detected
156
+ if (code.includes('import')) {
157
+ assert.ok(result.hasESM, `Should detect ESM after stripping comments: "${code}"`)
158
+ }
159
+ if (code.includes('module.exports')) {
160
+ assert.ok(result.hasCJS, `Should detect CJS after stripping comments: "${code}"`)
161
+ }
162
+ })
163
+
164
+ const commentedCode = '// import foo from "bar"\nmodule.exports = foo'
165
+ const result = detectSyntax(commentedCode)
166
+
167
+ // Should detect the actual syntax, not the commented syntax
168
+ assert.not(result.hasESM, 'Should not detect commented ESM')
169
+ assert.ok(result.hasCJS, 'Should detect actual CJS syntax')
170
+ })
171
+
172
+ test('should handle complex real-world examples', () => {
173
+ const complexESM = `
174
+ import React from 'react'
175
+ import { useState, useEffect } from 'react'
176
+ import * as utils from './utils'
177
+
178
+ export default function App() {
179
+ const [state, setState] = useState()
180
+ return <div>Hello</div>
181
+ }
182
+
183
+ export const helper = () => {}
184
+ `
185
+
186
+ const complexCJS = `
187
+ const express = require('express')
188
+ const path = require('path')
189
+
190
+ module.exports = function(app) {
191
+ app.get('/', (req, res) => {
192
+ res.send('Hello')
193
+ })
194
+ }
195
+
196
+ exports.helper = function() {}
197
+ `
198
+
199
+ const complexMixed = `
200
+ import React from 'react'
201
+ const express = require('express')
202
+
203
+ export default function App() {
204
+ return <div>Hello</div>
205
+ }
206
+
207
+ module.exports = { App }
208
+ `
209
+
210
+ // Test ESM
211
+ const esmResult = detectSyntax(complexESM)
212
+ assert.ok(esmResult.hasESM, 'Complex ESM should be detected')
213
+ assert.not(esmResult.hasCJS, 'Complex ESM should not have CJS')
214
+ assert.not(esmResult.isMixed, 'Complex ESM should not be mixed')
215
+
216
+ // Test CJS
217
+ const cjsResult = detectSyntax(complexCJS)
218
+ assert.not(cjsResult.hasESM, 'Complex CJS should not have ESM')
219
+ assert.ok(cjsResult.hasCJS, 'Complex CJS should be detected')
220
+ assert.not(cjsResult.isMixed, 'Complex CJS should not be mixed')
221
+
222
+ // Test Mixed
223
+ const mixedResult = detectSyntax(complexMixed)
224
+ assert.ok(mixedResult.hasESM, 'Complex mixed should have ESM')
225
+ assert.ok(mixedResult.hasCJS, 'Complex mixed should have CJS')
226
+ assert.ok(mixedResult.isMixed, 'Complex mixed should be mixed')
227
+ })
228
+
229
+ test('should handle edge cases and whitespace', () => {
230
+ // Empty string
231
+ const emptyResult = detectSyntax('')
232
+ assert.not(emptyResult.hasESM, 'Empty string should not have ESM')
233
+ assert.not(emptyResult.hasCJS, 'Empty string should not have CJS')
234
+ assert.not(emptyResult.isMixed, 'Empty string should not be mixed')
235
+
236
+ // Whitespace only
237
+ const whitespaceResult = detectSyntax(' \n\t ')
238
+ assert.not(whitespaceResult.hasESM, 'Whitespace only should not have ESM')
239
+ assert.not(whitespaceResult.hasCJS, 'Whitespace only should not have CJS')
240
+ assert.not(whitespaceResult.isMixed, 'Whitespace only should not be mixed')
241
+
242
+ // Syntax with extra whitespace
243
+ const spacedESM = ' import foo from "bar" '
244
+ const spacedCJS = ' module.exports = foo '
245
+
246
+ assert.ok(hasESMSyntax(spacedESM), 'ESM with extra whitespace should be detected')
247
+ assert.ok(hasCJSSyntax(spacedCJS), 'CJS with extra whitespace should be detected')
248
+ })
249
+
250
+ test('should handle regex edge cases', () => {
251
+ // Test that the regex doesn't match partial words
252
+ const partialESM = 'importing foo from "bar"'
253
+ const partialCJS = 'module.exporting = foo'
254
+
255
+ assert.not(hasESMSyntax(partialESM), 'Partial ESM word should not be detected')
256
+ assert.not(hasCJSSyntax(partialCJS), 'Partial CJS word should not be detected')
257
+
258
+ // Test that it matches at word boundaries
259
+ const boundaryESM = 'const x = "import foo from bar"'
260
+ const boundaryCJS = 'const x = "module.exports = foo"'
261
+
262
+ assert.not(hasESMSyntax(boundaryESM), 'ESM in string should not be detected')
263
+ assert.not(hasCJSSyntax(boundaryCJS), 'CJS in string should not be detected')
264
+ })
265
+
266
+ test('should handle TypeScript-specific syntax', () => {
267
+ const tsExamples = [
268
+ 'import type { Foo } from "bar"',
269
+ 'export type Foo = string',
270
+ 'import { type Foo } from "bar"',
271
+ 'export interface Foo {}',
272
+ 'export enum Foo {}'
273
+ ]
274
+
275
+ tsExamples.forEach((code, index) => {
276
+ assert.ok(hasESMSyntax(code), `TypeScript example ${index + 1} should be detected as ESM: "${code}"`)
277
+ })
278
+ })
279
+
280
+ test.run()
@@ -0,0 +1,346 @@
1
+ const { test } = require('uvu')
2
+ const assert = require('uvu/assert')
3
+ const {
4
+ getExports,
5
+ getDefaultExports,
6
+ getNamedExports,
7
+ getNamedDeclarationExports,
8
+ getAllExports,
9
+ getNamespaceExports,
10
+ getTypeExports,
11
+ getAssertionExports
12
+ } = require('../src/get-exports.js')
13
+
14
+ test('should extract default exports', () => {
15
+ const code = `
16
+ export default function myFunction() {}
17
+ export default class MyClass {}
18
+ export default { foo: 'bar' }
19
+ export default [1, 2, 3]
20
+ export default 'hello'
21
+ export default 42
22
+ export default () => {}
23
+ `
24
+
25
+ const exports = getDefaultExports(code)
26
+
27
+ assert.ok(exports.length === 6, 'Should find 6 default exports')
28
+
29
+ // Check function export
30
+ const functionExport = exports.find(exp => exp.exportName === 'myFunction')
31
+ assert.ok(functionExport, 'Should find function export')
32
+ assert.equal(functionExport.exportType, 'default')
33
+ assert.equal(functionExport.exportValue, 'function myFunction')
34
+
35
+ // Check class export
36
+ const classExport = exports.find(exp => exp.exportName === 'MyClass')
37
+ assert.ok(classExport, 'Should find class export')
38
+ assert.equal(classExport.exportType, 'default')
39
+ assert.equal(classExport.exportValue, 'class MyClass')
40
+
41
+ // Check object literal
42
+ const objectExport = exports.find(exp => exp.exportName === 'literal')
43
+ assert.ok(objectExport, 'Should find object literal export')
44
+ assert.equal(objectExport.exportValue, "{ foo: 'bar' }")
45
+
46
+ // Check array literal
47
+ const arrayExport = exports.find(exp => exp.exportValue === '[1, 2, 3]')
48
+ assert.ok(arrayExport, 'Should find array literal export')
49
+ assert.equal(arrayExport.exportName, 'literal')
50
+
51
+ // Check number literal
52
+ const numberExport = exports.find(exp => exp.exportValue === '42')
53
+ assert.ok(numberExport, 'Should find number literal export')
54
+ assert.equal(numberExport.exportName, '42')
55
+
56
+ // Check arrow function
57
+ const arrowExport = exports.find(exp => exp.exportValue === '() =>')
58
+ assert.ok(arrowExport, 'Should find arrow function export')
59
+ assert.equal(arrowExport.exportName, 'anonymous')
60
+ })
61
+
62
+ test('should extract named exports', () => {
63
+ const code = `
64
+ export const myVariable = 'value'
65
+ export let anotherVariable = 'value'
66
+ export var thirdVariable = 'value'
67
+ export function myFunction() {}
68
+ export class MyClass {}
69
+ export async function asyncFunction() {}
70
+ `
71
+
72
+ const exports = getNamedExports(code)
73
+
74
+ assert.ok(exports.length === 6, 'Should find 6 named exports')
75
+
76
+ const exportNames = exports.map(exp => exp.exportName)
77
+ assert.ok(exportNames.includes('myVariable'), 'Should find const export')
78
+ assert.ok(exportNames.includes('anotherVariable'), 'Should find let export')
79
+ assert.ok(exportNames.includes('thirdVariable'), 'Should find var export')
80
+ assert.ok(exportNames.includes('myFunction'), 'Should find function export')
81
+ assert.ok(exportNames.includes('MyClass'), 'Should find class export')
82
+ assert.ok(exportNames.includes('asyncFunction'), 'Should find async function export')
83
+
84
+ exports.forEach(exp => {
85
+ assert.equal(exp.exportType, 'named')
86
+ assert.ok(exp.isExport)
87
+ })
88
+ })
89
+
90
+ test('should extract named declaration exports', () => {
91
+ const code = `
92
+ export { myFunction, anotherFunction }
93
+ export { myFunction as aliasedFunction, anotherFunction }
94
+ export { default } from './module'
95
+ export { default as aliasedDefault } from './module'
96
+ export { Component, Button } from './components'
97
+ `
98
+
99
+ const exports = getNamedDeclarationExports(code)
100
+
101
+ assert.ok(exports.length === 8, 'Should find 8 named declaration exports')
102
+
103
+ // Check simple exports
104
+ const simpleExports = exports.filter(exp => !exp.exportAlias)
105
+ assert.ok(simpleExports.length === 6, 'Should have 6 simple exports')
106
+
107
+ // Check aliased exports
108
+ const aliasedExports = exports.filter(exp => exp.exportAlias)
109
+ assert.ok(aliasedExports.length === 2, 'Should have 2 aliased exports')
110
+
111
+ // Check specific aliases
112
+ const aliasedFunction = exports.find(exp => exp.exportAlias === 'aliasedFunction')
113
+ assert.ok(aliasedFunction, 'Should find aliased function export')
114
+ assert.equal(aliasedFunction.exportName, 'myFunction')
115
+
116
+ const aliasedDefault = exports.find(exp => exp.exportAlias === 'aliasedDefault')
117
+ assert.ok(aliasedDefault, 'Should find aliased default export')
118
+ assert.equal(aliasedDefault.exportName, 'default')
119
+
120
+ exports.forEach(exp => {
121
+ assert.equal(exp.exportType, 'named')
122
+ assert.ok(exp.isExport)
123
+ })
124
+ })
125
+
126
+ test('should extract all exports (re-exports)', () => {
127
+ const code = `
128
+ export * from './module'
129
+ export * from './utils'
130
+ export * from '@angular/core'
131
+ `
132
+
133
+ const exports = getAllExports(code)
134
+
135
+ assert.ok(exports.length === 3, 'Should find 3 all exports')
136
+
137
+ const fromModules = exports.map(exp => exp.from)
138
+ assert.ok(fromModules.includes('./module'), 'Should export all from ./module')
139
+ assert.ok(fromModules.includes('./utils'), 'Should export all from ./utils')
140
+ assert.ok(fromModules.includes('@angular/core'), 'Should export all from @angular/core')
141
+
142
+ exports.forEach(exp => {
143
+ assert.equal(exp.exportType, 'all')
144
+ assert.ok(exp.isExport)
145
+ })
146
+ })
147
+
148
+ test('should extract namespace exports', () => {
149
+ const code = `
150
+ export * as namespace from './module'
151
+ export * as utils from './utils'
152
+ export * as angular from '@angular/core'
153
+ `
154
+
155
+ const exports = getNamespaceExports(code)
156
+
157
+ assert.ok(exports.length === 3, 'Should find 3 namespace exports')
158
+
159
+ const namespaceNames = exports.map(exp => exp.namespace)
160
+ assert.ok(namespaceNames.includes('namespace'), 'Should find namespace export')
161
+ assert.ok(namespaceNames.includes('utils'), 'Should find utils namespace export')
162
+ assert.ok(namespaceNames.includes('angular'), 'Should find angular namespace export')
163
+
164
+ const fromModules = exports.map(exp => exp.from)
165
+ assert.ok(fromModules.includes('./module'), 'Should export namespace from ./module')
166
+ assert.ok(fromModules.includes('./utils'), 'Should export namespace from ./utils')
167
+ assert.ok(fromModules.includes('@angular/core'), 'Should export namespace from @angular/core')
168
+
169
+ exports.forEach(exp => {
170
+ assert.equal(exp.exportType, 'namespace')
171
+ assert.ok(exp.isExport)
172
+ })
173
+ })
174
+
175
+ test('should extract type exports', () => {
176
+ const code = `
177
+ export type MyType = string
178
+ export interface MyInterface {}
179
+ export type User = { id: string; name: string }
180
+ export interface ComponentProps { children: React.ReactNode }
181
+ `
182
+
183
+ const exports = getTypeExports(code)
184
+
185
+ assert.ok(exports.length === 4, 'Should find 4 type exports')
186
+
187
+ const typeNames = exports.map(exp => exp.exportName)
188
+ assert.ok(typeNames.includes('MyType'), 'Should find MyType export')
189
+ assert.ok(typeNames.includes('MyInterface'), 'Should find MyInterface export')
190
+ assert.ok(typeNames.includes('User'), 'Should find User type export')
191
+ assert.ok(typeNames.includes('ComponentProps'), 'Should find ComponentProps interface export')
192
+
193
+ exports.forEach(exp => {
194
+ assert.equal(exp.exportType, 'type')
195
+ assert.ok(exp.isExport)
196
+ })
197
+ })
198
+
199
+ test('should extract assertion exports', () => {
200
+ const code = `
201
+ export { default } from './module' assert { type: 'json' }
202
+ export { Component } from './component' with { type: 'css' }
203
+ export { data } from './data.json' assert { type: 'json' }
204
+ `
205
+
206
+ const exports = getAssertionExports(code)
207
+
208
+ assert.ok(exports.length === 3, 'Should find 3 assertion exports')
209
+
210
+ // Check JSON assertion
211
+ const jsonExports = exports.filter(exp => exp.assertion.includes('json'))
212
+ assert.ok(jsonExports.length === 2, 'Should find 2 JSON assertion exports')
213
+
214
+ // Check CSS assertion
215
+ const cssExports = exports.filter(exp => exp.assertion.includes('css'))
216
+ assert.ok(cssExports.length === 1, 'Should find 1 CSS assertion export')
217
+
218
+ // Check assertion types
219
+ const assertExports = exports.filter(exp => exp.assertionType === 'assert')
220
+ const withExports = exports.filter(exp => exp.assertionType === 'with')
221
+ assert.ok(assertExports.length === 2, 'Should find 2 assert exports')
222
+ assert.ok(withExports.length === 1, 'Should find 1 with export')
223
+
224
+ exports.forEach(exp => {
225
+ assert.equal(exp.exportType, 'assertion')
226
+ assert.ok(exp.isExport)
227
+ assert.ok(exp.isAssertion)
228
+ })
229
+ })
230
+
231
+ test('should combine all export types in getExports', () => {
232
+ const code = `
233
+ // Default exports
234
+ export default function myFunction() {}
235
+ export default class MyClass {}
236
+
237
+ // Named exports
238
+ export const myVariable = 'value'
239
+ export function myFunction() {}
240
+
241
+ // Named declaration exports
242
+ export { myFunction, anotherFunction }
243
+ export { myFunction as aliasedFunction }
244
+
245
+ // Re-exports
246
+ export * from './module'
247
+ export * as namespace from './module'
248
+
249
+ // Type exports
250
+ export type MyType = string
251
+ export interface MyInterface {}
252
+ `
253
+
254
+ const exports = getExports(code)
255
+
256
+ assert.ok(exports.length === 11, 'Should find 11 total exports')
257
+
258
+ const exportTypes = exports.map(exp => exp.exportType)
259
+ const defaultCount = exportTypes.filter(type => type === 'default').length
260
+ const namedCount = exportTypes.filter(type => type === 'named').length
261
+ const allCount = exportTypes.filter(type => type === 'all').length
262
+ const namespaceCount = exportTypes.filter(type => type === 'namespace').length
263
+ const typeCount = exportTypes.filter(type => type === 'type').length
264
+
265
+ assert.equal(defaultCount, 2, 'Should have 2 default exports')
266
+ assert.equal(namedCount, 5, 'Should have 5 named exports')
267
+ assert.equal(allCount, 1, 'Should have 1 all export')
268
+ assert.equal(namespaceCount, 1, 'Should have 1 namespace export')
269
+ assert.equal(typeCount, 2, 'Should have 2 type exports')
270
+
271
+ // Check sorting by index
272
+ for (let i = 1; i < exports.length; i++) {
273
+ assert.ok(exports[i].index >= exports[i-1].index, 'Exports should be sorted by index')
274
+ }
275
+ })
276
+
277
+ test('should handle empty code', () => {
278
+ const exports = getExports('')
279
+
280
+ assert.ok(exports.length === 0, 'Should return empty array for empty code')
281
+ })
282
+
283
+ test('should handle code with no exports', () => {
284
+ const code = `
285
+ function myFunction() {}
286
+ const myVariable = 'value'
287
+ import { something } from './module'
288
+ `
289
+
290
+ const exports = getExports(code)
291
+
292
+ assert.ok(exports.length === 0, 'Should return empty array for code with no exports')
293
+ })
294
+
295
+ test('should handle complex export patterns', () => {
296
+ const code = `
297
+ // Complex default exports
298
+ export default (props) => <Component {...props} />
299
+ export default function() { return 'anonymous' }
300
+ export default class { constructor() {} }
301
+
302
+ // Complex named exports with destructuring
303
+ export const { Component, Button } = React
304
+ export const [first, second] = ['a', 'b']
305
+
306
+ // Complex re-exports
307
+ export { default as MyComponent, Button, Input } from './components'
308
+ export {
309
+ default as App,
310
+ Header,
311
+ Footer
312
+ } from './app'
313
+ `
314
+
315
+ const exports = getExports(code)
316
+
317
+ assert.ok(exports.length > 0, 'Should find exports in complex code')
318
+
319
+ // Should handle anonymous functions and classes
320
+ const anonymousExports = exports.filter(exp => exp.exportName === 'anonymous')
321
+ assert.ok(anonymousExports.length >= 2, 'Should find anonymous function and class exports')
322
+
323
+ // Should handle complex named declaration exports
324
+ const namedDeclarations = exports.filter(exp => exp.exportType === 'named')
325
+ assert.ok(namedDeclarations.length >= 6, 'Should find complex named declaration exports')
326
+ })
327
+
328
+ test('should handle assertion exports with options', () => {
329
+ const code = `
330
+ export { default } from './module' assert { type: 'json' }
331
+ export { Component } from './component' with { type: 'css' }
332
+ `
333
+
334
+ const exports = getExports(code, { includeAssertionExports: true })
335
+
336
+ assert.ok(exports.length === 4, 'Should find 4 exports when assertions enabled')
337
+
338
+ const assertionExports = exports.filter(exp => exp.isAssertion)
339
+ assert.ok(assertionExports.length === 2, 'Should find 2 assertion exports')
340
+
341
+ // Test without assertion exports
342
+ const exportsWithoutAssertions = getExports(code, { includeAssertionExports: false })
343
+ assert.ok(exportsWithoutAssertions.length === 2, 'Should find 2 named exports when assertions disabled')
344
+ })
345
+
346
+ test.run()