archtracker-mcp 0.2.0 → 0.3.0

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/dist/cli/index.js CHANGED
@@ -1,10 +1,4 @@
1
1
  #!/usr/bin/env node
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
2
 
9
3
  // src/cli/index.ts
10
4
  import { Command } from "commander";
@@ -175,6 +169,514 @@ function detectCycles(edges) {
175
169
  return cycles;
176
170
  }
177
171
 
172
+ // src/analyzer/engines/strip-comments.ts
173
+ function stripComments(content, style) {
174
+ switch (style) {
175
+ case "c-style":
176
+ return stripCStyle(content);
177
+ case "hash":
178
+ return stripHash(content);
179
+ case "python":
180
+ return stripPython(content);
181
+ case "ruby":
182
+ return stripRuby(content);
183
+ case "php":
184
+ return stripPhp(content);
185
+ default: {
186
+ const _exhaustive = style;
187
+ throw new Error(`Unknown comment style: ${_exhaustive}`);
188
+ }
189
+ }
190
+ }
191
+ function stripCStyle(content) {
192
+ let result = "";
193
+ let i = 0;
194
+ while (i < content.length) {
195
+ if (content[i] === "r" && i + 1 < content.length) {
196
+ let hashes = 0;
197
+ let j = i + 1;
198
+ while (j < content.length && content[j] === "#") {
199
+ hashes++;
200
+ j++;
201
+ }
202
+ if (j < content.length && content[j] === '"') {
203
+ for (let k = i; k <= j; k++) {
204
+ result += " ";
205
+ }
206
+ i = j + 1;
207
+ while (i < content.length) {
208
+ if (content[i] === '"') {
209
+ let matchHashes = 0;
210
+ let m = i + 1;
211
+ while (m < content.length && content[m] === "#" && matchHashes < hashes) {
212
+ matchHashes++;
213
+ m++;
214
+ }
215
+ if (matchHashes === hashes) {
216
+ for (let k = i; k < m; k++) {
217
+ result += " ";
218
+ }
219
+ i = m;
220
+ break;
221
+ }
222
+ }
223
+ result += content[i] === "\n" ? "\n" : " ";
224
+ i++;
225
+ }
226
+ continue;
227
+ }
228
+ }
229
+ if (content[i] === "/" && content[i + 1] === "/") {
230
+ while (i < content.length && content[i] !== "\n") {
231
+ result += " ";
232
+ i++;
233
+ }
234
+ } else if (content[i] === "/" && content[i + 1] === "*") {
235
+ result += " ";
236
+ i++;
237
+ result += " ";
238
+ i++;
239
+ while (i < content.length) {
240
+ if (content[i] === "*" && content[i + 1] === "/") {
241
+ result += " ";
242
+ i++;
243
+ result += " ";
244
+ i++;
245
+ break;
246
+ }
247
+ result += content[i] === "\n" ? "\n" : " ";
248
+ i++;
249
+ }
250
+ } else if (content[i] === '"') {
251
+ result += content[i];
252
+ i++;
253
+ while (i < content.length && content[i] !== '"') {
254
+ if (content[i] === "\\" && i + 1 < content.length) {
255
+ result += content[i];
256
+ i++;
257
+ result += content[i];
258
+ i++;
259
+ } else if (content[i] === "\n") {
260
+ result += "\n";
261
+ i++;
262
+ } else {
263
+ result += content[i];
264
+ i++;
265
+ }
266
+ }
267
+ if (i < content.length) {
268
+ result += content[i];
269
+ i++;
270
+ }
271
+ } else if (content[i] === "'") {
272
+ result += content[i];
273
+ i++;
274
+ while (i < content.length && content[i] !== "'") {
275
+ if (content[i] === "\\" && i + 1 < content.length) {
276
+ result += content[i];
277
+ i++;
278
+ result += content[i];
279
+ i++;
280
+ } else {
281
+ result += content[i];
282
+ i++;
283
+ }
284
+ }
285
+ if (i < content.length) {
286
+ result += content[i];
287
+ i++;
288
+ }
289
+ } else if (content[i] === "`") {
290
+ result += " ";
291
+ i++;
292
+ while (i < content.length && content[i] !== "`") {
293
+ result += content[i] === "\n" ? "\n" : " ";
294
+ i++;
295
+ }
296
+ if (i < content.length) {
297
+ result += " ";
298
+ i++;
299
+ }
300
+ } else {
301
+ result += content[i];
302
+ i++;
303
+ }
304
+ }
305
+ return result;
306
+ }
307
+ function stripHash(content) {
308
+ let result = "";
309
+ let i = 0;
310
+ while (i < content.length) {
311
+ if (content[i] === "#") {
312
+ while (i < content.length && content[i] !== "\n") {
313
+ result += " ";
314
+ i++;
315
+ }
316
+ } else if (content[i] === '"') {
317
+ result += content[i];
318
+ i++;
319
+ while (i < content.length && content[i] !== '"') {
320
+ if (content[i] === "\\" && i + 1 < content.length) {
321
+ result += content[i++];
322
+ result += content[i++];
323
+ } else {
324
+ result += content[i++];
325
+ }
326
+ }
327
+ if (i < content.length) {
328
+ result += content[i];
329
+ i++;
330
+ }
331
+ } else if (content[i] === "'") {
332
+ result += content[i];
333
+ i++;
334
+ while (i < content.length && content[i] !== "'") {
335
+ if (content[i] === "\\" && i + 1 < content.length) {
336
+ result += content[i++];
337
+ result += content[i++];
338
+ } else {
339
+ result += content[i++];
340
+ }
341
+ }
342
+ if (i < content.length) {
343
+ result += content[i];
344
+ i++;
345
+ }
346
+ } else {
347
+ result += content[i];
348
+ i++;
349
+ }
350
+ }
351
+ return result;
352
+ }
353
+ function stripPython(content) {
354
+ let result = "";
355
+ let i = 0;
356
+ while (i < content.length) {
357
+ let prefixLen = 0;
358
+ let isRaw = false;
359
+ if (i < content.length) {
360
+ const c0 = content[i];
361
+ const c1 = i + 1 < content.length ? content[i + 1] : "";
362
+ const c2 = i + 2 < content.length ? content[i + 2] : "";
363
+ if ((c0 === "r" || c0 === "R" || c0 === "b" || c0 === "B" || c0 === "f" || c0 === "F") && (c1 === "r" || c1 === "R" || c1 === "b" || c1 === "B" || c1 === "f" || c1 === "F") && (c2 === '"' || c2 === "'")) {
364
+ const pair = (c0 + c1).toLowerCase();
365
+ if (pair === "rb" || pair === "br" || pair === "fr" || pair === "rf") {
366
+ prefixLen = 2;
367
+ isRaw = pair.includes("r");
368
+ }
369
+ }
370
+ if (prefixLen === 0 && (c0 === "r" || c0 === "R" || c0 === "b" || c0 === "B" || c0 === "f" || c0 === "F") && (c1 === '"' || c1 === "'")) {
371
+ prefixLen = 1;
372
+ isRaw = c0 === "r" || c0 === "R";
373
+ }
374
+ }
375
+ const quoteStart = i + prefixLen;
376
+ if (quoteStart + 2 < content.length && (content[quoteStart] === '"' || content[quoteStart] === "'") && content[quoteStart + 1] === content[quoteStart] && content[quoteStart + 2] === content[quoteStart]) {
377
+ const quote = content[quoteStart];
378
+ for (let k = 0; k < prefixLen; k++) {
379
+ result += " ";
380
+ }
381
+ result += " ";
382
+ i = quoteStart + 3;
383
+ while (i < content.length) {
384
+ if (content[i] === quote && content[i + 1] === quote && content[i + 2] === quote) {
385
+ result += " ";
386
+ i += 3;
387
+ break;
388
+ }
389
+ if (!isRaw && content[i] === "\\" && i + 1 < content.length) {
390
+ result += " ";
391
+ i++;
392
+ result += content[i] === "\n" ? "\n" : " ";
393
+ i++;
394
+ } else {
395
+ result += content[i] === "\n" ? "\n" : " ";
396
+ i++;
397
+ }
398
+ }
399
+ } else if (prefixLen === 0 && content[i] === "#") {
400
+ while (i < content.length && content[i] !== "\n") {
401
+ result += " ";
402
+ i++;
403
+ }
404
+ } else if (quoteStart < content.length && (content[quoteStart] === '"' || content[quoteStart] === "'") && (prefixLen > 0 || content[i] === '"' || content[i] === "'")) {
405
+ const quote = content[quoteStart];
406
+ for (let k = i; k < quoteStart; k++) {
407
+ result += content[k];
408
+ }
409
+ result += content[quoteStart];
410
+ i = quoteStart + 1;
411
+ while (i < content.length && content[i] !== quote) {
412
+ if (!isRaw && content[i] === "\\" && i + 1 < content.length) {
413
+ result += content[i++];
414
+ result += content[i++];
415
+ } else if (content[i] === "\n") {
416
+ result += "\n";
417
+ i++;
418
+ break;
419
+ } else {
420
+ result += content[i++];
421
+ }
422
+ }
423
+ if (i < content.length && content[i] === quote) {
424
+ result += content[i];
425
+ i++;
426
+ }
427
+ } else {
428
+ result += content[i];
429
+ i++;
430
+ }
431
+ }
432
+ return result;
433
+ }
434
+ function stripRuby(content) {
435
+ const lines = content.split("\n");
436
+ const result = [];
437
+ let inBlock = false;
438
+ for (const line of lines) {
439
+ if (!inBlock && /^=begin(\s|$)/.test(line)) {
440
+ inBlock = true;
441
+ result.push(" ".repeat(line.length));
442
+ continue;
443
+ }
444
+ if (inBlock) {
445
+ if (/^=end(\s|$)/.test(line)) {
446
+ inBlock = false;
447
+ }
448
+ result.push(" ".repeat(line.length));
449
+ continue;
450
+ }
451
+ let processed = "";
452
+ let i = 0;
453
+ while (i < line.length) {
454
+ if (line[i] === "#") {
455
+ processed += " ".repeat(line.length - i);
456
+ break;
457
+ } else if (line[i] === '"') {
458
+ processed += line[i];
459
+ i++;
460
+ processed = scanRubyDoubleQuotedString(line, i, processed);
461
+ i = scanRubyDoubleQuotedStringIndex(line, i);
462
+ } else if (line[i] === "'") {
463
+ processed += line[i];
464
+ i++;
465
+ while (i < line.length && line[i] !== "'") {
466
+ if (line[i] === "\\" && i + 1 < line.length) {
467
+ processed += line[i++];
468
+ processed += line[i++];
469
+ } else {
470
+ processed += line[i++];
471
+ }
472
+ }
473
+ if (i < line.length) {
474
+ processed += line[i];
475
+ i++;
476
+ }
477
+ } else {
478
+ processed += line[i];
479
+ i++;
480
+ }
481
+ }
482
+ result.push(processed);
483
+ }
484
+ return result.join("\n");
485
+ }
486
+ function scanRubyDoubleQuotedString(line, startI, processed) {
487
+ let i = startI;
488
+ while (i < line.length && line[i] !== '"') {
489
+ if (line[i] === "\\" && i + 1 < line.length) {
490
+ processed += line[i++];
491
+ processed += line[i++];
492
+ } else if (line[i] === "#" && i + 1 < line.length && line[i + 1] === "{") {
493
+ processed += line[i++];
494
+ processed += line[i++];
495
+ let depth = 1;
496
+ while (i < line.length && depth > 0) {
497
+ if (line[i] === "{") {
498
+ depth++;
499
+ processed += line[i++];
500
+ } else if (line[i] === "}") {
501
+ depth--;
502
+ processed += line[i++];
503
+ } else if (line[i] === "\\" && i + 1 < line.length) {
504
+ processed += line[i++];
505
+ processed += line[i++];
506
+ } else {
507
+ processed += line[i++];
508
+ }
509
+ }
510
+ } else {
511
+ processed += line[i++];
512
+ }
513
+ }
514
+ if (i < line.length) {
515
+ processed += line[i];
516
+ }
517
+ return processed;
518
+ }
519
+ function scanRubyDoubleQuotedStringIndex(line, startI) {
520
+ let i = startI;
521
+ while (i < line.length && line[i] !== '"') {
522
+ if (line[i] === "\\" && i + 1 < line.length) {
523
+ i += 2;
524
+ } else if (line[i] === "#" && i + 1 < line.length && line[i + 1] === "{") {
525
+ i += 2;
526
+ let depth = 1;
527
+ while (i < line.length && depth > 0) {
528
+ if (line[i] === "{") {
529
+ depth++;
530
+ i++;
531
+ } else if (line[i] === "}") {
532
+ depth--;
533
+ i++;
534
+ } else if (line[i] === "\\" && i + 1 < line.length) {
535
+ i += 2;
536
+ } else {
537
+ i++;
538
+ }
539
+ }
540
+ } else {
541
+ i++;
542
+ }
543
+ }
544
+ if (i < line.length) {
545
+ i++;
546
+ }
547
+ return i;
548
+ }
549
+ function stripPhp(content) {
550
+ let result = "";
551
+ let i = 0;
552
+ while (i < content.length) {
553
+ if (content[i] === "<" && content[i + 1] === "<" && content[i + 2] === "<") {
554
+ const heredocStart = i;
555
+ let j = i + 3;
556
+ let isNowdoc = false;
557
+ if (j < content.length && content[j] === "'") {
558
+ isNowdoc = true;
559
+ j++;
560
+ }
561
+ const identStart = j;
562
+ while (j < content.length && /[A-Za-z0-9_]/.test(content[j])) {
563
+ j++;
564
+ }
565
+ const identifier = content.slice(identStart, j);
566
+ if (identifier.length > 0) {
567
+ if (isNowdoc && j < content.length && content[j] === "'") {
568
+ j++;
569
+ }
570
+ let validHeredoc = false;
571
+ let lineEnd = j;
572
+ if (lineEnd < content.length && content[lineEnd] === "\n") {
573
+ validHeredoc = true;
574
+ }
575
+ if (validHeredoc) {
576
+ for (let k = heredocStart; k <= lineEnd; k++) {
577
+ if (content[k] === "\n") {
578
+ result += "\n";
579
+ } else {
580
+ result += content[k];
581
+ }
582
+ }
583
+ i = lineEnd + 1;
584
+ let found = false;
585
+ while (i < content.length && !found) {
586
+ const lineStart = i;
587
+ let lineEndIdx = i;
588
+ while (lineEndIdx < content.length && content[lineEndIdx] !== "\n") {
589
+ lineEndIdx++;
590
+ }
591
+ const currentLine = content.slice(lineStart, lineEndIdx);
592
+ const trimmedLine = currentLine.trimStart();
593
+ if (trimmedLine === identifier || trimmedLine === identifier + ";" || trimmedLine === identifier + "," || trimmedLine === identifier + ");" || trimmedLine === identifier + ")" || trimmedLine.startsWith(identifier + ";") || trimmedLine === identifier) {
594
+ for (let k = lineStart; k < lineEndIdx; k++) {
595
+ result += content[k];
596
+ }
597
+ i = lineEndIdx;
598
+ if (i < content.length && content[i] === "\n") {
599
+ result += "\n";
600
+ i++;
601
+ }
602
+ found = true;
603
+ } else {
604
+ for (let k = lineStart; k < lineEndIdx; k++) {
605
+ result += " ";
606
+ }
607
+ i = lineEndIdx;
608
+ if (i < content.length && content[i] === "\n") {
609
+ result += "\n";
610
+ i++;
611
+ }
612
+ }
613
+ }
614
+ continue;
615
+ }
616
+ }
617
+ result += content[i];
618
+ i++;
619
+ continue;
620
+ }
621
+ if (content[i] === "/" && content[i + 1] === "/" || content[i] === "#") {
622
+ while (i < content.length && content[i] !== "\n") {
623
+ result += " ";
624
+ i++;
625
+ }
626
+ } else if (content[i] === "/" && content[i + 1] === "*") {
627
+ result += " ";
628
+ i++;
629
+ result += " ";
630
+ i++;
631
+ while (i < content.length) {
632
+ if (content[i] === "*" && content[i + 1] === "/") {
633
+ result += " ";
634
+ i++;
635
+ result += " ";
636
+ i++;
637
+ break;
638
+ }
639
+ result += content[i] === "\n" ? "\n" : " ";
640
+ i++;
641
+ }
642
+ } else if (content[i] === '"') {
643
+ result += content[i];
644
+ i++;
645
+ while (i < content.length && content[i] !== '"') {
646
+ if (content[i] === "\\" && i + 1 < content.length) {
647
+ result += content[i++];
648
+ result += content[i++];
649
+ } else {
650
+ result += content[i++];
651
+ }
652
+ }
653
+ if (i < content.length) {
654
+ result += content[i];
655
+ i++;
656
+ }
657
+ } else if (content[i] === "'") {
658
+ result += content[i];
659
+ i++;
660
+ while (i < content.length && content[i] !== "'") {
661
+ if (content[i] === "\\" && i + 1 < content.length) {
662
+ result += content[i++];
663
+ result += content[i++];
664
+ } else {
665
+ result += content[i++];
666
+ }
667
+ }
668
+ if (i < content.length) {
669
+ result += content[i];
670
+ i++;
671
+ }
672
+ } else {
673
+ result += content[i];
674
+ i++;
675
+ }
676
+ }
677
+ return result;
678
+ }
679
+
178
680
  // src/analyzer/engines/regex-engine.ts
