ucn 3.4.2 → 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/.claude/skills/ucn/SKILL.md +102 -104
- package/README.md +22 -0
- package/cli/index.js +4 -1
- 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
|
@@ -1,142 +1,140 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ucn
|
|
3
|
-
description:
|
|
3
|
+
description: "Code relationship analyzer (callers, call trees, impact, dead code) via tree-sitter AST. PREFER over grep+read when you need: who calls a function, what breaks if you change it, or the full call chain of a pipeline. One `ucn about` replaces 3-4 grep+read cycles. One `ucn trace` maps an entire execution flow without reading any files. Works on Python, JS/TS, Go, Rust, Java. Skip for plain text search or codebases under 500 LOC."
|
|
4
4
|
allowed-tools: Bash(ucn *), Bash(npx ucn *)
|
|
5
5
|
argument-hint: "[command] [symbol-name] [--flags]"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# UCN
|
|
8
|
+
# UCN — Universal Code Navigator
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Understands code structure via tree-sitter ASTs: who calls what, what breaks if you change something, full call trees, dead code. Works on Python, JS/TS, Go, Rust, Java.
|
|
11
11
|
|
|
12
|
-
## When to
|
|
12
|
+
## When to Reach for UCN Instead of Grep/Read
|
|
13
13
|
|
|
14
|
-
**Use UCN when
|
|
15
|
-
- Who calls a function or what it calls
|
|
16
|
-
- What breaks if you change something
|
|
17
|
-
- One function from a large file (without reading the whole file)
|
|
18
|
-
- Unused code detection, dependency graphs
|
|
14
|
+
**Use UCN when your next action would be:**
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
16
|
+
- "Let me grep for all callers of this function" → `ucn impact <name>` — finds every call site, grouped by file, with args shown
|
|
17
|
+
- "Let me read this 800-line file to find one function" → `ucn fn <name> --file=<hint>` — extracts just that function
|
|
18
|
+
- "Let me trace through this code to understand the flow" → `ucn trace <name> --depth=3` — shows the full call tree without reading any files
|
|
19
|
+
- "I need to understand this function before changing it" → `ucn about <name>` — returns definition + callers + callees + tests + source in one call
|
|
20
|
+
- "I wonder if anything still uses this code" → `ucn deadcode` — lists every function/class with zero callers
|
|
21
|
+
|
|
22
|
+
**Stick with grep/read when:**
|
|
23
|
+
|
|
24
|
+
- Searching for a string literal, error message, TODO, or config value
|
|
25
|
+
- The codebase is under 500 LOC — just read the files
|
|
26
|
+
- Language not supported (only Python, JS/TS, Go, Rust, Java)
|
|
24
27
|
- Finding files by name — use glob
|
|
25
28
|
|
|
26
|
-
##
|
|
29
|
+
## The 5 Commands You'll Use Most
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
### 1. `about` — First stop for any investigation
|
|
32
|
+
|
|
33
|
+
One command returns: definition, source code, who calls it, what it calls, related tests.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ucn about compute_composite
|
|
30
37
|
```
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
Replaces: grep for definition → read the file → grep for callers → grep for tests. All in one call.
|
|
40
|
+
|
|
41
|
+
### 2. `impact` — Before changing any function
|
|
42
|
+
|
|
43
|
+
Shows every call site with arguments and surrounding context. Essential before modifying a signature, renaming, or deleting.
|
|
37
44
|
|
|
38
|
-
**Examples of correct invocation:**
|
|
39
45
|
```bash
|
|
40
|
-
ucn
|
|
41
|
-
ucn
|
|
42
|
-
ucn toc
|
|
43
|
-
ucn src/api/routes.js fn handleRequest
|
|
44
|
-
ucn impact createUser --exclude=test
|
|
46
|
+
ucn impact score_trend # Every caller, grouped by file
|
|
47
|
+
ucn impact score_trend --exclude=test # Only production callers
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
### Understand Code
|
|
50
|
-
| Command | Args | What it returns |
|
|
51
|
-
|---------|------|-----------------|
|
|
52
|
-
| `about <name>` | symbol name | Definition + callers + callees + tests + source code. **Start here.** |
|
|
53
|
-
| `context <name>` | symbol name | Callers and callees (numbered — use `expand N` to see code) |
|
|
54
|
-
| `smart <name>` | symbol name | Function source + all helper functions it calls, inline |
|
|
55
|
-
| `impact <name>` | symbol name | Every call site grouped by file. Use before modifying a function. |
|
|
56
|
-
| `trace <name>` | symbol name | Call tree (who calls who) at `--depth=N` (default 3) |
|
|
57
|
-
| `example <name>` | symbol name | Best real usage example with surrounding context |
|
|
58
|
-
|
|
59
|
-
### Find Code
|
|
60
|
-
| Command | Args | What it returns |
|
|
61
|
-
|---------|------|-----------------|
|
|
62
|
-
| `find <name>` | symbol name | Definitions ranked by usage count (top 5) |
|
|
63
|
-
| `usages <name>` | symbol name | All usages grouped: definitions, calls, imports, references |
|
|
64
|
-
| `toc` | none | Table of contents: all functions, classes, exports |
|
|
65
|
-
| `tests <name>` | symbol name | Test files and test functions for the given symbol |
|
|
66
|
-
| `search <text>` | search term | Text search (grep-like, but respects project ignores) |
|
|
67
|
-
| `deadcode` | none | Lists all functions/classes with zero callers |
|
|
68
|
-
|
|
69
|
-
### Extract Code
|
|
70
|
-
| Command | Args | What it returns |
|
|
71
|
-
|---------|------|-----------------|
|
|
72
|
-
| `fn <name>` | function name | Full function source code |
|
|
73
|
-
| `class <name>` | class name | Full class source code |
|
|
74
|
-
| `lines <range>` | e.g. `50-100` | Lines from file. In project mode requires `--file=<path>` |
|
|
75
|
-
| `expand <N>` | number | Source code for numbered item from last `context` output |
|
|
76
|
-
|
|
77
|
-
### Dependencies
|
|
78
|
-
| Command | Args | What it returns |
|
|
79
|
-
|---------|------|-----------------|
|
|
80
|
-
| `imports <file>` | relative path | What the file imports (modules, symbols) |
|
|
81
|
-
| `exporters <file>` | relative path | Which files import this file |
|
|
82
|
-
| `file-exports <file>` | relative path | What the file exports |
|
|
83
|
-
| `graph <file>` | relative path | Dependency tree at `--depth=N` |
|
|
84
|
-
|
|
85
|
-
### Refactoring
|
|
86
|
-
| Command | Args | What it returns |
|
|
87
|
-
|---------|------|-----------------|
|
|
88
|
-
| `verify <name>` | function name | Checks all call sites match the function's signature |
|
|
89
|
-
| `plan <name>` | function name | Preview refactoring with `--rename-to`, `--add-param`, `--remove-param` |
|
|
90
|
-
| `related <name>` | symbol name | Functions in same file or sharing dependencies |
|
|
50
|
+
Replaces: grep for the function name → manually filtering definitions vs calls vs imports → reading context around each match.
|
|
91
51
|
|
|
92
|
-
|
|
52
|
+
### 3. `trace` — Understand execution flow
|
|
53
|
+
|
|
54
|
+
Draws the call tree downward from any function. Depth-limited.
|
|
93
55
|
|
|
94
|
-
| Flag | Works with | Effect |
|
|
95
|
-
|------|-----------|--------|
|
|
96
|
-
| `--file=<pattern>` | any symbol command | Filter by file path when name is ambiguous (e.g., `--file=routes`) |
|
|
97
|
-
| `--exclude=a,b` | any | Exclude files matching patterns (e.g., `--exclude=test,mock`) |
|
|
98
|
-
| `--in=<path>` | any | Only search within path (e.g., `--in=src/core`) |
|
|
99
|
-
| `--include-tests` | any | Include test files in results (excluded by default) |
|
|
100
|
-
| `--include-methods` | `context`, `smart` | Include `obj.method()` calls (only direct calls shown by default) |
|
|
101
|
-
| `--depth=N` | `trace`, `graph`, `about`, `find` | Tree/expansion depth (default 3) |
|
|
102
|
-
| `--context=N` | `usages`, `search` | Lines of context around each match |
|
|
103
|
-
| `--json` | any | Machine-readable JSON output |
|
|
104
|
-
| `--code-only` | `search` | Exclude matches in comments and strings |
|
|
105
|
-
| `--with-types` | `smart`, `about` | Include type definitions |
|
|
106
|
-
| `--top=N` / `--all` | `find`, `usages` | Limit results to top N, or show all |
|
|
107
|
-
| `--no-cache` | any | Skip cached index (use after file changes) |
|
|
108
|
-
| `--clear-cache` | any | Delete cached index before running |
|
|
109
|
-
|
|
110
|
-
## Common Patterns
|
|
111
|
-
|
|
112
|
-
**Investigate a function (first stop):**
|
|
113
56
|
```bash
|
|
114
|
-
ucn
|
|
57
|
+
ucn trace generate_report --depth=3
|
|
115
58
|
```
|
|
116
59
|
|
|
117
|
-
|
|
60
|
+
This shows the entire pipeline — what `generate_report` calls, what those functions call, etc. — as an indented tree. No file reading needed. Invaluable for understanding orchestrator functions or entry points.
|
|
61
|
+
|
|
62
|
+
### 4. `fn` / `class` — Extract without reading the whole file
|
|
63
|
+
|
|
64
|
+
Pull one function or class out of a large file. Saves hundreds of lines of context window.
|
|
65
|
+
|
|
118
66
|
```bash
|
|
119
|
-
ucn
|
|
120
|
-
ucn
|
|
67
|
+
ucn fn handle_request --file=api # --file disambiguates when name exists in multiple files
|
|
68
|
+
ucn class MarketDataFetcher
|
|
121
69
|
```
|
|
122
70
|
|
|
123
|
-
|
|
71
|
+
### 5. `deadcode` — Find unused code
|
|
72
|
+
|
|
73
|
+
Lists all functions and classes with zero callers across the project.
|
|
74
|
+
|
|
124
75
|
```bash
|
|
125
|
-
ucn
|
|
76
|
+
ucn deadcode # Everything
|
|
77
|
+
ucn deadcode --exclude=test # Skip test files (most useful)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## When to Use the Other Commands
|
|
81
|
+
|
|
82
|
+
| Situation | Command | What it does |
|
|
83
|
+
|-----------|---------|-------------|
|
|
84
|
+
| Need function + all its helpers inline | `ucn smart <name>` | Returns function source with every helper it calls expanded below it |
|
|
85
|
+
| Checking if a refactor broke signatures | `ucn verify <name>` | Validates all call sites match the function's parameter count |
|
|
86
|
+
| Understanding a file's role in the project | `ucn imports <file>` | What it depends on |
|
|
87
|
+
| Understanding who depends on a file | `ucn exporters <file>` | Which files import it |
|
|
88
|
+
| Quick project overview | `ucn toc` | Every file with function/class counts and line counts |
|
|
89
|
+
| Finding all usages (not just calls) | `ucn usages <name>` | Groups into: definitions, calls, imports, type references |
|
|
90
|
+
| Finding related code to refactor together | `ucn related <name>` | Functions sharing dependencies or in same file |
|
|
91
|
+
| Preview a rename or param change | `ucn plan <name> --rename-to=new_name` | Shows what would change without doing it |
|
|
92
|
+
| Dependency tree for a file | `ucn graph <file> --depth=2` | Visual import tree |
|
|
93
|
+
| Find which tests cover a function | `ucn tests <name>` | Test files and test function names |
|
|
94
|
+
|
|
95
|
+
## Command Format
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
ucn [target] <command> [name] [--flags]
|
|
126
99
|
```
|
|
127
100
|
|
|
128
|
-
**
|
|
101
|
+
**Target** (optional, defaults to current directory):
|
|
102
|
+
- Omit — scans current project (most common)
|
|
103
|
+
- `path/to/file.py` — single file
|
|
104
|
+
- `path/to/dir` — specific directory
|
|
105
|
+
- `"src/**/*.py"` — glob pattern (quote it)
|
|
106
|
+
|
|
107
|
+
## Key Flags
|
|
108
|
+
|
|
109
|
+
| Flag | When to use it |
|
|
110
|
+
|------|---------------|
|
|
111
|
+
| `--file=<pattern>` | Disambiguate when a name exists in multiple files (e.g., `--file=api`) |
|
|
112
|
+
| `--exclude=test,mock` | Focus on production code only |
|
|
113
|
+
| `--in=src/core` | Limit search to a subdirectory |
|
|
114
|
+
| `--depth=N` | Control tree depth for `trace` and `graph` (default 3) |
|
|
115
|
+
| `--include-tests` | Include test files in results (excluded by default) |
|
|
116
|
+
| `--include-methods` | Include `obj.method()` calls in `context`/`smart` (only direct calls shown by default) |
|
|
117
|
+
| `--no-cache` | Force re-index after editing files |
|
|
118
|
+
| `--context=N` | Lines of surrounding context in `usages`/`search` output |
|
|
119
|
+
|
|
120
|
+
## Workflow Integration
|
|
121
|
+
|
|
122
|
+
**Investigating a bug:**
|
|
129
123
|
```bash
|
|
130
|
-
ucn
|
|
124
|
+
ucn about problematic_function # Understand it fully
|
|
125
|
+
ucn trace problematic_function --depth=2 # See what it calls
|
|
131
126
|
```
|
|
132
127
|
|
|
133
|
-
**
|
|
128
|
+
**Before modifying a function:**
|
|
134
129
|
```bash
|
|
135
|
-
ucn
|
|
136
|
-
ucn
|
|
130
|
+
ucn impact the_function # Who will break?
|
|
131
|
+
ucn smart the_function # See it + its helpers
|
|
132
|
+
# ... make your changes ...
|
|
133
|
+
ucn verify the_function # Did all call sites survive?
|
|
137
134
|
```
|
|
138
135
|
|
|
139
|
-
**
|
|
136
|
+
**Periodic maintenance:**
|
|
140
137
|
```bash
|
|
141
|
-
ucn --
|
|
138
|
+
ucn deadcode --exclude=test # What can be deleted?
|
|
139
|
+
ucn toc # Project overview
|
|
142
140
|
```
|
package/README.md
CHANGED
|
@@ -267,6 +267,28 @@ Quick Start:
|
|
|
267
267
|
ucn --interactive # Multiple queries
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
+
## Workflows
|
|
271
|
+
|
|
272
|
+
**Investigating a bug:**
|
|
273
|
+
```bash
|
|
274
|
+
ucn about problematic_function # Understand it fully
|
|
275
|
+
ucn trace problematic_function --depth=2 # See what it calls
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Before modifying a function:**
|
|
279
|
+
```bash
|
|
280
|
+
ucn impact the_function # Who will break?
|
|
281
|
+
ucn smart the_function # See it + its helpers
|
|
282
|
+
# ... make your changes ...
|
|
283
|
+
ucn verify the_function # Did all call sites survive?
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Periodic cleanup:**
|
|
287
|
+
```bash
|
|
288
|
+
ucn deadcode --exclude=test # What can be deleted?
|
|
289
|
+
ucn toc # Project overview
|
|
290
|
+
```
|
|
291
|
+
|
|
270
292
|
## License
|
|
271
293
|
|
|
272
294
|
MIT
|
package/cli/index.js
CHANGED
|
@@ -2226,7 +2226,7 @@ FIND CODE
|
|
|
2226
2226
|
═══════════════════════════════════════════════════════════════════════════════
|
|
2227
2227
|
find <name> Find symbol definitions (top 5 by usage count)
|
|
2228
2228
|
usages <name> All usages grouped: definitions, calls, imports, references
|
|
2229
|
-
toc Table of contents (
|
|
2229
|
+
toc Table of contents (compact; --detailed lists all symbols)
|
|
2230
2230
|
search <term> Text search (for simple patterns, consider grep instead)
|
|
2231
2231
|
tests <name> Find test files for a function
|
|
2232
2232
|
|
|
@@ -2275,6 +2275,9 @@ Common Flags:
|
|
|
2275
2275
|
--top=N / --all Limit or show all results
|
|
2276
2276
|
--include-tests Include test files
|
|
2277
2277
|
--include-methods Include method calls (obj.fn) in caller/callee analysis
|
|
2278
|
+
--include-uncertain Include ambiguous/uncertain matches
|
|
2279
|
+
--include-exported Include exported symbols in deadcode
|
|
2280
|
+
--detailed List all symbols in toc (compact by default)
|
|
2278
2281
|
--no-cache Disable caching
|
|
2279
2282
|
--clear-cache Clear cache before running
|
|
2280
2283
|
--no-follow-symlinks Don't follow symbolic links
|
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-'));
|