ucn 3.7.24 → 3.7.26
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/README.md +192 -463
- package/cli/index.js +285 -1054
- package/core/cache.js +193 -0
- package/core/callers.js +817 -0
- package/core/deadcode.js +320 -0
- package/core/discovery.js +1 -1
- package/core/execute.js +207 -10
- package/core/expand-cache.js +16 -5
- package/core/imports.js +21 -15
- package/core/output.js +370 -35
- package/core/project.js +365 -2272
- package/core/shared.js +11 -1
- package/core/stacktrace.js +313 -0
- package/core/verify.js +533 -0
- package/languages/go.js +57 -21
- package/languages/html.js +14 -3
- package/languages/java.js +4 -2
- package/languages/javascript.js +36 -9
- package/languages/rust.js +49 -17
- package/mcp/server.js +39 -172
- package/package.json +1 -1
package/core/imports.js
CHANGED
|
@@ -35,7 +35,7 @@ function extractImports(content, language) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
return { imports: [], dynamicCount: 0 };
|
|
38
|
+
return { imports: [], dynamicCount: 0, importAliases: null };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -83,7 +83,7 @@ function resolveImport(importPath, fromFile, config = {}) {
|
|
|
83
83
|
// Check aliases
|
|
84
84
|
if (config.aliases) {
|
|
85
85
|
for (const [alias, target] of Object.entries(config.aliases)) {
|
|
86
|
-
if (importPath.startsWith(alias)) {
|
|
86
|
+
if (importPath === alias || importPath.startsWith(alias + '/')) {
|
|
87
87
|
const relativePath = importPath.slice(alias.length);
|
|
88
88
|
const targetPath = path.join(config.root || fromDir, target, relativePath);
|
|
89
89
|
return resolveFilePath(targetPath, config.extensions || getExtensions(config.language));
|
|
@@ -101,7 +101,9 @@ function resolveImport(importPath, fromFile, config = {}) {
|
|
|
101
101
|
const match = importPath.match(regex);
|
|
102
102
|
if (match) {
|
|
103
103
|
for (const target of targets) {
|
|
104
|
-
|
|
104
|
+
let resolved = target;
|
|
105
|
+
let groupIdx = 1;
|
|
106
|
+
resolved = resolved.replace(/\*/g, () => match[groupIdx++] || '');
|
|
105
107
|
const basePath = tsconfig.baseUrl || path.dirname(tsconfig.configPath);
|
|
106
108
|
const fullPath = path.join(basePath, resolved);
|
|
107
109
|
const result = resolveFilePath(fullPath, config.extensions || getExtensions(config.language));
|
|
@@ -221,7 +223,7 @@ function resolveGoImport(importPath, fromFile, projectRoot) {
|
|
|
221
223
|
const { modulePath, root } = goMod;
|
|
222
224
|
|
|
223
225
|
// Check if the import is within this module
|
|
224
|
-
if (importPath.startsWith(modulePath)) {
|
|
226
|
+
if (importPath === modulePath || importPath.startsWith(modulePath + '/')) {
|
|
225
227
|
// Convert module path to relative path
|
|
226
228
|
// e.g., "github.com/user/proj/pkg/util" -> "pkg/util"
|
|
227
229
|
const relativePath = importPath.slice(modulePath.length).replace(/^\//, '');
|
|
@@ -231,7 +233,7 @@ function resolveGoImport(importPath, fromFile, projectRoot) {
|
|
|
231
233
|
if (fs.existsSync(pkgDir) && fs.statSync(pkgDir).isDirectory()) {
|
|
232
234
|
// Return the first .go file in the directory (not _test.go)
|
|
233
235
|
try {
|
|
234
|
-
const files = fs.readdirSync(pkgDir);
|
|
236
|
+
const files = fs.readdirSync(pkgDir).sort();
|
|
235
237
|
for (const file of files) {
|
|
236
238
|
if (file.endsWith('.go') && !file.endsWith('_test.go')) {
|
|
237
239
|
return path.join(pkgDir, file);
|
|
@@ -331,9 +333,10 @@ function resolveRustImport(importPath, fromFile, projectRoot) {
|
|
|
331
333
|
superCount++;
|
|
332
334
|
rest = rest.slice('super::'.length);
|
|
333
335
|
}
|
|
334
|
-
// For mod.rs: module IS the directory, so super:: goes up N levels
|
|
336
|
+
// For mod.rs/lib.rs/main.rs: module IS the directory, so super:: goes up N levels
|
|
335
337
|
// For regular .rs: file is a submodule of the directory, so super:: goes up (N-1) levels
|
|
336
|
-
const
|
|
338
|
+
const basename = path.basename(fromFile);
|
|
339
|
+
const isMod = basename === 'mod.rs' || basename === 'lib.rs' || basename === 'main.rs';
|
|
337
340
|
const ups = isMod ? superCount : superCount - 1;
|
|
338
341
|
for (let i = 0; i < ups; i++) {
|
|
339
342
|
dir = path.dirname(dir);
|
|
@@ -347,9 +350,9 @@ function resolveRustImport(importPath, fromFile, projectRoot) {
|
|
|
347
350
|
const rest = importPath.slice('self::'.length);
|
|
348
351
|
const segments = rest.split('::');
|
|
349
352
|
const basename = path.basename(fromFile);
|
|
350
|
-
// For mod.rs: self:: resolves within the directory containing
|
|
353
|
+
// For mod.rs/lib.rs/main.rs: self:: resolves within the directory containing the file
|
|
351
354
|
// For regular .rs: self:: resolves within a subdirectory named after the file stem
|
|
352
|
-
const dir = basename === 'mod.rs'
|
|
355
|
+
const dir = (basename === 'mod.rs' || basename === 'lib.rs' || basename === 'main.rs')
|
|
353
356
|
? fromDir
|
|
354
357
|
: path.join(fromDir, path.basename(fromFile, '.rs'));
|
|
355
358
|
return resolveRustModulePath(dir, segments);
|
|
@@ -384,17 +387,17 @@ function resolveFilePath(basePath, extensions) {
|
|
|
384
387
|
// Try adding extensions
|
|
385
388
|
for (const ext of extensions) {
|
|
386
389
|
const withExt = basePath + ext;
|
|
387
|
-
if (fs.existsSync(withExt)) return withExt;
|
|
390
|
+
try { if (fs.existsSync(withExt) && fs.statSync(withExt).isFile()) return withExt; } catch { /* skip */ }
|
|
388
391
|
}
|
|
389
392
|
|
|
390
393
|
// Try index files (index.js for JS/TS, __init__.py for Python)
|
|
391
394
|
for (const ext of extensions) {
|
|
392
395
|
const indexPath = path.join(basePath, 'index' + ext);
|
|
393
|
-
if (fs.existsSync(indexPath)) return indexPath;
|
|
396
|
+
try { if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) return indexPath; } catch { /* skip */ }
|
|
394
397
|
}
|
|
395
398
|
// Python __init__.py
|
|
396
399
|
const initPath = path.join(basePath, '__init__.py');
|
|
397
|
-
if (fs.existsSync(initPath)) return initPath;
|
|
400
|
+
try { if (fs.existsSync(initPath) && fs.statSync(initPath).isFile()) return initPath; } catch { /* skip */ }
|
|
398
401
|
|
|
399
402
|
return null;
|
|
400
403
|
}
|
|
@@ -435,6 +438,9 @@ function findTsConfig(fromDir, rootDir) {
|
|
|
435
438
|
const normalizedRoot = rootDir ? path.resolve(rootDir) : null;
|
|
436
439
|
|
|
437
440
|
while (true) {
|
|
441
|
+
// Check boundary BEFORE loading — don't escape the project root
|
|
442
|
+
if (normalizedRoot && !currentDir.startsWith(normalizedRoot + path.sep) && currentDir !== normalizedRoot) break;
|
|
443
|
+
|
|
438
444
|
const tsconfigPath = path.join(currentDir, 'tsconfig.json');
|
|
439
445
|
if (fs.existsSync(tsconfigPath)) {
|
|
440
446
|
try {
|
|
@@ -448,7 +454,6 @@ function findTsConfig(fromDir, rootDir) {
|
|
|
448
454
|
|
|
449
455
|
const parent = path.dirname(currentDir);
|
|
450
456
|
if (parent === currentDir) break;
|
|
451
|
-
if (normalizedRoot && !currentDir.startsWith(normalizedRoot)) break;
|
|
452
457
|
currentDir = parent;
|
|
453
458
|
}
|
|
454
459
|
|
|
@@ -506,7 +511,7 @@ function loadTsConfig(tsconfigPath, visited) {
|
|
|
506
511
|
const mergedPaths = { ...basePaths, ...(config.compilerOptions?.paths || {}) };
|
|
507
512
|
const compiledPaths = Object.entries(mergedPaths).map(([pattern, targets]) => ({
|
|
508
513
|
pattern,
|
|
509
|
-
regex: new RegExp('^' + pattern.replace(/[.+^$[\]\\{}()|]/g, '\\$&').replace(
|
|
514
|
+
regex: new RegExp('^' + pattern.replace(/[.+^$[\]\\{}()|]/g, '\\$&').replace(/\*/g, '(.*)') + '$'),
|
|
510
515
|
targets
|
|
511
516
|
}));
|
|
512
517
|
|
|
@@ -567,5 +572,6 @@ function stripJsonComments(content) {
|
|
|
567
572
|
module.exports = {
|
|
568
573
|
extractImports,
|
|
569
574
|
extractExports,
|
|
570
|
-
resolveImport
|
|
575
|
+
resolveImport,
|
|
576
|
+
resolveFilePath
|
|
571
577
|
};
|