179
681
  var RegexEngine = class {
180
682
  constructor(config) {
@@ -188,6 +690,7 @@ var RegexEngine = class {
188
690
  "\\.archtracker"
189
691
  ].map((p) => new RegExp(p));
190
692
  const projectFiles = await this.collectFiles(
693
+ absRootDir,
191
694
  absRootDir,
192
695
  excludePatterns,
193
696
  options.maxDepth ?? 0
@@ -195,6 +698,7 @@ var RegexEngine = class {
195
698
  const projectFileSet = new Set(projectFiles);
196
699
  const files = {};
197
700
  const edges = [];
701
+ const edgeSet = /* @__PURE__ */ new Set();
198
702
  for (const filePath of projectFiles) {
199
703
  const relPath = relative(absRootDir, filePath);
200
704
  files[relPath] = {
@@ -210,9 +714,11 @@ var RegexEngine = class {
210
714
  try {
211
715
  content = await readFile(filePath, "utf-8");
212
716
  } catch {
717
+ if (files[relSource]) files[relSource].exists = false;
213
718
  continue;
214
719
  }
215
- const imports = this.extractImports(content);
720
+ const stripped = stripComments(content, this.config.commentStyle);
721
+ const imports = this.extractImports(stripped);
216
722
  for (const importPath of imports) {
217
723
  const resolved = this.config.resolveImport(
218
724
  importPath,
@@ -223,6 +729,10 @@ var RegexEngine = class {
223
729
  if (!resolved) continue;
224
730
  const relTarget = relative(absRootDir, resolved);
225
731
  if (!files[relTarget]) continue;
732
+ if (relSource === relTarget) continue;
733
+ const edgeKey = `${relSource}\0${relTarget}`;
734
+ if (edgeSet.has(edgeKey)) continue;
735
+ edgeSet.add(edgeKey);
226
736
  edges.push({
227
737
  source: relSource,
228
738
  target: relTarget,
@@ -243,9 +753,13 @@ var RegexEngine = class {
243
753
  };
244
754
  }
245
755
  extractImports(content) {
756
+ if (this.config.extractImports) {
757
+ return this.config.extractImports(content);
758
+ }
246
759
  const imports = [];
247
760
  for (const pattern of this.config.importPatterns) {
248
- const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
761
+ const flags = pattern.regex.flags.includes("g") ? pattern.regex.flags : pattern.regex.flags + "g";
762
+ const regex = new RegExp(pattern.regex.source, flags);
249
763
  let match;
250
764
  while ((match = regex.exec(content)) !== null) {
251
765
  if (match[1]) {
@@ -255,7 +769,7 @@ var RegexEngine = class {
255
769
  }
256
770
  return imports;
257
771
  }
258
- async collectFiles(dir, excludePatterns, maxDepth, currentDepth = 0) {
772
+ async collectFiles(dir, absRootDir, excludePatterns, maxDepth, currentDepth = 0) {
259
773
  if (maxDepth > 0 && currentDepth >= maxDepth) return [];
260
774
  const results = [];
261
775
  let entries;
@@ -266,7 +780,7 @@ var RegexEngine = class {
266
780
  }
267
781
  for (const entry of entries) {
268
782
  const fullPath = join(dir, entry.name);
269
- const relPath = relative(dir, fullPath);
783
+ const relPath = relative(absRootDir, fullPath);
270
784
  if (excludePatterns.some(
271
785
  (p) => p.test(entry.name) || p.test(relPath) || p.test(fullPath)
272
786
  )) {
@@ -276,6 +790,7 @@ var RegexEngine = class {
276
790
  if (entry.name.startsWith(".")) continue;
277
791
  const sub = await this.collectFiles(
278
792
  fullPath,
793
+ absRootDir,
279
794
  excludePatterns,
280
795
  maxDepth,
281
796
  currentDepth + 1
@@ -308,14 +823,21 @@ var MARKERS = [
308
823
  { file: "pom.xml", language: "java" },
309
824
  { file: "build.gradle", language: "java" },
310
825
  { file: "build.gradle.kts", language: "kotlin" },
826
+ { file: "build.sbt", language: "scala" },
827
+ { file: "build.sc", language: "scala" },
311
828
  { file: "Package.swift", language: "swift" },
312
829
  { file: "Gemfile", language: "ruby" },
313
830
  { file: "composer.json", language: "php" },
831
+ { file: "pubspec.yaml", language: "dart" },
314
832
  { file: "CMakeLists.txt", language: "c-cpp" },
315
833
  { file: "Makefile", language: "c-cpp" },
316
834
  { file: "package.json", language: "javascript" },
317
835
  { file: "tsconfig.json", language: "javascript" }
318
836
  ];
837
+ var EXT_MARKERS = [
838
+ [".sln", "c-sharp"],
839
+ [".csproj", "c-sharp"]
840
+ ];
319
841
  var EXT_MAP = {
320
842
  ".ts": "javascript",
321
843
  ".tsx": "javascript",
@@ -337,7 +859,11 @@ var EXT_MAP = {
337
859
  ".php": "php",
338
860
  ".swift": "swift",
339
861
  ".kt": "kotlin",
340
- ".kts": "kotlin"
862
+ ".kts": "kotlin",
863
+ ".cs": "c-sharp",
864
+ ".dart": "dart",
865
+ ".scala": "scala",
866
+ ".sc": "scala"
341
867
  };
342
868
  async function detectLanguage(rootDir) {
343
869
  for (const marker of MARKERS) {
@@ -349,6 +875,16 @@ async function detectLanguage(rootDir) {
349
875
  } catch {
350
876
  }
351
877
  }
878
+ try {
879
+ const topEntries = await readdir2(rootDir, { withFileTypes: true });
880
+ for (const entry of topEntries) {
881
+ if (!entry.isFile()) continue;
882
+ for (const [ext, lang] of EXT_MARKERS) {
883
+ if (entry.name.endsWith(ext)) return lang;
884
+ }
885
+ }
886
+ } catch {
887
+ }
352
888
  const counts = /* @__PURE__ */ new Map();
353
889
  try {
354
890
  await scanExtensions(rootDir, counts, 2, 0);
@@ -393,16 +929,35 @@ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
393
929
  }
394
930
 
395
931
  // src/analyzer/engines/languages.ts
932
+ import { readFileSync } from "fs";
396
933
  import { join as join3, dirname, resolve as resolve3 } from "path";
397
934
  var python = {
398
935
  id: "python",
399
936
  extensions: [".py"],
937
+ commentStyle: "python",
400
938
  importPatterns: [
401
939
  // from package.module import something
402
- { regex: /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm },
403
- // import package.module
404
- { regex: /^import\s+([\w.]+)/gm }
940
+ { regex: /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm }
941
+ // import package.module (handled by extractImports for multi-module case)
405
942
  ],
943
+ // Bug #1 fix: custom extractImports to handle `import a, b, c`
944
+ extractImports(content) {
945
+ const imports = [];
946
+ const fromRegex = /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm;
947
+ let match;
948
+ while ((match = fromRegex.exec(content)) !== null) {
949
+ imports.push(match[1]);
950
+ }
951
+ const importRegex = /^import\s+([\w.]+(?:\s*,\s*[\w.]+)*)/gm;
952
+ while ((match = importRegex.exec(content)) !== null) {
953
+ const modules = match[1].split(",");
954
+ for (const mod of modules) {
955
+ const trimmed = mod.trim();
956
+ if (trimmed) imports.push(trimmed);
957
+ }
958
+ }
959
+ return imports;
960
+ },
406
961
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
407
962
  if (importPath.startsWith(".")) {
408
963
  const dots = importPath.match(/^\.+/)?.[0].length ?? 1;
@@ -424,12 +979,33 @@ function tryPythonResolve(base, projectFiles) {
424
979
  var rust = {
425
980
  id: "rust",
426
981
  extensions: [".rs"],
427
- importPatterns: [
428
- // use crate::module::item;
429
- { regex: /\buse\s+crate::(\w[\w:]*)/gm },
430
- // mod child;
431
- { regex: /\bmod\s+(\w+)\s*;/gm }
432
- ],
982
+ commentStyle: "c-style",
983
+ importPatterns: [],
984
+ // handled by extractImports
985
+ extractImports(content) {
986
+ const imports = [];
987
+ const modRegex = /\bmod\s+(\w+)\s*;/gm;
988
+ let match;
989
+ while ((match = modRegex.exec(content)) !== null) {
990
+ imports.push(match[1]);
991
+ }
992
+ const useRegex = /\buse\s+crate::([\s\S]*?);/gm;
993
+ while ((match = useRegex.exec(content)) !== null) {
994
+ const body = match[1].trim();
995
+ extractRustUsePaths(body, "", imports);
996
+ }
997
+ const useSuperRegex = /\buse\s+super::([\s\S]*?);/gm;
998
+ while ((match = useSuperRegex.exec(content)) !== null) {
999
+ const body = match[1].trim();
1000
+ extractRustUsePaths(body, "", imports, "super");
1001
+ }
1002
+ const useSelfRegex = /\buse\s+self::([\s\S]*?);/gm;
1003
+ while ((match = useSelfRegex.exec(content)) !== null) {
1004
+ const body = match[1].trim();
1005
+ extractRustUsePaths(body, "", imports, "self");
1006
+ }
1007
+ return imports;
1008
+ },
433
1009
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
434
1010
  const srcDir = join3(rootDir, "src");
435
1011
  if (!importPath.includes("::")) {
@@ -440,6 +1016,30 @@ var rust = {
440
1016
  if (projectFiles.has(asDir)) return asDir;
441
1017
  return null;
442
1018
  }
1019
+ if (importPath.startsWith("super::")) {
1020
+ const parentDir = dirname(dirname(sourceFile));
1021
+ const segments2 = importPath.slice("super::".length).split("::");
1022
+ for (let i = segments2.length; i > 0; i--) {
1023
+ const path = segments2.slice(0, i).join("/");
1024
+ const asFile = join3(parentDir, path + ".rs");
1025
+ if (projectFiles.has(asFile)) return asFile;
1026
+ const asDir = join3(parentDir, path, "mod.rs");
1027
+ if (projectFiles.has(asDir)) return asDir;
1028
+ }
1029
+ return null;
1030
+ }
1031
+ if (importPath.startsWith("self::")) {
1032
+ const selfDir = dirname(sourceFile);
1033
+ const segments2 = importPath.slice("self::".length).split("::");
1034
+ for (let i = segments2.length; i > 0; i--) {
1035
+ const path = segments2.slice(0, i).join("/");
1036
+ const asFile = join3(selfDir, path + ".rs");
1037
+ if (projectFiles.has(asFile)) return asFile;
1038
+ const asDir = join3(selfDir, path, "mod.rs");
1039
+ if (projectFiles.has(asDir)) return asDir;
1040
+ }
1041
+ return null;
1042
+ }
443
1043
  const segments = importPath.split("::");
444
1044
  for (let i = segments.length; i > 0; i--) {
445
1045
  const path = segments.slice(0, i).join("/");
@@ -452,15 +1052,88 @@ var rust = {
452
1052
  },
453
1053
  defaultExclude: ["target"]
454
1054
  };
1055
+ function extractRustUsePaths(body, prefix, results, rootPrefix) {
1056
+ const trimmed = body.trim();
1057
+ const braceStart = trimmed.indexOf("{");
1058
+ if (braceStart === -1) {
1059
+ let path = prefix ? `${prefix}::${trimmed}` : trimmed;
1060
+ if (rootPrefix) {
1061
+ path = `${rootPrefix}::${path}`;
1062
+ }
1063
+ if (path && !path.includes("{")) {
1064
+ results.push(path);
1065
+ }
1066
+ return;
1067
+ }
1068
+ let pathPrefix = trimmed.slice(0, braceStart).trim();
1069
+ if (pathPrefix.endsWith("::")) {
1070
+ pathPrefix = pathPrefix.slice(0, -2);
1071
+ }
1072
+ const fullPrefix = prefix ? `${prefix}::${pathPrefix}` : pathPrefix;
1073
+ let depth = 0;
1074
+ let braceEnd = -1;
1075
+ for (let i = braceStart; i < trimmed.length; i++) {
1076
+ if (trimmed[i] === "{") depth++;
1077
+ else if (trimmed[i] === "}") {
1078
+ depth--;
1079
+ if (depth === 0) {
1080
+ braceEnd = i;
1081
+ break;
1082
+ }
1083
+ }
1084
+ }
1085
+ if (braceEnd === -1) return;
1086
+ const inner = trimmed.slice(braceStart + 1, braceEnd).trim();
1087
+ const items = splitByTopLevelComma(inner);
1088
+ for (const item of items) {
1089
+ const cleaned = item.trim();
1090
+ if (cleaned === "self") {
1091
+ const selfPath = rootPrefix ? `${rootPrefix}::${fullPrefix}` : fullPrefix;
1092
+ results.push(selfPath);
1093
+ } else if (cleaned) {
1094
+ extractRustUsePaths(cleaned, fullPrefix, results, rootPrefix);
1095
+ }
1096
+ }
1097
+ }
1098
+ function splitByTopLevelComma(s) {
1099
+ const parts = [];
1100
+ let depth = 0;
1101
+ let start = 0;
1102
+ for (let i = 0; i < s.length; i++) {
1103
+ if (s[i] === "{") depth++;
1104
+ else if (s[i] === "}") depth--;
1105
+ else if (s[i] === "," && depth === 0) {
1106
+ parts.push(s.slice(start, i));
1107
+ start = i + 1;
1108
+ }
1109
+ }
1110
+ parts.push(s.slice(start));
1111
+ return parts;
1112
+ }
455
1113
  var go = {
456
1114
  id: "go",
457
1115
  extensions: [".go"],
458
- importPatterns: [
459
- // import "path" or import ( "path" )
460
- { regex: /\bimport\s+(?:\w+\s+)?"([^"]+)"/gm },
461
- // import block entries
462
- { regex: /^\s+(?:\w+\s+)?"([^"]+)"/gm }
463
- ],
1116
+ commentStyle: "c-style",
1117
+ importPatterns: [],
1118
+ // handled by extractImports
1119
+ extractImports(content) {
1120
+ const imports = [];
1121
+ const singleRegex = /\bimport\s+(?:\w+\s+)?"([^"]+)"/gm;
1122
+ let match;
1123
+ while ((match = singleRegex.exec(content)) !== null) {
1124
+ imports.push(match[1]);
1125
+ }
1126
+ const blockRegex = /\bimport\s*\(([^)]*)\)/gms;
1127
+ while ((match = blockRegex.exec(content)) !== null) {
1128
+ const block = match[1];
1129
+ const entryRegex = /(?:\w+\s+)?"([^"]+)"/g;
1130
+ let entry;
1131
+ while ((entry = entryRegex.exec(block)) !== null) {
1132
+ imports.push(entry[1]);
1133
+ }
1134
+ }
1135
+ return imports;
1136
+ },
464
1137
  resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
465
1138
  const modPrefix = goModulePrefix(rootDir);
466
1139
  if (!modPrefix || !importPath.startsWith(modPrefix)) return null;
@@ -477,8 +1150,7 @@ var goModCache = /* @__PURE__ */ new Map();
477
1150
  function goModulePrefix(rootDir) {
478
1151
  if (goModCache.has(rootDir)) return goModCache.get(rootDir);
479
1152
  try {
480
- const fs = __require("fs");
481
- const content = fs.readFileSync(join3(rootDir, "go.mod"), "utf-8");
1153
+ const content = readFileSync(join3(rootDir, "go.mod"), "utf-8");
482
1154
  const match = content.match(/^module\s+(.+)$/m);
483
1155
  const prefix = match ? match[1].trim() : null;
484
1156
  goModCache.set(rootDir, prefix);
@@ -491,15 +1163,23 @@ function goModulePrefix(rootDir) {
491
1163
  var java = {
492
1164
  id: "java",
493
1165
  extensions: [".java"],
1166
+ commentStyle: "c-style",
494
1167
  importPatterns: [
495
- // import com.example.ClassName;
496
- { regex: /^import\s+(?:static\s+)?([\w.]+);/gm }
1168
+ // Bug #5 fix: import com.example.ClassName; and import com.example.*; (wildcard)
1169
+ // Bug #6: static imports also captured here
1170
+ { regex: /^import\s+(?:static\s+)?([\w.*]+);/gm }
497
1171
  ],
498
1172
  resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
499
- const filePath = importPath.replace(/\./g, "/") + ".java";
500
- for (const srcRoot of ["", "src/main/java/", "src/", "app/src/main/java/"]) {
501
- const full = join3(rootDir, srcRoot, filePath);
502
- if (projectFiles.has(full)) return full;
1173
+ if (importPath.endsWith(".*")) {
1174
+ return null;
1175
+ }
1176
+ const segments = importPath.split(".");
1177
+ for (let i = segments.length; i > 0; i--) {
1178
+ const filePath = segments.slice(0, i).join("/") + ".java";
1179
+ for (const srcRoot of ["", "src/main/java/", "src/", "app/src/main/java/"]) {
1180
+ const full = join3(rootDir, srcRoot, filePath);
1181
+ if (projectFiles.has(full)) return full;
1182
+ }
503
1183
  }
504
1184
  return null;
505
1185
  },
@@ -508,6 +1188,7 @@ var java = {
508
1188
  var cCpp = {
509
1189
  id: "c-cpp",
510
1190
  extensions: [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp"],
1191
+ commentStyle: "c-style",
511
1192
  importPatterns: [
512
1193
  // #include "file.h" (skip <system> includes)
513
1194
  { regex: /^#include\s+"([^"]+)"/gm }
@@ -528,6 +1209,7 @@ var cCpp = {
528
1209
  var ruby = {
529
1210
  id: "ruby",
530
1211
  extensions: [".rb"],
1212
+ commentStyle: "ruby",
531
1213
  importPatterns: [
532
1214
  // require_relative 'path'
533
1215
  { regex: /\brequire_relative\s+['"]([^'"]+)['"]/gm },
@@ -549,11 +1231,12 @@ var ruby = {
549
1231
  var php = {
550
1232
  id: "php",
551
1233
  extensions: [".php"],
1234
+ commentStyle: "php",
552
1235
  importPatterns: [
553
1236
  // require/include/require_once/include_once 'path'
554
1237
  { regex: /\b(?:require|include)(?:_once)?\s+['"]([^'"]+)['"]/gm },
555
- // use Namespace\Class (PSR-4 style)
556
- { regex: /^use\s+([\w\\]+)/gm }
1238
+ // Bug #9 fix: use Namespace\Class skip `function` and `const` keywords
1239
+ { regex: /^use\s+(?:function\s+|const\s+)?([\w\\]+)/gm }
557
1240
  ],
558
1241
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
559
1242
  if (importPath.includes("/") || importPath.endsWith(".php")) {
@@ -576,11 +1259,12 @@ var php = {
576
1259
  var swift = {
577
1260
  id: "swift",
578
1261
  extensions: [".swift"],
1262
+ commentStyle: "c-style",
579
1263
  importPatterns: [
580
- // import ModuleName
581
- { regex: /^import\s+(?:class|struct|enum|protocol|func|var|let|typealias)?\s*(\w+)/gm }
1264
+ // Bug #10 fix: import ModuleName and @testable import ModuleName
1265
+ { regex: /^(?:@testable\s+)?import\s+(?:class\s+|struct\s+|enum\s+|protocol\s+|func\s+|var\s+|let\s+|typealias\s+)?(\w+)/gm }
582
1266
  ],
583
- resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
1267
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
584
1268
  const spmDir = join3(rootDir, "Sources", importPath);
585
1269
  for (const f of projectFiles) {
586
1270
  if (f.startsWith(spmDir + "/") && f.endsWith(".swift")) return f;
@@ -592,12 +1276,20 @@ var swift = {
592
1276
  var kotlin = {
593
1277
  id: "kotlin",
594
1278
  extensions: [".kt", ".kts"],
1279
+ commentStyle: "c-style",
595
1280
  importPatterns: [
596
- // import com.example.ClassName
597
- { regex: /^import\s+([\w.]+)/gm }
1281
+ // Bug #7/#8 fix: import com.example.ClassName and import com.example.*
1282
+ { regex: /^import\s+([\w.*]+)/gm }
598
1283
  ],
599
1284
  resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
600
- const filePath = importPath.replace(/\./g, "/");
1285
+ if (importPath.endsWith(".*")) {
1286
+ return null;
1287
+ }
1288
+ let cleanPath = importPath;
1289
+ if (cleanPath.endsWith(".")) {
1290
+ cleanPath = cleanPath.slice(0, -1);
1291
+ }
1292
+ const filePath = cleanPath.replace(/\./g, "/");
601
1293
  for (const ext of [".kt", ".kts"]) {
602
1294
  for (const srcRoot of [
603
1295
  "",
@@ -615,6 +1307,124 @@ var kotlin = {
615
1307
  },
616
1308
  defaultExclude: ["build", "\\.gradle", "\\.idea"]
617
1309
  };
1310
+ var cSharp = {
1311
+ id: "c-sharp",
1312
+ extensions: [".cs"],
1313
+ commentStyle: "c-style",
1314
+ importPatterns: [
1315
+ // using Namespace; and using Namespace.SubNamespace;
1316
+ // using static Namespace.Class;
1317
+ // Skip: using Alias = Namespace.Class; (captured but resolved same way)
1318
+ { regex: /^using\s+(?:static\s+)?([\w.]+)\s*;/gm }
1319
+ ],
1320
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
1321
+ const segments = importPath.split(".");
1322
+ for (let i = segments.length; i > 0; i--) {
1323
+ const filePath = segments.slice(0, i).join("/") + ".cs";
1324
+ for (const srcRoot of ["", "src/", "lib/"]) {
1325
+ const full = join3(rootDir, srcRoot, filePath);
1326
+ if (projectFiles.has(full)) return full;
1327
+ }
1328
+ }
1329
+ for (let start = 0; start < segments.length; start++) {
1330
+ const dirPath = segments.slice(start).join("/");
1331
+ for (const srcRoot of ["", "src/", "lib/"]) {
1332
+ const prefix = join3(rootDir, srcRoot, dirPath) + "/";
1333
+ for (const f of projectFiles) {
1334
+ if (f.startsWith(prefix) && f.endsWith(".cs")) return f;
1335
+ }
1336
+ }
1337
+ }
1338
+ return null;
1339
+ },
1340
+ defaultExclude: ["bin", "obj", "\\.vs", "packages", "TestResults"]
1341
+ };
1342
+ var dart = {
1343
+ id: "dart",
1344
+ extensions: [".dart"],
1345
+ commentStyle: "c-style",
1346
+ importPatterns: [
1347
+ // import 'package:pkg/file.dart'; or import 'relative/path.dart';
1348
+ { regex: /^import\s+['"]([^'"]+)['"]/gm },
1349
+ // export 'file.dart'; (re-exports create dependencies too)
1350
+ { regex: /^export\s+['"]([^'"]+)['"]/gm }
1351
+ ],
1352
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1353
+ if (importPath.startsWith("dart:")) return null;
1354
+ if (importPath.startsWith("package:")) {
1355
+ const ownPackage = dartPackageName(rootDir);
1356
+ if (!ownPackage) return null;
1357
+ const prefix = `package:${ownPackage}/`;
1358
+ if (!importPath.startsWith(prefix)) return null;
1359
+ const relPath = importPath.slice(prefix.length);
1360
+ const full = join3(rootDir, "lib", relPath);
1361
+ if (projectFiles.has(full)) return full;
1362
+ return null;
1363
+ }
1364
+ const resolved = resolve3(dirname(sourceFile), importPath);
1365
+ if (projectFiles.has(resolved)) return resolved;
1366
+ return null;
1367
+ },
1368
+ defaultExclude: ["\\.dart_tool", "build", "\\.packages"]
1369
+ };
1370
+ var dartPackageCache = /* @__PURE__ */ new Map();
1371
+ function dartPackageName(rootDir) {
1372
+ if (dartPackageCache.has(rootDir)) return dartPackageCache.get(rootDir);
1373
+ try {
1374
+ const content = readFileSync(join3(rootDir, "pubspec.yaml"), "utf-8");
1375
+ const match = content.match(/^name:\s*(\S+)/m);
1376
+ const name = match ? match[1] : null;
1377
+ dartPackageCache.set(rootDir, name);
1378
+ return name;
1379
+ } catch {
1380
+ dartPackageCache.set(rootDir, null);
1381
+ return null;
1382
+ }
1383
+ }
1384
+ var scala = {
1385
+ id: "scala",
1386
+ extensions: [".scala", ".sc"],
1387
+ commentStyle: "c-style",
1388
+ importPatterns: [],
1389
+ // handled by extractImports for grouped syntax
1390
+ extractImports(content) {
1391
+ const imports = [];
1392
+ const importRegex = /\bimport\s+([\w.]+(?:\.\{[^}]+\}|\.\w+|\._))/gm;
1393
+ let match;
1394
+ while ((match = importRegex.exec(content)) !== null) {
1395
+ const full = match[1];
1396
+ const braceMatch = full.match(/^([\w.]+)\.\{([^}]+)\}$/);
1397
+ if (braceMatch) {
1398
+ const prefix = braceMatch[1];
1399
+ const items = braceMatch[2].split(",");
1400
+ for (const item of items) {
1401
+ const trimmed = item.trim().split(/\s+/)[0];
1402
+ if (trimmed === "_") continue;
1403
+ imports.push(`${prefix}.${trimmed}`);
1404
+ }
1405
+ } else if (full.endsWith("._")) {
1406
+ continue;
1407
+ } else {
1408
+ imports.push(full);
1409
+ }
1410
+ }
1411
+ return imports;
1412
+ },
1413
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
1414
+ const segments = importPath.split(".");
1415
+ for (let i = segments.length; i > 0; i--) {
1416
+ const filePath = segments.slice(0, i).join("/");
1417
+ for (const ext of [".scala", ".sc"]) {
1418
+ for (const srcRoot of ["", "src/main/scala/", "src/", "app/"]) {
1419
+ const full = join3(rootDir, srcRoot, filePath + ext);
1420
+ if (projectFiles.has(full)) return full;
1421
+ }
1422
+ }
1423
+ }
1424
+ return null;
1425
+ },
1426
+ defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
1427
+ };
618
1428
  var LANGUAGE_CONFIGS = {
619
1429
  javascript: null,
620
1430
  // handled by DependencyCruiserEngine
@@ -623,10 +1433,13 @@ var LANGUAGE_CONFIGS = {
623
1433
  go,
624
1434
  java,
625
1435
  "c-cpp": cCpp,
1436
+ "c-sharp": cSharp,
626
1437
  ruby,
627
1438
  php,
628
1439
  swift,
629
- kotlin
1440
+ kotlin,
1441
+ dart,
1442
+ scala
630
1443
  };
631
1444
  function getLanguageConfig(id) {
632
1445
  return LANGUAGE_CONFIGS[id] ?? null;