ucn 5.5.1 → 5.5.2
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 +38 -16
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +30 -21
- package/cli/index.js +1 -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/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/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 +35 -15
- 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/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 +1 -0
- package/core/output/reporting.js +6 -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 +255 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -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 +2 -2
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
|
}
|
package/core/output/analysis.js
CHANGED
|
@@ -222,6 +222,33 @@ function unverifiedCalleeLines(entries, compact) {
|
|
|
222
222
|
return lines;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* " (constructs X)" for a constructor body edge beside its class callee (fix
|
|
227
|
+
* #421); " (build alternative of F:L)" for a Go declaration of the callee in
|
|
228
|
+
* a file of another build constraint (fix #425).
|
|
229
|
+
*/
|
|
230
|
+
function constructsSuffix(callee) {
|
|
231
|
+
if (callee?.initializerOf) return ` (constructs ${callee.initializerOf})`;
|
|
232
|
+
return callee?.configurationAlternativeOf ? ` (build alternative of ${callee.configurationAlternativeOf})` : '';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Constructions whose class edge is confirmed while the constructor body
|
|
237
|
+
* they run is undecided (fix #421). Their sites are in the class edge's
|
|
238
|
+
* account; these lines say which body is not proven.
|
|
239
|
+
*/
|
|
240
|
+
function initializerReviewLines(entries, compact) {
|
|
241
|
+
if (!entries || entries.length === 0) return [];
|
|
242
|
+
const lines = [`${compact ? '' : '\n'}CONSTRUCTOR BODIES — UNDECIDED (${entries.length}) — class dependency confirmed above; which constructor runs is not proven:`];
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
const candidates = entry.candidates > 1 ? ` (${entry.candidates} candidates)` : '';
|
|
245
|
+
const sites = entry.sites?.length ? ` L${entry.sites.join(',L')}` : '';
|
|
246
|
+
const label = entry.name === entry.initializerOf ? entry.name : `${entry.initializerOf}.${entry.name}`;
|
|
247
|
+
lines.push(` ${label} ×${entry.callCount} — ${entry.reason}${candidates}${sites}`);
|
|
248
|
+
}
|
|
249
|
+
return lines;
|
|
250
|
+
}
|
|
251
|
+
|
|
225
252
|
/** "NON-CALL OCCURRENCES" summary line from the account. */
|
|
226
253
|
function formatNonCallLine(account, hintName, usagesHint) {
|
|
227
254
|
if (!account || !account.nonCall || account.nonCall.total === 0) return null;
|
|
@@ -388,11 +415,14 @@ function formatContextJson(context) {
|
|
|
388
415
|
...(c.tier && { tier: c.tier }),
|
|
389
416
|
...(c.sites && { sites: c.sites }),
|
|
390
417
|
...(c.functionReference && { functionReference: true }),
|
|
418
|
+
...(c.initializerOf && { initializerOf: c.initializerOf }),
|
|
419
|
+
...(c.configurationAlternativeOf && { configurationAlternativeOf: c.configurationAlternativeOf }),
|
|
391
420
|
...(c.reachable !== undefined && { reachable: c.reachable }),
|
|
392
421
|
})),
|
|
393
422
|
// v4 callee contract: visible unresolved-call entries (aggregated
|
|
394
423
|
// name+reason), reconciled by meta.calleeAccount.
|
|
395
424
|
unverifiedCallees: context.unverifiedCallees || [],
|
|
425
|
+
...(context.initializerReviews?.length && { initializerReviews: context.initializerReviews }),
|
|
396
426
|
...(context.warnings && { warnings: context.warnings })
|
|
397
427
|
}
|
|
398
428
|
}, provenanceReplacer);
|
|
@@ -747,9 +777,9 @@ function formatContext(ctx, options = {}) {
|
|
|
747
777
|
if (compact) {
|
|
748
778
|
const snip = c.docstring ? calleeDocstringSnippet(c.docstring) : '';
|
|
749
779
|
const docPart = snip ? `: ${snip}` : '';
|
|
750
|
-
lines.push(` [${itemNum}] ${c.name}${returnSuffix}${sideEffects} - ${c.relativePath}:${c.startLine}${docPart}${unreachableMark}`);
|
|
780
|
+
lines.push(` [${itemNum}] ${c.name}${returnSuffix}${sideEffects} - ${c.relativePath}:${c.startLine}${constructsSuffix(c)}${docPart}${unreachableMark}`);
|
|
751
781
|
} else {
|
|
752
|
-
lines.push(` [${itemNum}] ${c.name}${weight}${returnSuffix}${sideEffects} - ${c.relativePath}:${c.startLine}${unreachableMark}`);
|
|
782
|
+
lines.push(` [${itemNum}] ${c.name}${weight}${returnSuffix}${sideEffects} - ${c.relativePath}:${c.startLine}${constructsSuffix(c)}${unreachableMark}`);
|
|
753
783
|
if (c.docstring) {
|
|
754
784
|
const snip = calleeDocstringSnippet(c.docstring);
|
|
755
785
|
if (snip) lines.push(` "${snip}"`);
|
|
@@ -770,6 +800,7 @@ function formatContext(ctx, options = {}) {
|
|
|
770
800
|
// Unverified callee band (v4): aggregated name+reason entries — visible,
|
|
771
801
|
// never silently dropped. Not expandable (no definition resolved).
|
|
772
802
|
lines.push(...unverifiedCalleeLines(ctx.unverifiedCallees, compact));
|
|
803
|
+
lines.push(...initializerReviewLines(ctx.initializerReviews, compact));
|
|
773
804
|
|
|
774
805
|
// Conservation contract lines: non-call summary + ACCOUNT/WARNING/FILTERED
|
|
775
806
|
const account = ctx.meta && ctx.meta.account;
|
|
@@ -793,6 +824,38 @@ function formatContext(ctx, options = {}) {
|
|
|
793
824
|
}
|
|
794
825
|
|
|
795
826
|
/** Format impact command output - text. Shows what would need updating if a function signature changes. */
|
|
827
|
+
/**
|
|
828
|
+
* The value band's token account (fix #420): every name token of the
|
|
829
|
+
* scanned lines is an access (confirmed, unverified, excluded by reason) or
|
|
830
|
+
* a declaration token.
|
|
831
|
+
*/
|
|
832
|
+
function formatValueAccount(name, band) {
|
|
833
|
+
const account = band.account;
|
|
834
|
+
const reasons = Object.entries(band.excluded?.byReason || {})
|
|
835
|
+
.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
836
|
+
.slice(0, 4).map(([reason, count]) => `${reason} ${count}`).join(', ');
|
|
837
|
+
return `VALUE ACCOUNT: "${name}" is ${account.tokens} name token(s) on ${account.lines} line(s) in ` +
|
|
838
|
+
`${account.files} file(s): ${account.confirmed} confirmed, ${account.unverified} unverified, ` +
|
|
839
|
+
`${account.excluded} excluded${reasons ? ` (${reasons})` : ''}, ${account.declarations} declaration` +
|
|
840
|
+
`${account.textLines ? `; ${account.textLines} text-only line(s)` : ''}` +
|
|
841
|
+
`${account.conserved ? '' : ' [NOT CONSERVED]'}`;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function formatTypeAccount(name, band) {
|
|
845
|
+
const account = band.account;
|
|
846
|
+
const reasons = Object.entries(band.excluded?.byReason || {})
|
|
847
|
+
.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
848
|
+
.slice(0, 4).map(([reason, count]) => `${reason} ${count}`).join(', ');
|
|
849
|
+
return `TYPE ACCOUNT: "${name}" is ${account.tokens} name token(s) on ${account.lines} line(s) in ` +
|
|
850
|
+
`${account.files} file(s): ${account.confirmed} confirmed, ${account.unverified} unverified, ` +
|
|
851
|
+
`${account.excluded} excluded${reasons ? ` (${reasons})` : ''}, ${account.constructions} at call sites, ` +
|
|
852
|
+
`${account.declarations} declaration` +
|
|
853
|
+
`${account.attributeTokens ? ` (${account.attributeTokens} attribute short name(s))` : ''}` +
|
|
854
|
+
`${account.unparsedLines ? `; ${account.unparsedLines} unparsed line(s)` : ''}` +
|
|
855
|
+
`${account.textLines ? `; ${account.textLines} text-only line(s)` : ''}` +
|
|
856
|
+
`${account.conserved ? '' : ' [NOT CONSERVED]'}`;
|
|
857
|
+
}
|
|
858
|
+
|
|
796
859
|
function formatImpact(impact, options = {}) {
|
|
797
860
|
if (!impact) {
|
|
798
861
|
return 'Function not found.';
|
|
@@ -814,8 +877,8 @@ function formatImpact(impact, options = {}) {
|
|
|
814
877
|
// Summary (confirmed + unverified tiers reported separately)
|
|
815
878
|
const impactUnverified = impact.unverifiedSites || [];
|
|
816
879
|
const unverifiedSuffix = impactUnverified.length > 0 ? ` confirmed + ${impactUnverified.length} unverified` : '';
|
|
817
|
-
if (impact.propertyAccesses || impact.typeReferences) {
|
|
818
|
-
const pa = impact.propertyAccesses || impact.typeReferences;
|
|
880
|
+
if (impact.propertyAccesses || impact.valueAccesses || impact.typeReferences) {
|
|
881
|
+
const pa = impact.propertyAccesses || impact.valueAccesses || impact.typeReferences;
|
|
819
882
|
const uv = pa.unverifiedCount ? ` + ${pa.unverifiedCount} unverified` : '';
|
|
820
883
|
lines.push(`DEPENDENCY SITES: ${impact.totalDependencySites} confirmed${uv}`);
|
|
821
884
|
}
|
|
@@ -891,9 +954,10 @@ function formatImpact(impact, options = {}) {
|
|
|
891
954
|
}
|
|
892
955
|
if (impactReach.note && !compact) lines.push(impactReach.note);
|
|
893
956
|
|
|
894
|
-
if (impact.propertyAccesses) {
|
|
895
|
-
const access = impact.propertyAccesses;
|
|
896
|
-
|
|
957
|
+
if (impact.propertyAccesses || impact.valueAccesses) {
|
|
958
|
+
const access = impact.propertyAccesses || impact.valueAccesses;
|
|
959
|
+
const label = impact.propertyAccesses ? 'PROPERTY' : 'VALUE';
|
|
960
|
+
lines.push(`${compact ? '' : '\n'}${label} ACCESS SITES: ${access.confirmedCount} confirmed` +
|
|
897
961
|
(access.unverifiedCount ? ` + ${access.unverifiedCount} unverified` : ''));
|
|
898
962
|
for (const group of access.byFile) {
|
|
899
963
|
for (const site of group.sites) {
|
|
@@ -903,44 +967,47 @@ function formatImpact(impact, options = {}) {
|
|
|
903
967
|
}
|
|
904
968
|
}
|
|
905
969
|
if (access.unverifiedSites.length > 0) {
|
|
906
|
-
|
|
970
|
+
const reason = impact.propertyAccesses ? 'matching attribute syntax, receiver identity unresolved'
|
|
971
|
+
: 'matching value syntax, binding identity unresolved';
|
|
972
|
+
lines.push(`${compact ? '' : '\n'}UNVERIFIED ${label} ACCESS CANDIDATES (${access.unverifiedSites.length}) — ${reason}:`);
|
|
907
973
|
for (const site of access.unverifiedSites.slice(0, 10)) {
|
|
908
974
|
const caller = site.callerName ? ` [${site.callerName}]` : '';
|
|
909
975
|
const expr = site.expression ? `: ${site.expression.replace(/\s+/g, ' ').slice(0, 100)}` : '';
|
|
910
|
-
|
|
976
|
+
const why = impact.valueAccesses && site.reason ? ` (${site.reason})` : '';
|
|
977
|
+
lines.push(` ${site.file}:${site.line}${caller} [${site.accessKind || 'access'}${Number.isInteger(site.column) ? `, column ${site.column + 1}` : ''}]${expr}${why}`);
|
|
911
978
|
}
|
|
912
979
|
if (access.unverifiedSites.length > 10) {
|
|
913
980
|
lines.push(` (+${access.unverifiedSites.length - 10} more unverified)`);
|
|
914
981
|
}
|
|
915
982
|
}
|
|
983
|
+
const valueAccount = impact.valueAccesses?.account;
|
|
984
|
+
if (valueAccount) lines.push(formatValueAccount(impact.function, impact.valueAccesses));
|
|
916
985
|
}
|
|
917
986
|
|
|
918
|
-
//
|
|
919
|
-
//
|
|
987
|
+
// fixes #345, #428: the tokens naming a type outside its call sites,
|
|
988
|
+
// tiered by the type lookup, with their own account.
|
|
920
989
|
if (impact.typeReferences) {
|
|
921
990
|
const refs = impact.typeReferences;
|
|
922
991
|
lines.push(`${compact ? '' : '\n'}TYPE REFERENCE SITES: ${refs.confirmedCount} confirmed` +
|
|
923
|
-
(refs.unverifiedCount ? ` + ${refs.unverifiedCount} unverified` : '')
|
|
924
|
-
|
|
992
|
+
(refs.unverifiedCount ? ` + ${refs.unverifiedCount} unverified` : ''));
|
|
993
|
+
const position = site => `${site.kind || 'type'}${Number.isInteger(site.column) ? `, column ${site.column + 1}` : ''}`;
|
|
925
994
|
for (const group of refs.byFile) {
|
|
926
995
|
for (const site of group.sites) {
|
|
927
996
|
const expr = site.expression ? `: ${site.expression.replace(/\s+/g, ' ').slice(0, 100)}` : '';
|
|
928
|
-
lines.push(` ${group.file}:${site.line}${expr}`);
|
|
997
|
+
lines.push(` ${group.file}:${site.line} [${position(site)}]${expr}`);
|
|
929
998
|
}
|
|
930
999
|
}
|
|
931
1000
|
if (refs.unverifiedSites.length > 0) {
|
|
932
|
-
lines.push(`${compact ? '' : '\n'}UNVERIFIED TYPE REFERENCE CANDIDATES (${refs.unverifiedSites.length}) — name
|
|
1001
|
+
lines.push(`${compact ? '' : '\n'}UNVERIFIED TYPE REFERENCE CANDIDATES (${refs.unverifiedSites.length}) — the name, the type lookup cannot decide what it names:`);
|
|
933
1002
|
for (const site of refs.unverifiedSites.slice(0, 10)) {
|
|
934
1003
|
const expr = site.expression ? `: ${site.expression.replace(/\s+/g, ' ').slice(0, 100)}` : '';
|
|
935
|
-
lines.push(` ${site.file}:${site.line}${expr} (${site.reason})`);
|
|
1004
|
+
lines.push(` ${site.file}:${site.line} [${position(site)}]${expr} (${site.reason})`);
|
|
936
1005
|
}
|
|
937
1006
|
if (refs.unverifiedSites.length > 10) {
|
|
938
1007
|
lines.push(` (+${refs.unverifiedSites.length - 10} more unverified)`);
|
|
939
1008
|
}
|
|
940
1009
|
}
|
|
941
|
-
if (refs.
|
|
942
|
-
lines.push(' (no annotation sites outside the definition)');
|
|
943
|
-
}
|
|
1010
|
+
if (refs.account) lines.push(formatTypeAccount(impact.function, refs));
|
|
944
1011
|
}
|
|
945
1012
|
|
|
946
1013
|
// Unverified tier: visible, capped at 10 one-liners
|
|
@@ -1132,7 +1199,7 @@ function formatAbout(about, options = {}) {
|
|
|
1132
1199
|
const returnSuffix = c.returnType ? ` → ${c.returnType}` : '';
|
|
1133
1200
|
const sideEffects = (c.sideEffects && c.sideEffects.length) ? ` {${c.sideEffects.join(',')}}` : '';
|
|
1134
1201
|
const unreachableMark = (aboutCalleeReach.perLine && c.reachable === false) ? ' [unreachable]' : '';
|
|
1135
|
-
lines.push(` ${c.name}${weight}${returnSuffix}${sideEffects} - ${c.file}:${c.line} (${c.callCount}x)${unreachableMark}`);
|
|
1202
|
+
lines.push(` ${c.name}${weight}${returnSuffix}${sideEffects} - ${c.file}:${c.line} (${c.callCount}x)${constructsSuffix(c)}${unreachableMark}`);
|
|
1136
1203
|
if (!compact && c.docstring) {
|
|
1137
1204
|
const snip = calleeDocstringSnippet(c.docstring);
|
|
1138
1205
|
if (snip) lines.push(` "${snip}"`);
|
|
@@ -1261,4 +1328,6 @@ module.exports = {
|
|
|
1261
1328
|
// Shared with output/analysis-ext.js (smart): single-node callee contract.
|
|
1262
1329
|
formatCalleeAccountLine,
|
|
1263
1330
|
unverifiedCalleeLines,
|
|
1331
|
+
initializerReviewLines,
|
|
1332
|
+
constructsSuffix,
|
|
1264
1333
|
};
|
package/core/output/graph.js
CHANGED
|
@@ -443,8 +443,9 @@ function formatCircularDeps(result) {
|
|
|
443
443
|
return lines.join('\n');
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
const eager = result.cycles.filter(cycle => cycle.classification !== 'deferred');
|
|
446
|
+
const eager = result.cycles.filter(cycle => cycle.classification !== 'deferred' && cycle.classification !== 'static');
|
|
447
447
|
const deferred = result.cycles.filter(cycle => cycle.classification === 'deferred');
|
|
448
|
+
const staticCycles = result.cycles.filter(cycle => cycle.classification === 'static');
|
|
448
449
|
|
|
449
450
|
// Groups first: a strongly connected file set is the unit a refactor has
|
|
450
451
|
// to break, and a 14-file tangle can hold hundreds of elementary cycles.
|
|
@@ -454,8 +455,12 @@ function formatCircularDeps(result) {
|
|
|
454
455
|
lines.push(`CYCLE GROUPS (${groups.length}) — every member reaches every other member:`);
|
|
455
456
|
for (const group of groups) {
|
|
456
457
|
const counts = [];
|
|
457
|
-
|
|
458
|
-
|
|
458
|
+
// A group of files that run no module code on import has only
|
|
459
|
+
// static cycles; the import-time/deferred split does not apply.
|
|
460
|
+
const onlyStatic = group.staticCycles > 0 && !group.eagerCycles && !group.deferredCycles;
|
|
461
|
+
if (group.eagerCycles != null && !onlyStatic) counts.push(`${group.eagerCycles} import-time`);
|
|
462
|
+
if (group.deferredCycles != null && !onlyStatic) counts.push(`${group.deferredCycles} deferred`);
|
|
463
|
+
if (group.staticCycles > 0) counts.push(`${group.staticCycles} static`);
|
|
459
464
|
const suffix = counts.length > 0 ? ` [${counts.join(', ')}${result.summary.truncated ? '; enumerated counts only' : ''}]` : '';
|
|
460
465
|
lines.push(` ${group.size} files: ${group.files.join(', ')}${suffix}`);
|
|
461
466
|
}
|
|
@@ -489,6 +494,7 @@ function formatCircularDeps(result) {
|
|
|
489
494
|
};
|
|
490
495
|
renderGroup('IMPORT-TIME CYCLES', eager, false, EAGER_CYCLE_DISPLAY_LIMIT);
|
|
491
496
|
renderGroup('DEFERRED CYCLES', deferred, true, DEFERRED_CYCLE_DISPLAY_LIMIT);
|
|
497
|
+
renderGroup('STATIC CYCLES', staticCycles, false, EAGER_CYCLE_DISPLAY_LIMIT);
|
|
492
498
|
|
|
493
499
|
lines.push('');
|
|
494
500
|
const { totalCycles, filesInCycles } = result.summary;
|
|
@@ -502,6 +508,9 @@ function formatCircularDeps(result) {
|
|
|
502
508
|
}
|
|
503
509
|
lines.push('The CYCLE GROUPS list and files-in-cycles count are complete; enumerated cycle counts are lower bounds.');
|
|
504
510
|
}
|
|
511
|
+
if (staticCycles.length > 0) {
|
|
512
|
+
lines.push(`${staticCycles.length} chain${staticCycles.length === 1 ? ' links' : 's link'} only files whose imports and includes run no module code (Go, Rust, Java, C#, C, C++): static dependency cycles, not import-time cycles.`);
|
|
513
|
+
}
|
|
505
514
|
if (deferred.length > 0) {
|
|
506
515
|
lines.push(`${deferred.length} chain${deferred.length === 1 ? '' : 's'} close only through deferred edges (function-local, TYPE_CHECKING-only, or type-only imports); they are not unconditional import-time cycles, but a function-local edge may still matter if invoked during initialization.`);
|
|
507
516
|
}
|
package/core/output/lines.js
CHANGED
|
@@ -244,15 +244,16 @@ function impactRecords(result) {
|
|
|
244
244
|
}
|
|
245
245
|
if (result.typeReferences) {
|
|
246
246
|
const refs = result.typeReferences;
|
|
247
|
+
const where = site => `type-reference: ${site.kind || 'type'}${Number.isInteger(site.column) ? `, column ${site.column + 1}` : ''}`;
|
|
247
248
|
for (const group of refs.byFile || []) {
|
|
248
249
|
for (const site of group.sites || []) {
|
|
249
|
-
out.push(record(group.file, site.line, site.expression,
|
|
250
|
+
out.push(record(group.file, site.line, site.expression, where(site)));
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
for (const site of refs.unverifiedSites || []) {
|
|
253
|
-
out.push(record(pathOf(site), site.line, site.expression, `unverified: ${site.reason};
|
|
254
|
+
out.push(record(pathOf(site), site.line, site.expression, `unverified: ${site.reason}; ${where(site)}`));
|
|
254
255
|
}
|
|
255
|
-
notes.push(`# TYPE REFERENCE SITES: ${refs.confirmedCount} confirmed, ${refs.unverifiedCount} unverified, ${refs.excluded?.total || 0}
|
|
256
|
+
notes.push(`# TYPE REFERENCE SITES: ${refs.confirmedCount} confirmed, ${refs.unverifiedCount} unverified, ${refs.excluded?.total || 0} excluded (separate from caller ACCOUNT).`);
|
|
256
257
|
}
|
|
257
258
|
notes.push(...accountComments(result.account));
|
|
258
259
|
for (const warning of result.warnings || []) notes.push(...commentLines(warning.message));
|
package/core/output/public.js
CHANGED
|
@@ -410,6 +410,7 @@ function formatPublicJson(command, result, params = {}, execution = {}) {
|
|
|
410
410
|
commandMeta.excludedDecorated = result.excludedDecorated || 0;
|
|
411
411
|
commandMeta.excludedExternalContract = result.excludedExternalContract || 0;
|
|
412
412
|
commandMeta.excludedRuntimeContract = result.excludedRuntimeContract || 0;
|
|
413
|
+
commandMeta.excludedConstructors = result.excludedConstructors || 0;
|
|
413
414
|
commandMeta.pythonImplicitExportFiles = result.pythonImplicitExportFiles || 0;
|
|
414
415
|
commandMeta.excludedDynamicDispatch = result.excludedDynamicDispatch || 0;
|
|
415
416
|
commandMeta.computedDispatch = result.computedDispatch || { count: 0, names: [] };
|
package/core/output/reporting.js
CHANGED
|
@@ -214,8 +214,9 @@ function formatStatsJson(stats) {
|
|
|
214
214
|
function formatDeadcode(results, options = {}) {
|
|
215
215
|
if (results.length === 0 && !results.excludedDecorated &&
|
|
216
216
|
!results.excludedExported && !results.excludedExternalContract &&
|
|
217
|
-
!results.excludedRuntimeContract && !results.pythonImplicitExportFiles &&
|
|
217
|
+
!results.excludedRuntimeContract && !results.excludedConstructors && !results.pythonImplicitExportFiles &&
|
|
218
218
|
!results.excludedDynamicDispatch && !results.computedDispatch?.count &&
|
|
219
|
+
!results.reflection?.withheld && !results.reflection?.dynamicCount && !results.reflection?.literalCount &&
|
|
219
220
|
results.coverage?.complete !== false) {
|
|
220
221
|
return 'No dead code found.';
|
|
221
222
|
}
|
|
@@ -291,6 +292,9 @@ function formatDeadcode(results, options = {}) {
|
|
|
291
292
|
if (results.excludedRuntimeContract > 0) {
|
|
292
293
|
lines.push(`\n${results.excludedRuntimeContract} runtime callback(s) hidden (language/runtime registrations, not dead).`);
|
|
293
294
|
}
|
|
295
|
+
if (results.excludedConstructors > 0) {
|
|
296
|
+
lines.push(`\n${results.excludedConstructors} constructor(s) not listed: a constructor belongs to its type's allocation protocol, so the type is audited instead.`);
|
|
297
|
+
}
|
|
294
298
|
if (results.pythonImplicitExportFiles > 0) {
|
|
295
299
|
lines.push(`\nPython public-surface rule active in ${results.pythonImplicitExportFiles} file(s) without __all__: top-level non-underscore names are treated as externally reachable.`);
|
|
296
300
|
}
|
|
@@ -366,6 +370,7 @@ function formatDeadcodeJson(results) {
|
|
|
366
370
|
...(results.excludedDecorated > 0 && { excludedDecorated: results.excludedDecorated }),
|
|
367
371
|
...(results.excludedExternalContract > 0 && { excludedExternalContract: results.excludedExternalContract }),
|
|
368
372
|
...(results.excludedRuntimeContract > 0 && { excludedRuntimeContract: results.excludedRuntimeContract }),
|
|
373
|
+
...(results.excludedConstructors > 0 && { excludedConstructors: results.excludedConstructors }),
|
|
369
374
|
...(results.pythonImplicitExportFiles > 0 && { pythonImplicitExportFiles: results.pythonImplicitExportFiles }),
|
|
370
375
|
...(results.excludedDynamicDispatch > 0 && { excludedDynamicDispatch: results.excludedDynamicDispatch }),
|
|
371
376
|
...(results.computedDispatch?.count > 0 && { computedDispatch: results.computedDispatch }),
|
package/core/output/shared.js
CHANGED
|
@@ -177,6 +177,8 @@ function formatClassSignature(cls) {
|
|
|
177
177
|
if (cls.implements && cls.implements.length > 0) {
|
|
178
178
|
parts.push(`implements ${cls.implements.join(', ')}`);
|
|
179
179
|
}
|
|
180
|
+
// `class State;` declares a class defined elsewhere (fix #427).
|
|
181
|
+
if (cls.forwardDeclaration) parts.push('(forward declaration)');
|
|
180
182
|
|
|
181
183
|
return parts.join(' ');
|
|
182
184
|
}
|