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 +9 -11
- package/languages/java.js +1 -1
- package/languages/rust.js +1 -1
- package/package.json +1 -1
- package/test/parser.test.js +158 -0
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 (
|
|
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
|
-
// -
|
|
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 (
|
|
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
|
|
1170
|
-
if (
|
|
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
|
|
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
package/test/parser.test.js
CHANGED
|
@@ -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-'));
|