@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,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(node:*)"
5
+ ],
6
+ "deny": [],
7
+ "ask": []
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidwells/extract-deps",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Get imports and requires from a codebase without AST",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -0,0 +1,351 @@
1
+ // Extracts CommonJS module.exports and exports patterns from JavaScript code
2
+
3
+ const { getNames } = require('./utils')
4
+
5
+ // Matches: module.exports = ... or exports = module.exports = ...
6
+ // https://regex101.com/r/example/cjs-1
7
+ const MODULE_EXPORTS_ASSIGNMENT_REGEX = /^(?!\s*\/\/)(?!\s*\/\*)\s*(?:exports\s*=\s*)?module\.exports\s*=\s*(?!.*module\.exports\.)(.*?)$/gm
8
+
9
+ // Matches: module.exports.propertyName = ... or module.exports['propertyName'] = ...
10
+ // https://regex101.com/r/example/cjs-2
11
+ const MODULE_EXPORTS_PROPERTY_REGEX = /module\.exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\s*(?:module\.exports|exports\.|\/\/|\/\*|$))/gs
12
+
13
+ // Matches: exports.propertyName = ... or exports['propertyName'] = ...
14
+ // https://regex101.com/r/example/cjs-3
15
+ const EXPORTS_PROPERTY_REGEX = /(?<!module\.)exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\s*(?:module\.exports|exports\.|\/\/|\/\*|$))/gs
16
+
17
+ function stripComments(code) {
18
+ let result = code
19
+
20
+ // Remove single-line comments but preserve newlines
21
+ result = result.replace(/\/\/.*$/gm, '')
22
+
23
+ // Remove multi-line comments but preserve line structure
24
+ result = result.replace(/\/\*[\s\S]*?\*\//g, (match) => {
25
+ // Replace comment with equivalent whitespace to preserve positions
26
+ return match.replace(/[^\n]/g, ' ')
27
+ })
28
+
29
+ return result
30
+ }
31
+
32
+ function getCjsExports(code) {
33
+ // Strip comments to prevent false positives from commented code
34
+ const sanitizedCode = stripComments(code)
35
+
36
+ const assignmentExports = getModuleExportsAssignment(code, sanitizedCode)
37
+ const modulePropertyExports = getModuleExportsProperty(code, sanitizedCode)
38
+ const propertyExports = getExportsProperty(code, sanitizedCode)
39
+ const objectAssignExports = getObjectAssignExports(code, sanitizedCode)
40
+
41
+ /*
42
+ console.log('assignmentExports', assignmentExports)
43
+ console.log('modulePropertyExports', modulePropertyExports)
44
+ console.log('propertyExports', propertyExports)
45
+ console.log('objectAssignExports', objectAssignExports)
46
+ /** */
47
+
48
+ const allExports = []
49
+ .concat(assignmentExports)
50
+ .concat(modulePropertyExports)
51
+ .concat(propertyExports)
52
+ .concat(objectAssignExports)
53
+
54
+ return allExports.sort((a, b) => a.index - b.index)
55
+ }
56
+
57
+ function extractExportDefName(exportValue) {
58
+ // Handle function declarations
59
+ if (exportValue.includes('function') && !exportValue.includes('=>')) {
60
+ const match = exportValue.match(/function\s+([\w$]+)/)
61
+ return match ? match[1] : 'anonymous'
62
+ }
63
+
64
+ // Handle class declarations
65
+ if (exportValue.includes('class')) {
66
+ const match = exportValue.match(/class\s+([\w$]+)/)
67
+ return match ? match[1] : 'anonymous'
68
+ }
69
+
70
+ // Handle arrow functions
71
+ if (exportValue.includes('=>')) {
72
+ return 'anonymous'
73
+ }
74
+
75
+ // Handle object/array literals
76
+ if (exportValue.startsWith('{') || exportValue.startsWith('[')) {
77
+ return 'literal'
78
+ }
79
+
80
+ // Handle simple values
81
+ return exportValue.trim()
82
+ }
83
+
84
+ function extractCompleteValue(text, startIndex) {
85
+ const firstChar = text[startIndex]
86
+
87
+ // For objects and arrays, balance braces/brackets
88
+ if (firstChar === '{' || firstChar === '[') {
89
+ const openChar = firstChar
90
+ const closeChar = firstChar === '{' ? '}' : ']'
91
+ let depth = 0
92
+ let i = startIndex
93
+
94
+ while (i < text.length) {
95
+ const char = text[i]
96
+ if (char === openChar) depth++
97
+ else if (char === closeChar) {
98
+ depth--
99
+ if (depth === 0) {
100
+ return text.substring(startIndex, i + 1)
101
+ }
102
+ }
103
+ i++
104
+ }
105
+ // If we couldn't find closing brace, return what we have
106
+ return text.substring(startIndex).split('\n')[0]
107
+ }
108
+
109
+ // For other values, extract to end of line
110
+ return text.substring(startIndex).split('\n')[0]
111
+ }
112
+
113
+ function getModuleExportsAssignment(originalText = '', sanitizedText = originalText) {
114
+ let exportStatements = []
115
+ var result
116
+
117
+ // Run regex on sanitized text to avoid matching commented code
118
+ while((result = MODULE_EXPORTS_ASSIGNMENT_REGEX.exec(sanitizedText)) !== null) {
119
+ const [ line, exportValue ] = result
120
+ const trimmedValue = exportValue.trim()
121
+
122
+ // Extract value from ORIGINAL text to preserve comments
123
+ // Since stripComments preserves positions, indices are the same
124
+ let completeValue = trimmedValue
125
+ if ((trimmedValue.startsWith('{') || trimmedValue.startsWith('[')) &&
126
+ !(trimmedValue.endsWith('}') || trimmedValue.endsWith(']'))) {
127
+ const valueStartIndex = result.index + line.indexOf(exportValue)
128
+ completeValue = extractCompleteValue(originalText, valueStartIndex).trim()
129
+ }
130
+
131
+ // Check if we have unbalanced parentheses (multiline function call)
132
+ const openParens = (trimmedValue.match(/\(/g) || []).length
133
+ const closeParens = (trimmedValue.match(/\)/g) || []).length
134
+ if (openParens > closeParens) {
135
+ const valueStartIndex = result.index + line.indexOf(exportValue)
136
+ let depth = 0
137
+ let i = valueStartIndex
138
+
139
+ while (i < originalText.length) {
140
+ const char = originalText[i]
141
+ if (char === '(') depth++
142
+ else if (char === ')') {
143
+ depth--
144
+ if (depth === 0) {
145
+ completeValue = originalText.substring(valueStartIndex, i + 1).trim()
146
+ break
147
+ }
148
+ }
149
+ i++
150
+ }
151
+ }
152
+
153
+ // Extract the same value from SANITIZED text for parsing definitions
154
+ let sanitizedCompleteValue = trimmedValue
155
+ if ((trimmedValue.startsWith('{') || trimmedValue.startsWith('[')) &&
156
+ !(trimmedValue.endsWith('}') || trimmedValue.endsWith(']'))) {
157
+ const valueStartIndex = result.index + line.indexOf(exportValue)
158
+ sanitizedCompleteValue = extractCompleteValue(sanitizedText, valueStartIndex).trim()
159
+ }
160
+ if (openParens > closeParens) {
161
+ const valueStartIndex = result.index + line.indexOf(exportValue)
162
+ let depth = 0
163
+ let i = valueStartIndex
164
+
165
+ while (i < sanitizedText.length) {
166
+ const char = sanitizedText[i]
167
+ if (char === '(') depth++
168
+ else if (char === ')') {
169
+ depth--
170
+ if (depth === 0) {
171
+ sanitizedCompleteValue = sanitizedText.substring(valueStartIndex, i + 1).trim()
172
+ break
173
+ }
174
+ }
175
+ i++
176
+ }
177
+ }
178
+
179
+ const exportDefName = extractExportDefName(sanitizedCompleteValue)
180
+
181
+ // Determine export type based on sanitized patterns
182
+ let exportType = 'default'
183
+
184
+ if (sanitizedCompleteValue.trim().startsWith('Object.assign')) {
185
+ exportType = 'object-assign'
186
+ }
187
+ else if (sanitizedCompleteValue.startsWith('{') && sanitizedCompleteValue.includes('...')) {
188
+ exportType = 'object-spread'
189
+ }
190
+
191
+ const exportStatement = {
192
+ line: line.trim(),
193
+ exportType,
194
+ exportName: '',
195
+ exportDefinitionName: exportDefName,
196
+ exportValue: completeValue, // Use original value with comments
197
+ index: result.index,
198
+ isExport: true
199
+ }
200
+
201
+ // Parse object literals using SANITIZED value (no comments)
202
+ if (sanitizedCompleteValue.startsWith('{') && sanitizedCompleteValue.endsWith('}')) {
203
+ const definitions = getNames(sanitizedCompleteValue, 'definition')
204
+ if (definitions) {
205
+ exportStatement.definitions = definitions
206
+ exportStatement.keys = definitions.destructuredParameters.map(param => param.name)
207
+ }
208
+ }
209
+
210
+ exportStatements.push(exportStatement)
211
+ }
212
+
213
+ return exportStatements
214
+ }
215
+
216
+ function getModuleExportsProperty(originalText = '', sanitizedText = originalText) {
217
+ let exportStatements = []
218
+ var result
219
+
220
+ while((result = MODULE_EXPORTS_PROPERTY_REGEX.exec(sanitizedText)) !== null) {
221
+ const [ line, dotPropertyName, bracketPropertyName, propertyValue ] = result
222
+
223
+ // Use bracket notation property name if dot notation is undefined
224
+ const propertyName = dotPropertyName || bracketPropertyName
225
+
226
+ exportStatements.push({
227
+ line: line.trim(),
228
+ exportType: 'named',
229
+ exportName: propertyName.trim(),
230
+ exportValue: propertyValue.trim(),
231
+ index: result.index,
232
+ isExport: true
233
+ })
234
+ }
235
+
236
+ return exportStatements
237
+ }
238
+
239
+ function getExportsProperty(originalText = '', sanitizedText = originalText) {
240
+ let exportStatements = []
241
+ var result
242
+
243
+ while((result = EXPORTS_PROPERTY_REGEX.exec(sanitizedText)) !== null) {
244
+ const [ line, dotPropertyName, bracketPropertyName, propertyValue ] = result
245
+
246
+ // Use bracket notation property name if dot notation is undefined
247
+ const propertyName = dotPropertyName || bracketPropertyName
248
+
249
+ exportStatements.push({
250
+ line: line.trim(),
251
+ exportType: 'named',
252
+ exportName: propertyName.trim(),
253
+ exportValue: propertyValue.trim(),
254
+ index: result.index,
255
+ isExport: true
256
+ })
257
+ }
258
+
259
+ return exportStatements
260
+ }
261
+
262
+ function getObjectAssignExports(originalText = '', sanitizedText = originalText) {
263
+ let exportStatements = []
264
+
265
+ // Find all Object.assign calls with exports
266
+ const objectAssignPattern = /Object\.assign\s*\(\s*((?:module\.)?exports)\s*,/g
267
+ var result
268
+
269
+ while((result = objectAssignPattern.exec(sanitizedText)) !== null) {
270
+
271
+ const exportsTarget = result[1]
272
+
273
+ // Find the opening parenthesis of Object.assign and extract from ORIGINAL text
274
+ const openParenIndex = originalText.indexOf('(', result.index)
275
+
276
+ // Balance parentheses to find the complete Object.assign call
277
+ let depth = 0
278
+ let i = openParenIndex
279
+ let completeCall = ''
280
+
281
+ while (i < originalText.length) {
282
+ const char = originalText[i]
283
+ if (char === '(') depth++
284
+ else if (char === ')') {
285
+ depth--
286
+ if (depth === 0) {
287
+ completeCall = originalText.substring(result.index, i + 1)
288
+ break
289
+ }
290
+ }
291
+ i++
292
+ }
293
+
294
+ if (!completeCall) {
295
+ continue
296
+ }
297
+
298
+ const exportStatement = {
299
+ line: completeCall.split('\n')[0].trim(),
300
+ exportType: exportsTarget === 'module.exports' ? 'default' : 'object-assign',
301
+ exportName: 'Object.assign',
302
+ exportValue: completeCall, // Original text with comments
303
+ index: result.index,
304
+ isExport: true
305
+ }
306
+
307
+ exportStatements.push(exportStatement)
308
+ }
309
+
310
+ return exportStatements
311
+ }
312
+
313
+ if(require.main === module) {
314
+ const code = `
315
+ // module.exports assignment
316
+ module.exports = function hello() {}
317
+
318
+ // module.exports properties
319
+ module.exports.hello = async event => {
320
+ console.log('lodash', lodash)
321
+ return {
322
+ statusCode: 200,
323
+ body: JSON.stringify(
324
+ {
325
+ message: 'Go Serverless v1.0! Your function executed successfully!',
326
+ input: event,
327
+ },
328
+ null,
329
+ 2
330
+ ),
331
+ }
332
+ }
333
+ module.exports.world = function() { return 'world' }
334
+ module.exports.MyClass = class MyClass {}
335
+
336
+ // exports properties
337
+ exports.foo = function() {}
338
+ exports.bar = 'bar'
339
+ `
340
+
341
+ const exportsFound = getCjsExports(code)
342
+ exportsFound.forEach(item => console.log(item))
343
+ }
344
+
345
+ module.exports = {
346
+ getCjsExports,
347
+ getModuleExportsAssignment,
348
+ getModuleExportsProperty,
349
+ getExportsProperty,
350
+ getObjectAssignExports
351
+ }
@@ -180,12 +180,15 @@ function getAssertionExports(targetText = '') {
180
180
  const namesValues = (names || '').trim()
181
181
  // Remove leading newlines and whitespace from the line
182
182
  line = line.replace(/^\s+|\s+$/g, '')
183
-
183
+
184
+ const definitions = getNames(namesValues)
185
+
184
186
  exportStatements.push({
185
187
  line,
186
188
  exportType: 'assertion',
187
189
  exportName: namesValues,
188
- definitions: getNames(namesValues),
190
+ definitions,
191
+ keys: definitions.destructuredParameters.map(param => param.name),
189
192
  from,
190
193
  assertion: assertion.trim(),
191
194
  assertionType: keyword,
@@ -238,7 +241,7 @@ function parseExportsList(exportsList) {
238
241
  } else {
239
242
  exports.push({
240
243
  name: item,
241
- alias: null
244
+ alias: undefined
242
245
  })
243
246
  }
244
247
  })
package/src/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  const { getImports, getStaticImports, getDynamicImports } = require('./get-imports')
2
2
  const { getRequires, getStaticRequires, getDynamicRequires } = require('./get-requires')
3
+ const { getCjsExports } = require('./get-cjs-exports')
4
+ const { detectSyntax, hasESMSyntax, hasCJSSyntax } = require('./detect-syntax')
3
5
  const {
4
6
  getExports,
5
7
  getDefaultExports,
@@ -21,7 +23,15 @@ function extractDeps(code, filePath = '') {
21
23
  // console.log('requires', requires)
22
24
  const foundExports = getExports(code)
23
25
  // console.log('exports', exports)
24
- return { imports, requires, exports: foundExports }
26
+ const moduleExports = getCjsExports(code)
27
+ // console.log('moduleExports', moduleExports)
28
+ return {
29
+ imports,
30
+ exports: foundExports,
31
+ // CJS
32
+ requires,
33
+ moduleExports
34
+ }
25
35
  }
26
36
 
27
37
  if (require.main === module) {
@@ -71,6 +81,10 @@ const { execSync, spawnSync } = require('child_process')
71
81
 
72
82
  module.exports = {
73
83
  extractDeps,
84
+ /* syntax detection */
85
+ hasESMSyntax,
86
+ hasCJSSyntax,
87
+ detectSyntax,
74
88
  /* Requires */
75
89
  getRequires,
76
90
  getDynamicRequires,
package/src/utils.js CHANGED
@@ -11,27 +11,111 @@ function isRelativeImportWithQuotes(importLocation) {
11
11
  && importLocation.indexOf(' + ') === -1 // No string concatenation
12
12
  }
13
13
 
14
- function getNames(names) {
14
+ function extractTopLevelKeys(objectLiteral) {
15
+ // Remove outer braces and trim
16
+ const content = objectLiteral.trim().replace(/^\{/, '').replace(/\}$/, '').trim()
17
+
18
+ if (!content) {
19
+ return []
20
+ }
21
+
22
+ const keys = []
23
+ let currentKey = ''
24
+ let depth = 0
25
+ let inString = false
26
+ let stringChar = null
27
+ let i = 0
28
+
29
+ while (i < content.length) {
30
+ const char = content[i]
31
+ const prevChar = i > 0 ? content[i - 1] : ''
32
+
33
+ // Handle string boundaries
34
+ if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
35
+ if (!inString) {
36
+ inString = true
37
+ stringChar = char
38
+ } else if (char === stringChar) {
39
+ inString = false
40
+ stringChar = null
41
+ }
42
+ }
43
+
44
+ if (!inString) {
45
+ // Track depth for nested objects/arrays
46
+ if (char === '{' || char === '[') {
47
+ depth++
48
+ } else if (char === '}' || char === ']') {
49
+ depth--
50
+ }
51
+
52
+ // At top level (depth 0), look for property separators
53
+ if (depth === 0) {
54
+ if (char === ':') {
55
+ // Found a key
56
+ const key = currentKey.trim()
57
+ if (key) {
58
+ keys.push({ name: key })
59
+ }
60
+ currentKey = ''
61
+
62
+ // Skip to next comma or end
63
+ let j = i + 1
64
+ depth = 0
65
+ while (j < content.length) {
66
+ const c = content[j]
67
+ if (c === '{' || c === '[') depth++
68
+ else if (c === '}' || c === ']') depth--
69
+ else if (c === ',' && depth === 0) {
70
+ i = j
71
+ break
72
+ }
73
+ j++
74
+ }
75
+ if (j === content.length) {
76
+ i = j - 1
77
+ }
78
+ } else if (char === ',') {
79
+ currentKey = ''
80
+ } else if (char !== ' ' && char !== '\n' && char !== '\t') {
81
+ currentKey += char
82
+ }
83
+ }
84
+ }
85
+
86
+ i++
87
+ }
88
+
89
+ return keys
90
+ }
91
+
92
+ function getNames(names, aliasKey = 'alias') {
15
93
  // console.log('names', names)
16
94
  // Safety check for undefined or null names
17
95
  if (!names || names.trim() === '') {
18
96
  return null
19
97
  }
20
98
  const results = inspectParameters(names).map((item) => {
99
+ /*
100
+ console.log('item', item)
101
+ /** */
21
102
  item.name = item.parameter
22
103
  if (item.destructuredParameters && item.destructuredParameters.length > 0) {
23
104
  item.destructuredParameters = item.destructuredParameters.map((details) => {
105
+ /*
106
+ console.log('details', details)
107
+ /** */
24
108
  if (details.parameter.indexOf(':') > -1) {
25
- const [ name, alias ] = details.parameter.replace(/^{/, '').replace(/}$/, '').split(':')
109
+ const [ name, alias ] = details['declaration'].replace(/^{/, '').replace(/}$/, '').split(':')
26
110
  details.name = name.trim()
27
- details.alias = alias.trim()
111
+ details[aliasKey]= alias.trim()
28
112
  delete details.parameter
29
113
  return details
30
114
  }
31
115
  if (details.declaration.indexOf(' as ') > -1) {
32
116
  const [ parameter, alias ] = details.declaration.split(' as ')
33
117
  details.name = parameter.trim()
34
- details.alias = alias.trim()
118
+ details[aliasKey] = alias.trim()
35
119
  delete details.parameter
36
120
  return details
37
121
  }
@@ -51,9 +135,23 @@ function getNames(names) {
51
135
 
52
136
  return item
53
137
  })
54
-
138
+
55
139
  // Return the first (and only) result instead of the array
56
- return results.length > 0 ? results[0] : null
140
+ const result = results.length > 0 ? results[0] : null
141
+
142
+ // If we have an object with empty destructuredParameters, try to extract top-level keys
143
+ if (result &&
144
+ result.expectsDestructuring &&
145
+ result.destructuredParameters &&
146
+ result.destructuredParameters.length === 0 &&
147
+ names.trim().startsWith('{')) {
148
+ const topLevelKeys = extractTopLevelKeys(names)
149
+ if (topLevelKeys.length > 0) {
150
+ result.destructuredParameters = topLevelKeys
151
+ }
152
+ }
153
+
154
+ return result
57
155
  }
58
156
 
59
157