ucn 5.5.1 → 5.5.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.
- package/.claude/skills/ucn/SKILL.md +35 -9
- package/.claude/skills/ucn/references/commands.md +40 -18
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +32 -21
- package/cli/index.js +2 -1
- package/core/accessors.js +341 -54
- package/core/account.js +74 -20
- package/core/analysis.js +257 -125
- package/core/ast-analysis.js +12 -3
- package/core/bridge.js +15 -65
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +137 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +4726 -940
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/client-receivers.js +189 -0
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +61 -16
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/inprocess-clients.js +41 -15
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +2 -0
- package/core/output/reporting.js +12 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +217 -112
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +270 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/request-helpers.js +63 -7
- package/core/rust-macro-expansion.js +151 -19
- package/core/rust-modules.js +1 -1
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +124 -37
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +510 -82
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +3 -3
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Request verbs and receiver names discover candidates; only the value's
|
|
4
|
+
// origin can confirm HTTP. Shared by positional and configuration requests.
|
|
5
|
+
const rg = require('./route-graph');
|
|
6
|
+
const { RequestHelperProof, PY_HTTP_MODULES, pyImportedFunction } = require('./request-helpers');
|
|
7
|
+
const { PythonClientResolver } = require('./inprocess-clients');
|
|
8
|
+
const { field, namedChildren, unwrap, findDecl } = rg;
|
|
9
|
+
|
|
10
|
+
const PY_CLIENT_MODULES = [...PY_HTTP_MODULES, 'fastapi.testclient',
|
|
11
|
+
'starlette.testclient', 'flask.testing', 'werkzeug.test', 'django.test'];
|
|
12
|
+
const PY_FACTORIES = new Set(['Session', 'Client', 'AsyncClient', 'TestClient',
|
|
13
|
+
'FlaskClient', 'ClientSession', 'AsyncSession', 'HTTPConnection', 'HTTPSConnection',
|
|
14
|
+
'PoolManager']);
|
|
15
|
+
const NOMINAL_CLIENTS = {
|
|
16
|
+
go: ['net/http.Client', 'net/http'],
|
|
17
|
+
rust: ['reqwest.Client', 'reqwest.blocking.Client', 'reqwest'],
|
|
18
|
+
java: ['org.springframework.web.client.RestTemplate',
|
|
19
|
+
'org.springframework.web.reactive.function.client.WebClient', 'java.net.http.HttpClient'],
|
|
20
|
+
csharp: ['System.Net.Http.HttpClient'],
|
|
21
|
+
};
|
|
22
|
+
const HTTP_FRAMEWORK = { go: 'go-http', rust: 'reqwest', java: 'spring-client', csharp: 'dotnet-httpclient' };
|
|
23
|
+
const MAX_DEPTH = 12;
|
|
24
|
+
|
|
25
|
+
class ClientReceiverProof {
|
|
26
|
+
constructor(index, sess) {
|
|
27
|
+
this.index = index;
|
|
28
|
+
this.sess = sess;
|
|
29
|
+
this.helpers = new RequestHelperProof(index, sess);
|
|
30
|
+
this.python = new PythonClientResolver(index, sess, () => new Set());
|
|
31
|
+
this.memo = new Map();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
classify(file, call) {
|
|
35
|
+
const lang = this.sess.lang(file);
|
|
36
|
+
if (['javascript', 'typescript', 'tsx'].includes(lang)) {
|
|
37
|
+
if (!this.helpers.classify(file, call).proven) return null;
|
|
38
|
+
const binding = this.helpers.jsBinding(file, call.receiver);
|
|
39
|
+
const decl = call.receiver && findDecl(this.sess, file, call.receiver,
|
|
40
|
+
this.sess.root(file)?.descendantForIndex(call.callStart, call.callStart));
|
|
41
|
+
const module = binding?.binding.module || this.helpers.requireSpecOf(decl);
|
|
42
|
+
return ['axios', 'ky', 'got'].includes(module) ? module : 'http-client';
|
|
43
|
+
}
|
|
44
|
+
if (lang === 'python') {
|
|
45
|
+
const root = this.sess.root(file);
|
|
46
|
+
const start = call.callSite?.start;
|
|
47
|
+
if (!root || start == null) return null;
|
|
48
|
+
let node = root.descendantForIndex(start, start);
|
|
49
|
+
while (node && node.type !== 'call') node = node.parent;
|
|
50
|
+
const fn = node && field(node, 'function');
|
|
51
|
+
const receiver = fn?.type === 'attribute' ? unwrap(field(fn, 'object')) : null;
|
|
52
|
+
if (!receiver) return null;
|
|
53
|
+
if (PY_HTTP_MODULES.includes(this.pyModule(file, receiver))) return 'requests';
|
|
54
|
+
const values = this.python.clientValues(file, call.line, call.receiver, call.name);
|
|
55
|
+
if (!values?.length || !values.every(v => this.pyValue(v.file, v.expr, 0))) return null;
|
|
56
|
+
return findDecl(this.sess, file, receiver.text, receiver)?.kind === 'param'
|
|
57
|
+
? 'pytest-client-fixture' : 'python-client';
|
|
58
|
+
}
|
|
59
|
+
return this.nominal(file, call, lang);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pyModule(file, expr) {
|
|
63
|
+
if (expr?.type === 'identifier' && !findDecl(this.sess, file, expr.text, expr)) {
|
|
64
|
+
const entry = this.sess.entry(file);
|
|
65
|
+
const candidates = (entry?.importBindings || []).filter(b => (b.alias || b.name) === expr.text);
|
|
66
|
+
const visible = candidates.filter(binding => {
|
|
67
|
+
if (!binding.deferred) return true;
|
|
68
|
+
const row = binding.line - 1;
|
|
69
|
+
const imports = this.sess.root(file)?.descendantsOfType(
|
|
70
|
+
['import_statement', 'import_from_statement'],
|
|
71
|
+
{ row, column: 0 }, { row, column: 1 << 20 }) || [];
|
|
72
|
+
return imports.some(node => {
|
|
73
|
+
let scope = node.parent;
|
|
74
|
+
while (scope && scope.type !== 'function_definition' && scope.type !== 'class_definition') scope = scope.parent;
|
|
75
|
+
return scope?.type === 'function_definition' && scope.startIndex <= expr.startIndex &&
|
|
76
|
+
scope.endIndex >= expr.endIndex;
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
if (!visible.length || new Set(visible.map(b => `${b.kind}:${b.module}:${b.name}`)).size !== 1) return null;
|
|
80
|
+
const binding = visible[0];
|
|
81
|
+
if (entry.moduleResolved?.[binding.module]) return null;
|
|
82
|
+
const module = binding.module;
|
|
83
|
+
return PY_CLIENT_MODULES.includes(module)
|
|
84
|
+
? (binding.kind === 'from' ? `${module}.${binding.name}` : module) : null;
|
|
85
|
+
}
|
|
86
|
+
if (expr?.type === 'attribute') {
|
|
87
|
+
const parent = this.pyModule(file, unwrap(field(expr, 'object')));
|
|
88
|
+
return parent ? `${parent}.${field(expr, 'attribute')?.text}` : null;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pyValue(file, expr, depth) {
|
|
94
|
+
const e = unwrap(expr);
|
|
95
|
+
if (!e || depth > MAX_DEPTH) return false;
|
|
96
|
+
const key = `${file}:${e.startIndex}:${e.endIndex}`;
|
|
97
|
+
if (this.memo.has(key)) return this.memo.get(key);
|
|
98
|
+
this.memo.set(key, false); // cycles cannot prove an origin
|
|
99
|
+
let proven = false;
|
|
100
|
+
if (e.type === 'identifier') {
|
|
101
|
+
let fn = e.parent;
|
|
102
|
+
while (fn && fn.type !== 'function_definition') fn = fn.parent;
|
|
103
|
+
const value = this.python.followBinding(file, fn, e, 0);
|
|
104
|
+
if (value && (value.file !== file || value.expr?.startIndex !== e.startIndex)) {
|
|
105
|
+
proven = this.pyValue(value.file, value.expr, depth + 1);
|
|
106
|
+
} else {
|
|
107
|
+
const decl = findDecl(this.sess, file, e.text, e);
|
|
108
|
+
if (decl?.count === 1 && decl.valueNode) proven = this.pyValue(file, decl.valueNode, depth + 1);
|
|
109
|
+
}
|
|
110
|
+
} else if (e.type === 'call') {
|
|
111
|
+
const callee = unwrap(field(e, 'function'));
|
|
112
|
+
const identity = this.pyModule(file, callee);
|
|
113
|
+
if (identity && PY_FACTORIES.has(identity.split('.').pop())) proven = true;
|
|
114
|
+
if (!proven) {
|
|
115
|
+
// A helper must RETURN a proven client; merely performing
|
|
116
|
+
// HTTP somewhere in its body does not type its return value.
|
|
117
|
+
const decl = callee?.type === 'identifier' && findDecl(this.sess, file, callee.text, callee);
|
|
118
|
+
let target = decl?.kind === 'function'
|
|
119
|
+
? { kind: 'function', file, node: decl.valueNode }
|
|
120
|
+
: !decl && callee?.type === 'identifier' ? pyImportedFunction(this.sess, file, callee.text, 0) : null;
|
|
121
|
+
if (decl?.kind === 'param') {
|
|
122
|
+
const fixture = this.python.fixtureDef(file, callee.text, decl.scope);
|
|
123
|
+
const provided = fixture && this.python.fixtureValue(fixture.file, fixture.fnNode, 0);
|
|
124
|
+
if (provided?.expr?.type === 'function_definition') {
|
|
125
|
+
target = { kind: 'function', file: provided.file, node: provided.expr };
|
|
126
|
+
} else if (provided?.expr?.type === 'call') {
|
|
127
|
+
const fn = field(provided.expr, 'function');
|
|
128
|
+
// functools.partial(Client) still constructs the
|
|
129
|
+
// imported client when the fixture is called.
|
|
130
|
+
const binding = fn?.type === 'attribute' &&
|
|
131
|
+
this.helpers.pyBinding(provided.file, field(fn, 'object')?.text);
|
|
132
|
+
if (binding?.module === 'functools' && field(fn, 'attribute')?.text === 'partial') {
|
|
133
|
+
const ctor = namedChildren(field(provided.expr, 'arguments'))[0];
|
|
134
|
+
const identity = this.pyModule(provided.file, ctor);
|
|
135
|
+
proven = !!identity && PY_FACTORIES.has(identity.split('.').pop());
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (target?.kind === 'function') {
|
|
140
|
+
const fnNode = target.node || target.decl?.valueNode;
|
|
141
|
+
const value = fnNode && this.python.fixtureValue(target.file, fnNode, 0);
|
|
142
|
+
if (value) proven = this.pyValue(value.file, value.expr, depth + 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.memo.set(key, proven);
|
|
147
|
+
return proven;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
nominal(file, call, lang) {
|
|
151
|
+
const known = NOMINAL_CLIENTS[lang];
|
|
152
|
+
if (!known || call.localShadow || (call.isPathCall && call.receiverScopeBinding)) return null;
|
|
153
|
+
const entry = this.sess.entry(file);
|
|
154
|
+
const bindings = entry?.importBindings || [];
|
|
155
|
+
const qualifies = name => {
|
|
156
|
+
if (!name) return false;
|
|
157
|
+
const normalized = name.replace(/::/g, '.').replace(/^[*&]+/, '');
|
|
158
|
+
if (known.includes(normalized)) return true;
|
|
159
|
+
return bindings.some(b => {
|
|
160
|
+
const local = b.alias || b.name;
|
|
161
|
+
const module = String(b.module || '').replace(/::/g, '.');
|
|
162
|
+
return (normalized === local && (known.includes(module) || known.includes(`${module}.${b.name}`))) ||
|
|
163
|
+
(normalized.startsWith(local + '.') && known.includes(module + normalized.slice(local.length)));
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
if (!call.receiverLocalBinding && !call.isMethod && qualifies(call.receiver)) return HTTP_FRAMEWORK[lang];
|
|
167
|
+
if (call.isPathCall && qualifies(call.receiver)) return HTTP_FRAMEWORK[lang];
|
|
168
|
+
const qualifier = call.receiverTypeQualifier || call.receiverTypeNamespace;
|
|
169
|
+
const type = qualifier ? `${qualifier}.${call.receiverType}` : call.receiverType;
|
|
170
|
+
if (qualifies(type)) return HTTP_FRAMEWORK[lang];
|
|
171
|
+
// Java field receivers carry the enclosing owner rather than the
|
|
172
|
+
// field type in the call record. Read the declared field's AST type.
|
|
173
|
+
if (lang === 'java' && call.receiverRootType && call.receiverField) {
|
|
174
|
+
const root = this.sess.root(file);
|
|
175
|
+
for (const declaration of root?.descendantsOfType('field_declaration') || []) {
|
|
176
|
+
let owner = declaration.parent;
|
|
177
|
+
while (owner && owner.type !== 'class_declaration') owner = owner.parent;
|
|
178
|
+
if (field(owner, 'name')?.text !== call.receiverRootType) continue;
|
|
179
|
+
if (namedChildren(declaration).some(n => n.type === 'variable_declarator' &&
|
|
180
|
+
field(n, 'name')?.text === call.receiverField) && qualifies(field(declaration, 'type')?.text)) {
|
|
181
|
+
return HTTP_FRAMEWORK[lang];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { ClientReceiverProof };
|