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/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
|
|
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
|
|
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(
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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;
|