ucn 3.4.3 → 3.4.4

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/core/project.js CHANGED
@@ -1021,8 +1021,8 @@ class ProjectIndex {
1021
1021
  const matchesDef = definitions.some(d => d.className === targetClass);
1022
1022
  if (!matchesDef) continue;
1023
1023
  // Falls through to add as caller
1024
- } else if (fileEntry.language === 'python' && ['self', 'cls'].includes(call.receiver)) {
1025
- // self.method() / cls.method() — resolve to same-class method
1024
+ } else if (['self', 'cls', 'this'].includes(call.receiver)) {
1025
+ // self.method() / cls.method() / this.method() — resolve to same-class method
1026
1026
  const callerSymbol = this.findEnclosingFunction(filePath, call.line, true);
1027
1027
  if (!callerSymbol?.className) continue;
1028
1028
  // Check if any definition of searched function belongs to caller's class
@@ -1030,8 +1030,6 @@ class ProjectIndex {
1030
1030
  if (!matchesDef) continue;
1031
1031
  // Falls through to add as caller
1032
1032
  } else {
1033
- // Always skip this/self/cls calls (internal state access, not function calls)
1034
- if (['this', 'self', 'cls'].includes(call.receiver)) continue;
1035
1033
  // Go doesn't use this/self/cls - always include Go method calls
1036
1034
  // For other languages, skip method calls unless explicitly requested
1037
1035
  if (fileEntry.language !== 'go' && !options.includeMethods) continue;
@@ -1143,14 +1141,14 @@ class ProjectIndex {
1143
1141
 
1144
1142
  // Smart method call handling:
1145
1143
  // - Go: include all method calls (Go doesn't use this/self/cls)
1146
- // - Python self.method(): resolve to same-class method (handled below)
1144
+ // - self/this.method(): resolve to same-class method (handled below)
1147
1145
  // - Python self.attr.method(): resolve via selfAttribute (handled below)
1148
1146
  // - Other languages: skip method calls unless explicitly requested
1149
1147
  if (call.isMethod) {
1150
1148
  if (call.selfAttribute && language === 'python') {
1151
1149
  // Will be resolved in second pass below
1152
- } else if (language === 'python' && ['self', 'cls'].includes(call.receiver)) {
1153
- // self.method() / cls.method() — resolve to same-class method below
1150
+ } else if (['self', 'cls', 'this'].includes(call.receiver)) {
1151
+ // self.method() / cls.method() / this.method() — resolve to same-class method below
1154
1152
  } else if (language !== 'go' && !options.includeMethods) {
1155
1153
  continue;
1156
1154
  }
@@ -1166,8 +1164,8 @@ class ProjectIndex {
1166
1164
  continue;
1167
1165
  }
1168
1166
 
1169
- // Collect Python self.method() calls for same-class resolution
1170
- if (language === 'python' && call.isMethod && ['self', 'cls'].includes(call.receiver)) {
1167
+ // Collect self/this.method() calls for same-class resolution
1168
+ if (call.isMethod && ['self', 'cls', 'this'].includes(call.receiver)) {
1171
1169
  if (!selfMethodCalls) selfMethodCalls = [];
1172
1170
  selfMethodCalls.push(call);
1173
1171
  continue;
@@ -1255,7 +1253,7 @@ class ProjectIndex {
1255
1253
  }
1256
1254
  }
1257
1255
 
1258
- // Third pass: resolve Python self.method() calls to same-class methods
1256
+ // Third pass: resolve self/this.method() calls to same-class methods
1259
1257
  if (selfMethodCalls && def.className) {
1260
1258
  for (const call of selfMethodCalls) {
1261
1259
  const symbols = this.symbols.get(call.name);
@@ -1590,7 +1588,7 @@ class ProjectIndex {
1590
1588
  const fileEntry = this.files.get(filePath);
1591
1589
  if (!fileEntry) return null;
1592
1590
 
1593
- const nonCallableTypes = new Set(['class', 'struct', 'interface', 'type', 'state']);
1591
+ const nonCallableTypes = new Set(['class', 'struct', 'interface', 'type', 'state', 'impl']);
1594
1592
  for (const symbol of fileEntry.symbols) {
1595
1593
  if (!nonCallableTypes.has(symbol.type) &&
1596
1594
  symbol.startLine <= lineNum &&
package/languages/java.js CHANGED
@@ -567,7 +567,7 @@ function findCallsInCode(code, parser) {
567
567
  name: nameNode.text,
568
568
  line: node.startPosition.row + 1,
569
569
  isMethod: !!objNode,
570
- receiver: objNode?.type === 'identifier' ? objNode.text : undefined,
570
+ receiver: (objNode?.type === 'identifier' || objNode?.type === 'this') ? objNode.text : undefined,
571
571
  enclosingFunction
572
572
  });
573
573
  }
package/languages/rust.js CHANGED
@@ -622,7 +622,7 @@ function findCallsInCode(code, parser) {
622
622
  name: fieldNode.text,
623
623
  line: node.startPosition.row + 1,
624
624
  isMethod: true,
625
- receiver: valueNode?.type === 'identifier' ? valueNode.text : undefined,
625
+ receiver: (valueNode?.type === 'identifier' || valueNode?.type === 'self') ? valueNode.text : undefined,
626
626
  enclosingFunction
627
627
  });
628
628
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.4.3",
3
+ "version": "3.4.4",
4
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": {
@@ -6188,6 +6188,164 @@ class Line:
6188
6188
  });
6189
6189
  });
6190
6190
 
6191
+ describe('Regression: JS this.method() same-class resolution', () => {
6192
+ it('findCallees should resolve this.method() to same-class methods', () => {
6193
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-jsthis-'));
6194
+ try {
6195
+ fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"test"}');
6196
+ fs.writeFileSync(path.join(tmpDir, 'service.js'), `
6197
+ class DataService {
6198
+ _fetchRemote(key, days) {
6199
+ return this._makeRequest(\`/api/\${key}\`);
6200
+ }
6201
+
6202
+ _makeRequest(url) {
6203
+ return null;
6204
+ }
6205
+
6206
+ getRecords(key, days = 365) {
6207
+ if (this._isValid(key)) {
6208
+ return this._fetchRemote(key, days);
6209
+ }
6210
+ return null;
6211
+ }
6212
+
6213
+ _isValid(key) {
6214
+ return key.length > 0;
6215
+ }
6216
+ }
6217
+ `);
6218
+ const index = new ProjectIndex(tmpDir);
6219
+ index.build('**/*.js', { quiet: true });
6220
+
6221
+ // getRecords should have _fetchRemote and _isValid as callees
6222
+ const defs = index.symbols.get('getRecords');
6223
+ assert.ok(defs && defs.length > 0, 'Should find getRecords');
6224
+ const callees = index.findCallees(defs[0]);
6225
+ const calleeNames = callees.map(c => c.name);
6226
+ assert.ok(calleeNames.includes('_fetchRemote'),
6227
+ `Should resolve this._fetchRemote(), got: ${calleeNames.join(', ')}`);
6228
+ assert.ok(calleeNames.includes('_isValid'),
6229
+ `Should resolve this._isValid(), got: ${calleeNames.join(', ')}`);
6230
+
6231
+ // _fetchRemote should have getRecords as caller
6232
+ const callers = index.findCallers('_fetchRemote');
6233
+ const callerNames = callers.map(c => c.callerName);
6234
+ assert.ok(callerNames.includes('getRecords'),
6235
+ `Should find getRecords as caller of _fetchRemote, got: ${callerNames.join(', ')}`);
6236
+ } finally {
6237
+ fs.rmSync(tmpDir, { recursive: true, force: true });
6238
+ }
6239
+ });
6240
+ });
6241
+
6242
+ describe('Regression: Java this.method() same-class resolution', () => {
6243
+ it('findCallees should resolve this.method() to same-class methods', () => {
6244
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-javathis-'));
6245
+ try {
6246
+ fs.writeFileSync(path.join(tmpDir, 'pom.xml'), '<project></project>');
6247
+ fs.writeFileSync(path.join(tmpDir, 'DataService.java'), `
6248
+ public class DataService {
6249
+ private Object fetchRemote(String key, int days) {
6250
+ return this.makeRequest("/api/" + key);
6251
+ }
6252
+
6253
+ private Object makeRequest(String url) {
6254
+ return null;
6255
+ }
6256
+
6257
+ public Object getRecords(String key) {
6258
+ if (this.isValid(key)) {
6259
+ return this.fetchRemote(key, 365);
6260
+ }
6261
+ return null;
6262
+ }
6263
+
6264
+ private boolean isValid(String key) {
6265
+ return key.length() > 0;
6266
+ }
6267
+ }
6268
+ `);
6269
+ const index = new ProjectIndex(tmpDir);
6270
+ index.build('**/*.java', { quiet: true });
6271
+
6272
+ // getRecords should have fetchRemote and isValid as callees
6273
+ const defs = index.symbols.get('getRecords');
6274
+ assert.ok(defs && defs.length > 0, 'Should find getRecords');
6275
+ const callees = index.findCallees(defs[0]);
6276
+ const calleeNames = callees.map(c => c.name);
6277
+ assert.ok(calleeNames.includes('fetchRemote'),
6278
+ `Should resolve this.fetchRemote(), got: ${calleeNames.join(', ')}`);
6279
+ assert.ok(calleeNames.includes('isValid'),
6280
+ `Should resolve this.isValid(), got: ${calleeNames.join(', ')}`);
6281
+
6282
+ // fetchRemote should have getRecords as caller
6283
+ const callers = index.findCallers('fetchRemote');
6284
+ const callerNames = callers.map(c => c.callerName);
6285
+ assert.ok(callerNames.includes('getRecords'),
6286
+ `Should find getRecords as caller of fetchRemote, got: ${callerNames.join(', ')}`);
6287
+ } finally {
6288
+ fs.rmSync(tmpDir, { recursive: true, force: true });
6289
+ }
6290
+ });
6291
+ });
6292
+
6293
+ describe('Regression: Rust self.method() same-class resolution', () => {
6294
+ it('findCallees should resolve self.method() to same-class methods', () => {
6295
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-rustself-'));
6296
+ try {
6297
+ fs.writeFileSync(path.join(tmpDir, 'Cargo.toml'), '[package]\nname = "test"');
6298
+ fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
6299
+ fs.writeFileSync(path.join(tmpDir, 'src', 'service.rs'), `
6300
+ struct DataService {
6301
+ base_url: String,
6302
+ }
6303
+
6304
+ impl DataService {
6305
+ fn fetch_remote(&self, key: &str, days: i32) -> Option<String> {
6306
+ self.make_request(&format!("/api/{}", key))
6307
+ }
6308
+
6309
+ fn make_request(&self, url: &str) -> Option<String> {
6310
+ None
6311
+ }
6312
+
6313
+ fn get_records(&self, key: &str) -> Option<String> {
6314
+ if self.is_valid(key) {
6315
+ return self.fetch_remote(key, 365);
6316
+ }
6317
+ None
6318
+ }
6319
+
6320
+ fn is_valid(&self, key: &str) -> bool {
6321
+ !key.is_empty()
6322
+ }
6323
+ }
6324
+ `);
6325
+ const index = new ProjectIndex(tmpDir);
6326
+ index.build('**/*.rs', { quiet: true });
6327
+
6328
+ // get_records should have fetch_remote and is_valid as callees
6329
+ const defs = index.symbols.get('get_records');
6330
+ assert.ok(defs && defs.length > 0, 'Should find get_records');
6331
+ const callees = index.findCallees(defs[0]);
6332
+ const calleeNames = callees.map(c => c.name);
6333
+ assert.ok(calleeNames.includes('fetch_remote'),
6334
+ `Should resolve self.fetch_remote(), got: ${calleeNames.join(', ')}`);
6335
+ assert.ok(calleeNames.includes('is_valid'),
6336
+ `Should resolve self.is_valid(), got: ${calleeNames.join(', ')}`);
6337
+
6338
+ // fetch_remote should have get_records as caller
6339
+ const callers = index.findCallers('fetch_remote');
6340
+ const callerNames = callers.map(c => c.callerName);
6341
+ assert.ok(callerNames.includes('get_records'),
6342
+ `Should find get_records as caller of fetch_remote, got: ${callerNames.join(', ')}`);
6343
+ } finally {
6344
+ fs.rmSync(tmpDir, { recursive: true, force: true });
6345
+ }
6346
+ });
6347
+ });
6348
+
6191
6349
  describe('Regression: Python self.method() same-class resolution', () => {
6192
6350
  it('findCallees should resolve self.method() to same-class methods', () => {
6193
6351
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-selfmethod-'));