@chappibunny/repolens 1.15.1 → 1.15.2

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,716 @@
1
+ // Import extraction for the dependency graph.
2
+ // Comments, strings, and template text are not imports. Type-only
3
+ // TypeScript imports are returned with typeOnly: true so they do not
4
+ // become runtime edges.
5
+
6
+ const PYTHON_STDLIB = new Set([
7
+ "__future__", "abc", "aifc", "argparse", "array", "ast", "asynchat", "asyncio",
8
+ "asyncore", "atexit", "base64", "bdb", "binascii", "binhex", "bisect", "builtins",
9
+ "bz2", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs",
10
+ "codeop", "collections", "colorsys", "compileall", "concurrent", "configparser",
11
+ "contextlib", "contextvars", "copy", "copyreg", "cProfile", "crypt", "csv",
12
+ "ctypes", "curses", "dataclasses", "datetime", "dbm", "decimal", "difflib",
13
+ "dis", "distutils", "doctest", "email", "encodings", "enum", "errno", "faulthandler",
14
+ "fcntl", "filecmp", "fileinput", "fnmatch", "fractions", "ftplib", "functools",
15
+ "gc", "getopt", "getpass", "gettext", "glob", "graphlib", "grp", "gzip", "hashlib",
16
+ "heapq", "hmac", "html", "http", "idlelib", "imaplib", "imghdr", "imp", "importlib",
17
+ "inspect", "io", "ipaddress", "itertools", "json", "keyword", "lib2to3", "linecache",
18
+ "locale", "logging", "lzma", "mailbox", "mailcap", "marshal", "math", "mimetypes",
19
+ "mmap", "modulefinder", "msvcrt", "multiprocessing", "netrc", "nis", "nntplib", "nt", "ntpath", "numbers",
20
+ "operator", "optparse", "os", "ossaudiodev", "pathlib", "pdb", "pickle", "pickletools",
21
+ "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint",
22
+ "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "queue", "quopri",
23
+ "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched",
24
+ "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site",
25
+ "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "sqlite3", "ssl", "stat",
26
+ "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symtable",
27
+ "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios",
28
+ "test", "textwrap", "threading", "time", "timeit", "tkinter", "token", "tokenize",
29
+ "tomllib", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "types",
30
+ "typing", "unicodedata", "unittest", "urllib", "uu", "uuid", "venv", "warnings",
31
+ "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "xdrlib", "xml",
32
+ "xmlrpc", "zipapp", "zipfile", "zipimport", "zlib", "zoneinfo",
33
+ ]);
34
+
35
+ function isIdentStart(ch) {
36
+ return /[A-Za-z_$]/.test(ch || "");
37
+ }
38
+
39
+ function isIdentPart(ch) {
40
+ return /[A-Za-z0-9_$]/.test(ch || "");
41
+ }
42
+
43
+ function skipWs(source, i) {
44
+ while (i < source.length && /[\t\n\r \f]/.test(source[i])) i += 1;
45
+ return i;
46
+ }
47
+
48
+ function readWord(source, i) {
49
+ const start = i;
50
+ while (i < source.length && isIdentPart(source[i])) i += 1;
51
+ return { word: source.slice(start, i), next: i };
52
+ }
53
+
54
+ function skipLineComment(source, i) {
55
+ const nl = source.indexOf("\n", i);
56
+ return nl === -1 ? source.length : nl + 1;
57
+ }
58
+
59
+ function skipBlockComment(source, i) {
60
+ const end = source.indexOf("*/", i + 2);
61
+ return end === -1 ? source.length : end + 2;
62
+ }
63
+
64
+ function skipQuoted(source, i) {
65
+ const quote = source[i];
66
+ i += 1;
67
+ while (i < source.length) {
68
+ if (source[i] === "\\") {
69
+ i += 2;
70
+ continue;
71
+ }
72
+ if (source[i] === quote) return i + 1;
73
+ if (source[i] === "\n") return i;
74
+ i += 1;
75
+ }
76
+ return i;
77
+ }
78
+
79
+ function readJsString(source, i) {
80
+ i = skipWs(source, i);
81
+ const quote = source[i];
82
+ if (quote !== "'" && quote !== '"') return null;
83
+ let j = i + 1;
84
+ let value = "";
85
+ while (j < source.length) {
86
+ if (source[j] === "\\") {
87
+ value += source[j + 1] === "n" ? "\n" : (source[j + 1] || "");
88
+ j += 2;
89
+ continue;
90
+ }
91
+ if (source[j] === quote) return { value, next: j + 1 };
92
+ if (source[j] === "\n") return null;
93
+ value += source[j];
94
+ j += 1;
95
+ }
96
+ return null;
97
+ }
98
+
99
+ function skipRegex(source, i) {
100
+ i += 1;
101
+ let inClass = false;
102
+ while (i < source.length) {
103
+ if (source[i] === "\\") {
104
+ i += 2;
105
+ continue;
106
+ }
107
+ if (source[i] === "[") inClass = true;
108
+ else if (source[i] === "]") inClass = false;
109
+ else if (source[i] === "/" && !inClass) return i + 1;
110
+ else if (source[i] === "\n") return i;
111
+ i += 1;
112
+ }
113
+ return i;
114
+ }
115
+
116
+ function prevCodeChar(source, i) {
117
+ let j = i - 1;
118
+ while (j >= 0 && /[\t\n\r \f]/.test(source[j])) j -= 1;
119
+ return j >= 0 ? source[j] : "";
120
+ }
121
+
122
+ function canStartRegex(ch) {
123
+ return ch === "" || "([{:;,!?&|~^=<>+-*%".includes(ch);
124
+ }
125
+
126
+ function splitTopCommas(inner) {
127
+ const parts = [];
128
+ let current = "";
129
+ let depth = 0;
130
+ for (const ch of inner) {
131
+ if (ch === "{" || ch === "(" || ch === "[") depth += 1;
132
+ else if (ch === "}" || ch === ")" || ch === "]") depth -= 1;
133
+ if (ch === "," && depth === 0) {
134
+ parts.push(current);
135
+ current = "";
136
+ } else {
137
+ current += ch;
138
+ }
139
+ }
140
+ if (current.trim()) parts.push(current);
141
+ return parts;
142
+ }
143
+
144
+ function braceIsTypeOnly(inner) {
145
+ const parts = splitTopCommas(inner).map((part) => part.trim()).filter(Boolean);
146
+ if (parts.length === 0) return true;
147
+ return parts.every((part) => /^type\s+/.test(part) && !/^type\s+as\b/.test(part));
148
+ }
149
+
150
+ function findMatchingBrace(source, openIndex) {
151
+ let depth = 0;
152
+ for (let i = openIndex; i < source.length; i += 1) {
153
+ if (source[i] === "{") depth += 1;
154
+ else if (source[i] === "}") {
155
+ depth -= 1;
156
+ if (depth === 0) return i;
157
+ }
158
+ }
159
+ return -1;
160
+ }
161
+
162
+ function skipAsBinding(source, i) {
163
+ let j = skipWs(source, i);
164
+ const word = readWord(source, j);
165
+ if (word.word !== "as") return i;
166
+ j = skipWs(source, word.next);
167
+ const name = readWord(source, j);
168
+ return name.word ? name.next : j;
169
+ }
170
+
171
+ function readFromClause(source, i) {
172
+ i = skipAsBinding(source, i);
173
+ i = skipWs(source, i);
174
+ const word = readWord(source, i);
175
+ if (word.word !== "from") return null;
176
+ const lit = readJsString(source, word.next);
177
+ if (!lit) return null;
178
+ return lit;
179
+ }
180
+
181
+ /**
182
+ * @returns {{ specifier: string, typeOnly: boolean }[]}
183
+ */
184
+ export function extractJsImports(source) {
185
+ const imports = [];
186
+ const n = source.length;
187
+ let i = 0;
188
+
189
+ function add(specifier, typeOnly) {
190
+ if (typeof specifier !== "string") return;
191
+ imports.push({ specifier, typeOnly: Boolean(typeOnly) });
192
+ }
193
+
194
+ function parseRequire(at) {
195
+ const i0 = skipWs(source, at);
196
+ if (source[i0] !== "(") return at;
197
+ const lit = readJsString(source, i0 + 1);
198
+ if (!lit) return at;
199
+ const after = skipWs(source, lit.next);
200
+ if (source[after] !== ")") return at;
201
+ add(lit.value, false);
202
+ return after + 1;
203
+ }
204
+
205
+ function parseImport(at) {
206
+ let j = skipWs(source, at);
207
+ if (source[j] === "(") {
208
+ const lit = readJsString(source, j + 1);
209
+ if (!lit) return at;
210
+ const after = skipWs(source, lit.next);
211
+ if (source[after] !== ")") return at;
212
+ add(lit.value, false);
213
+ return after + 1;
214
+ }
215
+ if (source[j] === ".") return at;
216
+
217
+ let typeOnly = false;
218
+ const first = readWord(source, j);
219
+ if (first.word === "type") {
220
+ typeOnly = true;
221
+ j = skipWs(source, first.next);
222
+ }
223
+
224
+ if (source[j] === "{") {
225
+ const close = findMatchingBrace(source, j);
226
+ if (close === -1) return at;
227
+ const inner = source.slice(j + 1, close);
228
+ const clause = readFromClause(source, close + 1);
229
+ if (!clause) return close + 1;
230
+ const onlyTypes = typeOnly || braceIsTypeOnly(inner);
231
+ add(clause.value, onlyTypes);
232
+ return clause.next;
233
+ }
234
+
235
+ if (source[j] === "*" || isIdentStart(source[j]) || source[j] === "'" || source[j] === '"') {
236
+ if (source[j] === "'" || source[j] === '"') {
237
+ const lit = readJsString(source, j);
238
+ if (lit) add(lit.value, typeOnly);
239
+ return lit ? lit.next : j;
240
+ }
241
+ // default binding, namespace, or `name, { ... }`
242
+ let sawValue = !typeOnly;
243
+ if (source[j] !== "*") {
244
+ const binding = readWord(source, j);
245
+ j = skipWs(source, binding.next);
246
+ if (source[j] === ",") {
247
+ sawValue = !typeOnly;
248
+ j = skipWs(source, j + 1);
249
+ }
250
+ } else {
251
+ j = skipWs(source, j + 1);
252
+ j = skipAsBinding(source, j);
253
+ j = skipWs(source, j);
254
+ }
255
+ if (source[j] === "{") {
256
+ const close = findMatchingBrace(source, j);
257
+ if (close === -1) return j;
258
+ const inner = source.slice(j + 1, close);
259
+ const clause = readFromClause(source, close + 1);
260
+ if (!clause) return close + 1;
261
+ const onlyTypes = typeOnly || (!sawValue && braceIsTypeOnly(inner));
262
+ add(clause.value, onlyTypes);
263
+ return clause.next;
264
+ }
265
+ const clause = readFromClause(source, j);
266
+ if (!clause) return at;
267
+ add(clause.value, typeOnly);
268
+ return clause.next;
269
+ }
270
+
271
+ return at;
272
+ }
273
+
274
+ function parseExport(at) {
275
+ let j = skipWs(source, at);
276
+ let typeOnly = false;
277
+ const first = readWord(source, j);
278
+ if (first.word === "type") {
279
+ typeOnly = true;
280
+ j = skipWs(source, first.next);
281
+ }
282
+ if (source[j] === "*") {
283
+ const clause = readFromClause(source, j + 1);
284
+ if (!clause) return at;
285
+ add(clause.value, typeOnly);
286
+ return clause.next;
287
+ }
288
+ if (source[j] === "{") {
289
+ const close = findMatchingBrace(source, j);
290
+ if (close === -1) return at;
291
+ const inner = source.slice(j + 1, close);
292
+ const clause = readFromClause(source, close + 1);
293
+ if (!clause) return close + 1;
294
+ add(clause.value, typeOnly || braceIsTypeOnly(inner));
295
+ return clause.next;
296
+ }
297
+ return at;
298
+ }
299
+
300
+ while (i < n) {
301
+ const ch = source[i];
302
+ if (ch === "/" && source[i + 1] === "/") {
303
+ i = skipLineComment(source, i);
304
+ continue;
305
+ }
306
+ if (ch === "/" && source[i + 1] === "*") {
307
+ i = skipBlockComment(source, i);
308
+ continue;
309
+ }
310
+ if (ch === "'" || ch === '"') {
311
+ i = skipQuoted(source, i);
312
+ continue;
313
+ }
314
+ if (ch === "`") {
315
+ i = scanTemplate(source, i + 1, (inner, from, to) => {
316
+ // Scan the expression as code by continuing the outer walk on a slice.
317
+ // Handled below by recursion on the expression text with an offset.
318
+ const found = extractJsImports(source.slice(from, to));
319
+ for (const item of found) imports.push(item);
320
+ });
321
+ continue;
322
+ }
323
+ if (ch === "/" && canStartRegex(prevCodeChar(source, i))) {
324
+ i = skipRegex(source, i);
325
+ continue;
326
+ }
327
+ if (isIdentStart(ch)) {
328
+ const word = readWord(source, i);
329
+ const prev = prevCodeChar(source, i);
330
+ i = word.next;
331
+ if (prev === ".") continue;
332
+ if (word.word === "import") i = parseImport(i);
333
+ else if (word.word === "export") i = parseExport(i);
334
+ else if (word.word === "require") i = parseRequire(i);
335
+ continue;
336
+ }
337
+ i += 1;
338
+ }
339
+
340
+ return imports;
341
+ }
342
+
343
+ function scanTemplate(source, i, onExpression) {
344
+ while (i < source.length) {
345
+ if (source[i] === "\\") {
346
+ i += 2;
347
+ continue;
348
+ }
349
+ if (source[i] === "`") return i + 1;
350
+ if (source[i] === "$" && source[i + 1] === "{") {
351
+ const close = findMatchingBrace(source, i + 1);
352
+ if (close === -1) return source.length;
353
+ onExpression(source, i + 2, close);
354
+ i = close + 1;
355
+ continue;
356
+ }
357
+ i += 1;
358
+ }
359
+ return i;
360
+ }
361
+
362
+ export function externalPackageName(specifier) {
363
+ if (typeof specifier !== "string") return null;
364
+ const trimmed = specifier.trim();
365
+ if (!trimmed || trimmed.startsWith(".") || trimmed.startsWith("/") || trimmed.startsWith("node:")) return null;
366
+ if (trimmed.includes("://") || trimmed.startsWith("#")) return null;
367
+ const parts = trimmed.startsWith("@")
368
+ ? trimmed.split("/").slice(0, 2)
369
+ : trimmed.split("/").slice(0, 1);
370
+ const name = parts.filter(Boolean).join("/");
371
+ if (!name || name === "." || name === "..") return null;
372
+ return name;
373
+ }
374
+
375
+ function stripPythonNoise(source) {
376
+ let out = "";
377
+ let i = 0;
378
+ while (i < source.length) {
379
+ const ch = source[i];
380
+ if (ch === "#") {
381
+ const nl = source.indexOf("\n", i);
382
+ out += " ";
383
+ if (nl === -1) break;
384
+ i = nl;
385
+ continue;
386
+ }
387
+ if (ch === '"' || ch === "'") {
388
+ const triple = source.slice(i, i + 3) === ch.repeat(3);
389
+ const quote = triple ? ch.repeat(3) : ch;
390
+ out += " ";
391
+ i += quote.length;
392
+ while (i < source.length) {
393
+ if (source[i] === "\\") {
394
+ out += source[i + 1] === "\n" ? "\n" : " ";
395
+ i += 2;
396
+ continue;
397
+ }
398
+ if (source.startsWith(quote, i)) {
399
+ i += quote.length;
400
+ break;
401
+ }
402
+ out += source[i] === "\n" ? "\n" : " ";
403
+ i += 1;
404
+ }
405
+ continue;
406
+ }
407
+ out += ch;
408
+ i += 1;
409
+ }
410
+ return out;
411
+ }
412
+
413
+ /**
414
+ * @returns {{ module: string, level: number }[]}
415
+ * level 0 is an absolute import. level N is a relative import with N dots.
416
+ */
417
+ export function extractPythonImports(source) {
418
+ const stripped = stripPythonNoise(source).replace(/\\\n/g, " ");
419
+ const lines = stripped.split("\n");
420
+ const statements = [];
421
+ let buf = "";
422
+ let depth = 0;
423
+ for (const line of lines) {
424
+ buf = buf ? `${buf} ${line.trim()}` : line.trim();
425
+ depth += (line.match(/\(/g) || []).length;
426
+ depth -= (line.match(/\)/g) || []).length;
427
+ if (depth > 0) continue;
428
+ if (buf) statements.push(buf);
429
+ buf = "";
430
+ depth = 0;
431
+ }
432
+ if (buf) statements.push(buf);
433
+
434
+ const imports = [];
435
+ for (const statement of statements) {
436
+ if (statement.startsWith("import ")) {
437
+ const rest = statement.slice("import ".length).replace(/[()]/g, " ");
438
+ for (const part of rest.split(",")) {
439
+ const name = part.trim().split(/\s+/)[0];
440
+ if (name && /^[\w.]+$/.test(name)) imports.push({ module: name, level: 0 });
441
+ }
442
+ continue;
443
+ }
444
+ const match = statement.match(/^from\s+(\.+)([\w.]*)\s+import\b/) || statement.match(/^from\s+([\w.]+)\s+import\b/);
445
+ if (!match) continue;
446
+ if (match[1].startsWith(".")) {
447
+ imports.push({ module: match[2] || "", level: match[1].length });
448
+ } else {
449
+ imports.push({ module: match[1], level: 0 });
450
+ }
451
+ }
452
+ return imports;
453
+ }
454
+
455
+ function packageRootDirs(files) {
456
+ const initDirs = new Set();
457
+ for (const file of files) {
458
+ const norm = String(file).replace(/\\/g, "/");
459
+ if (!/\/__init__\.pyw?$/.test(norm)) continue;
460
+ initDirs.add(norm.slice(0, norm.lastIndexOf("/")));
461
+ }
462
+ const roots = [];
463
+ for (const dir of initDirs) {
464
+ const parent = dir.includes("/") ? dir.slice(0, dir.lastIndexOf("/")) : "";
465
+ if (!initDirs.has(parent)) roots.push(dir);
466
+ }
467
+ return roots;
468
+ }
469
+
470
+ export function pythonModuleName(file, roots = []) {
471
+ let rel = String(file).replace(/\\/g, "/");
472
+ const sorted = [...roots].sort((a, b) => b.length - a.length);
473
+ for (const root of sorted) {
474
+ if (rel !== root && !rel.startsWith(`${root}/`)) continue;
475
+ const parent = root.includes("/") ? root.slice(0, root.lastIndexOf("/")) : "";
476
+ rel = parent ? rel.slice(parent.length + 1) : rel;
477
+ break;
478
+ }
479
+ if (rel.startsWith("src/")) rel = rel.slice(4);
480
+ rel = rel.replace(/\.pyw?$/, "");
481
+ if (rel.endsWith("/__init__")) rel = rel.slice(0, -"/__init__".length);
482
+ if (rel === "__init__") return "";
483
+ return rel.replace(/\//g, ".");
484
+ }
485
+
486
+ export function pythonPackageName(file, roots = []) {
487
+ const rel = String(file).replace(/\\/g, "/");
488
+ const init = rel.endsWith("/__init__.py") || rel.endsWith("/__init__.pyw");
489
+ const mod = pythonModuleName(file, roots);
490
+ if (init) return mod;
491
+ const parts = mod.split(".").filter(Boolean);
492
+ parts.pop();
493
+ return parts.join(".");
494
+ }
495
+
496
+ export function resolvePythonImport(file, imported, index) {
497
+ const level = imported.level || 0;
498
+ let dotted;
499
+ const roots = index.packageRoots || [];
500
+ if (level > 0) {
501
+ const pkg = pythonPackageName(file, roots);
502
+ const parts = pkg ? pkg.split(".") : [];
503
+ const up = level - 1;
504
+ if (up > parts.length) return { kind: "unresolved" };
505
+ const base = parts.slice(0, parts.length - up);
506
+ if (imported.module) base.push(...imported.module.split(".").filter(Boolean));
507
+ dotted = base.join(".");
508
+ } else {
509
+ dotted = imported.module;
510
+ }
511
+
512
+ if (!dotted) return { kind: "unresolved" };
513
+ if (index.has(dotted)) return { kind: "internal", file: index.get(dotted) };
514
+
515
+ if (level > 0) {
516
+ const parent = dotted.split(".").slice(0, -1).join(".");
517
+ if (parent && index.has(parent)) return { kind: "internal", file: index.get(parent) };
518
+ return { kind: "unresolved" };
519
+ }
520
+
521
+ const top = dotted.split(".")[0];
522
+ if (!PYTHON_STDLIB.has(top)) {
523
+ const nearby = sameDirectoryModule(file, dotted, index);
524
+ if (nearby) return { kind: "internal", file: nearby };
525
+ }
526
+ if (index.has(top) || [...index.keys()].some((name) => name.startsWith(`${top}.`))) {
527
+ return { kind: "unresolved" };
528
+ }
529
+ if (PYTHON_STDLIB.has(top)) return { kind: "stdlib" };
530
+ return { kind: "external", name: top };
531
+ }
532
+
533
+ function sameDirectoryModule(file, dotted, index) {
534
+ const norm = String(file).replace(/\\/g, "/");
535
+ const slash = norm.lastIndexOf("/");
536
+ const dir = slash === -1 ? "" : norm.slice(0, slash);
537
+ const rel = dotted.replace(/\./g, "/");
538
+ const prefix = dir ? `${dir}/` : "";
539
+ const candidates = new Set([
540
+ `${prefix}${rel}.py`,
541
+ `${prefix}${rel}.pyw`,
542
+ `${prefix}${rel}/__init__.py`,
543
+ `${prefix}${rel}/__init__.pyw`,
544
+ ]);
545
+ for (const known of index.values()) {
546
+ if (candidates.has(String(known).replace(/\\/g, "/"))) return known;
547
+ }
548
+ return null;
549
+ }
550
+
551
+ export function indexPythonModules(files) {
552
+ const roots = packageRootDirs(files);
553
+ const index = new Map();
554
+ index.packageRoots = roots;
555
+ const looseStems = new Map();
556
+ for (const file of files) {
557
+ const name = pythonModuleName(file, roots);
558
+ if (name && !index.has(name)) index.set(name, file);
559
+ const norm = String(file).replace(/\\/g, "/");
560
+ const insidePackage = roots.some((root) => norm === `${root}/__init__.py` || norm === `${root}/__init__.pyw` || norm.startsWith(`${root}/`));
561
+ if (insidePackage) continue;
562
+ const stem = norm.split("/").pop().replace(/\.pyw?$/, "");
563
+ if (!stem || PYTHON_STDLIB.has(stem)) continue;
564
+ if (looseStems.has(stem)) looseStems.set(stem, null);
565
+ else looseStems.set(stem, file);
566
+ }
567
+ for (const [stem, file] of looseStems) {
568
+ if (file && !index.has(stem)) index.set(stem, file);
569
+ }
570
+ return index;
571
+ }
572
+
573
+ export function isPythonStdlib(name) {
574
+ return PYTHON_STDLIB.has(name);
575
+ }
576
+
577
+ /**
578
+ * @returns {string[]} import paths
579
+ */
580
+ export function extractGoImports(source) {
581
+ const paths = [];
582
+ const n = source.length;
583
+ let i = 0;
584
+
585
+ function skipSpaceAndComments(at) {
586
+ let j = at;
587
+ while (j < n) {
588
+ if (/[\t\n\r \f]/.test(source[j])) {
589
+ j += 1;
590
+ continue;
591
+ }
592
+ if (source[j] === "/" && source[j + 1] === "/") {
593
+ j = skipLineComment(source, j);
594
+ continue;
595
+ }
596
+ if (source[j] === "/" && source[j + 1] === "*") {
597
+ j = skipBlockComment(source, j);
598
+ continue;
599
+ }
600
+ break;
601
+ }
602
+ return j;
603
+ }
604
+
605
+ function readGoString(at) {
606
+ at = skipSpaceAndComments(at);
607
+ const quote = source[at];
608
+ if (quote !== '"' && quote !== "`") return null;
609
+ let j = at + 1;
610
+ let value = "";
611
+ if (quote === "`") {
612
+ while (j < n && source[j] !== "`") {
613
+ value += source[j];
614
+ j += 1;
615
+ }
616
+ return source[j] === "`" ? { value, next: j + 1 } : null;
617
+ }
618
+ while (j < n) {
619
+ if (source[j] === "\\") {
620
+ value += source[j + 1] || "";
621
+ j += 2;
622
+ continue;
623
+ }
624
+ if (source[j] === '"') return { value, next: j + 1 };
625
+ if (source[j] === "\n") return null;
626
+ value += source[j];
627
+ j += 1;
628
+ }
629
+ return null;
630
+ }
631
+
632
+ function takeImportPath(at) {
633
+ let j = skipSpaceAndComments(at);
634
+ if (source[j] === ".") j = skipSpaceAndComments(j + 1);
635
+ else if (isIdentStart(source[j]) || source[j] === "_") {
636
+ const word = readWord(source, j);
637
+ const after = skipSpaceAndComments(word.next);
638
+ if (source[after] === '"' || source[after] === "`") j = after;
639
+ }
640
+ const lit = readGoString(j);
641
+ if (!lit || !lit.value) return null;
642
+ return lit;
643
+ }
644
+
645
+ while (i < n) {
646
+ if (/[\t\n\r \f]/.test(source[i])) {
647
+ i += 1;
648
+ continue;
649
+ }
650
+ if (source[i] === "/" && source[i + 1] === "/") {
651
+ i = skipLineComment(source, i);
652
+ continue;
653
+ }
654
+ if (source[i] === "/" && source[i + 1] === "*") {
655
+ i = skipBlockComment(source, i);
656
+ continue;
657
+ }
658
+ if (source[i] === '"' || source[i] === "`" || source[i] === "'") {
659
+ i = source[i] === "'" ? skipQuoted(source, i) : (readGoString(i)?.next || i + 1);
660
+ continue;
661
+ }
662
+ if (isIdentStart(source[i])) {
663
+ const word = readWord(source, i);
664
+ const prev = prevCodeChar(source, i);
665
+ i = word.next;
666
+ if (word.word !== "import" || prev === ".") continue;
667
+ i = skipSpaceAndComments(i);
668
+ if (source[i] === "(") {
669
+ i += 1;
670
+ while (i < n) {
671
+ i = skipSpaceAndComments(i);
672
+ if (source[i] === ")" || i >= n) {
673
+ i += 1;
674
+ break;
675
+ }
676
+ const lit = takeImportPath(i);
677
+ if (!lit) {
678
+ i += 1;
679
+ continue;
680
+ }
681
+ paths.push(lit.value);
682
+ i = lit.next;
683
+ }
684
+ continue;
685
+ }
686
+ const lit = takeImportPath(i);
687
+ if (lit) {
688
+ paths.push(lit.value);
689
+ i = lit.next;
690
+ }
691
+ continue;
692
+ }
693
+ i += 1;
694
+ }
695
+
696
+ return paths;
697
+ }
698
+
699
+ export function goPackageDir(importPath, modulePath, moduleDir) {
700
+ if (!modulePath) return null;
701
+ if (importPath !== modulePath && !importPath.startsWith(`${modulePath}/`)) return null;
702
+ const rel = importPath === modulePath ? "" : importPath.slice(modulePath.length + 1);
703
+ const prefix = moduleDir ? moduleDir.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "") : "";
704
+ if (!prefix) return rel;
705
+ return rel ? `${prefix}/${rel}` : prefix;
706
+ }
707
+
708
+ export function isGoStdlib(importPath) {
709
+ const first = String(importPath).split("/")[0];
710
+ return Boolean(first) && !first.includes(".");
711
+ }
712
+
713
+ export function parseGoModule(text) {
714
+ const match = String(text || "").match(/^\s*module\s+(\S+)/m);
715
+ return match ? match[1] : null;
716
+ }