ucn 3.1.0 → 3.1.2
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/cli/index.js +4 -4
- package/core/imports.js +83 -0
- package/core/project.js +45 -7
- package/languages/go.js +5 -5
- package/package.json +2 -2
- package/test/parser.test.js +162 -0
package/cli/index.js
CHANGED
|
@@ -745,7 +745,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
745
745
|
|
|
746
746
|
case 'context': {
|
|
747
747
|
requireArg(arg, 'Usage: ucn . context <name>');
|
|
748
|
-
const ctx = index.context(arg, { includeMethods: flags.includeMethods });
|
|
748
|
+
const ctx = index.context(arg, { includeMethods: flags.includeMethods, file: flags.file });
|
|
749
749
|
printOutput(ctx,
|
|
750
750
|
output.formatContextJson,
|
|
751
751
|
r => { printContext(r, { expand: flags.expand, root: index.root }); }
|
|
@@ -787,7 +787,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
787
787
|
|
|
788
788
|
case 'about': {
|
|
789
789
|
requireArg(arg, 'Usage: ucn . about <name>');
|
|
790
|
-
const aboutResult = index.about(arg, { withTypes: flags.withTypes });
|
|
790
|
+
const aboutResult = index.about(arg, { withTypes: flags.withTypes, file: flags.file });
|
|
791
791
|
printOutput(aboutResult,
|
|
792
792
|
output.formatAboutJson,
|
|
793
793
|
r => output.formatAbout(r, { expand: flags.expand, root: index.root, depth: flags.depth })
|
|
@@ -797,7 +797,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
797
797
|
|
|
798
798
|
case 'impact': {
|
|
799
799
|
requireArg(arg, 'Usage: ucn . impact <name>');
|
|
800
|
-
const impactResult = index.impact(arg);
|
|
800
|
+
const impactResult = index.impact(arg, { file: flags.file });
|
|
801
801
|
printOutput(impactResult, output.formatImpactJson, output.formatImpact);
|
|
802
802
|
break;
|
|
803
803
|
}
|
|
@@ -821,7 +821,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
821
821
|
case 'trace': {
|
|
822
822
|
requireArg(arg, 'Usage: ucn . trace <name>');
|
|
823
823
|
const traceDepth = flags.depth ? parseInt(flags.depth) : 3;
|
|
824
|
-
const traceResult = index.trace(arg, { depth: traceDepth });
|
|
824
|
+
const traceResult = index.trace(arg, { depth: traceDepth, file: flags.file });
|
|
825
825
|
printOutput(traceResult, output.formatTraceJson, output.formatTrace);
|
|
826
826
|
break;
|
|
827
827
|
}
|
package/core/imports.js
CHANGED
|
@@ -431,6 +431,12 @@ function resolveImport(importPath, fromFile, config = {}) {
|
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
// Check Go module imports
|
|
435
|
+
if (config.language === 'go') {
|
|
436
|
+
const resolved = resolveGoImport(importPath, fromFile, config.root);
|
|
437
|
+
if (resolved) return resolved;
|
|
438
|
+
}
|
|
439
|
+
|
|
434
440
|
return null; // External package
|
|
435
441
|
}
|
|
436
442
|
|
|
@@ -439,6 +445,83 @@ function resolveImport(importPath, fromFile, config = {}) {
|
|
|
439
445
|
return resolveFilePath(resolved, config.extensions || getExtensions(config.language));
|
|
440
446
|
}
|
|
441
447
|
|
|
448
|
+
// Cache for Go module paths
|
|
449
|
+
const goModuleCache = new Map();
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Find and parse go.mod to get the module path
|
|
453
|
+
* @param {string} startDir - Directory to start searching from
|
|
454
|
+
* @returns {{modulePath: string, root: string}|null}
|
|
455
|
+
*/
|
|
456
|
+
function findGoModule(startDir) {
|
|
457
|
+
// Check cache first
|
|
458
|
+
if (goModuleCache.has(startDir)) {
|
|
459
|
+
return goModuleCache.get(startDir);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let dir = startDir;
|
|
463
|
+
while (dir !== path.dirname(dir)) {
|
|
464
|
+
const goModPath = path.join(dir, 'go.mod');
|
|
465
|
+
if (fs.existsSync(goModPath)) {
|
|
466
|
+
try {
|
|
467
|
+
const content = fs.readFileSync(goModPath, 'utf-8');
|
|
468
|
+
// Parse module line: module github.com/user/project
|
|
469
|
+
const match = content.match(/^module\s+(\S+)/m);
|
|
470
|
+
if (match) {
|
|
471
|
+
const result = { modulePath: match[1], root: dir };
|
|
472
|
+
goModuleCache.set(startDir, result);
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
} catch (e) {
|
|
476
|
+
// Ignore read errors
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
dir = path.dirname(dir);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
goModuleCache.set(startDir, null);
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Resolve Go package import to local files
|
|
488
|
+
* @param {string} importPath - Go import path (e.g., "github.com/user/proj/pkg/util")
|
|
489
|
+
* @param {string} fromFile - File containing the import
|
|
490
|
+
* @param {string} projectRoot - Project root directory
|
|
491
|
+
* @returns {string|null} - Directory path containing the package, or null if external
|
|
492
|
+
*/
|
|
493
|
+
function resolveGoImport(importPath, fromFile, projectRoot) {
|
|
494
|
+
const goMod = findGoModule(path.dirname(fromFile));
|
|
495
|
+
if (!goMod) return null;
|
|
496
|
+
|
|
497
|
+
const { modulePath, root } = goMod;
|
|
498
|
+
|
|
499
|
+
// Check if the import is within this module
|
|
500
|
+
if (importPath.startsWith(modulePath)) {
|
|
501
|
+
// Convert module path to relative path
|
|
502
|
+
// e.g., "github.com/user/proj/pkg/util" -> "pkg/util"
|
|
503
|
+
const relativePath = importPath.slice(modulePath.length).replace(/^\//, '');
|
|
504
|
+
const pkgDir = path.join(root, relativePath);
|
|
505
|
+
|
|
506
|
+
// Go imports are directories, find a .go file in the directory
|
|
507
|
+
if (fs.existsSync(pkgDir) && fs.statSync(pkgDir).isDirectory()) {
|
|
508
|
+
// Return the first .go file in the directory (not _test.go)
|
|
509
|
+
try {
|
|
510
|
+
const files = fs.readdirSync(pkgDir);
|
|
511
|
+
for (const file of files) {
|
|
512
|
+
if (file.endsWith('.go') && !file.endsWith('_test.go')) {
|
|
513
|
+
return path.join(pkgDir, file);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch (e) {
|
|
517
|
+
// Ignore read errors
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
|
|
442
525
|
/**
|
|
443
526
|
* Try to resolve a path with various extensions
|
|
444
527
|
*/
|
package/core/project.js
CHANGED
|
@@ -706,11 +706,21 @@ class ProjectIndex {
|
|
|
706
706
|
* Get context for a symbol (callers + callees)
|
|
707
707
|
*/
|
|
708
708
|
context(name, options = {}) {
|
|
709
|
-
|
|
709
|
+
let definitions = this.symbols.get(name) || [];
|
|
710
710
|
if (definitions.length === 0) {
|
|
711
711
|
return { function: name, file: null, callers: [], callees: [] };
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
+
// Filter by file if specified
|
|
715
|
+
if (options.file) {
|
|
716
|
+
const filtered = definitions.filter(d =>
|
|
717
|
+
d.relativePath && d.relativePath.includes(options.file)
|
|
718
|
+
);
|
|
719
|
+
if (filtered.length > 0) {
|
|
720
|
+
definitions = filtered;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
714
724
|
// Prefer class/struct/interface definitions over functions/methods/constructors
|
|
715
725
|
// This ensures context('ClassName') finds the class, not a constructor with same name
|
|
716
726
|
const typeOrder = ['class', 'struct', 'interface', 'type', 'impl'];
|
|
@@ -974,6 +984,10 @@ class ProjectIndex {
|
|
|
974
984
|
const calls = this.getCachedCalls(def.file);
|
|
975
985
|
if (!calls) return [];
|
|
976
986
|
|
|
987
|
+
// Get file language for smart method call handling
|
|
988
|
+
const fileEntry = this.files.get(def.file);
|
|
989
|
+
const language = fileEntry?.language;
|
|
990
|
+
|
|
977
991
|
const callees = new Map(); // name -> count
|
|
978
992
|
|
|
979
993
|
for (const call of calls) {
|
|
@@ -982,8 +996,12 @@ class ProjectIndex {
|
|
|
982
996
|
if (call.enclosingFunction.name !== def.name) continue;
|
|
983
997
|
if (call.enclosingFunction.startLine !== def.startLine) continue;
|
|
984
998
|
|
|
985
|
-
//
|
|
986
|
-
|
|
999
|
+
// Smart method call handling:
|
|
1000
|
+
// - Go: include all method calls (Go doesn't use this/self/cls)
|
|
1001
|
+
// - Other languages: skip method calls unless explicitly requested
|
|
1002
|
+
if (call.isMethod) {
|
|
1003
|
+
if (language !== 'go' && !options.includeMethods) continue;
|
|
1004
|
+
}
|
|
987
1005
|
|
|
988
1006
|
// Skip keywords and built-ins
|
|
989
1007
|
if (this.isKeyword(call.name)) continue;
|
|
@@ -2102,11 +2120,21 @@ class ProjectIndex {
|
|
|
2102
2120
|
const maxDepth = Math.max(0, rawDepth);
|
|
2103
2121
|
const direction = options.direction || 'down'; // 'down' = callees, 'up' = callers, 'both'
|
|
2104
2122
|
|
|
2105
|
-
|
|
2123
|
+
let definitions = this.symbols.get(name);
|
|
2106
2124
|
if (!definitions || definitions.length === 0) {
|
|
2107
2125
|
return null;
|
|
2108
2126
|
}
|
|
2109
2127
|
|
|
2128
|
+
// Filter by file if specified
|
|
2129
|
+
if (options.file) {
|
|
2130
|
+
const filtered = definitions.filter(d =>
|
|
2131
|
+
d.relativePath && d.relativePath.includes(options.file)
|
|
2132
|
+
);
|
|
2133
|
+
if (filtered.length > 0) {
|
|
2134
|
+
definitions = filtered;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2110
2138
|
const def = definitions[0];
|
|
2111
2139
|
const visited = new Set();
|
|
2112
2140
|
|
|
@@ -2184,11 +2212,21 @@ class ProjectIndex {
|
|
|
2184
2212
|
* @returns {object} Impact analysis
|
|
2185
2213
|
*/
|
|
2186
2214
|
impact(name, options = {}) {
|
|
2187
|
-
|
|
2215
|
+
let definitions = this.symbols.get(name);
|
|
2188
2216
|
if (!definitions || definitions.length === 0) {
|
|
2189
2217
|
return null;
|
|
2190
2218
|
}
|
|
2191
2219
|
|
|
2220
|
+
// Filter by file if specified
|
|
2221
|
+
if (options.file) {
|
|
2222
|
+
const filtered = definitions.filter(d =>
|
|
2223
|
+
d.relativePath && d.relativePath.includes(options.file)
|
|
2224
|
+
);
|
|
2225
|
+
if (filtered.length > 0) {
|
|
2226
|
+
definitions = filtered;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2192
2230
|
const def = definitions[0];
|
|
2193
2231
|
const usages = this.usages(name, { codeOnly: true });
|
|
2194
2232
|
const calls = usages.filter(u => u.usageType === 'call' && !u.isDefinition);
|
|
@@ -2899,10 +2937,10 @@ class ProjectIndex {
|
|
|
2899
2937
|
const maxCallees = options.maxCallees || 5;
|
|
2900
2938
|
|
|
2901
2939
|
// Find symbol definition(s)
|
|
2902
|
-
const definitions = this.find(name, { exact: true });
|
|
2940
|
+
const definitions = this.find(name, { exact: true, file: options.file });
|
|
2903
2941
|
if (definitions.length === 0) {
|
|
2904
2942
|
// Try fuzzy match
|
|
2905
|
-
const fuzzy = this.find(name);
|
|
2943
|
+
const fuzzy = this.find(name, { file: options.file });
|
|
2906
2944
|
if (fuzzy.length === 0) {
|
|
2907
2945
|
return null;
|
|
2908
2946
|
}
|
package/languages/go.js
CHANGED
|
@@ -567,8 +567,9 @@ function findUsagesInCode(code, name, parser) {
|
|
|
567
567
|
const usages = [];
|
|
568
568
|
|
|
569
569
|
traverseTree(tree.rootNode, (node) => {
|
|
570
|
-
//
|
|
571
|
-
|
|
570
|
+
// Look for both identifier and field_identifier (method names in selector expressions)
|
|
571
|
+
const isIdentifier = node.type === 'identifier' || node.type === 'field_identifier';
|
|
572
|
+
if (!isIdentifier || node.text !== name) {
|
|
572
573
|
return true;
|
|
573
574
|
}
|
|
574
575
|
|
|
@@ -622,9 +623,8 @@ function findUsagesInCode(code, name, parser) {
|
|
|
622
623
|
else if (parent.type === 'parameter_declaration') {
|
|
623
624
|
usageType = 'definition';
|
|
624
625
|
}
|
|
625
|
-
// Method call: selector_expression followed by call
|
|
626
|
-
else if (parent.type === 'selector_expression'
|
|
627
|
-
parent.childForFieldName('field') === node) {
|
|
626
|
+
// Method call: selector_expression followed by call (field_identifier case)
|
|
627
|
+
else if (parent.type === 'selector_expression') {
|
|
628
628
|
const grandparent = parent.parent;
|
|
629
629
|
if (grandparent && grandparent.type === 'call_expression') {
|
|
630
630
|
usageType = 'call';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.1.
|
|
4
|
-
"description": "Code navigation built by AI, for AI. Reduces context usage
|
|
3
|
+
"version": "3.1.2",
|
|
4
|
+
"description": "Code navigation built by AI, for AI. Reduces context usage when working with large codebases.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ucn": "./cli/index.js"
|
package/test/parser.test.js
CHANGED
|
@@ -4297,6 +4297,168 @@ class Product {
|
|
|
4297
4297
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4298
4298
|
}
|
|
4299
4299
|
});
|
|
4300
|
+
|
|
4301
|
+
it('should resolve Go module imports for exporters', () => {
|
|
4302
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-go-import-'));
|
|
4303
|
+
try {
|
|
4304
|
+
// Create go.mod file
|
|
4305
|
+
fs.writeFileSync(path.join(tmpDir, 'go.mod'), `module example.com/myproject
|
|
4306
|
+
|
|
4307
|
+
go 1.21
|
|
4308
|
+
`);
|
|
4309
|
+
|
|
4310
|
+
// Create package structure
|
|
4311
|
+
fs.mkdirSync(path.join(tmpDir, 'pkg', 'config'), { recursive: true });
|
|
4312
|
+
fs.writeFileSync(path.join(tmpDir, 'pkg', 'config', 'config.go'), `package config
|
|
4313
|
+
|
|
4314
|
+
type Config struct {
|
|
4315
|
+
Name string
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4318
|
+
func NewConfig() *Config {
|
|
4319
|
+
return &Config{}
|
|
4320
|
+
}
|
|
4321
|
+
`);
|
|
4322
|
+
|
|
4323
|
+
fs.writeFileSync(path.join(tmpDir, 'main.go'), `package main
|
|
4324
|
+
|
|
4325
|
+
import "example.com/myproject/pkg/config"
|
|
4326
|
+
|
|
4327
|
+
func main() {
|
|
4328
|
+
cfg := config.NewConfig()
|
|
4329
|
+
_ = cfg
|
|
4330
|
+
}
|
|
4331
|
+
`);
|
|
4332
|
+
|
|
4333
|
+
const index = new ProjectIndex(tmpDir);
|
|
4334
|
+
index.build('**/*.go', { quiet: true });
|
|
4335
|
+
|
|
4336
|
+
// Get exporters for the config package
|
|
4337
|
+
const exportersResult = index.exporters(path.join(tmpDir, 'pkg', 'config', 'config.go'));
|
|
4338
|
+
assert.ok(exportersResult.length > 0, 'Should find files that import the config package');
|
|
4339
|
+
|
|
4340
|
+
// main.go should be in the list
|
|
4341
|
+
const mainFile = exportersResult.find(e => e.file.includes('main.go'));
|
|
4342
|
+
assert.ok(mainFile, 'main.go should import the config package');
|
|
4343
|
+
} finally {
|
|
4344
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4345
|
+
}
|
|
4346
|
+
});
|
|
4347
|
+
|
|
4348
|
+
it('should detect Go method calls in usages (field_identifier)', () => {
|
|
4349
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-go-method-'));
|
|
4350
|
+
try {
|
|
4351
|
+
fs.writeFileSync(path.join(tmpDir, 'go.mod'), `module example.com/test
|
|
4352
|
+
go 1.21
|
|
4353
|
+
`);
|
|
4354
|
+
|
|
4355
|
+
fs.writeFileSync(path.join(tmpDir, 'service.go'), `package main
|
|
4356
|
+
|
|
4357
|
+
type Service struct{}
|
|
4358
|
+
|
|
4359
|
+
func (s *Service) CollectAll() error {
|
|
4360
|
+
return nil
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
func main() {
|
|
4364
|
+
svc := &Service{}
|
|
4365
|
+
svc.CollectAll()
|
|
4366
|
+
}
|
|
4367
|
+
`);
|
|
4368
|
+
|
|
4369
|
+
const index = new ProjectIndex(tmpDir);
|
|
4370
|
+
index.build('**/*.go', { quiet: true });
|
|
4371
|
+
|
|
4372
|
+
// usages should find the method call
|
|
4373
|
+
const usages = index.usages('CollectAll', { codeOnly: true });
|
|
4374
|
+
const calls = usages.filter(u => u.usageType === 'call' && !u.isDefinition);
|
|
4375
|
+
assert.ok(calls.length >= 1, 'Should find at least 1 call to CollectAll');
|
|
4376
|
+
} finally {
|
|
4377
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4378
|
+
}
|
|
4379
|
+
});
|
|
4380
|
+
|
|
4381
|
+
it('should find callees for Go receiver methods', () => {
|
|
4382
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-go-callees-'));
|
|
4383
|
+
try {
|
|
4384
|
+
fs.writeFileSync(path.join(tmpDir, 'go.mod'), `module example.com/test
|
|
4385
|
+
go 1.21
|
|
4386
|
+
`);
|
|
4387
|
+
|
|
4388
|
+
fs.writeFileSync(path.join(tmpDir, 'client.go'), `package main
|
|
4389
|
+
|
|
4390
|
+
type Client struct{}
|
|
4391
|
+
|
|
4392
|
+
func (c *Client) GetPods() []string {
|
|
4393
|
+
return nil
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
func (c *Client) GetNodes() []string {
|
|
4397
|
+
return nil
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
func (c *Client) CollectAll() {
|
|
4401
|
+
c.GetPods()
|
|
4402
|
+
c.GetNodes()
|
|
4403
|
+
}
|
|
4404
|
+
`);
|
|
4405
|
+
|
|
4406
|
+
const index = new ProjectIndex(tmpDir);
|
|
4407
|
+
index.build('**/*.go', { quiet: true });
|
|
4408
|
+
|
|
4409
|
+
// context should find callees (Go method calls)
|
|
4410
|
+
const ctx = index.context('CollectAll');
|
|
4411
|
+
assert.ok(ctx.callees, 'Should have callees');
|
|
4412
|
+
assert.ok(ctx.callees.length >= 2, 'Should find at least 2 callees (GetPods, GetNodes)');
|
|
4413
|
+
|
|
4414
|
+
const calleeNames = ctx.callees.map(c => c.name);
|
|
4415
|
+
assert.ok(calleeNames.includes('GetPods'), 'GetPods should be a callee');
|
|
4416
|
+
assert.ok(calleeNames.includes('GetNodes'), 'GetNodes should be a callee');
|
|
4417
|
+
} finally {
|
|
4418
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4419
|
+
}
|
|
4420
|
+
});
|
|
4421
|
+
|
|
4422
|
+
it('should filter by --file for Go methods with same name', () => {
|
|
4423
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-go-file-filter-'));
|
|
4424
|
+
try {
|
|
4425
|
+
fs.writeFileSync(path.join(tmpDir, 'go.mod'), `module example.com/test
|
|
4426
|
+
go 1.21
|
|
4427
|
+
`);
|
|
4428
|
+
|
|
4429
|
+
fs.writeFileSync(path.join(tmpDir, 'service_a.go'), `package main
|
|
4430
|
+
|
|
4431
|
+
type ServiceA struct{}
|
|
4432
|
+
|
|
4433
|
+
func (s *ServiceA) Process() error {
|
|
4434
|
+
return nil
|
|
4435
|
+
}
|
|
4436
|
+
`);
|
|
4437
|
+
|
|
4438
|
+
fs.writeFileSync(path.join(tmpDir, 'service_b.go'), `package main
|
|
4439
|
+
|
|
4440
|
+
type ServiceB struct{}
|
|
4441
|
+
|
|
4442
|
+
func (s *ServiceB) Process() error {
|
|
4443
|
+
return nil
|
|
4444
|
+
}
|
|
4445
|
+
`);
|
|
4446
|
+
|
|
4447
|
+
const index = new ProjectIndex(tmpDir);
|
|
4448
|
+
index.build('**/*.go', { quiet: true });
|
|
4449
|
+
|
|
4450
|
+
// Without file filter, should find both
|
|
4451
|
+
const allDefs = index.find('Process');
|
|
4452
|
+
assert.strictEqual(allDefs.length, 2, 'Should find 2 definitions of Process');
|
|
4453
|
+
|
|
4454
|
+
// With file filter, should find only one
|
|
4455
|
+
const filteredDefs = index.find('Process', { file: 'service_a.go' });
|
|
4456
|
+
assert.strictEqual(filteredDefs.length, 1, 'Should find 1 definition with file filter');
|
|
4457
|
+
assert.ok(filteredDefs[0].relativePath.includes('service_a.go'), 'Should be from service_a.go');
|
|
4458
|
+
} finally {
|
|
4459
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4460
|
+
}
|
|
4461
|
+
});
|
|
4300
4462
|
});
|
|
4301
4463
|
|
|
4302
4464
|
console.log('UCN v3 Test Suite');
|