@davidwells/extract-deps 0.1.9 → 0.1.11
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/package.json +1 -1
- package/src/get-cjs-exports.js +6 -8
- package/src/get-exports.js +5 -5
- package/src/get-imports.js +5 -6
- package/src/get-requires.js +5 -4
- package/src/utils.js +54 -0
- package/tests/get-exports.test.js +35 -0
package/package.json
CHANGED
package/src/get-cjs-exports.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Extracts CommonJS module.exports and exports patterns from JavaScript code
|
|
2
2
|
|
|
3
|
-
const { getNames, extractExportName,
|
|
3
|
+
const { getNames, extractExportName, stripCommentsAndStrings } = require('./utils')
|
|
4
4
|
|
|
5
5
|
// Matches: module.exports = ... or exports = module.exports = ...
|
|
6
6
|
// https://regex101.com/r/example/cjs-1
|
|
@@ -35,13 +35,11 @@ function stripComments(code) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function getCjsExports(code) {
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
// Strip comments to prevent false positives from commented code
|
|
44
|
-
const sanitizedCode = stripComments(code)
|
|
38
|
+
// Single tokenizer-based pass that blanks comments and code-bearing
|
|
39
|
+
// strings/templates together. Length is preserved so the position-based
|
|
40
|
+
// logic below (which slices into the original `code` for full export
|
|
41
|
+
// values) keeps working off the same indices.
|
|
42
|
+
const sanitizedCode = stripCommentsAndStrings(code)
|
|
45
43
|
|
|
46
44
|
const assignmentExports = getModuleExportsAssignment(code, sanitizedCode)
|
|
47
45
|
const modulePropertyExports = getModuleExportsProperty(code, sanitizedCode)
|
package/src/get-exports.js
CHANGED
|
@@ -16,12 +16,12 @@ const EXPORT_TYPE_REGEX = /export\s+(?:type|interface)\s+([\w$]+)/gm
|
|
|
16
16
|
const EXPORT_ASSERTION_REGEX = /export\s+(?:["'\s]*([\w*{}\n\r\t, ]+)from\s*)?["'\s](.*[@\w_-]+)["'\s]+(assert|with)\s*\{([^}]+)\};?$/gm
|
|
17
17
|
|
|
18
18
|
function getExports(code = '', options = {}) {
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// Strip comments to avoid false positives
|
|
19
|
+
// Comments first, then strings — order matters. Running stripStringContents
|
|
20
|
+
// alone would mis-pair an apostrophe inside a JSDoc comment (`Netlify's …`)
|
|
21
|
+
// with a later quote and shift subsequent backtick pairing, which could
|
|
22
|
+
// swallow real `export` declarations a few lines later.
|
|
24
23
|
code = stripComments(code)
|
|
24
|
+
code = stripStringContents(code)
|
|
25
25
|
const defaultExports = getDefaultExports(code)
|
|
26
26
|
const namedExports = getNamedExports(code)
|
|
27
27
|
const namedDeclarationExports = getNamedDeclarationExports(code)
|
package/src/get-imports.js
CHANGED
|
@@ -37,13 +37,12 @@ const DYNAMIC_IMPORT_REGEX = /(?:(const|var|let)\s+([\w*{}\n\r\t,:= $]+)\s*=\s*)
|
|
|
37
37
|
const ASSERTION_IMPORT_REGEX = /(?:^|\n)\s*import\s+(?:["'\s]*([\w*{}\n\r\t, $]+)from\s*)?["'\s]+([^"'`\n;\s]+)\s*["']\s+(assert|with)\s*\{([^}]+)\}/gm
|
|
38
38
|
|
|
39
39
|
function getImports(code = '', options = {}) {
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
code = stripStringContents(code)
|
|
45
|
-
// Strip comments to avoid false positives
|
|
40
|
+
// Strip comments FIRST so apostrophes/backticks inside JSDoc text get
|
|
41
|
+
// removed before `stripStringContents` looks for string literals.
|
|
42
|
+
// (Running the two in the opposite order let comment chars masquerade as
|
|
43
|
+
// string openers and swallow real `import`/`export` lines further down.)
|
|
46
44
|
code = stripComments(code)
|
|
45
|
+
code = stripStringContents(code)
|
|
47
46
|
const staticImports = getStaticImports(code)
|
|
48
47
|
const dynamicImports = getDynamicImports(code)
|
|
49
48
|
if (options.includeAssertionImports) {
|
package/src/get-requires.js
CHANGED
|
@@ -65,11 +65,12 @@ function getStaticRequires(targetText = '') {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function getRequires(code) {
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
68
|
+
// Strip comments first, then strings — order matters. `stripComments`
|
|
69
|
+
// alone would mis-eat `//` inside strings; running it first instead means
|
|
70
|
+
// comment chars (incl. apostrophes/backticks inside JSDoc) disappear
|
|
71
|
+
// before `stripStringContents` looks for real string literals.
|
|
72
72
|
code = stripComments(code)
|
|
73
|
+
code = stripStringContents(code)
|
|
73
74
|
const staticRequires = getStaticRequires(code)
|
|
74
75
|
const dynamicRequires = getDynamicRequires(code)
|
|
75
76
|
return staticRequires.concat(dynamicRequires).sort((a, b) => a.index - b.index)
|
package/src/utils.js
CHANGED
|
@@ -63,6 +63,59 @@ function stripStringContents(code) {
|
|
|
63
63
|
})
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Unified regex matching whichever comes first at each position: block
|
|
67
|
+
// comment, line comment, single-quoted string, double-quoted string, or
|
|
68
|
+
// template literal. Matching left-to-right means a comment that starts
|
|
69
|
+
// first consumes its body — including any apostrophes or backticks inside
|
|
70
|
+
// — before they can start a spurious string match a few lines later. That
|
|
71
|
+
// solves the JSDoc-apostrophe-shifts-backtick-pairing class of bugs that
|
|
72
|
+
// arose when comments and strings were stripped in two separate passes.
|
|
73
|
+
const COMMENT_OR_STRING_RE = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\\n]|\\[\s\S])*"|'(?:[^'\\\n]|\\[\s\S])*'|`(?:[^`\\]|\\[\s\S])*`/g
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Single-regex pass that blanks comment bodies and code-bearing
|
|
77
|
+
* string/template bodies together. Solves two bugs that occurred when the
|
|
78
|
+
* string-strip and comment-strip ran in separate passes:
|
|
79
|
+
*
|
|
80
|
+
* - Apostrophes inside JSDoc comments (e.g. `* Netlify's \`...`) were
|
|
81
|
+
* mis-paired with later quotes, which shifted subsequent backtick
|
|
82
|
+
* pairing and could swallow real `export` declarations a few lines
|
|
83
|
+
* later in the same file.
|
|
84
|
+
* - `//` and `/* *\/` sequences inside string bodies were mis-stripped
|
|
85
|
+
* as comments.
|
|
86
|
+
*
|
|
87
|
+
* Length is preserved (each consumed char replaced by a space or newline
|
|
88
|
+
* kept verbatim) so downstream character indices stay valid.
|
|
89
|
+
*
|
|
90
|
+
* Strings whose bodies don't reference `import` / `export` / `require` /
|
|
91
|
+
* `module.exports` pass through unchanged, so module specifiers like
|
|
92
|
+
* `'lodash'` or `'./foo.js'` stay readable by the downstream regexes.
|
|
93
|
+
*
|
|
94
|
+
* Caveat: `${...}` interpolations inside template literals are part of the
|
|
95
|
+
* body, so `${import('./x.js')}` inside a template gets blanked. Rare in
|
|
96
|
+
* practice; a full parser would be needed otherwise.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} code
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
function stripCommentsAndStrings(code) {
|
|
102
|
+
return code.replace(COMMENT_OR_STRING_RE, (match) => {
|
|
103
|
+
const first = match[0]
|
|
104
|
+
// Comments (line or block) are fully blanked — they have no extractable
|
|
105
|
+
// content. Newlines are preserved so line counts hold.
|
|
106
|
+
if (first === '/') {
|
|
107
|
+
return match.replace(/[^\n]/g, ' ')
|
|
108
|
+
}
|
|
109
|
+
// Strings/templates — only blank if the body references code keywords;
|
|
110
|
+
// otherwise leave the literal intact so `'lodash'` etc. remain readable.
|
|
111
|
+
const body = match.slice(1, -1)
|
|
112
|
+
if (!EMBEDDED_CODE_RE.test(body)) {
|
|
113
|
+
return match
|
|
114
|
+
}
|
|
115
|
+
return first + body.replace(/[^\n]/g, ' ') + first
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
66
119
|
/**
|
|
67
120
|
* Strip comments from code, preserving :// in URLs
|
|
68
121
|
* @param {string} code
|
|
@@ -292,6 +345,7 @@ function extractExportName(exportValue) {
|
|
|
292
345
|
module.exports = {
|
|
293
346
|
stripComments,
|
|
294
347
|
stripStringContents,
|
|
348
|
+
stripCommentsAndStrings,
|
|
295
349
|
isBuiltIn,
|
|
296
350
|
isRelativeImport,
|
|
297
351
|
isRelativeImportWithQuotes,
|
|
@@ -563,6 +563,41 @@ export const real = 'value' // this comment should be stripped
|
|
|
563
563
|
assert.equal(foundExports[2].exportName, 'real', 'Should find real export')
|
|
564
564
|
})
|
|
565
565
|
|
|
566
|
+
test('apostrophe inside JSDoc with surrounding backticks does not swallow later exports', () => {
|
|
567
|
+
// Regression: when `stripStringContents` ran BEFORE `stripComments`, an
|
|
568
|
+
// apostrophe inside a JSDoc block (e.g. `Netlify's \`promptType: 'foo'\``)
|
|
569
|
+
// was treated as the opener of a single-quoted string and consumed text up
|
|
570
|
+
// to the next `'`, shifting every subsequent backtick pair by one. The
|
|
571
|
+
// newly mis-paired template literal then spanned multiple lines, including
|
|
572
|
+
// the real `export async function` declaration that followed the JSDoc,
|
|
573
|
+
// and the embedded-code heuristic blanked it. The fix runs `stripComments`
|
|
574
|
+
// first so JSDoc-internal punctuation is gone before string matching.
|
|
575
|
+
//
|
|
576
|
+
// This fixture replicates the structural shape of the AWS SDK adapter file
|
|
577
|
+
// that originally surfaced the bug: a JSDoc block containing both an
|
|
578
|
+
// apostrophe and multiple backticks, followed by an `export async function`,
|
|
579
|
+
// followed by ANOTHER JSDoc with backticks.
|
|
580
|
+
const code = `
|
|
581
|
+
/**
|
|
582
|
+
* Stuff with \`code-one\`. Netlify's \`promptType: 'foo'\`. End \`Error\` on
|
|
583
|
+
* non-2xx upstream responses or malformed bodies.
|
|
584
|
+
*/
|
|
585
|
+
export async function generateThing(request) {
|
|
586
|
+
return request
|
|
587
|
+
}
|
|
588
|
+
/** Test seam: override the global \`fetch\` for unit tests. */
|
|
589
|
+
export const helper = 1
|
|
590
|
+
`
|
|
591
|
+
|
|
592
|
+
const found = getExports(code)
|
|
593
|
+
const names = found.map(e => e.exportName).sort()
|
|
594
|
+
assert.equal(
|
|
595
|
+
names,
|
|
596
|
+
['generateThing', 'helper'],
|
|
597
|
+
'Both exports must survive past the JSDoc apostrophe+backtick combo'
|
|
598
|
+
)
|
|
599
|
+
})
|
|
600
|
+
|
|
566
601
|
test('ignores ESM exports embedded in template literals and strings', () => {
|
|
567
602
|
const code = `
|
|
568
603
|
export const real = 'real'
|