ucn 3.1.1 → 3.1.3
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.
Potentially problematic release.
This version of ucn might be problematic. Click here for more details.
- package/cli/index.js +4 -4
- package/core/project.js +45 -7
- package/languages/go.js +5 -5
- package/languages/javascript.js +14 -2
- package/languages/rust.js +3 -2
- package/package.json +1 -1
- package/test/parser.test.js +191 -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/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/languages/javascript.js
CHANGED
|
@@ -1248,8 +1248,9 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1248
1248
|
const usages = [];
|
|
1249
1249
|
|
|
1250
1250
|
traverseTree(tree.rootNode, (node) => {
|
|
1251
|
-
//
|
|
1252
|
-
|
|
1251
|
+
// Look for both identifier and property_identifier (method names in obj.method() calls)
|
|
1252
|
+
const isIdentifier = node.type === 'identifier' || node.type === 'property_identifier';
|
|
1253
|
+
if (!isIdentifier || node.text !== name) {
|
|
1253
1254
|
return true;
|
|
1254
1255
|
}
|
|
1255
1256
|
|
|
@@ -1318,6 +1319,17 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1318
1319
|
// Property access (method call): a.name() - the name after dot
|
|
1319
1320
|
else if (parent.type === 'member_expression' &&
|
|
1320
1321
|
parent.childForFieldName('property') === node) {
|
|
1322
|
+
// Skip built-in objects and common module names (JSON.parse, path.parse, etc.)
|
|
1323
|
+
const object = parent.childForFieldName('object');
|
|
1324
|
+
const builtins = [
|
|
1325
|
+
// JS built-in objects
|
|
1326
|
+
'JSON', 'Math', 'console', 'Object', 'Array', 'String', 'Number', 'Date', 'RegExp', 'Promise', 'Reflect', 'Proxy', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Symbol', 'Intl', 'WebAssembly', 'Atomics', 'SharedArrayBuffer', 'ArrayBuffer', 'DataView', 'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array', 'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'URL', 'URLSearchParams',
|
|
1327
|
+
// Node.js core modules
|
|
1328
|
+
'path', 'fs', 'os', 'http', 'https', 'net', 'dgram', 'dns', 'tls', 'crypto', 'zlib', 'stream', 'util', 'events', 'buffer', 'child_process', 'cluster', 'readline', 'repl', 'vm', 'assert', 'querystring', 'url', 'punycode', 'string_decoder', 'timers', 'tty', 'v8', 'perf_hooks', 'worker_threads', 'inspector', 'trace_events', 'async_hooks', 'process'
|
|
1329
|
+
];
|
|
1330
|
+
if (object && object.type === 'identifier' && builtins.includes(object.text)) {
|
|
1331
|
+
return true; // Skip built-in method calls
|
|
1332
|
+
}
|
|
1321
1333
|
// Check if this is a method call
|
|
1322
1334
|
const grandparent = parent.parent;
|
|
1323
1335
|
if (grandparent && grandparent.type === 'call_expression') {
|
package/languages/rust.js
CHANGED
|
@@ -910,8 +910,9 @@ function findUsagesInCode(code, name, parser) {
|
|
|
910
910
|
const usages = [];
|
|
911
911
|
|
|
912
912
|
traverseTree(tree.rootNode, (node) => {
|
|
913
|
-
//
|
|
914
|
-
|
|
913
|
+
// Look for both identifier and field_identifier (method names in obj.method() calls)
|
|
914
|
+
const isIdentifier = node.type === 'identifier' || node.type === 'field_identifier';
|
|
915
|
+
if (!isIdentifier || node.text !== name) {
|
|
915
916
|
return true;
|
|
916
917
|
}
|
|
917
918
|
|
package/package.json
CHANGED
package/test/parser.test.js
CHANGED
|
@@ -4344,6 +4344,197 @@ func main() {
|
|
|
4344
4344
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4345
4345
|
}
|
|
4346
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
|
+
});
|
|
4462
|
+
|
|
4463
|
+
it('should detect JavaScript method calls but filter built-ins', () => {
|
|
4464
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-js-method-'));
|
|
4465
|
+
try {
|
|
4466
|
+
fs.writeFileSync(path.join(tmpDir, 'test.js'), `
|
|
4467
|
+
class Service {
|
|
4468
|
+
process() {}
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
function main() {
|
|
4472
|
+
const svc = new Service();
|
|
4473
|
+
svc.process(); // user method - SHOULD be counted
|
|
4474
|
+
JSON.parse('{}'); // built-in - should NOT be counted
|
|
4475
|
+
process(); // direct call - SHOULD be counted
|
|
4476
|
+
}
|
|
4477
|
+
`);
|
|
4478
|
+
|
|
4479
|
+
const index = new ProjectIndex(tmpDir);
|
|
4480
|
+
index.build('**/*.js', { quiet: true });
|
|
4481
|
+
|
|
4482
|
+
const usages = index.usages('process', { codeOnly: true });
|
|
4483
|
+
const calls = usages.filter(u => u.usageType === 'call' && !u.isDefinition);
|
|
4484
|
+
|
|
4485
|
+
// Should find 2 calls: svc.process() and process()
|
|
4486
|
+
assert.strictEqual(calls.length, 2, 'Should find 2 calls (user method + direct)');
|
|
4487
|
+
|
|
4488
|
+
// Should NOT include JSON.parse
|
|
4489
|
+
const hasJsonParse = calls.some(c => c.content && c.content.includes('JSON.parse'));
|
|
4490
|
+
assert.strictEqual(hasJsonParse, false, 'JSON.parse should NOT be counted');
|
|
4491
|
+
|
|
4492
|
+
// Should include svc.process()
|
|
4493
|
+
const hasUserMethod = calls.some(c => c.content && c.content.includes('svc.process'));
|
|
4494
|
+
assert.strictEqual(hasUserMethod, true, 'svc.process() SHOULD be counted');
|
|
4495
|
+
} finally {
|
|
4496
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4497
|
+
}
|
|
4498
|
+
});
|
|
4499
|
+
|
|
4500
|
+
it('should detect Rust method calls in usages', () => {
|
|
4501
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-rust-method-'));
|
|
4502
|
+
try {
|
|
4503
|
+
fs.writeFileSync(path.join(tmpDir, 'Cargo.toml'), `[package]
|
|
4504
|
+
name = "test"
|
|
4505
|
+
version = "0.1.0"
|
|
4506
|
+
`);
|
|
4507
|
+
|
|
4508
|
+
fs.writeFileSync(path.join(tmpDir, 'main.rs'), `
|
|
4509
|
+
struct Client {}
|
|
4510
|
+
|
|
4511
|
+
impl Client {
|
|
4512
|
+
fn process(&self) {}
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4515
|
+
fn main() {
|
|
4516
|
+
let c = Client{};
|
|
4517
|
+
c.process();
|
|
4518
|
+
process();
|
|
4519
|
+
}
|
|
4520
|
+
`);
|
|
4521
|
+
|
|
4522
|
+
const index = new ProjectIndex(tmpDir);
|
|
4523
|
+
index.build('**/*.rs', { quiet: true });
|
|
4524
|
+
|
|
4525
|
+
const usages = index.usages('process', { codeOnly: true });
|
|
4526
|
+
const calls = usages.filter(u => u.usageType === 'call' && !u.isDefinition);
|
|
4527
|
+
|
|
4528
|
+
// Should find 2 calls: c.process() and process()
|
|
4529
|
+
assert.ok(calls.length >= 2, 'Should find at least 2 calls');
|
|
4530
|
+
|
|
4531
|
+
// Should include c.process()
|
|
4532
|
+
const hasMethodCall = calls.some(c => c.content && c.content.includes('c.process'));
|
|
4533
|
+
assert.strictEqual(hasMethodCall, true, 'c.process() SHOULD be counted');
|
|
4534
|
+
} finally {
|
|
4535
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
4536
|
+
}
|
|
4537
|
+
});
|
|
4347
4538
|
});
|
|
4348
4539
|
|
|
4349
4540
|
console.log('UCN v3 Test Suite');
|