@davidwells/extract-deps 0.1.11 → 0.1.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidwells/extract-deps",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Get imports and requires from a codebase without AST",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/dep-graph.js CHANGED
@@ -3,7 +3,7 @@ const path = require('path')
3
3
  const fs = require('fs')
4
4
  const { pathToFileURL, fileURLToPath } = require('url')
5
5
  const { resolve: resolveImport } = require('@dual-bundle/import-meta-resolve')
6
- const { extractDeps } = require('./index')
6
+ const { extractDepsShared: extractDeps } = require('./index')
7
7
  const { isBuiltIn } = require('./utils')
8
8
 
9
9
  function fsExistsSync(path) {
@@ -3,7 +3,7 @@ const { getNames, extractExportName, stripComments, stripStringContents } = requ
3
3
  // https://regex101.com/r/example/1
4
4
  const EXPORT_DEFAULT_REGEX = /export\s+default\s+(?:(?:async\s+)?function\s+([\w$]+)|class\s+([\w$]+)|([\w$]+|\([^)]*\)\s*=>|{[\s\S]*?}|\[[\s\S]*?\]|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`|[\w$]+(?:\s*[+\-*/%]\s*[\w$]+)*))/gm
5
5
  // https://regex101.com/r/example/2
6
- const EXPORT_NAMED_REGEX = /export\s+(?:const|let|var|function|class|async\s+function)\s+([\w$]+)/gm
6
+ const EXPORT_NAMED_REGEX = /export\s+(?:const\s+enum|enum|const|let|var|function|class|async\s+function)\s+([\w$]+)/gm
7
7
  // https://regex101.com/r/example/3
8
8
  const EXPORT_NAMED_DECLARATION_REGEX = /export\s*{([^}]+)}/gm
9
9
  // https://regex101.com/r/example/4
@@ -303,4 +303,4 @@ module.exports = {
303
303
  getNamespaceExports,
304
304
  getTypeExports,
305
305
  getAssertionExports
306
- }
306
+ }
@@ -38,11 +38,51 @@ function getDynamicRequires(code) {
38
38
  return results
39
39
  }
40
40
 
41
+ // Sticky copy of REQUIRE_REGEX: matches only at an exact start position.
42
+ const REQUIRE_REGEX_AT = new RegExp(REQUIRE_REGEX.source, 'my')
43
+ // Every char REQUIRE_REGEX can consume before its literal `require(`.
44
+ const REQUIRE_PREFIX_CHAR = /[\w\s*{}:=,$]/
45
+
46
+ /**
47
+ * Same result as `REQUIRE_REGEX.exec` with `lastIndex = from`, but only
48
+ * attempts start positions that can reach a `require(` literal. Scanning
49
+ * every position is quadratic on long lines because of the leading
50
+ * `(?!.*await\s+import)` lookahead.
51
+ *
52
+ * A match starting at `s` must contain `require(` at some `p >= s` with
53
+ * every char in [s, p) matching REQUIRE_PREFIX_CHAR. `(` is not in that set,
54
+ * so the match uses the first `require(` at or after `s`. For each
55
+ * `require(` at `p`, the only viable starts are therefore [q, p], where q
56
+ * walks back from p over prefix chars. Trying those in ascending order finds
57
+ * the same leftmost match.
58
+ * @param {string} text
59
+ * @param {number} from
60
+ */
61
+ function execRequireFrom(text, from) {
62
+ let searchFrom = from
63
+ let tried = from
64
+ while (true) {
65
+ const requireIndex = text.indexOf('require(', searchFrom)
66
+ if (requireIndex === -1) return null
67
+ let start = requireIndex
68
+ while (start > tried && REQUIRE_PREFIX_CHAR.test(text[start - 1])) start--
69
+ for (; start <= requireIndex; start++) {
70
+ REQUIRE_REGEX_AT.lastIndex = start
71
+ const match = REQUIRE_REGEX_AT.exec(text)
72
+ if (match) return match
73
+ }
74
+ tried = requireIndex + 1
75
+ searchFrom = requireIndex + 1
76
+ }
77
+ }
78
+
41
79
  // https://regex101.com/r/sR1TIP/1
42
80
  function getStaticRequires(targetText = '') {
43
81
  let requireStatements = []
44
82
  var result;
45
- while((result = REQUIRE_REGEX.exec(targetText)) !== null) {
83
+ let searchStart = 0
84
+ while((result = execRequireFrom(targetText, searchStart)) !== null) {
85
+ searchStart = result.index + result[0].length
46
86
  const [ line, varType, names, from ] = result
47
87
  // console.log('getStaticRequires result', result)
48
88
  const importLocation = from
package/src/index.js CHANGED
@@ -16,7 +16,35 @@ const {
16
16
  // https://github.com/ganeshkbhat/get-imports/blob/main/index.js
17
17
  // Alt get imports https://github.com/beenotung/fix-esm-import-path/blob/master/fix-esm-import-path.js#L186
18
18
 
19
+ // Parse results keyed by source text. Extraction is a pure function of the
20
+ // code, and dep-graph walks re-visit the same shared modules once per entry
21
+ // point, so repeat parses dominate runtime without this. Bounded FIFO so a
22
+ // long-lived process scanning many files doesn't grow without limit.
23
+ const PARSE_CACHE_MAX = 2000
24
+ const parseCache = new Map()
25
+
26
+ /**
27
+ * Cached extraction. Returns a shared object — callers must treat it as
28
+ * read-only. Use `extractDeps` for a private copy.
29
+ * @param {string} code
30
+ */
31
+ function extractDepsShared(code) {
32
+ let result = parseCache.get(code)
33
+ if (result === undefined) {
34
+ result = parseDeps(code)
35
+ if (parseCache.size >= PARSE_CACHE_MAX) {
36
+ parseCache.delete(parseCache.keys().next().value)
37
+ }
38
+ parseCache.set(code, result)
39
+ }
40
+ return result
41
+ }
42
+
19
43
  function extractDeps(code, filePath = '') {
44
+ return structuredClone(extractDepsShared(code))
45
+ }
46
+
47
+ function parseDeps(code) {
20
48
  const imports = getImports(code)
21
49
  //console.log('imports', imports)
22
50
  const requires = getRequires(code)
@@ -94,6 +122,7 @@ const { execSync, spawnSync } = require('child_process')
94
122
 
95
123
  module.exports = {
96
124
  extractDeps,
125
+ extractDepsShared,
97
126
  /* syntax detection */
98
127
  hasESMSyntax,
99
128
  hasCJSSyntax,
package/src/utils.js CHANGED
@@ -1,22 +1,6 @@
1
1
  const { inspectParameters, getParametersNames } = require('./vendor/inspect-parameters-declaration')
2
2
  const builtinModules = require('module').builtinModules
3
3
 
4
- const COMMENT_RE = /\/\*[\s\S]*?\*\/|\/\/.*(?:\r?\n|$)/g
5
- const PROTOCOL_PLACEHOLDER = '__PROTO_SLASH__'
6
-
7
- // Single-quoted and double-quoted strings can't contain unescaped newlines
8
- // (per JS spec — they'd need `\n` escapes or `\<NL>` line continuations). The
9
- // `[^"\\\n]` / `[^'\\\n]` char classes enforce that, so an unbalanced quote
10
- // followed by a newline doesn't sweep up the next several lines as "string".
11
- // Template literals (`` `...` ``) DO span newlines, so their inner class only
12
- // excludes backtick and backslash. Escape sequences (`\.`) are allowed in all
13
- // three so escaped quotes don't prematurely end the match.
14
- // Note: this treats `${...}` interpolations as part of the string body — code
15
- // inside `${...}` (rare, but possible: `${import('./x.js')}`) gets blanked too.
16
- // Acceptable for the change-detection use case; a full tokenizer would be
17
- // needed to preserve interpolations.
18
- const STRING_LITERAL_RE = /"(?:[^"\\\n]|\\[\s\S])*"|'(?:[^'\\\n]|\\[\s\S])*'|`(?:[^`\\]|\\[\s\S])*`/g
19
-
20
4
  // Strings whose bodies contain these tokens are treated as embedded code
21
5
  // samples (template strings, generator helpers, fenced docs) and get blanked.
22
6
  // Plain module specifiers like `'lodash'` or `'./foo.js'` don't contain any
@@ -38,8 +22,9 @@ const EMBEDDED_CODE_RE = /\b(?:import|export|require|module\.exports)\b/
38
22
  * Without this pass, regex-based extractors falsely match `import`, `export`,
39
23
  * and `require` keywords that appear inside code samples embedded in strings.
40
24
  *
41
- * Run this BEFORE `stripComments` — the existing comment regex would otherwise
42
- * mis-strip `//` and `/* *\/` sequences that occur inside strings.
25
+ * Run after `stripComments` in the import/require/export extractors. The
26
+ * comment scanner already skips strings and regex literals, so this pass can
27
+ * focus on embedded-code strings without risking `//` in URLs or regexes.
43
28
  *
44
29
  * Caveat: `${...}` interpolations inside template literals are treated as
45
30
  * part of the body, so a runtime `${import('./x.js')}` inside a template
@@ -50,32 +35,54 @@ const EMBEDDED_CODE_RE = /\b(?:import|export|require|module\.exports)\b/
50
35
  * @returns {string}
51
36
  */
52
37
  function stripStringContents(code) {
53
- return code.replace(STRING_LITERAL_RE, (match) => {
38
+ let output = ''
39
+ let i = 0
40
+
41
+ while (i < code.length) {
42
+ const char = code[i]
43
+
44
+ if (char === '/' && isRegexLiteralStart(code, i)) {
45
+ const end = readRegexLiteral(code, i)
46
+ output += code.slice(i, end)
47
+ i = end
48
+ continue
49
+ }
50
+
51
+ if (char !== '"' && char !== "'" && char !== '`') {
52
+ output += char
53
+ i++
54
+ continue
55
+ }
56
+
57
+ const end = readQuoted(code, i, char)
58
+ const match = code.slice(i, end)
59
+ if (match.length < 2 || match[match.length - 1] !== char) {
60
+ output += char
61
+ i++
62
+ continue
63
+ }
64
+
54
65
  const body = match.slice(1, -1)
55
66
  if (!EMBEDDED_CODE_RE.test(body)) {
56
67
  // Plain string — leave it alone so module specifiers stay extractable.
57
- return match
68
+ output += match
69
+ i = end
70
+ continue
58
71
  }
59
72
  const opener = match[0]
60
73
  const closer = match[match.length - 1]
61
74
  const middle = body.replace(/[^\n]/g, ' ')
62
- return opener + middle + closer
63
- })
64
- }
75
+ output += opener + middle + closer
76
+ i = end
77
+ }
65
78
 
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
79
+ return output
80
+ }
74
81
 
75
82
  /**
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:
83
+ * Blank comment bodies and code-bearing string/template bodies together.
84
+ * Solves two bugs that occurred when the string-strip and comment-strip ran
85
+ * as naive regex passes:
79
86
  *
80
87
  * - Apostrophes inside JSDoc comments (e.g. `* Netlify's \`...`) were
81
88
  * mis-paired with later quotes, which shifted subsequent backtick
@@ -83,6 +90,8 @@ const COMMENT_OR_STRING_RE = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\\n]|\\[\s\S])
83
90
  * later in the same file.
84
91
  * - `//` and `/* *\/` sequences inside string bodies were mis-stripped
85
92
  * as comments.
93
+ * - `//` inside regex literals such as `/^https?:\/\//i` was mis-stripped
94
+ * as a line comment.
86
95
  *
87
96
  * Length is preserved (each consumed char replaced by a space or newline
88
97
  * kept verbatim) so downstream character indices stay valid.
@@ -99,38 +108,167 @@ const COMMENT_OR_STRING_RE = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\\n]|\\[\s\S])
99
108
  * @returns {string}
100
109
  */
101
110
  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, ' ')
111
+ return stripStringContents(stripCommentsImpl(code, { preserveLength: true }))
112
+ }
113
+
114
+ const REGEX_PREFIX_KEYWORDS = new Set([
115
+ 'case',
116
+ 'delete',
117
+ 'do',
118
+ 'else',
119
+ 'in',
120
+ 'instanceof',
121
+ 'of',
122
+ 'return',
123
+ 'throw',
124
+ 'typeof',
125
+ 'void',
126
+ 'yield',
127
+ 'await',
128
+ ])
129
+
130
+ function isRegexLiteralStart(code, slashIndex) {
131
+ let i = slashIndex - 1
132
+ while (i >= 0 && /\s/.test(code[i])) i--
133
+ if (i < 0) return true
134
+
135
+ const previousChar = code[i]
136
+ if ('([{=,:;!&|?+-*~^<>'.includes(previousChar)) return true
137
+
138
+ // Same result as `code.slice(0, i + 1).match(/([A-Za-z_$][\w$]*)$/)[1]`
139
+ // without copying and re-scanning the whole prefix on every `/` (quadratic
140
+ // on large files): the leftmost match starts at the first non-digit of the
141
+ // trailing run of [\w$] chars.
142
+ let start = i
143
+ while (start >= 0 && isIdentifierChar(code.charCodeAt(start))) start--
144
+ start++
145
+ while (start <= i && isDigit(code.charCodeAt(start))) start++
146
+ if (start > i) return false
147
+ return REGEX_PREFIX_KEYWORDS.has(code.slice(start, i + 1))
148
+ }
149
+
150
+ function isDigit(charCode) {
151
+ return charCode >= 48 && charCode <= 57
152
+ }
153
+
154
+ // [\w$] — ASCII letters, digits, underscore, dollar
155
+ function isIdentifierChar(charCode) {
156
+ return (
157
+ (charCode >= 97 && charCode <= 122) ||
158
+ (charCode >= 65 && charCode <= 90) ||
159
+ isDigit(charCode) ||
160
+ charCode === 95 ||
161
+ charCode === 36
162
+ )
163
+ }
164
+
165
+ function readQuoted(code, start, quote) {
166
+ let i = start + 1
167
+ while (i < code.length) {
168
+ if (code[i] === '\\') {
169
+ i += 2
170
+ continue
108
171
  }
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
172
+ if (code[i] === quote) return i + 1
173
+ if (quote !== '`' && (code[i] === '\n' || code[i] === '\r')) return i
174
+ i++
175
+ }
176
+ return i
177
+ }
178
+
179
+ function readRegexLiteral(code, start) {
180
+ let i = start + 1
181
+ let inCharacterClass = false
182
+
183
+ while (i < code.length) {
184
+ const char = code[i]
185
+ if (char === '\\') {
186
+ i += 2
187
+ continue
114
188
  }
115
- return first + body.replace(/[^\n]/g, ' ') + first
116
- })
189
+ if (char === '[') {
190
+ inCharacterClass = true
191
+ i++
192
+ continue
193
+ }
194
+ if (char === ']') {
195
+ inCharacterClass = false
196
+ i++
197
+ continue
198
+ }
199
+ if (char === '/' && !inCharacterClass) {
200
+ i++
201
+ while (i < code.length && /[a-z]/i.test(code[i])) i++
202
+ return i
203
+ }
204
+ if (char === '\n' || char === '\r') return i
205
+ i++
206
+ }
207
+
208
+ return i
209
+ }
210
+
211
+ function appendSkippedComment(output, commentText, preserveLength) {
212
+ if (!preserveLength) {
213
+ return output + commentText.replace(/[^\n\r]/g, '')
214
+ }
215
+ return output + commentText.replace(/[^\n\r]/g, ' ')
216
+ }
217
+
218
+ function stripCommentsImpl(code, { preserveLength = false } = {}) {
219
+ let output = ''
220
+ let i = 0
221
+
222
+ while (i < code.length) {
223
+ const char = code[i]
224
+ const next = code[i + 1]
225
+
226
+ if (char === '"' || char === "'" || char === '`') {
227
+ const end = readQuoted(code, i, char)
228
+ output += code.slice(i, end)
229
+ i = end
230
+ continue
231
+ }
232
+
233
+ if (char === '/' && next === '/') {
234
+ let end = i + 2
235
+ while (end < code.length && code[end] !== '\n' && code[end] !== '\r') end++
236
+ output = appendSkippedComment(output, code.slice(i, end), preserveLength)
237
+ i = end
238
+ continue
239
+ }
240
+
241
+ if (char === '/' && next === '*') {
242
+ let end = code.indexOf('*/', i + 2)
243
+ end = end === -1 ? code.length : end + 2
244
+ output = appendSkippedComment(output, code.slice(i, end), preserveLength)
245
+ i = end
246
+ continue
247
+ }
248
+
249
+ if (char === '/' && isRegexLiteralStart(code, i)) {
250
+ const end = readRegexLiteral(code, i)
251
+ output += code.slice(i, end)
252
+ i = end
253
+ continue
254
+ }
255
+
256
+ output += char
257
+ i++
258
+ }
259
+
260
+ return output
117
261
  }
118
262
 
119
263
  /**
120
- * Strip comments from code, preserving :// in URLs
264
+ * Strip comments from code while skipping strings, templates, and regex
265
+ * literals. Regex literals can legally contain `//`, for example `/https?:\/\//`,
266
+ * and must not be treated as line comments.
121
267
  * @param {string} code
122
268
  * @returns {string}
123
269
  */
124
270
  function stripComments(code) {
125
- const hasProtocol = code.indexOf('://') > -1
126
- if (hasProtocol) {
127
- code = code.replace(/:\/\//g, PROTOCOL_PLACEHOLDER)
128
- }
129
- code = code.replace(COMMENT_RE, '')
130
- if (hasProtocol) {
131
- code = code.replace(/__PROTO_SLASH__/g, '://')
132
- }
133
- return code
271
+ return stripCommentsImpl(code)
134
272
  }
135
273
 
136
274
  /**
@@ -351,4 +489,4 @@ module.exports = {
351
489
  isRelativeImportWithQuotes,
352
490
  getNames,
353
491
  extractExportName
354
- }
492
+ }
@@ -654,6 +654,7 @@ test('dynamic import should still build the dep edge', () => {
654
654
 
655
655
  // TypeScript patterns fixture — exercises all fixed false positive patterns
656
656
  const TS_PATTERNS_FIXTURE = path.resolve(__dirname, './fixtures/ts-patterns-fixture/index.ts')
657
+ const TS_REGEX_EXPORT_FIXTURE = path.resolve(__dirname, './fixtures/ts-regex-export-fixture/index.ts')
657
658
 
658
659
  test('TS patterns: import type, inline type, import * as, $-prefixed names should all pass', () => {
659
660
  // This fixture contains:
@@ -700,4 +701,30 @@ test('TS patterns: resolveDepPaths with returnErrors should have zero errors', (
700
701
  assert.is(result.errors.length, 0, 'Should have zero verification errors')
701
702
  })
702
703
 
704
+ test('TS regex exports: verifies named imports from .js specifier resolved to .ts file', () => {
705
+ assert.not.throws(() => {
706
+ depGraph(TS_REGEX_EXPORT_FIXTURE, { verifyGraph: true })
707
+ }, 'Regex literals in the target module should not hide later exports')
708
+ })
709
+
710
+ test('TS regex exports: should build dependency edge to resolved .ts module', () => {
711
+ const graph = depGraph(TS_REGEX_EXPORT_FIXTURE, { verifyGraph: true })
712
+
713
+ const indexPath = Array.from(graph.keys()).find(key => key.includes('ts-regex-export-fixture/index.ts'))
714
+ assert.ok(indexPath, 'Should have index.ts in graph')
715
+
716
+ const deps = graph.get(indexPath)
717
+ assert.ok(deps.some(d => d.includes('ts-regex-export-fixture/message-builder.ts')), 'Should depend on message-builder.ts')
718
+ })
719
+
720
+ test('TS regex exports: resolveDepPaths with returnErrors should have zero errors', () => {
721
+ const result = resolveDepPaths(TS_REGEX_EXPORT_FIXTURE, { returnErrors: true })
722
+
723
+ assert.ok(Array.isArray(result.errors), 'Should have errors array')
724
+ if (result.errors.length > 0) {
725
+ console.log('Unexpected errors:', result.errors.map(e => e.message))
726
+ }
727
+ assert.is(result.errors.length, 0, 'Should have zero verification errors')
728
+ })
729
+
703
730
  test.run()
@@ -0,0 +1,8 @@
1
+ import { buildRawMessage, DeliveryStatus, sanitizeEmailHtml } from './message-builder.js'
2
+
3
+ const clean = sanitizeEmailHtml('<a href="https://example.com">ok</a>')
4
+
5
+ export const result = buildRawMessage({
6
+ htmlBody: clean,
7
+ status: DeliveryStatus.Ready,
8
+ })
@@ -0,0 +1,23 @@
1
+ export enum DeliveryStatus {
2
+ Ready = 'ready',
3
+ }
4
+
5
+ function escapeHtmlAttribute(value: string): string {
6
+ return value.replace(/`/g, '&#96;')
7
+ }
8
+
9
+ export function sanitizeEmailHtml(html: string): string {
10
+ return html.replace(/<!--[\s\S]*?-->/g, '').replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, '').replace(
11
+ /<\/?([a-z][a-z0-9]*)\b([^>]*)>/gi,
12
+ (tag, rawName: string, rawAttributes: string) => {
13
+ const hrefMatch = rawAttributes.match(/\shref\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/i)
14
+ const href = hrefMatch?.[2] ?? hrefMatch?.[3] ?? hrefMatch?.[4] ?? ''
15
+ if (!/^https?:\/\//i.test(href)) return '<a>'
16
+ return `<a href="${escapeHtmlAttribute(href)}">`
17
+ },
18
+ )
19
+ }
20
+
21
+ export function buildRawMessage({ htmlBody, status }: { htmlBody: string; status: DeliveryStatus }): string {
22
+ return `${status}:${htmlBody}`
23
+ }
@@ -78,11 +78,13 @@ export var thirdVariable = 'value'
78
78
  export function myFunction() {}
79
79
  export class MyClass {}
80
80
  export async function asyncFunction() {}
81
+ export enum MyEnum {}
82
+ export const enum MyConstEnum {}
81
83
  `
82
84
 
83
85
  const foundExports = getNamedExports(code)
84
86
 
85
- assert.ok(foundExports.length === 6, 'Should find 6 named exports')
87
+ assert.ok(foundExports.length === 8, 'Should find 8 named exports')
86
88
 
87
89
  const exportNames = foundExports.map(exp => exp.exportName)
88
90
  assert.ok(exportNames.includes('myVariable'), 'Should find const export')
@@ -91,6 +93,8 @@ export async function asyncFunction() {}
91
93
  assert.ok(exportNames.includes('myFunction'), 'Should find function export')
92
94
  assert.ok(exportNames.includes('MyClass'), 'Should find class export')
93
95
  assert.ok(exportNames.includes('asyncFunction'), 'Should find async function export')
96
+ assert.ok(exportNames.includes('MyEnum'), 'Should find enum export')
97
+ assert.ok(exportNames.includes('MyConstEnum'), 'Should find const enum export')
94
98
 
95
99
  foundExports.forEach(exp => {
96
100
  assert.equal(exp.exportType, 'named')
@@ -615,4 +619,25 @@ const dq = "export { foo } from 'fake'"
615
619
  assert.equal(names, ['real'], 'Only the real export should be detected')
616
620
  })
617
621
 
618
- test.run()
622
+ test('finds exports after regex literals that contain //', () => {
623
+ const code = `
624
+ export function sanitizeEmailHtml(html: string): string {
625
+ return html.replace(/<!--[\\s\\S]*?-->/g, '')
626
+ .replace(/<(script|style)\\b[\\s\\S]*?<\\/\\1>/gi, '')
627
+ .replace(/<\\/?([a-z][a-z0-9]*)\\b([^>]*)>/gi, (tag, rawName: string) => {
628
+ if (!/^https?:\\/\\//i.test(tag)) return '<a>'
629
+ return '<a>'
630
+ })
631
+ }
632
+
633
+ export function buildRawMessage(input: { htmlBody: string }): string {
634
+ return input.htmlBody
635
+ }
636
+ `
637
+
638
+ const found = getExports(code)
639
+ const names = found.map(e => e.exportName).sort()
640
+ assert.equal(names, ['buildRawMessage', 'sanitizeEmailHtml'])
641
+ })
642
+
643
+ test.run()
@@ -199,4 +199,15 @@ test('should handle packages with no dependencies gracefully', async () => {
199
199
  }
200
200
  })
201
201
 
202
+ test('extractDeps - repeat calls on same code return equal, independent results', () => {
203
+ const code = `const path = require('path')\nimport foo from './foo'\nexport const bar = 1\n`
204
+ const first = extractDeps(code)
205
+ first.requires[0].from = 'mutated'
206
+ first.imports.length = 0
207
+ const second = extractDeps(code)
208
+ assert.is(second.requires[0].from, 'path')
209
+ assert.is(second.imports.length, 1)
210
+ assert.equal(extractDeps(code), second)
211
+ })
212
+
202
213
  test.run()
@@ -0,0 +1,101 @@
1
+ const { test } = require('uvu')
2
+ const assert = require('uvu/assert')
3
+ const { detectSyntax, extractDeps } = require('../src')
4
+
5
+ function exportNames(code) {
6
+ return extractDeps(code).exports.map(exp => exp.exportName).filter(Boolean).sort()
7
+ }
8
+
9
+ function cjsExportNames(code) {
10
+ return extractDeps(code).moduleExports.map(exp => exp.exportName).filter(Boolean).sort()
11
+ }
12
+
13
+ test('regex literals with // do not hide later ESM exports', () => {
14
+ const code = `
15
+ const protocol = /^https?:\\/\\//i
16
+ const slashy = /\\/\\*not-a-comment\\*\\//
17
+ export function sanitizeEmailHtml(html: string): string {
18
+ return html
19
+ }
20
+ export function buildRawMessage(): string {
21
+ return ''
22
+ }
23
+ `
24
+
25
+ assert.equal(exportNames(code), ['buildRawMessage', 'sanitizeEmailHtml'])
26
+ })
27
+
28
+ test('regex literals with // do not hide later imports, requires, or CJS exports', () => {
29
+ const code = `
30
+ const protocol = /^https?:\\/\\//i
31
+ const commentText = /\\/\\/ not really a comment/
32
+ import real from './real.js'
33
+ const actual = require('./actual.js')
34
+ module.exports.afterRegex = { real, actual }
35
+ `
36
+
37
+ const deps = extractDeps(code)
38
+ assert.equal(deps.imports.map(dep => dep.from), ['./real.js'])
39
+ assert.equal(deps.requires.map(dep => dep.from), ['./actual.js'])
40
+ assert.equal(cjsExportNames(code), ['afterRegex'])
41
+ })
42
+
43
+ test('regex literals with backticks do not start fake template literals', () => {
44
+ const code = `
45
+ function escapeHtmlAttribute(value: string): string {
46
+ return value.replace(/\\\`/g, '&#96;')
47
+ }
48
+
49
+ export const afterBacktickRegex = true
50
+ `
51
+
52
+ assert.equal(exportNames(code), ['afterBacktickRegex'])
53
+ })
54
+
55
+ test('regex literals containing parser keywords are not imports/requires/exports', () => {
56
+ const code = `
57
+ const importPattern = /import\\s+fake\\s+from/
58
+ const exportPattern = /export\\s+const\\s+fake/
59
+ const requirePattern = /require\\(['"]fake['"]\\)/
60
+ const cjsPattern = /module\\.exports\\.fake/
61
+
62
+ import real from './real.js'
63
+ const actual = require('./actual.js')
64
+ export const realExport = true
65
+ module.exports.cjsExport = actual
66
+ `
67
+
68
+ const deps = extractDeps(code)
69
+ assert.equal(deps.imports.map(dep => dep.from), ['./real.js'])
70
+ assert.equal(deps.requires.map(dep => dep.from), ['./actual.js'])
71
+ assert.equal(exportNames(code), ['realExport'])
72
+ assert.equal(cjsExportNames(code), ['cjsExport'])
73
+ })
74
+
75
+ test('detectSyntax ignores parser keywords inside regex literals', () => {
76
+ const result = detectSyntax(`
77
+ const importPattern = /import\\s+fake\\s+from/
78
+ const exportPattern = /export\\s+const\\s+fake/
79
+ const requirePattern = /require\\(['"]fake['"]\\)/
80
+ const cjsPattern = /module\\.exports\\.fake/
81
+ `)
82
+
83
+ assert.equal(result, { hasESM: false, hasCJS: false, isMixed: false })
84
+ })
85
+
86
+ test('TypeScript enum exports are named exports', () => {
87
+ const code = `
88
+ export enum DeliveryStatus {
89
+ Pending = 'pending',
90
+ Sent = 'sent',
91
+ }
92
+
93
+ export const enum InlineStatus {
94
+ Ready = 'ready',
95
+ }
96
+ `
97
+
98
+ assert.equal(exportNames(code), ['DeliveryStatus', 'InlineStatus'])
99
+ })
100
+
101
+ test.run()
@@ -142,4 +142,12 @@ test('stripStringContents — neutralizes // inside code-bearing strings so stri
142
142
  assert.ok(after.includes('"\nconst y = 1'), 'closing quote and following code survive')
143
143
  })
144
144
 
145
+ test('stripComments — preserves regex literals that contain //', () => {
146
+ const input = `if (!/^https?:\\/\\//i.test(href)) return '<a>'\nexport function afterRegex() {}`
147
+ const output = stripComments(input)
148
+
149
+ assert.ok(output.includes('/^https?:\\/\\//i.test(href)'), 'regex literal survives')
150
+ assert.ok(output.includes('export function afterRegex() {}'), 'following export survives')
151
+ })
152
+
145
153
  test.run()
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Build dependency graph for a file and its dependencies
3
+ * @param {string} filePath - File to analyze
4
+ * @param {object} options - Options
5
+ * @param {boolean} [options.buildGraph] - If true, build and return dependency graph. Default: true
6
+ * @param {boolean} [options.verifyGraph] - If true, verify imports/exports. Default: true
7
+ * @param {boolean} [options.verifyNodeModules] - If true, verify node_modules packages can be resolved. Default: false
8
+ * @param {boolean} [options.includeNodeModules] - If true, include node_modules package names in the graph. Default: false
9
+ * @param {boolean} [options.throwOnFirstError] - If true, throw immediately on first error. Default: false
10
+ * @param {boolean} [options.returnErrors] - If true, return { graph, errors } instead of throwing. Default: false
11
+ * @returns {Map<string, string[]>|{graph: Map, errors: Error[]}|undefined} - Dependency graph, or { graph, errors } if returnErrors is true. Returns undefined if buildGraph is false.
12
+ */
13
+ export function depGraph(filePath: string, options?: {
14
+ buildGraph?: boolean;
15
+ verifyGraph?: boolean;
16
+ verifyNodeModules?: boolean;
17
+ includeNodeModules?: boolean;
18
+ throwOnFirstError?: boolean;
19
+ returnErrors?: boolean;
20
+ }): Map<string, string[]> | {
21
+ graph: Map<any, any>;
22
+ errors: Error[];
23
+ } | undefined;
24
+ /**
25
+ * Get all dependency file paths in build order (dependencies first), deduplicated
26
+ * @param {string} filePath - Entry file path
27
+ * @param {object} options - Options to pass to depGraph
28
+ * @param {boolean} [options.verifyGraph] - If true, verify imports/exports. Default: true
29
+ * @param {boolean} [options.verifyNodeModules] - If true, verify node_modules packages can be resolved. Default: false
30
+ * @param {boolean} [options.throwOnFirstError] - If true, throw immediately on first error. Default: false
31
+ * @param {boolean} [options.returnErrors] - If true, include errors array in result instead of throwing
32
+ * @returns {{ files: string[], nodeModules: string[], builtIns: string[], errors?: Error[] }} - Object with files in topological order, npm packages, and Node.js built-ins
33
+ */
34
+ export function resolveDepPaths(filePath: string, options?: {
35
+ verifyGraph?: boolean;
36
+ verifyNodeModules?: boolean;
37
+ throwOnFirstError?: boolean;
38
+ returnErrors?: boolean;
39
+ }): {
40
+ files: string[];
41
+ nodeModules: string[];
42
+ builtIns: string[];
43
+ errors?: Error[];
44
+ };
45
+ //# sourceMappingURL=dep-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-graph.d.ts","sourceRoot":"","sources":["../src/dep-graph.js"],"names":[],"mappings":"AAmUA;;;;;;;;;;;GAWG;AACH,mCAVW,MAAM,YAEd;IAA0B,UAAU,GAA5B,OAAO;IACW,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,kBAAkB,GAApC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAC;IAAC,KAAK,gBAAM;IAAC,MAAM,EAAE,KAAK,EAAE,CAAA;CAAC,GAAC,SAAS,CA8BzE;AAGD;;;;;;;;;GASG;AACH,0CARW,MAAM,YAEd;IAA0B,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;CAAE,CAkF5F"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Options for detecting syntax within a code string.
3
+ */
4
+ export type DetectSyntaxOptions = {
5
+ /**
6
+ * - Indicates whether comments should be stripped from the code before syntax checking.
7
+ */
8
+ stripComments?: boolean;
9
+ };
10
+ /**
11
+ * Options for detecting syntax within a code string.
12
+ * @typedef {Object} DetectSyntaxOptions
13
+ * @property {boolean} [stripComments=false] - Indicates whether comments should be stripped from the code before syntax checking.
14
+ */
15
+ /**
16
+ * Determines if a given code string contains ECMAScript module syntax.
17
+ *
18
+ * @param {string} code - The source code to analyze.
19
+ * @param {DetectSyntaxOptions} opts - See {@link DetectSyntaxOptions}.
20
+ * @returns {boolean} `true` if the code contains ESM syntax, otherwise `false`.
21
+ */
22
+ export function hasESMSyntax(code: string, opts?: DetectSyntaxOptions): boolean;
23
+ /**
24
+ * Determines if a given string of code contains CommonJS syntax.
25
+ *
26
+ * @param {string} code - The source code to analyze.
27
+ * @param {DetectSyntaxOptions} opts - See {@link DetectSyntaxOptions}.
28
+ * @returns {boolean} `true` if the code contains CommonJS syntax, `false` otherwise.
29
+ */
30
+ export function hasCJSSyntax(code: string, opts?: DetectSyntaxOptions): boolean;
31
+ /**
32
+ * Analyzes the supplied code to determine if it contains ECMAScript module syntax, CommonJS syntax, or both.
33
+ *
34
+ * @param {string} code - The source code to analyze.
35
+ * @param {DetectSyntaxOptions} opts - See {@link DetectSyntaxOptions}.
36
+ * @returns {Object} An object indicating the presence of ESM syntax (`hasESM`), CJS syntax (`hasCJS`) and whether both syntaxes are present (`isMixed`).
37
+ */
38
+ export function detectSyntax(code: string, opts?: DetectSyntaxOptions): any;
39
+ //# sourceMappingURL=detect-syntax.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-syntax.d.ts","sourceRoot":"","sources":["../src/detect-syntax.js"],"names":[],"mappings":";;;;;;;oBAcc,OAAO;;AAHrB;;;;GAIG;AAEH;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,GACjB,OAAO,CAMnB;AAED;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,GACjB,OAAO,CAMnB;AAED;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,OAe7B"}
@@ -0,0 +1,45 @@
1
+ export function getCjsExports(code: any): any[];
2
+ export function getModuleExportsAssignment(originalText?: string, sanitizedText?: string): {
3
+ line: string;
4
+ exportType: string;
5
+ exportName: string;
6
+ exportDefinitionName: any;
7
+ exportValue: string;
8
+ index: number;
9
+ isExport: boolean;
10
+ }[];
11
+ export function getModuleExportsProperty(originalText?: string, sanitizedText?: string): {
12
+ line: string;
13
+ exportType: string;
14
+ exportName: string;
15
+ exportValue: string;
16
+ index: number;
17
+ isExport: boolean;
18
+ }[];
19
+ export function getExportsProperty(originalText?: string, sanitizedText?: string): {
20
+ line: string;
21
+ exportType: string;
22
+ exportName: string;
23
+ exportValue: string;
24
+ index: number;
25
+ isExport: boolean;
26
+ }[];
27
+ export function getObjectAssignExports(originalText?: string, sanitizedText?: string): {
28
+ line: string;
29
+ exportType: string;
30
+ exportName: string;
31
+ exportValue: string;
32
+ index: number;
33
+ isExport: boolean;
34
+ }[];
35
+ export function getDefinePropertyExports(originalText?: string, sanitizedText?: string): {
36
+ line: string;
37
+ exportType: string;
38
+ exportName: string;
39
+ exportValue: string;
40
+ exportDefinitionName: string;
41
+ index: number;
42
+ isExport: boolean;
43
+ isDefineProperty: boolean;
44
+ }[];
45
+ //# sourceMappingURL=get-cjs-exports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-cjs-exports.d.ts","sourceRoot":"","sources":["../src/get-cjs-exports.js"],"names":[],"mappings":"AAoCA,gDAgDC;AA+BD;;;;;;;;IAqGC;AAED;;;;;;;IAqBC;AAED;;;;;;;IAqBC;AAED;;;;;;;IAiDC;AAED;;;;;;;;;IAwEC"}
@@ -0,0 +1,54 @@
1
+ export function getExports(code?: string, options?: {}): any[];
2
+ export function getDefaultExports(targetText?: string): {
3
+ line: string;
4
+ exportType: string;
5
+ exportName: string;
6
+ exportDefinitionName: any;
7
+ exportValue: string;
8
+ index: number;
9
+ isExport: boolean;
10
+ }[];
11
+ export function getNamedExports(targetText?: string): {
12
+ line: string;
13
+ exportType: string;
14
+ exportName: string;
15
+ index: number;
16
+ isExport: boolean;
17
+ }[];
18
+ export function getNamedDeclarationExports(targetText?: string): any[];
19
+ export function getAllExports(targetText?: string): {
20
+ line: string;
21
+ exportType: string;
22
+ from: string;
23
+ index: number;
24
+ isExport: boolean;
25
+ }[];
26
+ export function getNamespaceExports(targetText?: string): {
27
+ line: string;
28
+ exportType: string;
29
+ namespace: string;
30
+ from: string;
31
+ index: number;
32
+ isExport: boolean;
33
+ }[];
34
+ export function getTypeExports(targetText?: string): {
35
+ line: string;
36
+ exportType: string;
37
+ exportName: string;
38
+ index: number;
39
+ isExport: boolean;
40
+ }[];
41
+ export function getAssertionExports(targetText?: string): {
42
+ line: string;
43
+ exportType: string;
44
+ exportName: string;
45
+ definitions: any;
46
+ keys: any;
47
+ from: string;
48
+ assertion: string;
49
+ assertionType: string;
50
+ index: number;
51
+ isExport: boolean;
52
+ isAssertion: boolean;
53
+ }[];
54
+ //# sourceMappingURL=get-exports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-exports.d.ts","sourceRoot":"","sources":["../src/get-exports.js"],"names":[],"mappings":"AAiBA,+DAqCC;AAED;;;;;;;;IAqDC;AAED;;;;;;IAeC;AAED,uEAuBC;AAED;;;;;;IAeC;AAED;;;;;;;IAgBC;AAED;;;;;;IAeC;AAED;;;;;;;;;;;;IA0BC"}
@@ -0,0 +1,59 @@
1
+ export function getImports(code?: string, options?: {}): {
2
+ line: string;
3
+ varType: string;
4
+ varName: string;
5
+ definitions: any;
6
+ from: string;
7
+ index: number;
8
+ isImport: boolean;
9
+ isRelative: boolean;
10
+ isBuiltIn: boolean;
11
+ }[];
12
+ export function getStaticImports(targetText?: string): {
13
+ line: string;
14
+ varType: string;
15
+ varName: string;
16
+ definitions: any;
17
+ from: string;
18
+ index: number;
19
+ isImport: boolean;
20
+ isRelative: boolean;
21
+ isBuiltIn: boolean;
22
+ }[];
23
+ export function getTypeImports(targetText?: string): {
24
+ importStatement: string;
25
+ importPath: string;
26
+ importName: string;
27
+ index: number;
28
+ isImport: boolean;
29
+ isTypeImport: boolean;
30
+ isRelative: boolean;
31
+ isBuiltIn: boolean;
32
+ }[];
33
+ export function getDynamicImports(targetText?: string): {
34
+ line: string;
35
+ varType: string;
36
+ varName: string;
37
+ definitions: any;
38
+ from: string;
39
+ index: number;
40
+ isImport: boolean;
41
+ isRelative: boolean;
42
+ isBuiltIn: boolean;
43
+ isDynamic: boolean;
44
+ }[];
45
+ export function getAssertionImports(targetText?: string): {
46
+ line: string;
47
+ varType: string;
48
+ varName: string;
49
+ definitions: any;
50
+ from: string;
51
+ assertion: string;
52
+ assertionType: string;
53
+ index: number;
54
+ isImport: boolean;
55
+ isRelative: boolean;
56
+ isBuiltIn: boolean;
57
+ isAssertion: boolean;
58
+ }[];
59
+ //# sourceMappingURL=get-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-imports.d.ts","sourceRoot":"","sources":["../src/get-imports.js"],"names":[],"mappings":"AAsCA;;;;;;;;;;IAcC;AAGD;;;;;;;;;;IAsCC;AAED;;;;;;;;;IAkBC;AAED;;;;;;;;;;;IAuBC;AAED;;;;;;;;;;;;;IA0BC"}
@@ -0,0 +1,37 @@
1
+ export function getRequires(code: any): {
2
+ line: string;
3
+ varType: string;
4
+ varName: string;
5
+ definitions: any;
6
+ from: string;
7
+ index: number;
8
+ isRequire: boolean;
9
+ isRelative: boolean;
10
+ isBuiltIn: boolean;
11
+ isDynamic: boolean;
12
+ }[];
13
+ export function getStaticRequires(targetText?: string): {
14
+ line: string;
15
+ varType: string;
16
+ varName: string;
17
+ definitions: any;
18
+ from: string;
19
+ index: number;
20
+ isRequire: boolean;
21
+ isRelative: boolean;
22
+ isBuiltIn: boolean;
23
+ isDynamic: boolean;
24
+ }[];
25
+ export function getDynamicRequires(code: any): {
26
+ line: string;
27
+ varType: string;
28
+ varName: string;
29
+ definitions: any;
30
+ from: string;
31
+ index: number;
32
+ isRequire: boolean;
33
+ isRelative: boolean;
34
+ isBuiltIn: boolean;
35
+ isDynamic: boolean;
36
+ }[];
37
+ //# sourceMappingURL=get-requires.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-requires.d.ts","sourceRoot":"","sources":["../src/get-requires.js"],"names":[],"mappings":"AA0GA;;;;;;;;;;;IAUC;AArCD;;;;;;;;;;;IAyBC;AA/FD;;;;;;;;;;;IA6BC"}
@@ -0,0 +1,25 @@
1
+ export function extractDeps(code: any, filePath?: string): any;
2
+ /**
3
+ * Cached extraction. Returns a shared object — callers must treat it as
4
+ * read-only. Use `extractDeps` for a private copy.
5
+ * @param {string} code
6
+ */
7
+ export function extractDepsShared(code: string): any;
8
+ import { hasESMSyntax } from "./detect-syntax";
9
+ import { hasCJSSyntax } from "./detect-syntax";
10
+ import { detectSyntax } from "./detect-syntax";
11
+ import { getRequires } from "./get-requires";
12
+ import { getDynamicRequires } from "./get-requires";
13
+ import { getStaticRequires } from "./get-requires";
14
+ import { getImports } from "./get-imports";
15
+ import { getStaticImports } from "./get-imports";
16
+ import { getDynamicImports } from "./get-imports";
17
+ import { getExports } from "./get-exports";
18
+ import { getDefaultExports } from "./get-exports";
19
+ import { getNamedExports } from "./get-exports";
20
+ import { getNamedDeclarationExports } from "./get-exports";
21
+ import { getAllExports } from "./get-exports";
22
+ import { getNamespaceExports } from "./get-exports";
23
+ import { getTypeExports } from "./get-exports";
24
+ export { hasESMSyntax, hasCJSSyntax, detectSyntax, getRequires, getDynamicRequires, getStaticRequires, getImports, getStaticImports, getDynamicImports, getExports, getDefaultExports, getNamedExports, getNamedDeclarationExports, getAllExports, getNamespaceExports, getTypeExports };
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA0CA,+DAEC;AAnBD;;;;GAIG;AACH,wCAFW,MAAM,OAYhB"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Strip comments from code while skipping strings, templates, and regex
3
+ * literals. Regex literals can legally contain `//`, for example `/https?:\/\//`,
4
+ * and must not be treated as line comments.
5
+ * @param {string} code
6
+ * @returns {string}
7
+ */
8
+ export function stripComments(code: string): string;
9
+ /**
10
+ * Blank the contents of every string/template literal in `code` that looks
11
+ * like it contains embedded code (its body mentions `import`, `export`,
12
+ * `require`, or `module.exports`). The opening and closing quote chars are
13
+ * preserved, as are newlines. Total length is preserved so character indices
14
+ * remain stable.
15
+ *
16
+ * Strings whose bodies don't reference those keywords — including ordinary
17
+ * module specifiers like `'lodash'`, paths like `'./foo.js'`, URLs, and
18
+ * arbitrary user text — pass through untouched, so the static-import/require
19
+ * regexes can still read their content.
20
+ *
21
+ * Without this pass, regex-based extractors falsely match `import`, `export`,
22
+ * and `require` keywords that appear inside code samples embedded in strings.
23
+ *
24
+ * Run after `stripComments` in the import/require/export extractors. The
25
+ * comment scanner already skips strings and regex literals, so this pass can
26
+ * focus on embedded-code strings without risking `//` in URLs or regexes.
27
+ *
28
+ * Caveat: `${...}` interpolations inside template literals are treated as
29
+ * part of the body, so a runtime `${import('./x.js')}` inside a template
30
+ * would be blanked. Very rare in practice; a proper tokenizer would be
31
+ * needed to do better.
32
+ *
33
+ * @param {string} code
34
+ * @returns {string}
35
+ */
36
+ export function stripStringContents(code: string): string;
37
+ /**
38
+ * Blank comment bodies and code-bearing string/template bodies together.
39
+ * Solves two bugs that occurred when the string-strip and comment-strip ran
40
+ * as naive regex passes:
41
+ *
42
+ * - Apostrophes inside JSDoc comments (e.g. `* Netlify's \`...`) were
43
+ * mis-paired with later quotes, which shifted subsequent backtick
44
+ * pairing and could swallow real `export` declarations a few lines
45
+ * later in the same file.
46
+ * - `//` and `/* *\/` sequences inside string bodies were mis-stripped
47
+ * as comments.
48
+ * - `//` inside regex literals such as `/^https?:\/\//i` was mis-stripped
49
+ * as a line comment.
50
+ *
51
+ * Length is preserved (each consumed char replaced by a space or newline
52
+ * kept verbatim) so downstream character indices stay valid.
53
+ *
54
+ * Strings whose bodies don't reference `import` / `export` / `require` /
55
+ * `module.exports` pass through unchanged, so module specifiers like
56
+ * `'lodash'` or `'./foo.js'` stay readable by the downstream regexes.
57
+ *
58
+ * Caveat: `${...}` interpolations inside template literals are part of the
59
+ * body, so `${import('./x.js')}` inside a template gets blanked. Rare in
60
+ * practice; a full parser would be needed otherwise.
61
+ *
62
+ * @param {string} code
63
+ * @returns {string}
64
+ */
65
+ export function stripCommentsAndStrings(code: string): string;
66
+ /**
67
+ * Check if a module specifier is a Node.js built-in
68
+ * @param {string} moduleName - The module specifier (e.g., 'fs', 'node:fs/promises')
69
+ * @returns {boolean}
70
+ */
71
+ export function isBuiltIn(moduleName: string): boolean;
72
+ export function isRelativeImport(importLocation: any): boolean;
73
+ export function isRelativeImportWithQuotes(importLocation: any): boolean;
74
+ export function getNames(names: any, aliasKey?: string): any;
75
+ export function extractExportName(exportValue: any): any;
76
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAsQA;;;;;;GAMG;AACH,oCAHW,MAAM,GACJ,MAAM,CAIlB;AAtQD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,0CAHW,MAAM,GACJ,MAAM,CA6ClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,8CAHW,MAAM,GACJ,MAAM,CAIlB;AAkKD;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAUnB;AAED,+DAEC;AAED,yEAKC;AAgFD,6DA+DC;AAGD,yDAqCC"}
@@ -0,0 +1,4 @@
1
+ export function inspectParameters(source: any): any;
2
+ export function getParametersNames(source: any): any;
3
+ export function getParametersNamesFromInspection(inspectedParameters: any): any;
4
+ //# sourceMappingURL=inspect-parameters-declaration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect-parameters-declaration.d.ts","sourceRoot":"","sources":["../../src/vendor/inspect-parameters-declaration.js"],"names":[],"mappings":"AA0QA,oDAaC;AAtBD,qDAGC;AAED,gFAEC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Extract all entry points from package.json
3
+ * @param {string} packageJsonPath - Path to package.json
4
+ * @returns {Array<{field: string, path: string, resolvedPath: string}>} - Array of entry point objects
5
+ */
6
+ export function getPackageEntryPoints(packageJsonPath: string): Array<{
7
+ field: string;
8
+ path: string;
9
+ resolvedPath: string;
10
+ }>;
11
+ /**
12
+ * Verify all package.json entry points and collect dependencies
13
+ * @param {string} packagePath - Path to package.json
14
+ * @param {object} options - Options
15
+ * @param {boolean} [options.collectDeps] - If true, collect dependency paths for each entry point. Default: true
16
+ * @param {boolean} [options.verifyGraph] - If true, verify imports/exports. Default: true
17
+ * @param {boolean} [options.throwIfErrors] - If true, throw an error if there are any errors. Default: false
18
+ * @returns {{entryPoints: Array, sourceFiles: Set, errors: Array}} - Verification results
19
+ */
20
+ export function verifyPackageEntries(packagePath: string, options?: {
21
+ collectDeps?: boolean;
22
+ verifyGraph?: boolean;
23
+ throwIfErrors?: boolean;
24
+ }): {
25
+ entryPoints: any[];
26
+ sourceFiles: Set<any>;
27
+ errors: any[];
28
+ };
29
+ export function collectEntryPoints(packageJson: any, packageDir: any): any[];
30
+ //# sourceMappingURL=verify-package-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-package-entries.d.ts","sourceRoot":"","sources":["../src/verify-package-entries.js"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,uDAHW,MAAM,GACJ,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAC,CAAC,CAOtE;AAmGD;;;;;;;;GAQG;AACH,kDAPW,MAAM,YAEd;IAA0B,WAAW,GAA7B,OAAO;IACW,WAAW,GAA7B,OAAO;IACW,aAAa,GAA/B,OAAO;CACf,GAAU;IAAC,WAAW,QAAQ;IAAC,WAAW,WAAM;IAAC,MAAM,QAAO;CAAC,CAoEjE;AA5KD,6EA8DC"}