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