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
|
@@ -195,9 +195,23 @@ class PythonClientResolver {
|
|
|
195
195
|
|
|
196
196
|
/** The fixture named `name` a test in `file` requests: same module first,
|
|
197
197
|
* then conftest.py files from the test's directory up to the root. */
|
|
198
|
-
fixtureDef(file, name) {
|
|
199
|
-
|
|
198
|
+
fixtureDef(file, name, requester = null) {
|
|
199
|
+
let owner = requester?.parent;
|
|
200
|
+
while (owner && owner.type !== 'class_definition') owner = owner.parent;
|
|
201
|
+
const key = `${file}\0${name}\0${owner?.startIndex ?? ''}`;
|
|
200
202
|
if (this.fixtureMemo.has(key)) return this.fixtureMemo.get(key);
|
|
203
|
+
this.fixtureMemo.set(key, null);
|
|
204
|
+
if (owner) {
|
|
205
|
+
const matches = namedChildren(field(owner, 'body')).map(stmt =>
|
|
206
|
+
stmt.type === 'decorated_definition' ? field(stmt, 'definition') : stmt)
|
|
207
|
+
.filter(fn => fn?.type === 'function_definition' && field(fn, 'name')?.text === name);
|
|
208
|
+
if (matches.length) {
|
|
209
|
+
const found = matches.length === 1 && this.isFixture(file, matches[0])
|
|
210
|
+
? { file, fnNode: matches[0] } : null;
|
|
211
|
+
this.fixtureMemo.set(key, found);
|
|
212
|
+
return found;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
201
215
|
let found = this.moduleFixture(file, name);
|
|
202
216
|
if (!found) {
|
|
203
217
|
for (let dir = path.dirname(file); dir.startsWith(this.index.root); dir = path.dirname(dir)) {
|
|
@@ -213,7 +227,8 @@ class PythonClientResolver {
|
|
|
213
227
|
return found;
|
|
214
228
|
}
|
|
215
229
|
|
|
216
|
-
moduleFixture(file, name) {
|
|
230
|
+
moduleFixture(file, name, depth = 0) {
|
|
231
|
+
if (depth > MAX_HOPS) return null;
|
|
217
232
|
const root = this.sess.root(file);
|
|
218
233
|
if (!root) return null;
|
|
219
234
|
const hits = [];
|
|
@@ -222,7 +237,12 @@ class PythonClientResolver {
|
|
|
222
237
|
if (fn?.type === 'function_definition' && field(fn, 'name')?.text === name &&
|
|
223
238
|
this.isFixture(file, fn)) hits.push(fn);
|
|
224
239
|
}
|
|
225
|
-
return hits.length === 1 ? { file, fnNode: hits[0] } : null;
|
|
240
|
+
if (hits.length) return hits.length === 1 ? { file, fnNode: hits[0] } : null;
|
|
241
|
+
const entry = this.sess.entry(file);
|
|
242
|
+
const bindings = (entry?.importBindings || []).filter(b => (b.alias || b.name) === name && !b.deferred);
|
|
243
|
+
if (bindings.length !== 1 || bindings[0].kind !== 'from') return null;
|
|
244
|
+
const relative = entry.moduleResolved?.[bindings[0].module];
|
|
245
|
+
return relative ? this.moduleFixture(path.join(this.index.root, relative), bindings[0].name, depth + 1) : null;
|
|
226
246
|
}
|
|
227
247
|
|
|
228
248
|
/** The expression a fixture provides: its single yielded/returned value,
|
|
@@ -253,11 +273,14 @@ class PythonClientResolver {
|
|
|
253
273
|
if (!expr || expr.type !== 'identifier' || hops > MAX_HOPS) return { file, expr };
|
|
254
274
|
if (fnNode) {
|
|
255
275
|
const values = boundValues(field(fnNode, 'body'), expr.text, this.bindingMemo);
|
|
256
|
-
if (values === null)
|
|
276
|
+
if (values === null) {
|
|
277
|
+
const decl = routeGraph.findDecl(this.sess, file, expr.text, expr);
|
|
278
|
+
return decl?.kind === 'function' ? { file, expr: decl.valueNode } : null;
|
|
279
|
+
}
|
|
257
280
|
if (values.length === 1) return this.followBinding(file, fnNode, unwrap(values[0].node), hops + 1);
|
|
258
281
|
if (values.length > 1) return null;
|
|
259
282
|
if (isParameterOf(fnNode, expr.text)) {
|
|
260
|
-
const fixture = this.fixtureDef(file, expr.text);
|
|
283
|
+
const fixture = this.fixtureDef(file, expr.text, fnNode);
|
|
261
284
|
return fixture ? this.fixtureValue(fixture.file, fixture.fnNode, hops + 1) : null;
|
|
262
285
|
}
|
|
263
286
|
}
|
|
@@ -319,7 +342,7 @@ class PythonClientResolver {
|
|
|
319
342
|
if (expr.type !== 'identifier') return null;
|
|
320
343
|
const fnNode = enclosingFunctionNode(expr);
|
|
321
344
|
if (fnNode && isParameterOf(fnNode, expr.text)) {
|
|
322
|
-
const fixture = this.fixtureDef(file, expr.text);
|
|
345
|
+
const fixture = this.fixtureDef(file, expr.text, fnNode);
|
|
323
346
|
if (!fixture) return null;
|
|
324
347
|
const value = this.fixtureValue(fixture.file, fixture.fnNode, hops + 1);
|
|
325
348
|
if (!value?.expr) return null;
|
|
@@ -345,13 +368,8 @@ class PythonClientResolver {
|
|
|
345
368
|
return null;
|
|
346
369
|
}
|
|
347
370
|
|
|
348
|
-
/**
|
|
349
|
-
|
|
350
|
-
* through an in-process client, { inProcess: true } when the client is
|
|
351
|
-
* built from an app that did not resolve, else null (not a client built
|
|
352
|
-
* from an app).
|
|
353
|
-
*/
|
|
354
|
-
resolve(file, line, receiver, name) {
|
|
371
|
+
/** Values bound to a request receiver, without assuming they are clients. */
|
|
372
|
+
clientValues(file, line, receiver, name) {
|
|
355
373
|
const root = this.sess.root(file);
|
|
356
374
|
if (!root) return null;
|
|
357
375
|
const sites = requestCallNodes(root, line, receiver, name);
|
|
@@ -365,7 +383,7 @@ class PythonClientResolver {
|
|
|
365
383
|
if (values.length > 0) {
|
|
366
384
|
clients = this.bindingInEffect(values, receiverNode).map(node => ({ file, expr: unwrap(node) }));
|
|
367
385
|
} else if (isParameterOf(fnNode, receiver)) {
|
|
368
|
-
const fixture = this.fixtureDef(file, receiver);
|
|
386
|
+
const fixture = this.fixtureDef(file, receiver, fnNode);
|
|
369
387
|
const value = fixture ? this.fixtureValue(fixture.file, fixture.fnNode, 1) : null;
|
|
370
388
|
clients = value ? [value] : [];
|
|
371
389
|
}
|
|
@@ -376,6 +394,14 @@ class PythonClientResolver {
|
|
|
376
394
|
clients = this.bindingInEffect(values, receiverNode).map(node => ({ file, expr: unwrap(node) }));
|
|
377
395
|
}
|
|
378
396
|
}
|
|
397
|
+
const values = clients?.map(c => this.followBinding(c.file,
|
|
398
|
+
enclosingFunctionNode(c.expr), c.expr, 0));
|
|
399
|
+
return values?.every(Boolean) ? values : null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** The served app key, an unresolved in-process app, or null. */
|
|
403
|
+
resolve(file, line, receiver, name) {
|
|
404
|
+
const clients = this.clientValues(file, line, receiver, name);
|
|
379
405
|
if (!clients || clients.length === 0 || clients.some(c => c.expr?.type !== 'call')) return null;
|
|
380
406
|
const keys = clients.map(c => this.clientAppOnce(c.file, c.expr));
|
|
381
407
|
if (keys.every(key => key && key === keys[0])) {
|
package/core/ir.js
CHANGED
|
@@ -62,8 +62,8 @@ function normalizeSymbol(symbol, family, language, kind, owner = null) {
|
|
|
62
62
|
modifiers: [...(symbol.modifiers || [])],
|
|
63
63
|
};
|
|
64
64
|
const passthrough = [
|
|
65
|
-
'docstring', 'returnedFunctionResult', 'isFunctionVariable', 'paramTypes',
|
|
66
|
-
'isAsync', 'isGenerator', 'generics', 'ownerGenerics', 'genericBounds', 'extends', 'implements', 'indent',
|
|
65
|
+
'docstring', 'sourceSpan', 'returnedFunctionResult', 'isFunctionVariable', 'paramTypes',
|
|
66
|
+
'isAsync', 'isGenerator', 'generics', 'ownerGenerics', 'genericBounds', 'typeParameterConstraints', 'extends', 'implements', 'indent',
|
|
67
67
|
'isNested', 'enclosingType', 'isMethod', 'memberType', 'fieldType', 'embedded',
|
|
68
68
|
'aliasOf', 'aliasQualifier', 'aliasMembers', 'aliasTypeText', 'aliasTypeParameters', 'aliasTypeDefaults', 'derefTarget', 'decorators', 'decoratorsWithArgs',
|
|
69
69
|
'annotationsWithArgs', 'attributesWithArgs', 'nameLine', 'traitImpl',
|
|
@@ -82,7 +82,9 @@ function normalizeSymbol(symbol, family, language, kind, owner = null) {
|
|
|
82
82
|
'generatedByMacro',
|
|
83
83
|
'typeArity', 'ownerTypeArity',
|
|
84
84
|
'annotationType', 'valueShape', 'friendOf', 'templateParams', 'delegateParams',
|
|
85
|
-
'typedefName',
|
|
85
|
+
'typedefName', 'forwardDeclaration', 'anonymousNamespace', 'specialization',
|
|
86
|
+
'memberConst', 'refQualifier', 'fieldPointers', 'memberPaths', 'instanceAttribute',
|
|
87
|
+
'ambientAugmentation',
|
|
86
88
|
];
|
|
87
89
|
for (const field of passthrough) {
|
|
88
90
|
if (symbol[field] !== undefined && symbol[field] !== null) {
|
|
@@ -110,9 +112,15 @@ function createFileIR({
|
|
|
110
112
|
const append = (symbol, family, kind, owner = null) => {
|
|
111
113
|
// Members one macro invocation declares share its lines (fix #385):
|
|
112
114
|
// their own binding ids keep overloads apart.
|
|
113
|
-
const key =
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
const key = JSON.stringify([
|
|
116
|
+
owner || symbol.className || '', symbol.name, kind,
|
|
117
|
+
symbol.startLine, symbol.endLine, symbol.namespace || '',
|
|
118
|
+
symbol.enclosingType || '', symbol.lexicalScopeStartLine ?? null,
|
|
119
|
+
symbol.lexicalScopeEndLine ?? null, symbol.anonymousNamespace || false,
|
|
120
|
+
symbol.typeArity ?? null, symbol.ownerTypeArity ?? null,
|
|
121
|
+
symbol.params ?? null,
|
|
122
|
+
symbol.generatedByMacro ? symbol.bindingId || '' : '',
|
|
123
|
+
]);
|
|
116
124
|
if (seen.has(key)) return;
|
|
117
125
|
seen.add(key);
|
|
118
126
|
normalizedSymbols.push(normalizeSymbol(symbol, family, language, kind, owner));
|
|
@@ -236,6 +244,10 @@ function createFileIR({
|
|
|
236
244
|
...(parsed.asyncClosureNames?.length > 0 && {
|
|
237
245
|
asyncClosureNames: [...parsed.asyncClosureNames],
|
|
238
246
|
}),
|
|
247
|
+
// Rust item-position macro invocations of each module (fix #422).
|
|
248
|
+
...(parsed.itemMacros?.length > 0 && {
|
|
249
|
+
itemMacros: parsed.itemMacros.map(item => ({ ...item })),
|
|
250
|
+
}),
|
|
239
251
|
// Shapes of calls whose value is not consumed where it is produced
|
|
240
252
|
// (fix #371/#372): audit-async reads a file's call records only when
|
|
241
253
|
// one of these can reach a future producer.
|
|
@@ -249,12 +261,18 @@ function createFileIR({
|
|
|
249
261
|
...(parsed.reflectionSites?.length > 0 && {
|
|
250
262
|
reflectionSites: parsed.reflectionSites.map(item => ({ ...item })),
|
|
251
263
|
}),
|
|
264
|
+
...(Array.isArray(parsed.computedDispatchSites) && {
|
|
265
|
+
computedDispatchSites: parsed.computedDispatchSites.map(item => ({ ...item })),
|
|
266
|
+
}),
|
|
252
267
|
...(parsed.macroScopeMarkers?.length > 0 && {
|
|
253
268
|
macroScopeMarkers: parsed.macroScopeMarkers.map(item => ({ ...item })),
|
|
254
269
|
}),
|
|
255
270
|
...(parsed.cppUsings?.length > 0 && {
|
|
256
271
|
cppUsings: parsed.cppUsings.map(item => ({ ...item })),
|
|
257
272
|
}),
|
|
273
|
+
// Type-parameter names a file declares (fix #421), when the parser
|
|
274
|
+
// read them from the raw source.
|
|
275
|
+
...(Array.isArray(parsed.typeParameterNames) && { typeParameterNames: [...parsed.typeParameterNames] }),
|
|
258
276
|
...(parsed.languageFeatures?.length > 0 && {
|
|
259
277
|
languageFeatures: [...parsed.languageFeatures],
|
|
260
278
|
}),
|
|
@@ -289,6 +307,9 @@ function createFileIR({
|
|
|
289
307
|
}),
|
|
290
308
|
// The byte ranges the C/C++ recovery blanked (fix #387): a query
|
|
291
309
|
// rebuilds the recovered tree with one parse.
|
|
310
|
+
...(Array.isArray(parsed.cppBaseSyntax) && { cppBaseSyntax: parsed.cppBaseSyntax.map(row => ({
|
|
311
|
+
...row, syntax: row.syntax && { ...row.syntax, namespaces: [...row.syntax.namespaces], classes: [...row.syntax.classes] },
|
|
312
|
+
})) }),
|
|
292
313
|
...(Array.isArray(parsed.recoveryBlanks) && {
|
|
293
314
|
recoveryBlanks: parsed.recoveryBlanks.map(range => [...range]),
|
|
294
315
|
}),
|
package/core/macro-expansion.js
CHANGED
|
@@ -384,6 +384,37 @@ function macroBodyTokens(index, def) {
|
|
|
384
384
|
return expansionState(index).tokensOf(def);
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
class DeclarationChoice extends Error {
|
|
388
|
+
constructor(name, definitions) { super(name); this.definitions = definitions; }
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Expand every bounded macro configuration of a declaration fragment. */
|
|
392
|
+
function expandDeclaration(index, file, line, source) {
|
|
393
|
+
const state = expansionState(index);
|
|
394
|
+
const pending = [new Map()], variants = [];
|
|
395
|
+
while (pending.length) {
|
|
396
|
+
const choices = pending.pop();
|
|
397
|
+
const site = { file, line, lines: index._getFileLines(file), fileState: {},
|
|
398
|
+
lookupMemo: new Map(), usedDefs: new Set(), budget: MAX_TOKENS, ambiguous: false,
|
|
399
|
+
declarationChoices: choices };
|
|
400
|
+
try {
|
|
401
|
+
const tokens = expandTokens(index, state, site, lexPP(source), new Set(), 0);
|
|
402
|
+
const text = tokens.map(token => token.v).join(' ');
|
|
403
|
+
const complete = !site.ambiguous && !tokens.some(token => token.opaque || token.noexpand);
|
|
404
|
+
if (!variants.some(variant => variant.text === text)) variants.push({ text, complete });
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (error instanceof DeclarationChoice) {
|
|
407
|
+
if (pending.length + variants.length + error.definitions.length > MAX_VARIANTS) {
|
|
408
|
+
variants.push({ text: source, complete: false }); break;
|
|
409
|
+
}
|
|
410
|
+
for (const definition of error.definitions) pending.push(new Map(choices).set(error.message, definition));
|
|
411
|
+
} else if (error instanceof BlindExpansion) variants.push({ text: source, complete: false });
|
|
412
|
+
else throw error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return { ...variants[0], complete: variants.length === 1 && variants[0].complete, variants };
|
|
416
|
+
}
|
|
417
|
+
|
|
387
418
|
/** Does an invocation with `args` fit the macro's parameter list? */
|
|
388
419
|
function arityFits(def, args) {
|
|
389
420
|
const params = def.ppParams || [];
|
|
@@ -508,7 +539,12 @@ function expandTokens(index, state, site, tokens, hide, depth) {
|
|
|
508
539
|
out.push({ ...t, noexpand: true });
|
|
509
540
|
continue;
|
|
510
541
|
}
|
|
511
|
-
|
|
542
|
+
let scope = definitionsInScope(index, state, site, t.v);
|
|
543
|
+
if (site.declarationChoices && scope.defs.length > 1 && !scope.truncated) {
|
|
544
|
+
const chosen = site.declarationChoices.get(t.v);
|
|
545
|
+
if (!chosen) throw new DeclarationChoice(t.v, scope.defs);
|
|
546
|
+
scope = { defs: [chosen], ambiguous: false, truncated: false };
|
|
547
|
+
}
|
|
512
548
|
let def = scope.defs[0];
|
|
513
549
|
if (!def) {
|
|
514
550
|
out.push(t);
|
|
@@ -1413,4 +1449,4 @@ function macroExpansionSummary(index) {
|
|
|
1413
1449
|
}
|
|
1414
1450
|
|
|
1415
1451
|
module.exports = { withMacroExpansion, macroExpansionSummary, expansionState, persistableMacroExpansion,
|
|
1416
|
-
macroDefinitionsAt, macroBodyTokens, codeTokens, pastedNameSites };
|
|
1452
|
+
macroDefinitionsAt, macroBodyTokens, expandDeclaration, codeTokens, pastedNameSites };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* core/out-of-class-members.js - constructors defined outside their class
|
|
5
|
+
* body (fix #421): the C++ header + source layout, where `W::W(int)` in
|
|
6
|
+
* w.cpp defines the `W(int)` its class declares in w.h (trait
|
|
7
|
+
* `outOfClassMemberDefinitions`).
|
|
8
|
+
*
|
|
9
|
+
* The constructor family of a class is its in-class declarations; an
|
|
10
|
+
* out-of-class definition holds the body of exactly one of them. Linkage is
|
|
11
|
+
* by the owner's scope path, textual visibility of the class (same file or
|
|
12
|
+
* include closure) and the declaration's parameter signature. Anything less
|
|
13
|
+
* certain links nothing: the caller keeps its review result.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { langTraits } = require('../languages');
|
|
17
|
+
|
|
18
|
+
const OWNER_KINDS = new Set(['class', 'struct', 'union']);
|
|
19
|
+
|
|
20
|
+
const scopePath = def => [def.namespace, def.enclosingType].filter(Boolean).join('::');
|
|
21
|
+
|
|
22
|
+
const outOfClassLanguage = (index, file) => !!langTraits(index.files.get(file)?.language)?.outOfClassMemberDefinitions;
|
|
23
|
+
const applies = (index, def) => !!def?.className && outOfClassLanguage(index, def.file);
|
|
24
|
+
|
|
25
|
+
function memo(index, name) {
|
|
26
|
+
return index.isProvenanceFactIndex ? null : index._opMemo?.(name, () => new WeakMap());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A definition outside every class body (its symbol's span lies in no owner). */
|
|
30
|
+
function isOutOfClass(index, def) {
|
|
31
|
+
const { ownerDefinitionOf } = require('./class-identity');
|
|
32
|
+
return !def.isSignature && !ownerDefinitionOf(index, def);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The class an out-of-class constructor definition belongs to: the one
|
|
37
|
+
* class of its name whose scope path equals the definition's qualifier and
|
|
38
|
+
* that the definition's file sees. Null when none or several qualify.
|
|
39
|
+
*/
|
|
40
|
+
function definitionOwner(index, def) {
|
|
41
|
+
if (!applies(index, def) || !isOutOfClass(index, def)) return null;
|
|
42
|
+
const saved = memo(index, 'outOfClassOwner');
|
|
43
|
+
if (saved?.has(def)) return saved.get(def);
|
|
44
|
+
const visible = require('./cpp-scope').includeClosure(index, def.file);
|
|
45
|
+
const path = scopePath(def);
|
|
46
|
+
const owners = (index.symbols.get(def.className) || []).filter(owner => OWNER_KINDS.has(owner.type) &&
|
|
47
|
+
!owner.forwardDeclaration && scopePath(owner) === path && visible.has(owner.file));
|
|
48
|
+
const owner = owners.length === 1 ? owners[0] : null;
|
|
49
|
+
saved?.set(def, owner);
|
|
50
|
+
return owner;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The in-class constructor declaration an out-of-class definition defines. */
|
|
54
|
+
function declarationOf(index, def) {
|
|
55
|
+
const owner = definitionOwner(index, def);
|
|
56
|
+
if (!owner) return null;
|
|
57
|
+
const { ownConstructors } = require('./constructor-calls');
|
|
58
|
+
const { _cFamilySignatureKey } = require('./callers');
|
|
59
|
+
const key = _cFamilySignatureKey(def);
|
|
60
|
+
const declared = ownConstructors(index, owner).filter(member => member.isSignature &&
|
|
61
|
+
_cFamilySignatureKey(member) === key);
|
|
62
|
+
return declared.length === 1 ? declared[0] : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The definition holding the body of an in-class constructor declaration:
|
|
67
|
+
* the declaration itself when it has a body, else its one linked
|
|
68
|
+
* out-of-class definition (null when there is none or several).
|
|
69
|
+
*/
|
|
70
|
+
function bodyDefinitionOf(index, declaration) {
|
|
71
|
+
if (!declaration?.isSignature || !applies(index, declaration)) return declaration;
|
|
72
|
+
const saved = memo(index, 'outOfClassBody');
|
|
73
|
+
if (saved?.has(declaration)) return saved.get(declaration);
|
|
74
|
+
const definitions = (index.symbols.get(declaration.name) || []).filter(def => def !== declaration &&
|
|
75
|
+
def.type === declaration.type && def.className === declaration.className && !def.isSignature &&
|
|
76
|
+
declarationOf(index, def) === declaration);
|
|
77
|
+
const body = definitions.length === 1 ? definitions[0] : null;
|
|
78
|
+
saved?.set(declaration, body);
|
|
79
|
+
return body;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The out-of-class constructor definition enclosing a call record of `file`. */
|
|
83
|
+
function enclosingDefinition(index, file, call, owner) {
|
|
84
|
+
const enclosing = call.enclosingFunction;
|
|
85
|
+
if (!enclosing || !outOfClassLanguage(index, file)) return null;
|
|
86
|
+
const matches = (index.files.get(file)?.symbols || []).filter(def => def.name === owner.name &&
|
|
87
|
+
def.className === owner.name && !def.isSignature && def.startLine === enclosing.startLine &&
|
|
88
|
+
def.endLine === enclosing.endLine && require('./constructor-calls').isConstructor(def, index));
|
|
89
|
+
return matches.length === 1 && definitionOwner(index, matches[0]) === owner ? matches[0] : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Does a mem-initializer of an out-of-class constructor definition
|
|
94
|
+
* (parser `memberInitializer`) initialize a data member rather than
|
|
95
|
+
* construct a base (fix #421)? A name that is no project type, or a field
|
|
96
|
+
* of the definition's class, is a member; the record is then no call.
|
|
97
|
+
*/
|
|
98
|
+
function initializesMember(index, file, call, namesType) {
|
|
99
|
+
if (!call.memberInitializer) return false;
|
|
100
|
+
if (!namesType(call.name)) return true;
|
|
101
|
+
const enclosing = call.enclosingFunction;
|
|
102
|
+
if (!enclosing) return false;
|
|
103
|
+
const { isConstructor } = require('./constructor-calls');
|
|
104
|
+
const definitions = (index.files.get(file)?.symbols || []).filter(def => !def.isSignature &&
|
|
105
|
+
def.startLine === enclosing.startLine && def.endLine === enclosing.endLine && isConstructor(def, index));
|
|
106
|
+
const owner = definitions.length === 1 ? definitionOwner(index, definitions[0]) : null;
|
|
107
|
+
return !!owner && (index.symbols.get(call.name) || []).some(def => def.className === owner.name &&
|
|
108
|
+
def.file === owner.file && def.startLine >= owner.startLine && def.endLine <= owner.endLine &&
|
|
109
|
+
(def.type === 'field' || def.memberType === 'field'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The project bases an out-of-class constructor definition leaves to
|
|
114
|
+
* implicit construction (fix #421): its class's written bases that none of
|
|
115
|
+
* its mem-initializers names, none when it delegates. `isProjectType(name)`
|
|
116
|
+
* keeps the bases the project declares.
|
|
117
|
+
*/
|
|
118
|
+
function omittedBases(index, def, isProjectType) {
|
|
119
|
+
const owner = definitionOwner(index, def);
|
|
120
|
+
if (!owner?.extends) return [];
|
|
121
|
+
const { getCachedCalls } = require('./callers');
|
|
122
|
+
const { splitParentList } = require('./graph-build');
|
|
123
|
+
const initializers = (getCachedCalls(index, def.file) || []).filter(call => call.constructorSyntax === 'delegation' &&
|
|
124
|
+
call.line >= def.startLine && call.line <= def.endLine &&
|
|
125
|
+
call.enclosingFunction?.startLine === def.startLine && call.enclosingFunction?.endLine === def.endLine);
|
|
126
|
+
if (initializers.some(call => call.ctorDelegation === 'this')) return [];
|
|
127
|
+
const initialized = new Set(initializers.map(call => call.name));
|
|
128
|
+
return splitParentList(owner.extends).map(spelling => {
|
|
129
|
+
const parts = String(spelling).replace(/<.*$/s, '').trim().split('::');
|
|
130
|
+
const name = parts.pop().trim();
|
|
131
|
+
return { name, qualifier: parts.join('::') || null };
|
|
132
|
+
}).filter(base => base.name && !initialized.has(base.name) && isProjectType(base.name));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Call records for the omitted base initializers of an out-of-class
|
|
137
|
+
* constructor definition (fix #421): the base's default constructor runs
|
|
138
|
+
* there, read from the class declaration in another place, so the site is
|
|
139
|
+
* a visible unverified candidate (`implicitOutOfClass`), never a confirmed
|
|
140
|
+
* edge.
|
|
141
|
+
*/
|
|
142
|
+
function implicitBaseRecords(index, def, isProjectType) {
|
|
143
|
+
if (!applies(index, def) || def.isSignature || !require('./constructor-calls').isConstructor(def, index)) return [];
|
|
144
|
+
const saved = memo(index, 'outOfClassImplicitBases');
|
|
145
|
+
if (saved?.has(def)) return saved.get(def);
|
|
146
|
+
const records = omittedBases(index, def, isProjectType).map(base => ({
|
|
147
|
+
name: base.name, line: def.startLine, isMethod: false, isConstructor: true,
|
|
148
|
+
constructorSyntax: 'implicit', argCount: 0, argKinds: [],
|
|
149
|
+
...(base.qualifier && { constructorQualifier: base.qualifier }),
|
|
150
|
+
implicitOutOfClass: { owner: def.className },
|
|
151
|
+
enclosingFunction: { name: def.name, startLine: def.startLine, endLine: def.endLine, className: def.className },
|
|
152
|
+
}));
|
|
153
|
+
saved?.set(def, records);
|
|
154
|
+
return records;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The out-of-class constructor definitions of classes derived from `base`
|
|
159
|
+
* that construct it implicitly (fix #421), as unverified caller rows.
|
|
160
|
+
*/
|
|
161
|
+
function implicitBaseCallerRows(index, base, isProjectType) {
|
|
162
|
+
if (!outOfClassLanguage(index, base.file)) return [];
|
|
163
|
+
const rows = [];
|
|
164
|
+
const derived = new Set((index.extendedByGraph?.get(base.name) || [])
|
|
165
|
+
.map(child => typeof child === 'string' ? child : child.name));
|
|
166
|
+
for (const name of [...derived].sort()) {
|
|
167
|
+
for (const def of index.symbols.get(name) || []) {
|
|
168
|
+
if (def.className !== name || def.isSignature || !require('./constructor-calls').isConstructor(def, index)) continue;
|
|
169
|
+
const owner = definitionOwner(index, def);
|
|
170
|
+
if (!owner || !implicitBaseRecords(index, def, isProjectType).some(record => record.name === base.name)) continue;
|
|
171
|
+
// The derived class's written base must be this class.
|
|
172
|
+
const { resolveClassRef } = require('./class-identity');
|
|
173
|
+
const ref = resolveClassRef(index, base.name, owner.file);
|
|
174
|
+
if (ref?.def && ref.def !== base) continue;
|
|
175
|
+
rows.push(def);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return rows;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { definitionOwner, declarationOf, bodyDefinitionOf, enclosingDefinition, initializesMember,
|
|
182
|
+
implicitBaseRecords, implicitBaseCallerRows };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { dynamicImportsNote, formatLineRanges, unverifiedReasonLabel, advisoryLine } = require('./shared');
|
|
6
|
-
const { formatAccountLines, formatCalleeAccountLine, unverifiedCalleeLines } = require('./analysis');
|
|
6
|
+
const { formatAccountLines, formatCalleeAccountLine, unverifiedCalleeLines, initializerReviewLines } = require('./analysis');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Format related command output - text
|
|
@@ -146,6 +146,7 @@ function formatSmart(smart, options = {}) {
|
|
|
146
146
|
// v4 callee contract: unresolved calls inside the target — visible with
|
|
147
147
|
// reasons (they may be dependencies smart could not inline).
|
|
148
148
|
lines.push(...unverifiedCalleeLines(smart.unverifiedCallees, false));
|
|
149
|
+
lines.push(...initializerReviewLines(smart.initializerReviews, false));
|
|
149
150
|
const smartCalleeAcct = formatCalleeAccountLine(smart.meta && smart.meta.calleeAccount);
|
|
150
151
|
if (smartCalleeAcct) lines.push(`\n${smartCalleeAcct}`);
|
|
151
152
|
|
|
@@ -185,7 +186,8 @@ function formatSmartJson(result) {
|
|
|
185
186
|
types: result.types || [],
|
|
186
187
|
// v4 callee contract: visible unresolved-call entries, reconciled
|
|
187
188
|
// by meta.calleeAccount.
|
|
188
|
-
unverifiedCallees: result.unverifiedCallees || []
|
|
189
|
+
unverifiedCallees: result.unverifiedCallees || [],
|
|
190
|
+
...(result.initializerReviews?.length && { initializerReviews: result.initializerReviews }),
|
|
189
191
|
}
|
|
190
192
|
});
|
|
191
193
|
}
|