@davidwells/extract-deps 0.0.4 → 0.0.6
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.
- package/.claude/settings.local.json +7 -1
- package/examples/verify-basic.js +45 -0
- package/examples/verify-build-graph.js +58 -0
- package/examples/verify-build-order.js +113 -0
- package/examples/verify-find-dependents.js +74 -0
- package/examples/verify-impact-analysis.js +97 -0
- package/examples/verify-package-entries.js +74 -0
- package/package.json +6 -2
- package/src/dep-graph.js +337 -0
- package/src/get-cjs-exports.js +112 -7
- package/src/get-imports.js +1 -0
- package/src/utils.js +1 -1
- package/src/vendor/inspect-parameters-declaration.js +282 -0
- package/src/verify-package-entries.js +228 -0
- package/tests/dep-graph.test.js +358 -0
- package/tests/fixtures/cjs-broken-example/index.js +8 -0
- package/tests/fixtures/cjs-broken-example/one.js +9 -0
- package/tests/fixtures/cjs-broken-example/package.json +7 -0
- package/tests/fixtures/cjs-broken-example/sub-folder/sub-sub-folder/three.js +6 -0
- package/tests/fixtures/cjs-broken-example/sub-folder/two.js +8 -0
- package/tests/fixtures/cjs-deep-example/d-one.js +10 -0
- package/tests/fixtures/cjs-deep-example/d-sub-folder/d-sub-sub-folder/d-three.js +7 -0
- package/tests/fixtures/cjs-deep-example/d-sub-folder/d-two-a.js +8 -0
- package/tests/fixtures/cjs-deep-example/d-sub-folder/d-two-b.js +8 -0
- package/tests/fixtures/cjs-deep-example/d-sub-folder/d-two.js +8 -0
- package/tests/fixtures/cjs-deep-example/index.js +8 -0
- package/tests/fixtures/cjs-deep-example/package.json +15 -0
- package/tests/fixtures/cjs-example/index.js +8 -0
- package/tests/fixtures/cjs-example/one.js +9 -0
- package/tests/fixtures/cjs-example/package.json +7 -0
- package/tests/fixtures/cjs-example/sub-folder/sub-sub-folder/three.js +6 -0
- package/tests/fixtures/cjs-example/sub-folder/two.js +8 -0
- package/tests/fixtures/cjs-parent.js +5 -0
- package/tests/fixtures/esm-broken-fixture/index.js +8 -0
- package/tests/fixtures/esm-broken-fixture/one.js +9 -0
- package/tests/fixtures/esm-broken-fixture/package.json +15 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder/three.js +6 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/deep.js +6 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/even-deeper/wow-deep.js +5 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/two.js +8 -0
- package/tests/fixtures/esm-broken-fixture/valid.js +7 -0
- package/tests/fixtures/esm-fixture/index.js +8 -0
- package/tests/fixtures/esm-fixture/one.js +9 -0
- package/tests/fixtures/esm-fixture/package.json +11 -0
- package/tests/fixtures/esm-fixture/sub-folder/sub-sub-folder/three.js +6 -0
- package/tests/fixtures/esm-fixture/sub-folder/two.js +8 -0
- package/tests/get-cjs-exports.test.js +171 -0
- package/tests/get-imports.test.js +4 -1
- package/tests/manual/broken-cjs.js +38 -0
- package/tests/manual/broken-esm.js +37 -0
- package/tests/verify-package-entries.test.js +272 -0
- package/types/dep-graph.d.ts +22 -0
- package/types/dep-graph.d.ts.map +1 -0
- package/types/get-cjs-exports.d.ts +35 -0
- package/types/get-cjs-exports.d.ts.map +1 -0
- package/types/get-exports.d.ts +3 -1
- package/types/get-exports.d.ts.map +1 -1
- package/types/index.d.ts +7 -3
- package/types/index.d.ts.map +1 -1
- package/types/utils.d.ts +2 -1
- package/types/utils.d.ts.map +1 -1
package/src/dep-graph.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// Build dependency graph and optionally verify imports
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const { pathToFileURL, fileURLToPath } = require('url')
|
|
5
|
+
const { resolve: resolveImport } = require('@dual-bundle/import-meta-resolve')
|
|
6
|
+
const { extractDeps } = require('./index')
|
|
7
|
+
|
|
8
|
+
function fsExistsSync(path) {
|
|
9
|
+
try {
|
|
10
|
+
fs.accessSync(path, fs.constants.F_OK)
|
|
11
|
+
return true
|
|
12
|
+
} catch (error) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a relative import to an actual file path
|
|
19
|
+
* @param {string} baseDir - Directory containing the importing file
|
|
20
|
+
* @param {string} importPath - The relative import path
|
|
21
|
+
* @returns {string|null} - Resolved file path or null if not found
|
|
22
|
+
*/
|
|
23
|
+
function resolveImportPath(baseDir, importPath) {
|
|
24
|
+
const possibleExtensions = ['.js', '.mjs', '.ts', '.json', '']
|
|
25
|
+
|
|
26
|
+
// Try direct file resolution
|
|
27
|
+
for (const ext of possibleExtensions) {
|
|
28
|
+
const testPath = path.resolve(baseDir, importPath + ext)
|
|
29
|
+
if (fsExistsSync(testPath)) {
|
|
30
|
+
return testPath
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Try directory with index file
|
|
35
|
+
const indexExtensions = ['/index.js', '/index.mjs', '/index.ts']
|
|
36
|
+
for (const indexExt of indexExtensions) {
|
|
37
|
+
const testPath = path.resolve(baseDir, importPath + indexExt)
|
|
38
|
+
if (fsExistsSync(testPath)) {
|
|
39
|
+
return testPath
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Verify that imported values are exported from a file
|
|
48
|
+
* @param {object} dep - Dependency object with import info
|
|
49
|
+
* @param {object} targetDeps - Extracted deps from the target file
|
|
50
|
+
* @param {string} targetPath - Path to the target file
|
|
51
|
+
* @param {string} sourcePath - Path to the file doing the importing
|
|
52
|
+
* @param {Array<Error>} errors - Array to collect errors
|
|
53
|
+
* @param {boolean} throwOnFirstError - Whether to throw immediately on first error
|
|
54
|
+
*/
|
|
55
|
+
function verifyImportedValuesExist(dep, targetDeps, targetPath, sourcePath, errors, throwOnFirstError) {
|
|
56
|
+
// Check if this is a destructured import
|
|
57
|
+
if (dep.definitions && dep.definitions.expectsDestructuring && dep.definitions.destructuredParameters) {
|
|
58
|
+
const importedNames = dep.definitions.destructuredParameters.map(p => p.name.replace(/,$/, ''))
|
|
59
|
+
|
|
60
|
+
// Check CommonJS exports
|
|
61
|
+
const exportedNames = new Set()
|
|
62
|
+
if (targetDeps.moduleExports && targetDeps.moduleExports.length > 0) {
|
|
63
|
+
for (const exp of targetDeps.moduleExports) {
|
|
64
|
+
if (exp.exportType === 'named') {
|
|
65
|
+
exportedNames.add(exp.exportName)
|
|
66
|
+
} else if (exp.exportType === 'default' && exp.keys) {
|
|
67
|
+
exp.keys.forEach(key => exportedNames.add(key))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check ESM exports
|
|
73
|
+
if (targetDeps.exports && targetDeps.exports.length > 0) {
|
|
74
|
+
for (const exp of targetDeps.exports) {
|
|
75
|
+
const exportedName = exp.exportAlias || exp.exportName
|
|
76
|
+
if (exportedName) {
|
|
77
|
+
exportedNames.add(exportedName)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Verify each imported name is exported
|
|
83
|
+
for (const importedName of importedNames) {
|
|
84
|
+
if (!exportedNames.has(importedName)) {
|
|
85
|
+
const type = (dep.line.indexOf('import') !== -1 ? 'Import' : 'Require')
|
|
86
|
+
const error = new Error(
|
|
87
|
+
`\n${type} "${importedName}" not found from statement:\n` +
|
|
88
|
+
`${dep.line}\n` +
|
|
89
|
+
`in "${sourcePath} file.\n\n` +
|
|
90
|
+
`The "${importedName}" value is not exported from file "${targetPath}".\n\n` +
|
|
91
|
+
`Available exports: ${Array.from(exportedNames).join(', ') || 'none'}`
|
|
92
|
+
)
|
|
93
|
+
if (throwOnFirstError) {
|
|
94
|
+
throw error
|
|
95
|
+
}
|
|
96
|
+
errors.push(error)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// For non-destructured imports, just verify there's a default export
|
|
101
|
+
else {
|
|
102
|
+
const hasDefaultExport =
|
|
103
|
+
(targetDeps.moduleExports && targetDeps.moduleExports.length > 0) ||
|
|
104
|
+
(targetDeps.exports && targetDeps.exports.some(exp => exp.exportType === 'default'))
|
|
105
|
+
|
|
106
|
+
if (!hasDefaultExport) {
|
|
107
|
+
const error = new Error(
|
|
108
|
+
`Import not found: "${targetPath}" does not have a default export.\n` +
|
|
109
|
+
`Import statement: ${dep.line}\n` +
|
|
110
|
+
`Importing from: ${sourcePath}`
|
|
111
|
+
)
|
|
112
|
+
if (throwOnFirstError) {
|
|
113
|
+
throw error
|
|
114
|
+
}
|
|
115
|
+
errors.push(error)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Recursively build dependency graph and optionally validate imports
|
|
122
|
+
* @param {string} filePath - File to process
|
|
123
|
+
* @param {Array<Error>} errors - Array to collect errors
|
|
124
|
+
* @param {boolean} throwOnFirstError - Whether to throw immediately on first error
|
|
125
|
+
* @param {boolean} verify - Whether to verify imports/exports
|
|
126
|
+
* @param {boolean} verifyNodeModules - Whether to verify node_modules can be resolved
|
|
127
|
+
* @param {Map<string, string[]>|null} graph - Optional map to collect dependency graph
|
|
128
|
+
* @param {Set} visited - Set of already-visited file paths to avoid cycles
|
|
129
|
+
*/
|
|
130
|
+
function buildGraphRecursively(
|
|
131
|
+
filePath,
|
|
132
|
+
errors,
|
|
133
|
+
throwOnFirstError,
|
|
134
|
+
verify,
|
|
135
|
+
verifyNodeModules,
|
|
136
|
+
graph = null,
|
|
137
|
+
visited = new Set(),
|
|
138
|
+
) {
|
|
139
|
+
// Avoid infinite loops
|
|
140
|
+
const normalizedPath = fs.realpathSync(filePath)
|
|
141
|
+
if (visited.has(normalizedPath)) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
visited.add(normalizedPath)
|
|
145
|
+
|
|
146
|
+
// Initialize graph entry for this file if building graph
|
|
147
|
+
if (graph !== null && !graph.has(normalizedPath)) {
|
|
148
|
+
graph.set(normalizedPath, [])
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Read and extract deps from this file
|
|
152
|
+
const content = fs.readFileSync(filePath, 'utf8')
|
|
153
|
+
const deps = extractDeps(content)
|
|
154
|
+
|
|
155
|
+
// Collect all relative dependencies
|
|
156
|
+
const relativeDeps = []
|
|
157
|
+
if (deps.requires && deps.requires.length > 0) {
|
|
158
|
+
relativeDeps.push(...deps.requires.filter(req => req.isRelative))
|
|
159
|
+
}
|
|
160
|
+
if (deps.imports && deps.imports.length > 0) {
|
|
161
|
+
relativeDeps.push(...deps.imports.filter(imp => imp.isRelative))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Collect all non-relative dependencies (node_modules)
|
|
165
|
+
const nodeModuleDeps = []
|
|
166
|
+
if (verifyNodeModules) {
|
|
167
|
+
if (deps.requires && deps.requires.length > 0) {
|
|
168
|
+
nodeModuleDeps.push(...deps.requires.filter(req => !req.isRelative))
|
|
169
|
+
}
|
|
170
|
+
if (deps.imports && deps.imports.length > 0) {
|
|
171
|
+
nodeModuleDeps.push(...deps.imports.filter(imp => !imp.isRelative))
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Process each relative dependency
|
|
176
|
+
const fileDir = path.dirname(filePath)
|
|
177
|
+
for (const dep of relativeDeps) {
|
|
178
|
+
const { from, line } = dep
|
|
179
|
+
|
|
180
|
+
// Resolve the import path
|
|
181
|
+
const resolvedPath = resolveImportPath(fileDir, from)
|
|
182
|
+
|
|
183
|
+
if (!resolvedPath) {
|
|
184
|
+
const error = new Error(
|
|
185
|
+
`"${line}" could not be resolved in ${filePath}\n\n` +
|
|
186
|
+
`Fix by ensuring "${path.resolve(fileDir, from)}" exists and is valid.`
|
|
187
|
+
)
|
|
188
|
+
if (throwOnFirstError) {
|
|
189
|
+
throw error
|
|
190
|
+
}
|
|
191
|
+
errors.push(error)
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Add edge to graph if building
|
|
196
|
+
if (graph !== null) {
|
|
197
|
+
const normalizedResolvedPath = fs.realpathSync(resolvedPath)
|
|
198
|
+
graph.get(normalizedPath).push(normalizedResolvedPath)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Verify imports if verification is enabled
|
|
202
|
+
if (verify) {
|
|
203
|
+
// Extract deps from the imported file
|
|
204
|
+
const targetContent = fs.readFileSync(resolvedPath, 'utf8')
|
|
205
|
+
const targetDeps = extractDeps(targetContent)
|
|
206
|
+
|
|
207
|
+
// Verify the imported values are exported
|
|
208
|
+
verifyImportedValuesExist(dep, targetDeps, resolvedPath, filePath, errors, throwOnFirstError)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Recursively process the imported file
|
|
212
|
+
buildGraphRecursively(resolvedPath, errors, throwOnFirstError, verify, verifyNodeModules, graph, visited)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Verify node_modules dependencies if enabled
|
|
216
|
+
if (verifyNodeModules && nodeModuleDeps.length > 0) {
|
|
217
|
+
const parentURL = pathToFileURL(filePath).href
|
|
218
|
+
|
|
219
|
+
for (const dep of nodeModuleDeps) {
|
|
220
|
+
const { from, line } = dep
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// Try to resolve the module using Node's resolution algorithm
|
|
224
|
+
const resolved = resolveImport(from, parentURL)
|
|
225
|
+
//console.log('resolved', resolved)
|
|
226
|
+
// If we get here, the module resolved successfully
|
|
227
|
+
} catch (error) {
|
|
228
|
+
const err = new Error(
|
|
229
|
+
`"${line}" could not be resolved in ${filePath}\n\n` +
|
|
230
|
+
`The package "${from}" is not installed or cannot be found.\n` +
|
|
231
|
+
`Run "npm install ${from}" to fix this issue.\n\n` +
|
|
232
|
+
`Resolution error: ${error.message}`
|
|
233
|
+
)
|
|
234
|
+
if (throwOnFirstError) {
|
|
235
|
+
throw err
|
|
236
|
+
}
|
|
237
|
+
errors.push(err)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Build dependency graph for a file and its dependencies
|
|
245
|
+
* @param {string} filePath - File to analyze
|
|
246
|
+
* @param {object} options - Options
|
|
247
|
+
* @param {boolean} [options.buildGraph] - If true, build and return dependency graph. Default: true
|
|
248
|
+
* @param {boolean} [options.verifyGraph] - If true, verify imports/exports. Default: true
|
|
249
|
+
* @param {boolean} [options.verifyNodeModules] - If true, verify node_modules packages can be resolved. Default: false
|
|
250
|
+
* @param {boolean} [options.throwOnFirstError] - If true, throw immediately on first error. Default: false
|
|
251
|
+
* @returns {Map<string, string[]>|undefined} - Dependency graph where keys are file paths and values are arrays of dependency paths. Returns undefined if buildGraph is false.
|
|
252
|
+
*/
|
|
253
|
+
function depGraph(filePath, options = {}) {
|
|
254
|
+
const {
|
|
255
|
+
buildGraph = true,
|
|
256
|
+
verifyGraph = true,
|
|
257
|
+
verifyNodeModules = false,
|
|
258
|
+
throwOnFirstError = false,
|
|
259
|
+
} = options
|
|
260
|
+
const errors = []
|
|
261
|
+
const graph = buildGraph ? new Map() : null
|
|
262
|
+
|
|
263
|
+
buildGraphRecursively(filePath, errors, throwOnFirstError, verifyGraph, verifyNodeModules, graph)
|
|
264
|
+
|
|
265
|
+
if (errors.length > 0) {
|
|
266
|
+
const errorMessage = errors.map((err, idx) => `Dep graph error:\n ${err.message}`).join('\n')
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Found ${errors.length} validation error${errors.length === 1 ? '' : 's'}:\n\n${errorMessage}`
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (buildGraph) {
|
|
273
|
+
return graph
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get all dependency file paths in build order (dependencies first), deduplicated
|
|
280
|
+
* @param {string} filePath - Entry file path
|
|
281
|
+
* @param {object} options - Options to pass to depGraph
|
|
282
|
+
* @returns {string[]} - Array of unique dependency file paths in topological order
|
|
283
|
+
*/
|
|
284
|
+
function resolveDepPaths(filePath, options = {}) {
|
|
285
|
+
const graph = depGraph(filePath, options)
|
|
286
|
+
|
|
287
|
+
// Calculate in-degree (number of dependencies) for each file
|
|
288
|
+
const inDegree = new Map()
|
|
289
|
+
for (const [file, deps] of graph) {
|
|
290
|
+
inDegree.set(file, deps.length)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Queue of files with no dependencies (can be processed first)
|
|
294
|
+
const queue = []
|
|
295
|
+
for (const [file, degree] of inDegree) {
|
|
296
|
+
if (degree === 0) {
|
|
297
|
+
queue.push(file)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const buildOrder = []
|
|
302
|
+
|
|
303
|
+
while (queue.length > 0) {
|
|
304
|
+
const file = queue.shift()
|
|
305
|
+
buildOrder.push(file)
|
|
306
|
+
|
|
307
|
+
// Find all files that depend on this file and decrement their in-degree
|
|
308
|
+
for (const [dependent, deps] of graph) {
|
|
309
|
+
if (deps.includes(file)) {
|
|
310
|
+
const newDegree = inDegree.get(dependent) - 1
|
|
311
|
+
inDegree.set(dependent, newDegree)
|
|
312
|
+
if (newDegree === 0) {
|
|
313
|
+
queue.push(dependent)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return buildOrder
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if (require.main === module) {
|
|
324
|
+
const entry = path.resolve(__dirname, '../tests/fixtures/cjs-deep-example/index.js')
|
|
325
|
+
const graph = depGraph(entry, {
|
|
326
|
+
verifyNodeModules: false,
|
|
327
|
+
throwOnFirstError: true
|
|
328
|
+
})
|
|
329
|
+
console.log('graph', graph)
|
|
330
|
+
const depPathsProper = resolveDepPaths(entry)
|
|
331
|
+
console.log('depPathsProper', depPathsProper)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = {
|
|
335
|
+
depGraph,
|
|
336
|
+
resolveDepPaths
|
|
337
|
+
}
|
package/src/get-cjs-exports.js
CHANGED
|
@@ -8,17 +8,22 @@ const MODULE_EXPORTS_ASSIGNMENT_REGEX = /^(?!\s*\/\/)(?!\s*\/\*)\s*(?:exports\s*
|
|
|
8
8
|
|
|
9
9
|
// Matches: module.exports.propertyName = ... or module.exports['propertyName'] = ...
|
|
10
10
|
// https://regex101.com/r/example/cjs-2
|
|
11
|
-
const MODULE_EXPORTS_PROPERTY_REGEX = /module\.exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\s
|
|
11
|
+
const MODULE_EXPORTS_PROPERTY_REGEX = /module\.exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\n\s*\n|(?:module\.exports|exports\.|Object\.defineProperty|\/\/|\/\*|$))/gs
|
|
12
12
|
|
|
13
13
|
// Matches: exports.propertyName = ... or exports['propertyName'] = ...
|
|
14
14
|
// https://regex101.com/r/example/cjs-3
|
|
15
|
-
const EXPORTS_PROPERTY_REGEX = /(?<!module\.)exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\s
|
|
15
|
+
const EXPORTS_PROPERTY_REGEX = /(?<!module\.)exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\n\s*\n|(?:module\.exports|exports\.|Object\.defineProperty|\/\/|\/\*|$))/gs
|
|
16
16
|
|
|
17
17
|
function stripComments(code) {
|
|
18
18
|
let result = code
|
|
19
19
|
|
|
20
|
-
// Remove single-line comments but preserve
|
|
21
|
-
|
|
20
|
+
// Remove single-line comments but preserve string positions
|
|
21
|
+
// This is critical: we must preserve the exact length of the string so that
|
|
22
|
+
// indices found in sanitized text match indices in original text
|
|
23
|
+
result = result.replace(/\/\/(.*)$/gm, (match, commentText) => {
|
|
24
|
+
// Replace // and comment text with spaces (same length as original)
|
|
25
|
+
return ' ' + ' '.repeat(commentText.length)
|
|
26
|
+
})
|
|
22
27
|
|
|
23
28
|
// Remove multi-line comments but preserve line structure
|
|
24
29
|
result = result.replace(/\/\*[\s\S]*?\*\//g, (match) => {
|
|
@@ -36,13 +41,22 @@ function getCjsExports(code) {
|
|
|
36
41
|
const assignmentExports = getModuleExportsAssignment(code, sanitizedCode)
|
|
37
42
|
const modulePropertyExports = getModuleExportsProperty(code, sanitizedCode)
|
|
38
43
|
const propertyExports = getExportsProperty(code, sanitizedCode)
|
|
39
|
-
|
|
44
|
+
|
|
45
|
+
// Quick pre-checks to avoid expensive regex operations when not needed
|
|
46
|
+
const objectAssignExports = sanitizedCode.includes('Object.assign')
|
|
47
|
+
? getObjectAssignExports(code, sanitizedCode)
|
|
48
|
+
: []
|
|
49
|
+
|
|
50
|
+
const definePropertyExports = sanitizedCode.includes('Object.defineProperty')
|
|
51
|
+
? getDefinePropertyExports(code, sanitizedCode)
|
|
52
|
+
: []
|
|
40
53
|
|
|
41
54
|
/*
|
|
42
55
|
console.log('assignmentExports', assignmentExports)
|
|
43
56
|
console.log('modulePropertyExports', modulePropertyExports)
|
|
44
57
|
console.log('propertyExports', propertyExports)
|
|
45
58
|
console.log('objectAssignExports', objectAssignExports)
|
|
59
|
+
console.log('definePropertyExports', definePropertyExports)
|
|
46
60
|
/** */
|
|
47
61
|
|
|
48
62
|
const allExports = []
|
|
@@ -50,6 +64,7 @@ function getCjsExports(code) {
|
|
|
50
64
|
.concat(modulePropertyExports)
|
|
51
65
|
.concat(propertyExports)
|
|
52
66
|
.concat(objectAssignExports)
|
|
67
|
+
.concat(definePropertyExports)
|
|
53
68
|
|
|
54
69
|
return allExports.sort((a, b) => a.index - b.index)
|
|
55
70
|
}
|
|
@@ -283,7 +298,81 @@ function getObjectAssignExports(originalText = '', sanitizedText = originalText)
|
|
|
283
298
|
return exportStatements
|
|
284
299
|
}
|
|
285
300
|
|
|
286
|
-
|
|
301
|
+
function getDefinePropertyExports(originalText = '', sanitizedText = originalText) {
|
|
302
|
+
let exportStatements = []
|
|
303
|
+
|
|
304
|
+
// Find all Object.defineProperty calls with module or exports
|
|
305
|
+
// Matches: Object.defineProperty(module, 'exports', ...) or Object.defineProperty(exports, 'propertyName', ...)
|
|
306
|
+
const definePropertyPattern = /Object\.defineProperty\s*\(\s*(module|exports)\s*,\s*['"]([^'"]+)['"]/g
|
|
307
|
+
var result
|
|
308
|
+
|
|
309
|
+
while((result = definePropertyPattern.exec(sanitizedText)) !== null) {
|
|
310
|
+
const target = result[1]
|
|
311
|
+
const propertyName = result[2]
|
|
312
|
+
|
|
313
|
+
// Find the opening parenthesis of Object.defineProperty
|
|
314
|
+
const openParenIndex = result.index + result[0].indexOf('(')
|
|
315
|
+
|
|
316
|
+
// Balance parentheses to find the complete Object.defineProperty call
|
|
317
|
+
let depth = 0
|
|
318
|
+
let i = openParenIndex
|
|
319
|
+
let completeCall = ''
|
|
320
|
+
|
|
321
|
+
while (i < originalText.length) {
|
|
322
|
+
const char = originalText[i]
|
|
323
|
+
if (char === '(') depth++
|
|
324
|
+
else if (char === ')') {
|
|
325
|
+
depth--
|
|
326
|
+
if (depth === 0) {
|
|
327
|
+
completeCall = originalText.substring(result.index, i + 1)
|
|
328
|
+
break
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
i++
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!completeCall) {
|
|
335
|
+
continue
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Try to extract the getter function if present
|
|
339
|
+
// Matches: get: functionName or get: () => ... or get() { ... }
|
|
340
|
+
const getterMatch = completeCall.match(/get\s*:\s*(\w+)|get\s*:\s*(\([^)]*\)\s*=>)|get\s*\(/)
|
|
341
|
+
let getterValue = ''
|
|
342
|
+
if (getterMatch) {
|
|
343
|
+
if (getterMatch[1]) {
|
|
344
|
+
// Named function: get: functionName
|
|
345
|
+
getterValue = getterMatch[1]
|
|
346
|
+
} else if (getterMatch[2]) {
|
|
347
|
+
// Arrow function: get: () =>
|
|
348
|
+
getterValue = 'arrow function'
|
|
349
|
+
} else {
|
|
350
|
+
// Method: get() { ... }
|
|
351
|
+
getterValue = 'getter method'
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Determine if this is a module.exports or property export
|
|
356
|
+
const isModuleExports = target === 'module' && propertyName === 'exports'
|
|
357
|
+
|
|
358
|
+
const exportStatement = {
|
|
359
|
+
line: completeCall.trim(),
|
|
360
|
+
exportType: isModuleExports ? 'default' : 'named',
|
|
361
|
+
exportName: isModuleExports ? '' : propertyName,
|
|
362
|
+
exportValue: completeCall, // Original text with comments
|
|
363
|
+
exportDefinitionName: getterValue || propertyName,
|
|
364
|
+
index: result.index,
|
|
365
|
+
isExport: true,
|
|
366
|
+
isDefineProperty: true
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
exportStatements.push(exportStatement)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return exportStatements
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (require.main === module) {
|
|
287
376
|
const code = `
|
|
288
377
|
// module.exports assignment
|
|
289
378
|
module.exports = function hello() {}
|
|
@@ -309,6 +398,21 @@ module.exports.MyClass = class MyClass {}
|
|
|
309
398
|
// exports properties
|
|
310
399
|
exports.foo = function() {}
|
|
311
400
|
exports.bar = 'bar'
|
|
401
|
+
|
|
402
|
+
// Object.defineProperty exports
|
|
403
|
+
function assembleStyles() {
|
|
404
|
+
return { color: 'red' }
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
Object.defineProperty(module, 'exports', {
|
|
408
|
+
enumerable: true,
|
|
409
|
+
get: assembleStyles
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
Object.defineProperty(exports, 'myProperty', {
|
|
413
|
+
enumerable: true,
|
|
414
|
+
get: () => 'value'
|
|
415
|
+
})
|
|
312
416
|
`
|
|
313
417
|
|
|
314
418
|
const exportsFound = getCjsExports(code)
|
|
@@ -320,5 +424,6 @@ module.exports = {
|
|
|
320
424
|
getModuleExportsAssignment,
|
|
321
425
|
getModuleExportsProperty,
|
|
322
426
|
getExportsProperty,
|
|
323
|
-
getObjectAssignExports
|
|
427
|
+
getObjectAssignExports,
|
|
428
|
+
getDefinePropertyExports
|
|
324
429
|
}
|
package/src/get-imports.js
CHANGED
package/src/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { inspectParameters, getParametersNames } = require('inspect-parameters-declaration')
|
|
1
|
+
const { inspectParameters, getParametersNames } = require('./vendor/inspect-parameters-declaration')
|
|
2
2
|
|
|
3
3
|
function isRelativeImport(importLocation) {
|
|
4
4
|
return !!importLocation.match(/^\./)
|