ucn 5.5.0 → 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 +41 -27
- package/cli/index.js +6 -1
- package/core/accessors.js +341 -54
- package/core/account.js +106 -31
- package/core/analysis.js +266 -127
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +144 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +5111 -1024
- 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 +228 -114
- 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 +1202 -157
- package/core/rust-modules.js +21 -3
- 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 +157 -46
- 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 +548 -89
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
package/core/brief.js
CHANGED
|
@@ -102,7 +102,7 @@ function brief(index, name, options = {}) {
|
|
|
102
102
|
const { def } = index.resolveSymbol(name, { file: options.file, className: options.className, line: options.line });
|
|
103
103
|
if (!def) return null;
|
|
104
104
|
|
|
105
|
-
const language =
|
|
105
|
+
const language = index.languageOf(def.file || def.relativePath);
|
|
106
106
|
const symbol = {
|
|
107
107
|
name: def.name,
|
|
108
108
|
type: def.type,
|
|
@@ -229,7 +229,10 @@ function firstSentence(text) {
|
|
|
229
229
|
function countMembers(index, def) {
|
|
230
230
|
if (!def || !def.file) return 0;
|
|
231
231
|
let count = 0;
|
|
232
|
-
|
|
232
|
+
// Build and cache loading keep each file's declaration list alongside
|
|
233
|
+
// the name index. This count only reads declarations of the pinned file.
|
|
234
|
+
const fileSymbols = index.files?.get(def.file)?.symbols;
|
|
235
|
+
for (const arr of fileSymbols ? [fileSymbols] : index.symbols.values()) {
|
|
233
236
|
for (const s of arr) {
|
|
234
237
|
if (s.file === def.file && s.className === def.name && s.isMethod) count++;
|
|
235
238
|
}
|
|
@@ -362,8 +365,9 @@ function sideEffectsFor(index, symbol) {
|
|
|
362
365
|
index._sideEffectCache.set(key, null);
|
|
363
366
|
return null;
|
|
364
367
|
}
|
|
365
|
-
const language =
|
|
366
|
-
|
|
368
|
+
const language = typeof index.languageOf === 'function'
|
|
369
|
+
? index.languageOf(filePath) : detectLanguage(filePath, index.root);
|
|
370
|
+
const fileEntry = index.files.get(filePath);
|
|
367
371
|
const fileImports = collectImportNames(fileEntry);
|
|
368
372
|
const tags = classifySideEffects(bodyText, language, fileImports);
|
|
369
373
|
index._sideEffectCache.set(key, tags);
|
package/core/build-worker.js
CHANGED
|
@@ -55,6 +55,7 @@ function externalMacrosFor(language) {
|
|
|
55
55
|
consulted: new Set(),
|
|
56
56
|
get objectBodies() { return resolve()?.objectBodies || EMPTY_MACROS; },
|
|
57
57
|
get functionMacros() { return resolve()?.functionMacros || EMPTY_MACROS; },
|
|
58
|
+
get functionHeads() { return resolve()?.functionHeads || EMPTY_MACROS; },
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
const signalArray = new Int32Array(signal);
|
|
@@ -180,7 +181,7 @@ if (control) {
|
|
|
180
181
|
Atomics.wait(controlArray, workerIndex, 0);
|
|
181
182
|
if (Atomics.load(controlArray, workerIndex) === 2) {
|
|
182
183
|
const job = receiveMessageOnPort(port)?.message;
|
|
183
|
-
if (job) require('./rust-macro-expansion').runExpansionQueue(job, port);
|
|
184
|
+
if (job) await require('./rust-macro-expansion').runExpansionQueue(job, port);
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
try { port.close(); } catch (_) { /* closed */ }
|
package/core/cache.js
CHANGED
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
classifyUnsupportedSourceFile,
|
|
15
15
|
} = require('./discovery');
|
|
16
16
|
const { codeUnitCompare } = require('./shared');
|
|
17
|
+
const { syntaxWeightKind } = require('./source-memo');
|
|
17
18
|
|
|
18
19
|
// Read UCN version for cache invalidation
|
|
19
20
|
const UCN_VERSION = require('../package.json').version;
|
|
@@ -918,9 +919,130 @@ function clearAllCaches() {
|
|
|
918
919
|
// `passedAsArgument`.
|
|
919
920
|
// v274 (fix #403): a Python reference inside a lambda whose enclosing def
|
|
920
921
|
// binds the name records that def as its `localShadowScope`.
|
|
921
|
-
|
|
922
|
+
// v275 (fix #405): a Rust file whose macro-expansion calls are pending
|
|
923
|
+
// records every expansion's hygiene start and expanded length
|
|
924
|
+
// (`rustMacroExpansion.expansions`); its `words` leave out the text of
|
|
925
|
+
// literals and comments.
|
|
926
|
+
// v276 (fix #405b): a pending Rust file whose expansions the calls pass
|
|
927
|
+
// reads by groups records each group's lines, expanded length and the words
|
|
928
|
+
// its expansions output and its headers hold (`rustMacroExpansion.groups`).
|
|
929
|
+
// v277: refactoring usages distinguish declarator-name tokens from value
|
|
930
|
+
// references; C# local-function name tokens are definitions.
|
|
931
|
+
// v278: generic invocation usages preserve the terminal name and receiver.
|
|
932
|
+
// v279: C++ using facts retain their enclosing preprocessor branch groups.
|
|
933
|
+
// v291: Value declarations retain lexical scope and forward-declaration facts;
|
|
934
|
+
// value queries retain source-local shadow/text proofs and lexical block
|
|
935
|
+
// ranges with each parse view's error positions.
|
|
936
|
+
// v292: Python class syntax facts retain members, writes and dynamic stores.
|
|
937
|
+
// v300: Java constructor declarations, construction arguments and delegation
|
|
938
|
+
// records; qualified construction types, declaration source extents and
|
|
939
|
+
// JS/TS assigned value names.
|
|
940
|
+
// v303: Java producer chains retain argument counts for collection contracts.
|
|
941
|
+
// v304: Java receiver producer paths retain their written type and source scope.
|
|
942
|
+
// v305: Java variadic parameters retain their declared array type.
|
|
943
|
+
// v306: Overload witnesses omit absent optional object fields consistently.
|
|
944
|
+
// v307: Overload witnesses capture observed file fields separately.
|
|
945
|
+
// v308 (fix #419): Java records without a declared canonical constructor
|
|
946
|
+
// carry its parameters (`primaryParams`); record constructors delegate.
|
|
947
|
+
// v309 (fix #421): C++ out-of-class constructor mem-initializers and
|
|
948
|
+
// default initializations are call records.
|
|
949
|
+
// v310 (fix #422): Rust bare and path call records carry the binding the
|
|
950
|
+
// enclosing blocks give their name (`scopeBinding`, `receiverScopeBinding`),
|
|
951
|
+
// qualified-path receivers and `macroScopeUnknown`; block `use` imports carry
|
|
952
|
+
// their block lines and `use` items their visibility; files list their
|
|
953
|
+
// item-position macro invocations (`itemMacros`).
|
|
954
|
+
// v311 (fix #423): JS/TS module resolution follows a directory's package.json
|
|
955
|
+
// `main` and maps workspace subpath exports naming absent build output to
|
|
956
|
+
// their `src/` sources (persisted `moduleResolved` and import edges).
|
|
957
|
+
// v312 (fix #424): Python usage records mark module-path segments
|
|
958
|
+
// (`modulePath`) and the source-side name of an aliased from-import
|
|
959
|
+
// (`importSource`).
|
|
960
|
+
// v313 (fix #425): Go call records read method expressions `(*T).M` like
|
|
961
|
+
// `T.M`, mark receivers no local binds (`receiverPackageLevel`), record
|
|
962
|
+
// conversion operands, instantiated values and positional literal-element
|
|
963
|
+
// selectors as values, and type type-switch bindings per clause.
|
|
964
|
+
// v314 (fix #426): C# `out var` / implicitly typed pattern locals record no
|
|
965
|
+
// receiver type (was the written `var`).
|
|
966
|
+
// v315 (fix #427): C/C++ call records mark `field->m()` receivers
|
|
967
|
+
// (`receiverFieldArrow`), the implicit object's constness
|
|
968
|
+
// (`receiverConst`) and `(N)(x)` cast-or-call shapes
|
|
969
|
+
// (`parenthesizedCallee`); member functions record `memberConst` /
|
|
970
|
+
// `refQualifier`; a definition after a `static` declaration is `static`;
|
|
971
|
+
// out-of-class conversion operators, pointer-to-member parameters,
|
|
972
|
+
// `decltype(x)` locals and macro-wrapped C bodies are read; deduction
|
|
973
|
+
// guides are no functions.
|
|
974
|
+
// v316 (fix #428): C# call records mark implicitly typed lambda arguments
|
|
975
|
+
// (`lambda:N:i`); Java files whose only parse errors are type annotations
|
|
976
|
+
// before a variable arity `...` are read from the recovered parse (the
|
|
977
|
+
// parameter is kept); reflection sites keyed by a parameter of the enclosing
|
|
978
|
+
// callable record it (`keyParameter`).
|
|
979
|
+
// v317 (fix #430): Python instance attributes (`self.x = v`), every name of
|
|
980
|
+
// a Go multi-name field, Java enum fields, C/C++ members of unnamed nested
|
|
981
|
+
// structs and unions (`memberPaths`) and C++ variable templates are indexed;
|
|
982
|
+
// C# receivers drop the null-forgiving `!`; Java resources take their
|
|
983
|
+
// declared type; C/C++ macro bodies read `#param` as a string literal.
|
|
984
|
+
// v318 (fix #431): JS/TS properties a class's methods assign on `this` are
|
|
985
|
+
// indexed as fields (`instanceAttribute`); value-reference records read
|
|
986
|
+
// destructuring assignment targets as writes, parameter defaults as reads
|
|
987
|
+
// and Python dict keys as values.
|
|
988
|
+
// v319 (fix #433): a saved symbol keeps `file` (and a default `bindingId`)
|
|
989
|
+
// as a 0 placeholder at its built position, so a loaded symbol has the key
|
|
990
|
+
// order of a fresh build.
|
|
991
|
+
// v320 (fix #434): call records of constructions through a method's own
|
|
992
|
+
// receiver (`cls(..)`, `type(self)(..)`, `self.__class__(..)`, `new
|
|
993
|
+
// this(..)`, `new this.constructor(..)`) carry `ownClass`.
|
|
994
|
+
// v321 (fix #436): file entries carry `computedDispatchSites`, extracted
|
|
995
|
+
// from the literal parse at index time (the doctor read them by parsing every
|
|
996
|
+
// file on the first `repo` of a process).
|
|
997
|
+
// v322: C# constructions retain terminal names and arity; nested generic receivers retain their qualifier.
|
|
998
|
+
// v326: C++ files retain raw base-clause positions and their syntactic scopes.
|
|
999
|
+
// v327: Python explicit-argument super calls retain a producer, not a lexical parent receiver.
|
|
1000
|
+
// v328: ambiguous C++ direct initializers retain empty template-call records.
|
|
1001
|
+
const CACHE_FORMAT_VERSION = 330;
|
|
922
1002
|
const USAGE_CACHE_FILE = 'usage-results.json';
|
|
923
1003
|
|
|
1004
|
+
function usageResultKey(index, filePath, name, mode) {
|
|
1005
|
+
const hash = index.files.get(filePath)?.hash || '';
|
|
1006
|
+
return `${filePath}\0${hash}\0${name}${mode ? `\0${mode}` : ''}`;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// A syntax family's stored value weighs what its producer declares
|
|
1010
|
+
// (core/source-memo.js SYNTAX_FAMILIES); a usage list weighs its sites.
|
|
1011
|
+
function usageResultWeight(value, mode) {
|
|
1012
|
+
const kind = syntaxWeightKind(mode);
|
|
1013
|
+
if (kind === 'rows') return 128 + value.length * 128;
|
|
1014
|
+
return kind === 'json' ? 128 + 2 * Buffer.byteLength(JSON.stringify(value)) : 64 + value.length * 40;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/** Read an immutable, source-local analysis result from the shared LRU. */
|
|
1018
|
+
function getUsageResult(index, filePath, name, mode = '') {
|
|
1019
|
+
const key = usageResultKey(index, filePath, name, mode);
|
|
1020
|
+
const cached = index._usageResultCache?.get(key);
|
|
1021
|
+
if (!cached) return undefined;
|
|
1022
|
+
index._usageResultCache.delete(key);
|
|
1023
|
+
index._usageResultCache.set(key, cached);
|
|
1024
|
+
return cached.value;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/** Store file-local facts only; project-dependent resolutions do not belong here. */
|
|
1028
|
+
function storeUsageResult(index, filePath, name, mode, value) {
|
|
1029
|
+
if (!index._usageResultCache || !Array.isArray(value)) return;
|
|
1030
|
+
const key = usageResultKey(index, filePath, name, mode);
|
|
1031
|
+
const prior = index._usageResultCache.get(key);
|
|
1032
|
+
if (prior) index._usageResultCacheWeight -= prior.weight;
|
|
1033
|
+
const weight = usageResultWeight(value, mode);
|
|
1034
|
+
index._usageResultCache.delete(key);
|
|
1035
|
+
index._usageResultCache.set(key, { value, weight });
|
|
1036
|
+
index._usageResultCacheWeight += weight;
|
|
1037
|
+
index.usageCacheDirty = true;
|
|
1038
|
+
while (index._usageResultCache.size > 4096 || index._usageResultCacheWeight > 16 * 1024 * 1024) {
|
|
1039
|
+
const oldest = index._usageResultCache.entries().next().value;
|
|
1040
|
+
if (!oldest) break;
|
|
1041
|
+
index._usageResultCache.delete(oldest[0]);
|
|
1042
|
+
index._usageResultCacheWeight -= oldest[1].weight;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
924
1046
|
/**
|
|
925
1047
|
* Persist the small hot-name usage cache independently of index/call shards.
|
|
926
1048
|
* This keeps one-shot CLI repeats fast without rewriting the whole project
|
|
@@ -980,7 +1102,7 @@ function loadUsageCache(index, cacheFile) {
|
|
|
980
1102
|
if (!fileEntry || fileEntry.hash !== fileHash) continue;
|
|
981
1103
|
const suffix = mode ? `\0${mode}` : '';
|
|
982
1104
|
const key = `${filePath}\0${fileHash}\0${name}${suffix}`;
|
|
983
|
-
const weight =
|
|
1105
|
+
const weight = usageResultWeight(value, mode);
|
|
984
1106
|
restored.set(key, { value, weight });
|
|
985
1107
|
weightTotal += weight;
|
|
986
1108
|
while (restored.size > 4096 || weightTotal > 16 * 1024 * 1024) {
|
|
@@ -1051,14 +1173,18 @@ function saveCache(index, cachePath) {
|
|
|
1051
1173
|
const strippedSymbols = [];
|
|
1052
1174
|
for (const [name, defs] of index.symbols) {
|
|
1053
1175
|
const stripped = defs.map(s => {
|
|
1054
|
-
const { file, bindingId, ...rest } = s;
|
|
1055
1176
|
const defaultBindingId = s.relativePath && s.type && s.startLine
|
|
1056
1177
|
? `${s.relativePath}:${s.type}:${s.startLine}`
|
|
1057
1178
|
: null;
|
|
1058
|
-
|
|
1059
|
-
|
|
1179
|
+
// `file` and a default `bindingId` are rebuilt on load; their 0
|
|
1180
|
+
// placeholders keep the key order of the built symbol (fix #433).
|
|
1181
|
+
const out = {};
|
|
1182
|
+
for (const key in s) {
|
|
1183
|
+
if (key === 'file') out.file = 0;
|
|
1184
|
+
else if (key === 'bindingId') out.bindingId = s.bindingId && s.bindingId !== defaultBindingId ? s.bindingId : 0;
|
|
1185
|
+
else out[key] = s[key];
|
|
1060
1186
|
}
|
|
1061
|
-
return
|
|
1187
|
+
return out;
|
|
1062
1188
|
});
|
|
1063
1189
|
strippedSymbols.push([name, stripped]);
|
|
1064
1190
|
}
|
|
@@ -1121,6 +1247,7 @@ function saveCache(index, cachePath) {
|
|
|
1121
1247
|
// argKinds for overload discipline (stale shapes would silently
|
|
1122
1248
|
// disable arity pruning)
|
|
1123
1249
|
version: CACHE_FORMAT_VERSION,
|
|
1250
|
+
callsGeneration: crypto.randomBytes(16).toString('hex'),
|
|
1124
1251
|
ucnVersion: UCN_VERSION, // Invalidate cache when UCN is updated
|
|
1125
1252
|
configHash,
|
|
1126
1253
|
discoveryHash,
|
|
@@ -1183,6 +1310,7 @@ function saveCache(index, cachePath) {
|
|
|
1183
1310
|
|
|
1184
1311
|
// PERF-3: atomic write — tmp file + rename so concurrent readers/writers
|
|
1185
1312
|
// never see a torn JSON. The calls/ shard write below already does this.
|
|
1313
|
+
index._callsGenerationKey = cacheData.callsGeneration;
|
|
1186
1314
|
const tmpFile = cacheFile + '.tmp';
|
|
1187
1315
|
fs.writeFileSync(tmpFile, JSON.stringify(cacheData));
|
|
1188
1316
|
fs.renameSync(tmpFile, cacheFile);
|
|
@@ -1247,6 +1375,7 @@ function saveCache(index, cachePath) {
|
|
|
1247
1375
|
fs.rmSync(callsDir, { recursive: true, force: true });
|
|
1248
1376
|
}
|
|
1249
1377
|
fs.renameSync(callsTmpDir, callsDir);
|
|
1378
|
+
require('./call-name-cache').save(index, callsDir);
|
|
1250
1379
|
|
|
1251
1380
|
// Clean up legacy monolithic file
|
|
1252
1381
|
const legacyFile = path.join(cacheDir, 'calls-cache.json');
|
|
@@ -1332,6 +1461,8 @@ function loadCache(index, cachePath) {
|
|
|
1332
1461
|
index._rustMacroState = null;
|
|
1333
1462
|
index._cppScope = null;
|
|
1334
1463
|
index._contractIndex = null;
|
|
1464
|
+
index._implicitConversionOwners = null;
|
|
1465
|
+
index._constructionAliases = null;
|
|
1335
1466
|
index._typeDenotation = null;
|
|
1336
1467
|
index._languageFeatureMemo = null; // fix #380 protocol-type feature memo
|
|
1337
1468
|
|
|
@@ -1398,6 +1529,11 @@ function loadCache(index, cachePath) {
|
|
|
1398
1529
|
// extendsGraph/extendedByGraph are derived below from the rehydrated
|
|
1399
1530
|
// symbols (fix #342) — never read from the payload.
|
|
1400
1531
|
|
|
1532
|
+
index._callsGenerationKey = typeof cacheData.callsGeneration === 'string'
|
|
1533
|
+
? cacheData.callsGeneration : null;
|
|
1534
|
+
index._callNameCache = null;
|
|
1535
|
+
index._callNameCacheReadGeneration = null;
|
|
1536
|
+
|
|
1401
1537
|
// Prepare lazy calls cache loading — load manifest but defer shard parsing.
|
|
1402
1538
|
// Shards are loaded on first getCachedCalls access via ensureCallsCacheLoaded().
|
|
1403
1539
|
if (index.callsCache.size === 0) {
|
|
@@ -1686,6 +1822,7 @@ function _prepareCallsCache(index, cacheFile) {
|
|
|
1686
1822
|
});
|
|
1687
1823
|
}
|
|
1688
1824
|
index._callsCachePrepared = true;
|
|
1825
|
+
index._callNameCacheDir = path.join(cacheDir, 'calls');
|
|
1689
1826
|
return;
|
|
1690
1827
|
} catch (e) {
|
|
1691
1828
|
// Corrupted manifest — fall through
|
|
@@ -2107,6 +2244,7 @@ function buildWithLock(index, buildOpts = {}, options = {}) {
|
|
|
2107
2244
|
module.exports = {
|
|
2108
2245
|
buildWithLock,
|
|
2109
2246
|
saveCache, saveUsageCache, loadCache, loadCallsCache, isCacheStale, ensureCallsCacheLoaded,
|
|
2247
|
+
getUsageResult, storeUsageResult,
|
|
2110
2248
|
ensureCallsShardForFile,
|
|
2111
2249
|
getUserCacheRoot, getProjectCacheDir, getProjectCachePath,
|
|
2112
2250
|
getLegacyProjectCacheDir, migrateLegacyProjectCache, clearProjectCache,
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { calleeNames, codeUnitCompare } = require('./shared');
|
|
6
|
+
const { fileMtime } = require('./source-memo');
|
|
7
|
+
const { version } = require('../package.json');
|
|
8
|
+
|
|
9
|
+
// Call-record names only. Binding, dispatch, aliases and type queries still
|
|
10
|
+
// run through their ordinary record consumers.
|
|
11
|
+
function snapshot(index, files, captured) {
|
|
12
|
+
return { files, captured, filesMap: index.files, graph: index.importGraph,
|
|
13
|
+
config: JSON.stringify(index.config), generation: index._callsGenerationKey };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function current(index, cache) {
|
|
17
|
+
return !!cache && cache.filesMap === index.files && cache.graph === index.importGraph &&
|
|
18
|
+
cache.config === JSON.stringify(index.config) && cache.files.size === index.files.size &&
|
|
19
|
+
[...cache.files].every(([file, item]) => index.files.get(file) === item.entry &&
|
|
20
|
+
item.entry.hash === item.hash);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ensure(index) {
|
|
24
|
+
if (index.isProvenanceFactIndex || !index._callsGenerationKey ||
|
|
25
|
+
index._callNameCacheReadGeneration === index._callsGenerationKey) return;
|
|
26
|
+
index._callNameCacheReadGeneration = index._callsGenerationKey;
|
|
27
|
+
if (index._callNameCacheDir) load(index, index._callNameCacheDir);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function capture(index, collected, file, calls) {
|
|
31
|
+
if (index.isProvenanceFactIndex) return;
|
|
32
|
+
const entry = index.files.get(file), raw = index.callsCache.get(file);
|
|
33
|
+
if (!entry?.hash || raw?.hash !== entry.hash || !Array.isArray(calls) ||
|
|
34
|
+
!Number.isFinite(raw.mtime)) return;
|
|
35
|
+
const names = calleeNames(calls);
|
|
36
|
+
if ([...names].some(name => typeof name !== 'string')) return;
|
|
37
|
+
collected.set(file, { entry, hash: entry.hash, raw, rawCalls: raw.calls, names, mtime: raw.mtime });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function remember(index, files) {
|
|
41
|
+
if (index.isProvenanceFactIndex || files.size !== index.files.size) return;
|
|
42
|
+
index._callNameCache = snapshot(index, files, true);
|
|
43
|
+
if (index._callsGenerationKey && !index.callsCacheDirty && !index.macroExpansionDirty && index._callsCacheDir) save(index, path.join(index._callsCacheDir, 'calls'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function save(index, directory) {
|
|
47
|
+
if (index.isProvenanceFactIndex) return;
|
|
48
|
+
const cache = index._callNameCache;
|
|
49
|
+
if (!cache?.captured || !index._callsGenerationKey ||
|
|
50
|
+
!current(index, cache) || !fs.existsSync(directory)) return;
|
|
51
|
+
for (const [file, item] of cache.files) {
|
|
52
|
+
if (index.files.get(file) !== item.entry ||
|
|
53
|
+
index.callsCache.get(file) !== item.raw || item.raw.calls !== item.rawCalls ||
|
|
54
|
+
item.raw.mtime !== item.mtime) return;
|
|
55
|
+
}
|
|
56
|
+
const dictionary = [...new Set([...cache.files.values()].flatMap(item =>
|
|
57
|
+
[...item.names]))].sort(codeUnitCompare);
|
|
58
|
+
const ids = new Map(dictionary.map((name, id) => [name, id]));
|
|
59
|
+
const files = [...cache.files].map(([file, item]) => [
|
|
60
|
+
path.relative(index.root, file), item.mtime,
|
|
61
|
+
[...item.names].map(name => ids.get(name)),
|
|
62
|
+
]).sort((a, b) => codeUnitCompare(a[0], b[0]));
|
|
63
|
+
const target = path.join(directory, 'names.json');
|
|
64
|
+
try {
|
|
65
|
+
fs.writeFileSync(target + '.tmp', JSON.stringify({
|
|
66
|
+
version, format: 2, generation: index._callsGenerationKey, dictionary, files,
|
|
67
|
+
}));
|
|
68
|
+
fs.renameSync(target + '.tmp', target);
|
|
69
|
+
} catch (_) { /* A missing optimization falls back to the record shards. */ }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function load(index, directory) {
|
|
73
|
+
if (index.isProvenanceFactIndex) return;
|
|
74
|
+
index._callNameCache = null;
|
|
75
|
+
try {
|
|
76
|
+
const data = JSON.parse(fs.readFileSync(path.join(directory, 'names.json'), 'utf8'));
|
|
77
|
+
if (data.version !== version || data.format !== 2 ||
|
|
78
|
+
data.generation !== index._callsGenerationKey ||
|
|
79
|
+
!Array.isArray(data.dictionary) || !Array.isArray(data.files) ||
|
|
80
|
+
data.files.length !== index.files.size ||
|
|
81
|
+
data.dictionary.some(name => typeof name !== 'string') ||
|
|
82
|
+
new Set(data.dictionary).size !== data.dictionary.length) return;
|
|
83
|
+
const files = new Map();
|
|
84
|
+
for (const row of data.files) {
|
|
85
|
+
if (!Array.isArray(row) || row.length !== 3 || typeof row[0] !== 'string' ||
|
|
86
|
+
!Number.isFinite(row[1]) || !Array.isArray(row[2])) return;
|
|
87
|
+
const file = path.resolve(index.root, row[0]), entry = index.files.get(file);
|
|
88
|
+
if (!entry || files.has(file) || row[2].some(id =>
|
|
89
|
+
!Number.isInteger(id) || id < 0 || id >= data.dictionary.length) ||
|
|
90
|
+
new Set(row[2]).size !== row[2].length) return;
|
|
91
|
+
files.set(file, {
|
|
92
|
+
entry, hash: entry.hash, mtime: row[1], names: new Set(row[2].map(id => data.dictionary[id])),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
index._callNameCache = snapshot(index, files, false);
|
|
96
|
+
} catch (_) { /* Missing or invalid names: use the record shards. */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function restore(index) {
|
|
100
|
+
if (index.isProvenanceFactIndex) return false;
|
|
101
|
+
ensure(index);
|
|
102
|
+
const cache = index._callNameCache;
|
|
103
|
+
// A fresh in-memory parse wins over persisted names, as it does over
|
|
104
|
+
// persisted call records. A rebuild discards this cache explicitly.
|
|
105
|
+
if (index.callsCache.size || !current(index, cache) ||
|
|
106
|
+
cache.generation !== index._callsGenerationKey) return false;
|
|
107
|
+
for (const [file, item] of cache.files) {
|
|
108
|
+
if (index.files.get(file) !== item.entry) return false;
|
|
109
|
+
}
|
|
110
|
+
const byName = new Map();
|
|
111
|
+
for (const [file] of index.files) {
|
|
112
|
+
for (const name of cache.files.get(file).names) {
|
|
113
|
+
let files = byName.get(name);
|
|
114
|
+
if (!files) byName.set(name, files = new Set());
|
|
115
|
+
files.add(file);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
index.calleeIndex = byName;
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function absent(index, file, name) {
|
|
123
|
+
if (index.isProvenanceFactIndex || index.callsCache.has(file)) return false;
|
|
124
|
+
ensure(index);
|
|
125
|
+
const item = index._callNameCache?.files.get(file);
|
|
126
|
+
if (!item || index.files.get(file) !== item.entry || item.entry.hash !== item.hash ||
|
|
127
|
+
item.entry.rustMacroExpansion || item.names.has(name)) return false;
|
|
128
|
+
// Match the raw record cache's mtime rule. Changed source must reach
|
|
129
|
+
// its normal read/hash/reparse path even if its previous names lacked
|
|
130
|
+
// this query. No negative binding or ownership verdict is cached.
|
|
131
|
+
try { return fileMtime(index, file) === item.mtime; } catch (_) { return false; }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { capture, remember, save, load, restore, absent };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** Whether the written call lies inside the declaration's AST extent. */
|
|
4
|
+
function containsCall(definition, call) {
|
|
5
|
+
const span = definition?.sourceSpan;
|
|
6
|
+
if (!span || call?.macroExpansion || definition.macroExpansion || definition.generatedByMacro) return null;
|
|
7
|
+
const line = call.line, column = call.column ?? call.callSite?.column;
|
|
8
|
+
if (!Number.isInteger(line) || !Number.isInteger(span.startLine) || !Number.isInteger(span.endLine)) return null;
|
|
9
|
+
if (line < span.startLine || line > span.endLine) return false;
|
|
10
|
+
if (Number.isInteger(column)) {
|
|
11
|
+
return (line > span.startLine || column >= span.startColumn) &&
|
|
12
|
+
(line < span.endLine || column < span.endColumn);
|
|
13
|
+
}
|
|
14
|
+
const start = call.callStart ?? call.callSite?.start;
|
|
15
|
+
if (Number.isInteger(start) && Number.isInteger(span.start) && Number.isInteger(span.end)) {
|
|
16
|
+
return start >= span.start && start < span.end;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { containsCall };
|