archtracker-mcp 0.2.1 → 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/index.js CHANGED
@@ -177,12 +177,50 @@ function stripComments(content, style) {
177
177
  return stripRuby(content);
178
178
  case "php":
179
179
  return stripPhp(content);
180
+ default: {
181
+ const _exhaustive = style;
182
+ throw new Error(`Unknown comment style: ${_exhaustive}`);
183
+ }
180
184
  }
181
185
  }
182
186
  function stripCStyle(content) {
183
187
  let result = "";
184
188
  let i = 0;
185
189
  while (i < content.length) {
190
+ if (content[i] === "r" && i + 1 < content.length) {
191
+ let hashes = 0;
192
+ let j = i + 1;
193
+ while (j < content.length && content[j] === "#") {
194
+ hashes++;
195
+ j++;
196
+ }
197
+ if (j < content.length && content[j] === '"') {
198
+ for (let k = i; k <= j; k++) {
199
+ result += " ";
200
+ }
201
+ i = j + 1;
202
+ while (i < content.length) {
203
+ if (content[i] === '"') {
204
+ let matchHashes = 0;
205
+ let m = i + 1;
206
+ while (m < content.length && content[m] === "#" && matchHashes < hashes) {
207
+ matchHashes++;
208
+ m++;
209
+ }
210
+ if (matchHashes === hashes) {
211
+ for (let k = i; k < m; k++) {
212
+ result += " ";
213
+ }
214
+ i = m;
215
+ break;
216
+ }
217
+ }
218
+ result += content[i] === "\n" ? "\n" : " ";
219
+ i++;
220
+ }
221
+ continue;
222
+ }
223
+ }
186
224
  if (content[i] === "/" && content[i + 1] === "/") {
187
225
  while (i < content.length && content[i] !== "\n") {
188
226
  result += " ";
@@ -225,6 +263,24 @@ function stripCStyle(content) {
225
263
  result += content[i];
226
264
  i++;
227
265
  }
266
+ } else if (content[i] === "'") {
267
+ result += content[i];
268
+ i++;
269
+ while (i < content.length && content[i] !== "'") {
270
+ if (content[i] === "\\" && i + 1 < content.length) {
271
+ result += content[i];
272
+ i++;
273
+ result += content[i];
274
+ i++;
275
+ } else {
276
+ result += content[i];
277
+ i++;
278
+ }
279
+ }
280
+ if (i < content.length) {
281
+ result += content[i];
282
+ i++;
283
+ }
228
284
  } else if (content[i] === "`") {
229
285
  result += " ";
230
286
  i++;
@@ -293,31 +349,62 @@ function stripPython(content) {
293
349
  let result = "";
294
350
  let i = 0;
295
351
  while (i < content.length) {
296
- if (content[i] === '"' && content[i + 1] === '"' && content[i + 2] === '"' || content[i] === "'" && content[i + 1] === "'" && content[i + 2] === "'") {
297
- const quote = content[i];
298
- const tripleQuote = quote + quote + quote;
352
+ let prefixLen = 0;
353
+ let isRaw = false;
354
+ if (i < content.length) {
355
+ const c0 = content[i];
356
+ const c1 = i + 1 < content.length ? content[i + 1] : "";
357
+ const c2 = i + 2 < content.length ? content[i + 2] : "";
358
+ 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 === "'")) {
359
+ const pair = (c0 + c1).toLowerCase();
360
+ if (pair === "rb" || pair === "br" || pair === "fr" || pair === "rf") {
361
+ prefixLen = 2;
362
+ isRaw = pair.includes("r");
363
+ }
364
+ }
365
+ if (prefixLen === 0 && (c0 === "r" || c0 === "R" || c0 === "b" || c0 === "B" || c0 === "f" || c0 === "F") && (c1 === '"' || c1 === "'")) {
366
+ prefixLen = 1;
367
+ isRaw = c0 === "r" || c0 === "R";
368
+ }
369
+ }
370
+ const quoteStart = i + prefixLen;
371
+ if (quoteStart + 2 < content.length && (content[quoteStart] === '"' || content[quoteStart] === "'") && content[quoteStart + 1] === content[quoteStart] && content[quoteStart + 2] === content[quoteStart]) {
372
+ const quote = content[quoteStart];
373
+ for (let k = 0; k < prefixLen; k++) {
374
+ result += " ";
375
+ }
299
376
  result += " ";
300
- i += 3;
377
+ i = quoteStart + 3;
301
378
  while (i < content.length) {
302
379
  if (content[i] === quote && content[i + 1] === quote && content[i + 2] === quote) {
303
380
  result += " ";
304
381
  i += 3;
305
382
  break;
306
383
  }
307
- result += content[i] === "\n" ? "\n" : " ";
308
- i++;
384
+ if (!isRaw && content[i] === "\\" && i + 1 < content.length) {
385
+ result += " ";
386
+ i++;
387
+ result += content[i] === "\n" ? "\n" : " ";
388
+ i++;
389
+ } else {
390
+ result += content[i] === "\n" ? "\n" : " ";
391
+ i++;
392
+ }
309
393
  }
310
- } else if (content[i] === "#") {
394
+ } else if (prefixLen === 0 && content[i] === "#") {
311
395
  while (i < content.length && content[i] !== "\n") {
312
396
  result += " ";
313
397
  i++;
314
398
  }
315
- } else if (content[i] === '"' || content[i] === "'") {
316
- const quote = content[i];
317
- result += content[i];
318
- i++;
399
+ } else if (quoteStart < content.length && (content[quoteStart] === '"' || content[quoteStart] === "'") && (prefixLen > 0 || content[i] === '"' || content[i] === "'")) {
400
+ const quote = content[quoteStart];
401
+ for (let k = i; k < quoteStart; k++) {
402
+ result += content[k];
403
+ }
404
+ result += content[quoteStart];
405
+ i = quoteStart + 1;
319
406
  while (i < content.length && content[i] !== quote) {
320
- if (content[i] === "\\" && i + 1 < content.length) {
407
+ if (!isRaw && content[i] === "\\" && i + 1 < content.length) {
321
408
  result += content[i++];
322
409
  result += content[i++];
323
410
  } else if (content[i] === "\n") {
@@ -344,13 +431,13 @@ function stripRuby(content) {
344
431
  const result = [];
345
432
  let inBlock = false;
346
433
  for (const line of lines) {
347
- if (!inBlock && line.startsWith("=begin")) {
434
+ if (!inBlock && /^=begin(\s|$)/.test(line)) {
348
435
  inBlock = true;
349
436
  result.push(" ".repeat(line.length));
350
437
  continue;
351
438
  }
352
439
  if (inBlock) {
353
- if (line.startsWith("=end")) {
440
+ if (/^=end(\s|$)/.test(line)) {
354
441
  inBlock = false;
355
442
  }
356
443
  result.push(" ".repeat(line.length));
@@ -365,18 +452,8 @@ function stripRuby(content) {
365
452
  } else if (line[i] === '"') {
366
453
  processed += line[i];
367
454
  i++;
368
- while (i < line.length && line[i] !== '"') {
369
- if (line[i] === "\\" && i + 1 < line.length) {
370
- processed += line[i++];
371
- processed += line[i++];
372
- } else {
373
- processed += line[i++];
374
- }
375
- }
376
- if (i < line.length) {
377
- processed += line[i];
378
- i++;
379
- }
455
+ processed = scanRubyDoubleQuotedString(line, i, processed);
456
+ i = scanRubyDoubleQuotedStringIndex(line, i);
380
457
  } else if (line[i] === "'") {
381
458
  processed += line[i];
382
459
  i++;
@@ -401,10 +478,141 @@ function stripRuby(content) {
401
478
  }
402
479
  return result.join("\n");
403
480
  }
481
+ function scanRubyDoubleQuotedString(line, startI, processed) {
482
+ let i = startI;
483
+ while (i < line.length && line[i] !== '"') {
484
+ if (line[i] === "\\" && i + 1 < line.length) {
485
+ processed += line[i++];
486
+ processed += line[i++];
487
+ } else if (line[i] === "#" && i + 1 < line.length && line[i + 1] === "{") {
488
+ processed += line[i++];
489
+ processed += line[i++];
490
+ let depth = 1;
491
+ while (i < line.length && depth > 0) {
492
+ if (line[i] === "{") {
493
+ depth++;
494
+ processed += line[i++];
495
+ } else if (line[i] === "}") {
496
+ depth--;
497
+ processed += line[i++];
498
+ } else if (line[i] === "\\" && i + 1 < line.length) {
499
+ processed += line[i++];
500
+ processed += line[i++];
501
+ } else {
502
+ processed += line[i++];
503
+ }
504
+ }
505
+ } else {
506
+ processed += line[i++];
507
+ }
508
+ }
509
+ if (i < line.length) {
510
+ processed += line[i];
511
+ }
512
+ return processed;
513
+ }
514
+ function scanRubyDoubleQuotedStringIndex(line, startI) {
515
+ let i = startI;
516
+ while (i < line.length && line[i] !== '"') {
517
+ if (line[i] === "\\" && i + 1 < line.length) {
518
+ i += 2;
519
+ } else if (line[i] === "#" && i + 1 < line.length && line[i + 1] === "{") {
520
+ i += 2;
521
+ let depth = 1;
522
+ while (i < line.length && depth > 0) {
523
+ if (line[i] === "{") {
524
+ depth++;
525
+ i++;
526
+ } else if (line[i] === "}") {
527
+ depth--;
528
+ i++;
529
+ } else if (line[i] === "\\" && i + 1 < line.length) {
530
+ i += 2;
531
+ } else {
532
+ i++;
533
+ }
534
+ }
535
+ } else {
536
+ i++;
537
+ }
538
+ }
539
+ if (i < line.length) {
540
+ i++;
541
+ }
542
+ return i;
543
+ }
404
544
  function stripPhp(content) {
405
545
  let result = "";
406
546
  let i = 0;
407
547
  while (i < content.length) {
548
+ if (content[i] === "<" && content[i + 1] === "<" && content[i + 2] === "<") {
549
+ const heredocStart = i;
550
+ let j = i + 3;
551
+ let isNowdoc = false;
552
+ if (j < content.length && content[j] === "'") {
553
+ isNowdoc = true;
554
+ j++;
555
+ }
556
+ const identStart = j;
557
+ while (j < content.length && /[A-Za-z0-9_]/.test(content[j])) {
558
+ j++;
559
+ }
560
+ const identifier = content.slice(identStart, j);
561
+ if (identifier.length > 0) {
562
+ if (isNowdoc && j < content.length && content[j] === "'") {
563
+ j++;
564
+ }
565
+ let validHeredoc = false;
566
+ let lineEnd = j;
567
+ if (lineEnd < content.length && content[lineEnd] === "\n") {
568
+ validHeredoc = true;
569
+ }
570
+ if (validHeredoc) {
571
+ for (let k = heredocStart; k <= lineEnd; k++) {
572
+ if (content[k] === "\n") {
573
+ result += "\n";
574
+ } else {
575
+ result += content[k];
576
+ }
577
+ }
578
+ i = lineEnd + 1;
579
+ let found = false;
580
+ while (i < content.length && !found) {
581
+ const lineStart = i;
582
+ let lineEndIdx = i;
583
+ while (lineEndIdx < content.length && content[lineEndIdx] !== "\n") {
584
+ lineEndIdx++;
585
+ }
586
+ const currentLine = content.slice(lineStart, lineEndIdx);
587
+ const trimmedLine = currentLine.trimStart();
588
+ if (trimmedLine === identifier || trimmedLine === identifier + ";" || trimmedLine === identifier + "," || trimmedLine === identifier + ");" || trimmedLine === identifier + ")" || trimmedLine.startsWith(identifier + ";") || trimmedLine === identifier) {
589
+ for (let k = lineStart; k < lineEndIdx; k++) {
590
+ result += content[k];
591
+ }
592
+ i = lineEndIdx;
593
+ if (i < content.length && content[i] === "\n") {
594
+ result += "\n";
595
+ i++;
596
+ }
597
+ found = true;
598
+ } else {
599
+ for (let k = lineStart; k < lineEndIdx; k++) {
600
+ result += " ";
601
+ }
602
+ i = lineEndIdx;
603
+ if (i < content.length && content[i] === "\n") {
604
+ result += "\n";
605
+ i++;
606
+ }
607
+ }
608
+ }
609
+ continue;
610
+ }
611
+ }
612
+ result += content[i];
613
+ i++;
614
+ continue;
615
+ }
408
616
  if (content[i] === "/" && content[i + 1] === "/" || content[i] === "#") {
409
617
  while (i < content.length && content[i] !== "\n") {
410
618
  result += " ";
@@ -477,6 +685,7 @@ var RegexEngine = class {
477
685
  "\\.archtracker"
478
686
  ].map((p) => new RegExp(p));
479
687
  const projectFiles = await this.collectFiles(
688
+ absRootDir,
480
689
  absRootDir,
481
690
  excludePatterns,
482
691
  options.maxDepth ?? 0
@@ -500,6 +709,7 @@ var RegexEngine = class {
500
709
  try {
501
710
  content = await readFile(filePath, "utf-8");
502
711
  } catch {
712
+ if (files[relSource]) files[relSource].exists = false;
503
713
  continue;
504
714
  }
505
715
  const stripped = stripComments(content, this.config.commentStyle);
@@ -543,7 +753,8 @@ var RegexEngine = class {
543
753
  }
544
754
  const imports = [];
545
755
  for (const pattern of this.config.importPatterns) {
546
- const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
756
+ const flags = pattern.regex.flags.includes("g") ? pattern.regex.flags : pattern.regex.flags + "g";
757
+ const regex = new RegExp(pattern.regex.source, flags);
547
758
  let match;
548
759
  while ((match = regex.exec(content)) !== null) {
549
760
  if (match[1]) {
@@ -553,7 +764,7 @@ var RegexEngine = class {
553
764
  }
554
765
  return imports;
555
766
  }
556
- async collectFiles(dir, excludePatterns, maxDepth, currentDepth = 0) {
767
+ async collectFiles(dir, absRootDir, excludePatterns, maxDepth, currentDepth = 0) {
557
768
  if (maxDepth > 0 && currentDepth >= maxDepth) return [];
558
769
  const results = [];
559
770
  let entries;
@@ -564,7 +775,7 @@ var RegexEngine = class {
564
775
  }
565
776
  for (const entry of entries) {
566
777
  const fullPath = join(dir, entry.name);
567
- const relPath = relative(dir, fullPath);
778
+ const relPath = relative(absRootDir, fullPath);
568
779
  if (excludePatterns.some(
569
780
  (p) => p.test(entry.name) || p.test(relPath) || p.test(fullPath)
570
781
  )) {
@@ -574,6 +785,7 @@ var RegexEngine = class {
574
785
  if (entry.name.startsWith(".")) continue;
575
786
  const sub = await this.collectFiles(
576
787
  fullPath,
788
+ absRootDir,
577
789
  excludePatterns,
578
790
  maxDepth,
579
791
  currentDepth + 1
@@ -606,14 +818,21 @@ var MARKERS = [
606
818
  { file: "pom.xml", language: "java" },
607
819
  { file: "build.gradle", language: "java" },
608
820
  { file: "build.gradle.kts", language: "kotlin" },
821
+ { file: "build.sbt", language: "scala" },
822
+ { file: "build.sc", language: "scala" },
609
823
  { file: "Package.swift", language: "swift" },
610
824
  { file: "Gemfile", language: "ruby" },
611
825
  { file: "composer.json", language: "php" },
826
+ { file: "pubspec.yaml", language: "dart" },
612
827
  { file: "CMakeLists.txt", language: "c-cpp" },
613
828
  { file: "Makefile", language: "c-cpp" },
614
829
  { file: "package.json", language: "javascript" },
615
830
  { file: "tsconfig.json", language: "javascript" }
616
831
  ];
832
+ var EXT_MARKERS = [
833
+ [".sln", "c-sharp"],
834
+ [".csproj", "c-sharp"]
835
+ ];
617
836
  var EXT_MAP = {
618
837
  ".ts": "javascript",
619
838
  ".tsx": "javascript",
@@ -635,7 +854,11 @@ var EXT_MAP = {
635
854
  ".php": "php",
636
855
  ".swift": "swift",
637
856
  ".kt": "kotlin",
638
- ".kts": "kotlin"
857
+ ".kts": "kotlin",
858
+ ".cs": "c-sharp",
859
+ ".dart": "dart",
860
+ ".scala": "scala",
861
+ ".sc": "scala"
639
862
  };
640
863
  async function detectLanguage(rootDir) {
641
864
  for (const marker of MARKERS) {
@@ -647,6 +870,16 @@ async function detectLanguage(rootDir) {
647
870
  } catch {
648
871
  }
649
872
  }
873
+ try {
874
+ const topEntries = await readdir2(rootDir, { withFileTypes: true });
875
+ for (const entry of topEntries) {
876
+ if (!entry.isFile()) continue;
877
+ for (const [ext, lang] of EXT_MARKERS) {
878
+ if (entry.name.endsWith(ext)) return lang;
879
+ }
880
+ }
881
+ } catch {
882
+ }
650
883
  const counts = /* @__PURE__ */ new Map();
651
884
  try {
652
885
  await scanExtensions(rootDir, counts, 2, 0);
@@ -699,10 +932,27 @@ var python = {
699
932
  commentStyle: "python",
700
933
  importPatterns: [
701
934
  // from package.module import something
702
- { regex: /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm },
703
- // import package.module
704
- { regex: /^import\s+([\w.]+)/gm }
935
+ { regex: /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm }
936
+ // import package.module (handled by extractImports for multi-module case)
705
937
  ],
938
+ // Bug #1 fix: custom extractImports to handle `import a, b, c`
939
+ extractImports(content) {
940
+ const imports = [];
941
+ const fromRegex = /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm;
942
+ let match;
943
+ while ((match = fromRegex.exec(content)) !== null) {
944
+ imports.push(match[1]);
945
+ }
946
+ const importRegex = /^import\s+([\w.]+(?:\s*,\s*[\w.]+)*)/gm;
947
+ while ((match = importRegex.exec(content)) !== null) {
948
+ const modules = match[1].split(",");
949
+ for (const mod of modules) {
950
+ const trimmed = mod.trim();
951
+ if (trimmed) imports.push(trimmed);
952
+ }
953
+ }
954
+ return imports;
955
+ },
706
956
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
707
957
  if (importPath.startsWith(".")) {
708
958
  const dots = importPath.match(/^\.+/)?.[0].length ?? 1;
@@ -739,6 +989,16 @@ var rust = {
739
989
  const body = match[1].trim();
740
990
  extractRustUsePaths(body, "", imports);
741
991
  }
992
+ const useSuperRegex = /\buse\s+super::([\s\S]*?);/gm;
993
+ while ((match = useSuperRegex.exec(content)) !== null) {
994
+ const body = match[1].trim();
995
+ extractRustUsePaths(body, "", imports, "super");
996
+ }
997
+ const useSelfRegex = /\buse\s+self::([\s\S]*?);/gm;
998
+ while ((match = useSelfRegex.exec(content)) !== null) {
999
+ const body = match[1].trim();
1000
+ extractRustUsePaths(body, "", imports, "self");
1001
+ }
742
1002
  return imports;
743
1003
  },
744
1004
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
@@ -751,6 +1011,30 @@ var rust = {
751
1011
  if (projectFiles.has(asDir)) return asDir;
752
1012
  return null;
753
1013
  }
1014
+ if (importPath.startsWith("super::")) {
1015
+ const parentDir = dirname(dirname(sourceFile));
1016
+ const segments2 = importPath.slice("super::".length).split("::");
1017
+ for (let i = segments2.length; i > 0; i--) {
1018
+ const path = segments2.slice(0, i).join("/");
1019
+ const asFile = join3(parentDir, path + ".rs");
1020
+ if (projectFiles.has(asFile)) return asFile;
1021
+ const asDir = join3(parentDir, path, "mod.rs");
1022
+ if (projectFiles.has(asDir)) return asDir;
1023
+ }
1024
+ return null;
1025
+ }
1026
+ if (importPath.startsWith("self::")) {
1027
+ const selfDir = dirname(sourceFile);
1028
+ const segments2 = importPath.slice("self::".length).split("::");
1029
+ for (let i = segments2.length; i > 0; i--) {
1030
+ const path = segments2.slice(0, i).join("/");
1031
+ const asFile = join3(selfDir, path + ".rs");
1032
+ if (projectFiles.has(asFile)) return asFile;
1033
+ const asDir = join3(selfDir, path, "mod.rs");
1034
+ if (projectFiles.has(asDir)) return asDir;
1035
+ }
1036
+ return null;
1037
+ }
754
1038
  const segments = importPath.split("::");
755
1039
  for (let i = segments.length; i > 0; i--) {
756
1040
  const path = segments.slice(0, i).join("/");
@@ -763,11 +1047,14 @@ var rust = {
763
1047
  },
764
1048
  defaultExclude: ["target"]
765
1049
  };
766
- function extractRustUsePaths(body, prefix, results) {
1050
+ function extractRustUsePaths(body, prefix, results, rootPrefix) {
767
1051
  const trimmed = body.trim();
768
1052
  const braceStart = trimmed.indexOf("{");
769
1053
  if (braceStart === -1) {
770
- const path = prefix ? `${prefix}::${trimmed}` : trimmed;
1054
+ let path = prefix ? `${prefix}::${trimmed}` : trimmed;
1055
+ if (rootPrefix) {
1056
+ path = `${rootPrefix}::${path}`;
1057
+ }
771
1058
  if (path && !path.includes("{")) {
772
1059
  results.push(path);
773
1060
  }
@@ -796,9 +1083,10 @@ function extractRustUsePaths(body, prefix, results) {
796
1083
  for (const item of items) {
797
1084
  const cleaned = item.trim();
798
1085
  if (cleaned === "self") {
799
- results.push(fullPrefix);
1086
+ const selfPath = rootPrefix ? `${rootPrefix}::${fullPrefix}` : fullPrefix;
1087
+ results.push(selfPath);
800
1088
  } else if (cleaned) {
801
- extractRustUsePaths(cleaned, fullPrefix, results);
1089
+ extractRustUsePaths(cleaned, fullPrefix, results, rootPrefix);
802
1090
  }
803
1091
  }
804
1092
  }
@@ -872,14 +1160,21 @@ var java = {
872
1160
  extensions: [".java"],
873
1161
  commentStyle: "c-style",
874
1162
  importPatterns: [
875
- // import com.example.ClassName;
876
- { regex: /^import\s+(?:static\s+)?([\w.]+);/gm }
1163
+ // Bug #5 fix: import com.example.ClassName; and import com.example.*; (wildcard)
1164
+ // Bug #6: static imports also captured here
1165
+ { regex: /^import\s+(?:static\s+)?([\w.*]+);/gm }
877
1166
  ],
878
1167
  resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
879
- const filePath = importPath.replace(/\./g, "/") + ".java";
880
- for (const srcRoot of ["", "src/main/java/", "src/", "app/src/main/java/"]) {
881
- const full = join3(rootDir, srcRoot, filePath);
882
- if (projectFiles.has(full)) return full;
1168
+ if (importPath.endsWith(".*")) {
1169
+ return null;
1170
+ }
1171
+ const segments = importPath.split(".");
1172
+ for (let i = segments.length; i > 0; i--) {
1173
+ const filePath = segments.slice(0, i).join("/") + ".java";
1174
+ for (const srcRoot of ["", "src/main/java/", "src/", "app/src/main/java/"]) {
1175
+ const full = join3(rootDir, srcRoot, filePath);
1176
+ if (projectFiles.has(full)) return full;
1177
+ }
883
1178
  }
884
1179
  return null;
885
1180
  },
@@ -935,8 +1230,8 @@ var php = {
935
1230
  importPatterns: [
936
1231
  // require/include/require_once/include_once 'path'
937
1232
  { regex: /\b(?:require|include)(?:_once)?\s+['"]([^'"]+)['"]/gm },
938
- // use Namespace\Class (PSR-4 style)
939
- { regex: /^use\s+([\w\\]+)/gm }
1233
+ // Bug #9 fix: use Namespace\Class skip `function` and `const` keywords
1234
+ { regex: /^use\s+(?:function\s+|const\s+)?([\w\\]+)/gm }
940
1235
  ],
941
1236
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
942
1237
  if (importPath.includes("/") || importPath.endsWith(".php")) {
@@ -961,8 +1256,8 @@ var swift = {
961
1256
  extensions: [".swift"],
962
1257
  commentStyle: "c-style",
963
1258
  importPatterns: [
964
- // import ModuleName (for cross-module dependencies)
965
- { regex: /^import\s+(?:class\s+|struct\s+|enum\s+|protocol\s+|func\s+|var\s+|let\s+|typealias\s+)?(\w+)/gm }
1259
+ // Bug #10 fix: import ModuleName and @testable import ModuleName
1260
+ { regex: /^(?:@testable\s+)?import\s+(?:class\s+|struct\s+|enum\s+|protocol\s+|func\s+|var\s+|let\s+|typealias\s+)?(\w+)/gm }
966
1261
  ],
967
1262
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
968
1263
  const spmDir = join3(rootDir, "Sources", importPath);
@@ -978,11 +1273,18 @@ var kotlin = {
978
1273
  extensions: [".kt", ".kts"],
979
1274
  commentStyle: "c-style",
980
1275
  importPatterns: [
981
- // import com.example.ClassName
982
- { regex: /^import\s+([\w.]+)/gm }
1276
+ // Bug #7/#8 fix: import com.example.ClassName and import com.example.*
1277
+ { regex: /^import\s+([\w.*]+)/gm }
983
1278
  ],
984
1279
  resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
985
- const filePath = importPath.replace(/\./g, "/");
1280
+ if (importPath.endsWith(".*")) {
1281
+ return null;
1282
+ }
1283
+ let cleanPath = importPath;
1284
+ if (cleanPath.endsWith(".")) {
1285
+ cleanPath = cleanPath.slice(0, -1);
1286
+ }
1287
+ const filePath = cleanPath.replace(/\./g, "/");
986
1288
  for (const ext of [".kt", ".kts"]) {
987
1289
  for (const srcRoot of [
988
1290
  "",
@@ -1000,6 +1302,124 @@ var kotlin = {
1000
1302
  },
1001
1303
  defaultExclude: ["build", "\\.gradle", "\\.idea"]
1002
1304
  };
1305
+ var cSharp = {
1306
+ id: "c-sharp",
1307
+ extensions: [".cs"],
1308
+ commentStyle: "c-style",
1309
+ importPatterns: [
1310
+ // using Namespace; and using Namespace.SubNamespace;
1311
+ // using static Namespace.Class;
1312
+ // Skip: using Alias = Namespace.Class; (captured but resolved same way)
1313
+ { regex: /^using\s+(?:static\s+)?([\w.]+)\s*;/gm }
1314
+ ],
1315
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
1316
+ const segments = importPath.split(".");
1317
+ for (let i = segments.length; i > 0; i--) {
1318
+ const filePath = segments.slice(0, i).join("/") + ".cs";
1319
+ for (const srcRoot of ["", "src/", "lib/"]) {
1320
+ const full = join3(rootDir, srcRoot, filePath);
1321
+ if (projectFiles.has(full)) return full;
1322
+ }
1323
+ }
1324
+ for (let start = 0; start < segments.length; start++) {
1325
+ const dirPath = segments.slice(start).join("/");
1326
+ for (const srcRoot of ["", "src/", "lib/"]) {
1327
+ const prefix = join3(rootDir, srcRoot, dirPath) + "/";
1328
+ for (const f of projectFiles) {
1329
+ if (f.startsWith(prefix) && f.endsWith(".cs")) return f;
1330
+ }
1331
+ }
1332
+ }
1333
+ return null;
1334
+ },
1335
+ defaultExclude: ["bin", "obj", "\\.vs", "packages", "TestResults"]
1336
+ };
1337
+ var dart = {
1338
+ id: "dart",
1339
+ extensions: [".dart"],
1340
+ commentStyle: "c-style",
1341
+ importPatterns: [
1342
+ // import 'package:pkg/file.dart'; or import 'relative/path.dart';
1343
+ { regex: /^import\s+['"]([^'"]+)['"]/gm },
1344
+ // export 'file.dart'; (re-exports create dependencies too)
1345
+ { regex: /^export\s+['"]([^'"]+)['"]/gm }
1346
+ ],
1347
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1348
+ if (importPath.startsWith("dart:")) return null;
1349
+ if (importPath.startsWith("package:")) {
1350
+ const ownPackage = dartPackageName(rootDir);
1351
+ if (!ownPackage) return null;
1352
+ const prefix = `package:${ownPackage}/`;
1353
+ if (!importPath.startsWith(prefix)) return null;
1354
+ const relPath = importPath.slice(prefix.length);
1355
+ const full = join3(rootDir, "lib", relPath);
1356
+ if (projectFiles.has(full)) return full;
1357
+ return null;
1358
+ }
1359
+ const resolved = resolve3(dirname(sourceFile), importPath);
1360
+ if (projectFiles.has(resolved)) return resolved;
1361
+ return null;
1362
+ },
1363
+ defaultExclude: ["\\.dart_tool", "build", "\\.packages"]
1364
+ };
1365
+ var dartPackageCache = /* @__PURE__ */ new Map();
1366
+ function dartPackageName(rootDir) {
1367
+ if (dartPackageCache.has(rootDir)) return dartPackageCache.get(rootDir);
1368
+ try {
1369
+ const content = readFileSync(join3(rootDir, "pubspec.yaml"), "utf-8");
1370
+ const match = content.match(/^name:\s*(\S+)/m);
1371
+ const name = match ? match[1] : null;
1372
+ dartPackageCache.set(rootDir, name);
1373
+ return name;
1374
+ } catch {
1375
+ dartPackageCache.set(rootDir, null);
1376
+ return null;
1377
+ }
1378
+ }
1379
+ var scala = {
1380
+ id: "scala",
1381
+ extensions: [".scala", ".sc"],
1382
+ commentStyle: "c-style",
1383
+ importPatterns: [],
1384
+ // handled by extractImports for grouped syntax
1385
+ extractImports(content) {
1386
+ const imports = [];
1387
+ const importRegex = /\bimport\s+([\w.]+(?:\.\{[^}]+\}|\.\w+|\._))/gm;
1388
+ let match;
1389
+ while ((match = importRegex.exec(content)) !== null) {
1390
+ const full = match[1];
1391
+ const braceMatch = full.match(/^([\w.]+)\.\{([^}]+)\}$/);
1392
+ if (braceMatch) {
1393
+ const prefix = braceMatch[1];
1394
+ const items = braceMatch[2].split(",");
1395
+ for (const item of items) {
1396
+ const trimmed = item.trim().split(/\s+/)[0];
1397
+ if (trimmed === "_") continue;
1398
+ imports.push(`${prefix}.${trimmed}`);
1399
+ }
1400
+ } else if (full.endsWith("._")) {
1401
+ continue;
1402
+ } else {
1403
+ imports.push(full);
1404
+ }
1405
+ }
1406
+ return imports;
1407
+ },
1408
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
1409
+ const segments = importPath.split(".");
1410
+ for (let i = segments.length; i > 0; i--) {
1411
+ const filePath = segments.slice(0, i).join("/");
1412
+ for (const ext of [".scala", ".sc"]) {
1413
+ for (const srcRoot of ["", "src/main/scala/", "src/", "app/"]) {
1414
+ const full = join3(rootDir, srcRoot, filePath + ext);
1415
+ if (projectFiles.has(full)) return full;
1416
+ }
1417
+ }
1418
+ }
1419
+ return null;
1420
+ },
1421
+ defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
1422
+ };
1003
1423
  var LANGUAGE_CONFIGS = {
1004
1424
  javascript: null,
1005
1425
  // handled by DependencyCruiserEngine
@@ -1008,10 +1428,13 @@ var LANGUAGE_CONFIGS = {
1008
1428
  go,
1009
1429
  java,
1010
1430
  "c-cpp": cCpp,
1431
+ "c-sharp": cSharp,
1011
1432
  ruby,
1012
1433
  php,
1013
1434
  swift,
1014
- kotlin
1435
+ kotlin,
1436
+ dart,
1437
+ scala
1015
1438
  };
1016
1439
  function getLanguageConfig(id) {
1017
1440
  return LANGUAGE_CONFIGS[id] ?? null;