ucn 5.5.3 → 5.6.0
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 -12
- package/.claude/skills/ucn/references/commands.md +18 -14
- package/.claude/skills/ucn/references/resolution-rules.md +4 -0
- package/.claude/skills/ucn/references/trust-contract.md +4 -4
- package/CHANGELOG.md +474 -0
- package/README.md +69 -29
- package/cli/flags.js +184 -0
- package/cli/index.js +137 -324
- package/core/account.js +44 -87
- package/core/analysis.js +273 -107
- package/core/ast-analysis.js +2 -0
- package/core/bridge.js +14 -0
- package/core/cache.js +101 -16
- package/core/callers.js +352 -70
- package/core/capture.js +331 -0
- package/core/change-kinds.js +888 -0
- package/core/check.js +1117 -139
- package/core/command-contracts.js +7 -6
- package/core/constructed-class.js +1195 -0
- package/core/contract-membership.js +142 -0
- package/core/deadcode.js +362 -84
- package/core/deletion.js +401 -0
- package/core/discovery.js +104 -9
- package/core/entrypoints.js +12 -6
- package/core/evidence-bands.js +68 -0
- package/core/evidence-bands.json +44 -0
- package/core/execute.js +172 -161
- package/core/hot-summary.js +241 -0
- package/core/index-ir.js +3 -1
- package/core/ir.js +5 -1
- package/core/member-signatures.js +789 -0
- package/core/output/analysis-ext.js +20 -10
- package/core/output/analysis.js +98 -60
- package/core/output/check.js +98 -37
- package/core/output/doctor.js +34 -1
- package/core/output/find.js +2 -1
- package/core/output/lines.js +63 -3
- package/core/output/public.js +29 -11
- package/core/output/refactoring.js +32 -0
- package/core/output/reporting.js +15 -2
- package/core/output/search.js +11 -2
- package/core/output/tracing.js +35 -7
- package/core/output-budget.js +124 -57
- package/core/parallel-build.js +3 -3
- package/core/project.js +106 -23
- package/core/provenance-type.js +12 -5
- package/core/public-command.js +35 -9
- package/core/python-modules.js +6 -0
- package/core/reflection.js +151 -23
- package/core/registry.js +623 -72
- package/core/reporting.js +69 -34
- package/core/review-items.js +75 -0
- package/core/search.js +89 -128
- package/core/shared.js +0 -45
- package/core/snapshot.js +350 -0
- package/core/source-universe.js +707 -0
- package/core/test-units.js +3624 -0
- package/core/tracing.js +150 -96
- package/core/verify.js +27 -11
- package/languages/adapter.js +8 -3
- package/languages/c-family.js +12 -0
- package/languages/csharp.js +634 -0
- package/languages/go.js +120 -0
- package/languages/index.js +422 -4
- package/languages/java.js +712 -0
- package/languages/javascript.js +1460 -2
- package/languages/lexical-scope.js +68 -0
- package/languages/python.js +1672 -1
- package/languages/rust.js +91 -0
- package/mcp/server.js +44 -110
- package/package.json +10 -5
package/core/public-command.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { CANONICAL_COMMANDS } = require('./registry');
|
|
3
|
+
const { CANONICAL_COMMANDS, GLOBAL_PARAMS } = require('./registry');
|
|
4
4
|
|
|
5
5
|
const NAME_COMMANDS = new Set(['show', 'find', 'usages', 'trace', 'tests', 'plan']);
|
|
6
6
|
|
|
@@ -34,14 +34,16 @@ function sourceRangeTarget(params) {
|
|
|
34
34
|
/** Build normalized execute() params from the shared CLI-style positional arg. */
|
|
35
35
|
function buildPublicParams(command, arg, params = {}) {
|
|
36
36
|
const clean = { ...params };
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'topRaw', 'limitRaw', 'maxFilesRaw', 'maxLinesRaw', 'maxChars', 'maxCharsRaw',
|
|
42
|
-
'depthRaw', 'contextRaw', 'workersRaw',
|
|
43
|
-
'_fileFromFileMode']) delete clean[key];
|
|
37
|
+
// Process flags and the build/transport parameters every command accepts
|
|
38
|
+
// never belong in execute params: the surface applies them itself.
|
|
39
|
+
for (const key of ['json', 'quiet', 'cache', 'clearCache', 'interactive', 'workers', '_fileFromFileMode',
|
|
40
|
+
...GLOBAL_PARAMS]) delete clean[key];
|
|
44
41
|
|
|
42
|
+
return withoutUnset(subjectParams(command, arg, clean));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The params with the positional argument placed as the command's subject. */
|
|
46
|
+
function subjectParams(command, arg, clean) {
|
|
45
47
|
if (NAME_COMMANDS.has(command)) return { ...clean, name: arg || clean.name };
|
|
46
48
|
switch (command) {
|
|
47
49
|
case 'search': return { ...clean, term: arg || clean.term };
|
|
@@ -58,8 +60,32 @@ function buildPublicParams(command, arg, params = {}) {
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/** A parameter that was not given has no key: the same object MCP arguments make. */
|
|
64
|
+
function withoutUnset(params) {
|
|
65
|
+
for (const key of Object.keys(params)) {
|
|
66
|
+
if (params[key] === undefined) delete params[key];
|
|
67
|
+
}
|
|
68
|
+
return params;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* What to say about a parameter a command accepts and that changes nothing,
|
|
73
|
+
* or less than its name suggests, there. One text for every surface, in CLI
|
|
74
|
+
* spelling: the surface renders it with formatSurfaceMessage.
|
|
75
|
+
*/
|
|
76
|
+
function noOpNotes(command, params = {}) {
|
|
77
|
+
if (!params.includeMethods) return [];
|
|
78
|
+
if (['impact', 'trace', 'tests', 'check'].includes(command)) {
|
|
79
|
+
return [`--include-methods has no effect on '${command}' — method calls are always tiered by receiver evidence.`];
|
|
80
|
+
}
|
|
81
|
+
if (command === 'show') {
|
|
82
|
+
return [`--include-methods on '${command}' affects only method-callee display for standalone-function targets — caller tiers are always evidence-based, and method targets analyze method calls by default.`];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
function isPublicCommand(command) {
|
|
62
88
|
return CANONICAL_COMMANDS.includes(command);
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
module.exports = { buildPublicParams, isPublicCommand, parseSourceTarget, sourceRangeTarget };
|
|
91
|
+
module.exports = { buildPublicParams, isPublicCommand, noOpNotes, parseSourceTarget, sourceRangeTarget };
|
package/core/python-modules.js
CHANGED
|
@@ -141,6 +141,12 @@ function pythonModuleScope(index, file, receiver, site, memo = new Map()) {
|
|
|
141
141
|
for (const child of current.namedChildren) stack.push(child);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
// The name token of `module.Name`: the module root is read where
|
|
145
|
+
// the whole attribute is.
|
|
146
|
+
const { sameNode } = require('../languages/utils');
|
|
147
|
+
if (node?.parent?.type === 'attribute' && sameNode(node.parent.childForFieldName('attribute'), node)) {
|
|
148
|
+
node = node.parent;
|
|
149
|
+
}
|
|
144
150
|
if (node) {
|
|
145
151
|
const { referenceScope } = require('../languages/lexical-scope');
|
|
146
152
|
const scopesKey = 'py-module-scopes';
|
package/core/reflection.js
CHANGED
|
@@ -730,8 +730,31 @@ const MEMBER_CALLEE_FIELDS = {
|
|
|
730
730
|
member_expression: ['property', 'object'], // JS/TS
|
|
731
731
|
member_access_expression: ['name', 'expression'], // C#
|
|
732
732
|
attribute: ['attribute', 'object'], // Python
|
|
733
|
+
field_access: ['field', 'object'], // Java qualified namespace
|
|
733
734
|
};
|
|
734
735
|
|
|
736
|
+
/** Parentheses change neither a callee nor a namespace qualifier. */
|
|
737
|
+
function _calleeNode(node) {
|
|
738
|
+
while (node?.type === 'parenthesized_expression') {
|
|
739
|
+
node = node.namedChildren.find(child => child.type !== 'comment');
|
|
740
|
+
}
|
|
741
|
+
return node;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** A written identifier/member path, from fields rather than intervening text. */
|
|
745
|
+
function _calleeName(node) {
|
|
746
|
+
node = _calleeNode(node);
|
|
747
|
+
if (!node) return '';
|
|
748
|
+
if (['identifier', 'property_identifier', 'field_identifier', 'package_identifier'].includes(node.type)) {
|
|
749
|
+
return node.text;
|
|
750
|
+
}
|
|
751
|
+
const fields = MEMBER_CALLEE_FIELDS[node.type];
|
|
752
|
+
if (!fields) return '';
|
|
753
|
+
const owner = _calleeName(node.childForFieldName(fields[1]));
|
|
754
|
+
const member = _calleeName(node.childForFieldName(fields[0]));
|
|
755
|
+
return owner && member ? `${owner}.${member}` : '';
|
|
756
|
+
}
|
|
757
|
+
|
|
735
758
|
/** Callee text, member name and receiver of a call node (no arguments). */
|
|
736
759
|
function _calleeParts(node, wantMembers, wantQualified) {
|
|
737
760
|
if (node.type === 'method_invocation') {
|
|
@@ -739,7 +762,7 @@ function _calleeParts(node, wantMembers, wantQualified) {
|
|
|
739
762
|
const name = node.childForFieldName('name')?.text || '';
|
|
740
763
|
return { calleeText: name, member: name, object: node.childForFieldName('object') };
|
|
741
764
|
}
|
|
742
|
-
const fn = node.childForFieldName('function') || node.namedChild(0);
|
|
765
|
+
const fn = _calleeNode(node.childForFieldName('function') || node.namedChild(0));
|
|
743
766
|
if (!fn) return null;
|
|
744
767
|
if (fn.type === 'identifier') {
|
|
745
768
|
return { calleeText: fn.text || '', member: fn.text, object: null, bare: true };
|
|
@@ -762,18 +785,58 @@ function _callArgs(node) {
|
|
|
762
785
|
return argsNode ? argsNode.namedChildren.filter(c => c.type !== 'comment') : null;
|
|
763
786
|
}
|
|
764
787
|
|
|
788
|
+
// Construction nodes: `new Function(..)` builds a callable from a string.
|
|
789
|
+
const NEW_NODE_TYPES = ['new_expression', 'object_creation_expression'];
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Local names a file's imports bind to the modules of `{ package }` rules:
|
|
793
|
+
* `reflect` for `import "reflect"`, `r` for `import r "reflect"`. An import
|
|
794
|
+
* that binds no qualifier (Go `.` and `_`) names none.
|
|
795
|
+
* @returns {Map<string, string>} local name -> module
|
|
796
|
+
*/
|
|
797
|
+
function _packageQualifiers(rules, imports) {
|
|
798
|
+
const modules = new Set(rules.filter(rule => rule.package).map(rule => rule.package));
|
|
799
|
+
const qualifiers = new Map();
|
|
800
|
+
if (modules.size === 0) return qualifiers;
|
|
801
|
+
for (const item of imports || []) {
|
|
802
|
+
if (!item || !modules.has(item.module)) continue;
|
|
803
|
+
for (const name of item.names || []) {
|
|
804
|
+
// The parser already supplies valid import names, including
|
|
805
|
+
// Unicode identifiers. Dot and blank imports bind no qualifier.
|
|
806
|
+
if (name && name !== '_' && name !== '.') qualifiers.set(name, item.module);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return qualifiers;
|
|
810
|
+
}
|
|
811
|
+
|
|
765
812
|
/**
|
|
766
|
-
* Reflection
|
|
813
|
+
* Reflection facts of one parsed file: { sites, reflectionUses, evalUses }.
|
|
814
|
+
*
|
|
767
815
|
* Each site: { kind, line, expression, dynamic, name?, patterns?, receiver?,
|
|
768
|
-
* membersOnly? }.
|
|
816
|
+
* membersOnly? }: a reflective access that names its member by a string.
|
|
817
|
+
*
|
|
818
|
+
* The two counts are how often the file uses the language's reflection and
|
|
819
|
+
* code-execution vocabulary, whether or not a name can be read from the use.
|
|
820
|
+
* `reflectionUses`: the call-shaped sites (a `functions` or `members` call
|
|
821
|
+
* that passes its name argument) plus calls of the `unnamed` rules.
|
|
822
|
+
* `evalUses`: bare calls of `namespaceLookups.calls` that pass an argument,
|
|
823
|
+
* plus calls and constructions of the `codeExecution` rules. They count call
|
|
824
|
+
* and construction nodes of the tree, so a spelling inside a comment or a
|
|
825
|
+
* string is no use, and neither is a method or a definition that shares a
|
|
826
|
+
* name with a builtin (`self.exec(..)`, `def eval(..)`), nor a same-named
|
|
827
|
+
* method called without the name (`request.getMethod()`).
|
|
828
|
+
*
|
|
829
|
+
* `options.imports` are the file's import records: a `{ package }` rule
|
|
830
|
+
* matches a call qualified by a local name they bind to that module.
|
|
769
831
|
*/
|
|
770
|
-
function
|
|
832
|
+
function reflectionFactsInTree(tree, language, code = null, options = {}) {
|
|
833
|
+
const facts = { sites: [], reflectionUses: 0, evalUses: 0 };
|
|
771
834
|
const traits = langTraits(language) || {};
|
|
772
835
|
const api = traits.reflectionApi;
|
|
773
|
-
if (!api || !tree) return
|
|
836
|
+
if (!api || !tree) return facts;
|
|
774
837
|
const resolve = makeResolver();
|
|
775
838
|
const ctx = { formatCalls: api.formatCalls || null, resolve };
|
|
776
|
-
const sites =
|
|
839
|
+
const sites = facts.sites;
|
|
777
840
|
const push = (node, kind, nameNode, receiverNode, receiverFact) => {
|
|
778
841
|
const cls = classifyNameExpression(nameNode, ctx);
|
|
779
842
|
const expression = String(node.text || '');
|
|
@@ -796,22 +859,37 @@ function reflectionSitesInTree(tree, language, code = null) {
|
|
|
796
859
|
});
|
|
797
860
|
};
|
|
798
861
|
const ns = api.namespaceLookups;
|
|
862
|
+
// Vocabulary that names no member: counted, never a site.
|
|
863
|
+
const unnamed = api.unnamed || [];
|
|
864
|
+
const codeExecution = api.codeExecution || [];
|
|
865
|
+
const constructs = codeExecution.filter(rule => rule.construct);
|
|
866
|
+
const qualifiers = _packageQualifiers([...unnamed, ...codeExecution], options.imports);
|
|
867
|
+
const scopeMemo = new Map();
|
|
799
868
|
// Candidate nodes are located natively: reflective calls from the
|
|
800
869
|
// vocabulary's spelling positions (descendantForIndex, walking up to the
|
|
801
870
|
// enclosing call), computed member keys by node type. No JS-side walk of
|
|
802
871
|
// the whole tree.
|
|
803
|
-
const
|
|
804
|
-
|
|
872
|
+
const hintSources = [];
|
|
873
|
+
if (api.hintPattern) hintSources.push(api.hintPattern.source);
|
|
874
|
+
// A package's qualifier is the file's own choice (`r "reflect"`).
|
|
875
|
+
if (qualifiers.size > 0) {
|
|
876
|
+
const { literalNameRegex } = require('./shared');
|
|
877
|
+
hintSources.push(...[...qualifiers.keys()].map(name => literalNameRegex(name).source));
|
|
878
|
+
}
|
|
879
|
+
const useHints = code != null && hintSources.length > 0;
|
|
880
|
+
const callTypes = constructs.length > 0 ? [...CALL_NODE_TYPES, ...NEW_NODE_TYPES] : CALL_NODE_TYPES;
|
|
805
881
|
const types = [];
|
|
806
882
|
if (!useHints) {
|
|
807
|
-
types.push(...
|
|
883
|
+
types.push(...callTypes);
|
|
808
884
|
if (ns?.subscriptOf) types.push('subscript');
|
|
809
885
|
}
|
|
810
886
|
if (api.computedMembers) types.push('subscript_expression');
|
|
811
887
|
const wantMembers = (api.members || []).length > 0;
|
|
812
|
-
// Qualified
|
|
813
|
-
// member-shaped calls; bare vocabularies (`getattr`)
|
|
814
|
-
|
|
888
|
+
// Qualified callees (`Reflect.get`, a package's functions) need the
|
|
889
|
+
// callee text of member-shaped calls; bare vocabularies (`getattr`)
|
|
890
|
+
// never do.
|
|
891
|
+
const wantQualified = qualifiers.size > 0 ||
|
|
892
|
+
[...(api.functions || []), ...unnamed, ...codeExecution].some(rule => rule.qualified);
|
|
815
893
|
const visitNode = node => {
|
|
816
894
|
if (node.type === 'subscript_expression') {
|
|
817
895
|
// obj["on" + evt] / this[`handle${t}`] (JS/TS computed member
|
|
@@ -843,15 +921,23 @@ function reflectionSitesInTree(tree, language, code = null) {
|
|
|
843
921
|
}
|
|
844
922
|
return;
|
|
845
923
|
}
|
|
924
|
+
if (NEW_NODE_TYPES.includes(node.type)) {
|
|
925
|
+
// `new Function(code)`: the constructed class decides.
|
|
926
|
+
const constructed = node.childForFieldName('constructor') || node.childForFieldName('type');
|
|
927
|
+
const text = _calleeName(constructed);
|
|
928
|
+
if (constructs.some(rule => rule.construct.test(text))) facts.evalUses++;
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
846
931
|
const call = _calleeParts(node, wantMembers, wantQualified);
|
|
847
932
|
if (!call) return;
|
|
848
933
|
// Match on the callee spelling first; arguments are read only for a
|
|
849
934
|
// vocabulary hit (most calls in a hinted file are unrelated).
|
|
850
|
-
const calleeText = () => (call.calleeText ??= call.fn
|
|
935
|
+
const calleeText = () => (call.calleeText ??= _calleeName(call.fn));
|
|
851
936
|
for (const rule of api.functions || []) {
|
|
852
937
|
if (!rule.callee.test(calleeText())) continue;
|
|
853
938
|
const args = _callArgs(node);
|
|
854
939
|
if (args && args[rule.nameArg]) {
|
|
940
|
+
facts.reflectionUses++;
|
|
855
941
|
push(node, rule.kind || calleeText().split('.').pop(),
|
|
856
942
|
args[rule.nameArg], args[rule.receiverArg]);
|
|
857
943
|
}
|
|
@@ -861,7 +947,10 @@ function reflectionSitesInTree(tree, language, code = null) {
|
|
|
861
947
|
for (const rule of api.members || []) {
|
|
862
948
|
if (!rule.member.test(call.member)) continue;
|
|
863
949
|
const args = _callArgs(node);
|
|
864
|
-
if (args && args[rule.nameArg])
|
|
950
|
+
if (args && args[rule.nameArg]) {
|
|
951
|
+
facts.reflectionUses++;
|
|
952
|
+
push(node, call.member, args[rule.nameArg], call.object);
|
|
953
|
+
}
|
|
865
954
|
return;
|
|
866
955
|
}
|
|
867
956
|
}
|
|
@@ -869,25 +958,58 @@ function reflectionSitesInTree(tree, language, code = null) {
|
|
|
869
958
|
// so even a fully dynamic name is bounded to it.
|
|
870
959
|
if (ns?.calls && call.bare && ns.calls.test(call.calleeText)) {
|
|
871
960
|
const args = _callArgs(node);
|
|
872
|
-
if (args && args[0])
|
|
961
|
+
if (args && args[0]) {
|
|
962
|
+
facts.evalUses++;
|
|
963
|
+
push(node, call.calleeText, args[0], null, MODULE_RECEIVER);
|
|
964
|
+
}
|
|
965
|
+
return;
|
|
873
966
|
}
|
|
967
|
+
if (unnamed.length === 0 && codeExecution.length === 0) return;
|
|
968
|
+
// The callee as written, receiver included (`Class.forName`,
|
|
969
|
+
// `reflect.ValueOf`); a bare callee is its own text.
|
|
970
|
+
let written = null;
|
|
971
|
+
const writtenCallee = () => (written ??= (node.type === 'method_invocation'
|
|
972
|
+
? (call.object ? `${_calleeName(call.object)}.${call.member}` : call.member || '')
|
|
973
|
+
: calleeText()));
|
|
974
|
+
const matches = rule => {
|
|
975
|
+
if (rule.construct) return false;
|
|
976
|
+
if (rule.callee) return rule.callee.test(writtenCallee());
|
|
977
|
+
if (rule.package) {
|
|
978
|
+
const qualifier = _calleeNode(call.object);
|
|
979
|
+
if (qualifier?.type !== 'identifier' || qualifiers.get(qualifier.text) !== rule.package) return false;
|
|
980
|
+
// The file import owns the spelling only outside scopes
|
|
981
|
+
// that bind another value to it (parameters, locals, etc.).
|
|
982
|
+
return require('../languages/lexical-scope').referenceScope(
|
|
983
|
+
qualifier, language, scopeMemo) === 'module';
|
|
984
|
+
}
|
|
985
|
+
return false;
|
|
986
|
+
};
|
|
987
|
+
if (unnamed.some(matches)) facts.reflectionUses++;
|
|
988
|
+
else if (codeExecution.some(matches)) facts.evalUses++;
|
|
874
989
|
};
|
|
875
990
|
try {
|
|
876
991
|
const candidates = [];
|
|
877
992
|
if (useHints) {
|
|
993
|
+
// One candidate per node: nested calls can begin at one offset
|
|
994
|
+
// (`a.getMethod(x).getField(y)`), so the end is part of the key.
|
|
878
995
|
const seen = new Set();
|
|
879
|
-
const
|
|
996
|
+
const once = (kind, node) => {
|
|
997
|
+
const key = `${kind}${node.startIndex}:${node.endIndex}`;
|
|
998
|
+
if (seen.has(key)) return false;
|
|
999
|
+
seen.add(key);
|
|
1000
|
+
return true;
|
|
1001
|
+
};
|
|
1002
|
+
const re = new RegExp(hintSources.join('|'), 'gu');
|
|
880
1003
|
let m;
|
|
881
1004
|
while ((m = re.exec(code)) !== null) {
|
|
1005
|
+
if (m[0] === '') { re.lastIndex++; continue; }
|
|
882
1006
|
let node = tree.rootNode.descendantForIndex(m.index);
|
|
883
|
-
for (
|
|
884
|
-
if (!
|
|
885
|
-
if (
|
|
1007
|
+
for (; node; node = node.parent) {
|
|
1008
|
+
if (!callTypes.includes(node.type)) continue;
|
|
1009
|
+
if (once('c', node)) candidates.push(node);
|
|
886
1010
|
// `globals()[name]`: the lookup is the enclosing subscript.
|
|
887
1011
|
const parent = node.parent;
|
|
888
|
-
if (ns?.subscriptOf && parent?.type === 'subscript' &&
|
|
889
|
-
!seen.has(-parent.startIndex - 1)) {
|
|
890
|
-
seen.add(-parent.startIndex - 1);
|
|
1012
|
+
if (ns?.subscriptOf && parent?.type === 'subscript' && once('s', parent)) {
|
|
891
1013
|
candidates.push(parent);
|
|
892
1014
|
}
|
|
893
1015
|
break;
|
|
@@ -898,7 +1020,12 @@ function reflectionSitesInTree(tree, language, code = null) {
|
|
|
898
1020
|
candidates.sort((a, b) => a.startIndex - b.startIndex || (a.endIndex - b.endIndex));
|
|
899
1021
|
for (const node of candidates) visitNode(node);
|
|
900
1022
|
} catch (_) { /* partial inventory is disclosed as-is */ }
|
|
901
|
-
return
|
|
1023
|
+
return facts;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/** The reflection sites of one parsed file (see reflectionFactsInTree). */
|
|
1027
|
+
function reflectionSitesInTree(tree, language, code = null) {
|
|
1028
|
+
return reflectionFactsInTree(tree, language, code).sites;
|
|
902
1029
|
}
|
|
903
1030
|
|
|
904
1031
|
// ---------------------------------------------------------------------------
|
|
@@ -1132,6 +1259,7 @@ module.exports = {
|
|
|
1132
1259
|
stringAlternatives,
|
|
1133
1260
|
classifyNameExpression,
|
|
1134
1261
|
classifyReceiver,
|
|
1262
|
+
reflectionFactsInTree,
|
|
1135
1263
|
reflectionSitesInTree,
|
|
1136
1264
|
globMatches,
|
|
1137
1265
|
patternEntriesFor,
|