gitnexus 1.6.13-rc.7 → 1.6.13-rc.8
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/README.md +1 -1
- package/dist/core/ingestion/languages/rust/cargo-module-files.d.ts +9 -0
- package/dist/core/ingestion/languages/rust/cargo-module-files.js +319 -0
- package/dist/core/ingestion/languages/rust/cargo-targets.d.ts +21 -0
- package/dist/core/ingestion/languages/rust/cargo-targets.js +473 -0
- package/dist/core/ingestion/languages/rust/import-decomposer.js +10 -5
- package/dist/core/ingestion/languages/rust/name-fallback-visibility.d.ts +2 -0
- package/dist/core/ingestion/languages/rust/name-fallback-visibility.js +152 -13
- package/dist/core/ingestion/languages/rust/scope-resolver.js +2 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +2 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +1 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +1 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +1 -0
- package/dist/storage/parse-cache.js +7 -1
- package/package.json +2 -1
- package/scripts/cross-platform-tests.ts +3 -0
package/README.md
CHANGED
|
@@ -431,7 +431,7 @@ TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift,
|
|
|
431
431
|
| Kotlin | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
|
432
432
|
| C# | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
433
433
|
| Go | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
434
|
-
| Rust | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
434
|
+
| Rust | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
435
435
|
| PHP | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
436
436
|
| Ruby | ✓ | — | ✓ | ✓ | — | ✓ | — | ✓ | ✓ |
|
|
437
437
|
| Swift | — | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
/** Public use evidence from the same AST used for membership. Keeps restricted
|
|
3
|
+
* and private globs distinct without changing the shared import/cache shape. */
|
|
4
|
+
export declare function rustPublicUses(root: Parser.SyntaxNode): ReadonlySet<string>;
|
|
5
|
+
/** External modules reachable from one source file; undefined is incomplete. */
|
|
6
|
+
export declare function rustModuleFiles(root: Parser.SyntaxNode, file: string, ownsDirectory: boolean, files: ReadonlySet<string>, missingFiles?: Set<string>, isCrateRoot?: boolean): readonly {
|
|
7
|
+
file: string;
|
|
8
|
+
ownsDirectory: boolean;
|
|
9
|
+
}[] | undefined;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { splitRustUseDeclaration } from './import-decomposer.js';
|
|
3
|
+
// Built-in attributes cannot expand to new module declarations. cfg is a union:
|
|
4
|
+
// visiting both alternatives is conservative; cfg_attr may change a path.
|
|
5
|
+
const NON_EXPANDING_ATTRIBUTES = new Set([
|
|
6
|
+
'cfg',
|
|
7
|
+
'path',
|
|
8
|
+
'allow',
|
|
9
|
+
'warn',
|
|
10
|
+
'deny',
|
|
11
|
+
'forbid',
|
|
12
|
+
'expect',
|
|
13
|
+
'doc',
|
|
14
|
+
'test',
|
|
15
|
+
'should_panic',
|
|
16
|
+
'ignore',
|
|
17
|
+
'automatically_derived',
|
|
18
|
+
'proc_macro',
|
|
19
|
+
'proc_macro_derive',
|
|
20
|
+
'proc_macro_attribute',
|
|
21
|
+
'inline',
|
|
22
|
+
'cold',
|
|
23
|
+
'no_mangle',
|
|
24
|
+
'export_name',
|
|
25
|
+
'repr',
|
|
26
|
+
'non_exhaustive',
|
|
27
|
+
'must_use',
|
|
28
|
+
'deprecated',
|
|
29
|
+
'no_std',
|
|
30
|
+
'no_main',
|
|
31
|
+
'feature',
|
|
32
|
+
'crate_type',
|
|
33
|
+
'crate_name',
|
|
34
|
+
'recursion_limit',
|
|
35
|
+
'type_length_limit',
|
|
36
|
+
]);
|
|
37
|
+
const BUILTIN_DERIVES = new Set([
|
|
38
|
+
'Clone',
|
|
39
|
+
'Copy',
|
|
40
|
+
'Debug',
|
|
41
|
+
'Default',
|
|
42
|
+
'Eq',
|
|
43
|
+
'Hash',
|
|
44
|
+
'Ord',
|
|
45
|
+
'PartialEq',
|
|
46
|
+
'PartialOrd',
|
|
47
|
+
]);
|
|
48
|
+
const EXPRESSION_MACROS = new Set([
|
|
49
|
+
'print',
|
|
50
|
+
'println',
|
|
51
|
+
'eprint',
|
|
52
|
+
'eprintln',
|
|
53
|
+
'assert',
|
|
54
|
+
'assert_eq',
|
|
55
|
+
'assert_ne',
|
|
56
|
+
'debug_assert',
|
|
57
|
+
'debug_assert_eq',
|
|
58
|
+
'debug_assert_ne',
|
|
59
|
+
'vec',
|
|
60
|
+
'format',
|
|
61
|
+
'format_args',
|
|
62
|
+
'write',
|
|
63
|
+
'writeln',
|
|
64
|
+
'panic',
|
|
65
|
+
'todo',
|
|
66
|
+
'unimplemented',
|
|
67
|
+
'unreachable',
|
|
68
|
+
'dbg',
|
|
69
|
+
'matches',
|
|
70
|
+
]);
|
|
71
|
+
/** Token arguments can contain blocks/modules or further macro expansion.
|
|
72
|
+
* Inspect token nodes, never strings/comments that merely mention those words. */
|
|
73
|
+
function hasExpandingArguments(tokens) {
|
|
74
|
+
for (let i = 0; i < tokens.childCount; i++) {
|
|
75
|
+
const child = tokens.child(i);
|
|
76
|
+
if (child.type === 'mod' || child.type === '#')
|
|
77
|
+
return true;
|
|
78
|
+
if (child.type === '!' && tokens.child(i - 1)?.type === 'identifier')
|
|
79
|
+
return true;
|
|
80
|
+
if (child.type === 'token_tree' && hasExpandingArguments(child))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/** Public use evidence from the same AST used for membership. Keeps restricted
|
|
86
|
+
* and private globs distinct without changing the shared import/cache shape. */
|
|
87
|
+
export function rustPublicUses(root) {
|
|
88
|
+
const uses = new Set();
|
|
89
|
+
const pending = [{ node: root, module: '' }];
|
|
90
|
+
while (pending.length > 0) {
|
|
91
|
+
const { node, module } = pending.pop();
|
|
92
|
+
for (const child of node.namedChildren) {
|
|
93
|
+
if (child.type === 'mod_item') {
|
|
94
|
+
const body = child.childForFieldName('body');
|
|
95
|
+
const name = child.childForFieldName('name')?.text;
|
|
96
|
+
if (body && name)
|
|
97
|
+
pending.push({ node: body, module: [module, name].filter(Boolean).join('::') });
|
|
98
|
+
}
|
|
99
|
+
else if (child.type === 'use_declaration' &&
|
|
100
|
+
child.namedChildren.some((part) => part.type === 'visibility_modifier' && part.text === 'pub')) {
|
|
101
|
+
for (const capture of splitRustUseDeclaration(child)) {
|
|
102
|
+
uses.add(JSON.stringify([
|
|
103
|
+
module,
|
|
104
|
+
capture['@import.source']?.text,
|
|
105
|
+
capture['@import.kind']?.text,
|
|
106
|
+
capture['@import.name']?.text,
|
|
107
|
+
]));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return uses;
|
|
113
|
+
}
|
|
114
|
+
/** Decode a literal path without mistaking strings/comments for Rust syntax. */
|
|
115
|
+
function literalPath(text) {
|
|
116
|
+
const raw = /^r(#+)?"([\s\S]*)"\1$/.exec(text);
|
|
117
|
+
if (raw)
|
|
118
|
+
return raw[2];
|
|
119
|
+
// Escape forms beyond JSON's subset remain unknown, never a guessed path.
|
|
120
|
+
try {
|
|
121
|
+
const value = JSON.parse(text);
|
|
122
|
+
return typeof value === 'string' ? value : undefined;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** External modules reachable from one source file; undefined is incomplete. */
|
|
129
|
+
export function rustModuleFiles(root, file, ownsDirectory, files, missingFiles, isCrateRoot = false) {
|
|
130
|
+
if (root.hasError)
|
|
131
|
+
return undefined;
|
|
132
|
+
// Built-in spellings are not proof when an import/local macro can shadow
|
|
133
|
+
// them. Cross-file macro_use/macro_export remain unknown attributes below.
|
|
134
|
+
const shadowed = new Set();
|
|
135
|
+
const globs = [];
|
|
136
|
+
const scan = [root];
|
|
137
|
+
while (scan.length) {
|
|
138
|
+
const node = scan.pop();
|
|
139
|
+
if (node.type === 'macro_definition' || node.type === 'mod_item') {
|
|
140
|
+
const name = node.childForFieldName('name')?.text;
|
|
141
|
+
if (name)
|
|
142
|
+
shadowed.add(name);
|
|
143
|
+
// macro_rules textual scope can extend into child module files. Without
|
|
144
|
+
// expansion/scope receipts, do not assume their same-named calls are std.
|
|
145
|
+
if (node.type === 'macro_definition' && name && EXPRESSION_MACROS.has(name))
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
if (node.type === 'use_declaration') {
|
|
149
|
+
for (const capture of splitRustUseDeclaration(node)) {
|
|
150
|
+
if (capture['@import.kind']?.text === 'wildcard')
|
|
151
|
+
globs.push({ node, path: capture['@import.source']?.text ?? '' });
|
|
152
|
+
const name = capture['@import.name']?.text;
|
|
153
|
+
if (name)
|
|
154
|
+
shadowed.add(name);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (node.type !== 'macro_definition' && node.type !== 'token_tree')
|
|
158
|
+
scan.push(...node.namedChildren);
|
|
159
|
+
}
|
|
160
|
+
const wildcard = globs.some(({ node, path: imported }) => {
|
|
161
|
+
const parts = imported.split('::').filter(Boolean);
|
|
162
|
+
// Cargo aliases for these names are rejected by the loader. A local
|
|
163
|
+
// module/import can still shadow a standard-library path in this file.
|
|
164
|
+
if (['std', 'core', 'alloc'].includes(parts[0] ?? '') && !shadowed.has(parts[0]))
|
|
165
|
+
return false;
|
|
166
|
+
let depth = 0;
|
|
167
|
+
let localModuleScope = true;
|
|
168
|
+
for (let parent = node.parent; parent && parent !== root; parent = parent.parent) {
|
|
169
|
+
if (parent.type === 'mod_item')
|
|
170
|
+
depth++;
|
|
171
|
+
if (parent.type === 'function_item' ||
|
|
172
|
+
parent.type === 'block' ||
|
|
173
|
+
parent.type === 'closure_expression')
|
|
174
|
+
localModuleScope = false;
|
|
175
|
+
}
|
|
176
|
+
// The common inline unit-test `use super::*` stays within this AST.
|
|
177
|
+
// A file-level super glob has an external parent and remains unknown.
|
|
178
|
+
if (localModuleScope &&
|
|
179
|
+
parts.length > 0 &&
|
|
180
|
+
parts.every((part) => part === 'super') &&
|
|
181
|
+
parts.length <= depth)
|
|
182
|
+
return false;
|
|
183
|
+
// At a Cargo root, bare/self/crate paths to inline modules have the same
|
|
184
|
+
// meaning across editions. Do not extend this assumption to file modules.
|
|
185
|
+
if (isCrateRoot && node.parent === root) {
|
|
186
|
+
if (parts[0] === 'self' || parts[0] === 'crate')
|
|
187
|
+
parts.shift();
|
|
188
|
+
let body = root;
|
|
189
|
+
for (const part of parts) {
|
|
190
|
+
body =
|
|
191
|
+
body?.namedChildren
|
|
192
|
+
.find((child) => child.type === 'mod_item' && child.childForFieldName('name')?.text === part)
|
|
193
|
+
?.childForFieldName('body') ?? undefined;
|
|
194
|
+
}
|
|
195
|
+
if (parts.length > 0 && body !== undefined)
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
});
|
|
200
|
+
const builtin = (name) => !wildcard && !shadowed.has(name);
|
|
201
|
+
const fileDir = path.posix.dirname(file);
|
|
202
|
+
const moduleDir = ownsDirectory || path.posix.basename(file) === 'mod.rs'
|
|
203
|
+
? fileDir
|
|
204
|
+
: file.slice(0, -'.rs'.length);
|
|
205
|
+
const pending = [{ node: root, moduleDir, attributeDir: fileDir }];
|
|
206
|
+
const result = [];
|
|
207
|
+
let unresolved = false;
|
|
208
|
+
while (pending.length) {
|
|
209
|
+
const context = pending.pop();
|
|
210
|
+
let attributes = [];
|
|
211
|
+
for (const node of context.node.namedChildren) {
|
|
212
|
+
if (node.type === 'line_comment' || node.type === 'block_comment')
|
|
213
|
+
continue;
|
|
214
|
+
if (node.type === 'attribute_item' || node.type === 'inner_attribute_item') {
|
|
215
|
+
const attribute = node.namedChildren[0];
|
|
216
|
+
const name = attribute?.namedChildren[0]?.text;
|
|
217
|
+
if (name === 'derive') {
|
|
218
|
+
const argumentsNode = attribute?.childForFieldName('arguments');
|
|
219
|
+
if (!argumentsNode ||
|
|
220
|
+
!builtin('derive') ||
|
|
221
|
+
argumentsNode.namedChildren.length === 0 ||
|
|
222
|
+
argumentsNode.namedChildren.some((item) => item.type !== 'identifier' ||
|
|
223
|
+
!BUILTIN_DERIVES.has(item.text) ||
|
|
224
|
+
!builtin(item.text)) ||
|
|
225
|
+
argumentsNode.children.some((item) => !['(', ')', ',', 'identifier', 'line_comment', 'block_comment'].includes(item.type)))
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
else if (!name || !NON_EXPANDING_ATTRIBUTES.has(name))
|
|
229
|
+
return undefined;
|
|
230
|
+
if (node.type === 'attribute_item')
|
|
231
|
+
attributes.push(node);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const attrs = attributes;
|
|
235
|
+
attributes = [];
|
|
236
|
+
// The current scope captures do not carry extern-crate aliases. Do not
|
|
237
|
+
// certify a negative import-root proof from an incomplete namespace view.
|
|
238
|
+
if (node.type === 'extern_crate_declaration' && node.childForFieldName('alias') !== null) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
const macro = node.type === 'macro_invocation'
|
|
242
|
+
? node
|
|
243
|
+
: node.type === 'expression_statement' &&
|
|
244
|
+
node.namedChildren[0]?.type === 'macro_invocation'
|
|
245
|
+
? node.namedChildren[0]
|
|
246
|
+
: undefined;
|
|
247
|
+
if (macro) {
|
|
248
|
+
const name = macro.childForFieldName('macro')?.text;
|
|
249
|
+
const tokens = macro.namedChildren.find((child) => child.type === 'token_tree');
|
|
250
|
+
const parts = name?.split('::').filter(Boolean) ?? [];
|
|
251
|
+
const standard = parts.length === 1 ||
|
|
252
|
+
(parts.length === 2 && ['std', 'core', 'alloc'].includes(parts[0]));
|
|
253
|
+
if (!standard ||
|
|
254
|
+
!EXPRESSION_MACROS.has(parts.at(-1) ?? '') ||
|
|
255
|
+
!parts.every(builtin) ||
|
|
256
|
+
!tokens ||
|
|
257
|
+
hasExpandingArguments(tokens) ||
|
|
258
|
+
context.node.type === 'source_file' ||
|
|
259
|
+
context.node.type === 'declaration_list')
|
|
260
|
+
return undefined;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (node.type !== 'mod_item') {
|
|
264
|
+
// Items (including external #[path] modules) can also occur in blocks.
|
|
265
|
+
// Inspect them too; a macro expansion there can add shared membership.
|
|
266
|
+
// Macro definitions/token trees and literal contents are not expansions.
|
|
267
|
+
if (node.type !== 'macro_definition' &&
|
|
268
|
+
node.type !== 'token_tree' &&
|
|
269
|
+
node.namedChildCount > 0) {
|
|
270
|
+
pending.push({ ...context, node });
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const name = node.childForFieldName('name')?.text;
|
|
275
|
+
if (!name)
|
|
276
|
+
return undefined;
|
|
277
|
+
let override;
|
|
278
|
+
for (const attr of attrs) {
|
|
279
|
+
const attribute = attr.namedChildren[0];
|
|
280
|
+
if (attribute.namedChildren[0]?.text !== 'path')
|
|
281
|
+
continue;
|
|
282
|
+
const value = attribute.childForFieldName('value');
|
|
283
|
+
if (!value || override !== undefined)
|
|
284
|
+
return undefined;
|
|
285
|
+
override = literalPath(value.text);
|
|
286
|
+
if (override === undefined || path.posix.isAbsolute(override) || override.includes('\\'))
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
const body = node.childForFieldName('body');
|
|
290
|
+
if (body) {
|
|
291
|
+
const dir = override === undefined
|
|
292
|
+
? path.posix.join(context.moduleDir, name)
|
|
293
|
+
: path.posix.join(context.attributeDir, override);
|
|
294
|
+
pending.push({ node: body, moduleDir: dir, attributeDir: dir });
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const candidates = override !== undefined
|
|
298
|
+
? [path.posix.normalize(path.posix.join(context.attributeDir, override))]
|
|
299
|
+
: [
|
|
300
|
+
path.posix.join(context.moduleDir, `${name}.rs`),
|
|
301
|
+
path.posix.join(context.moduleDir, name, 'mod.rs'),
|
|
302
|
+
];
|
|
303
|
+
const existing = candidates.filter((candidate) => files.has(candidate));
|
|
304
|
+
if (existing.length === 0) {
|
|
305
|
+
if (!missingFiles)
|
|
306
|
+
return undefined;
|
|
307
|
+
for (const candidate of candidates)
|
|
308
|
+
missingFiles.add(candidate);
|
|
309
|
+
unresolved = true;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
// Union conditional alternatives; shared membership must never be erased.
|
|
313
|
+
// #[path] makes the loaded file own its containing directory, just like
|
|
314
|
+
// a crate root; its children do NOT acquire the file stem as a prefix.
|
|
315
|
+
result.push(...existing.map((file) => ({ file, ownsDirectory: override !== undefined })));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return unresolved ? undefined : result;
|
|
319
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** A target is identified by its entry file, not its package directory. */
|
|
2
|
+
export declare function cargoTargetRoots(manifest: string, content: string, files: ReadonlySet<string>, workspaceEditions?: ReadonlyMap<string, string>): readonly string[] | undefined;
|
|
3
|
+
/** Exact public-use evidence, independent of the capture's coarse reexport kind. */
|
|
4
|
+
export declare function rustCargoPubliclyReexports(config: unknown, file: string, module: string, target: string, kind: string, name: string): boolean;
|
|
5
|
+
/** Positive evidence that this import names this library's ROOT, not a module
|
|
6
|
+
* elsewhere (or a binary/test entry point that cannot be imported as a lib). */
|
|
7
|
+
export declare function rustImportNamesCargoRoot(config: unknown, caller: string, candidate: string, importedModule: string): boolean;
|
|
8
|
+
/** Every known membership must identify this file as the entry point. A file
|
|
9
|
+
* shared as a module in another target does not have a single root role. */
|
|
10
|
+
export declare function rustIsExclusiveCargoRoot(config: unknown, file: string): boolean;
|
|
11
|
+
/** Establish crate identity before the existing module-path plausibility test. */
|
|
12
|
+
export declare function rustImportReachesCargoTarget(config: unknown, caller: string, candidate: string, importedModule: string): boolean;
|
|
13
|
+
/** Undefined means no complete membership proof; never interpret it as disjoint. */
|
|
14
|
+
export declare function rustFilesShareCargoTarget(config: unknown, caller: string, candidate: string): boolean | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Static, bounded, one-shot provider loader. No cargo/rustc, build scripts,
|
|
17
|
+
* repository wrappers, or network. Parse only files reachable from Cargo roots.
|
|
18
|
+
* Unknown expansion anywhere can add shared membership, so it invalidates the
|
|
19
|
+
* negative proof for this snapshot rather than producing a partial veto.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadRustCargoTargets(repoPath: string): Promise<unknown>;
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/** Cargo target evidence for the name-guess veto, never a directory heuristic. */
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { glob, escape } from 'glob';
|
|
5
|
+
import { parse } from 'smol-toml';
|
|
6
|
+
import Parser from 'tree-sitter';
|
|
7
|
+
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
8
|
+
import { getLanguageGrammar } from '../../../tree-sitter/parser-loader.js';
|
|
9
|
+
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
|
10
|
+
import { readRepoControlFile } from '../../../../config/repo-control-file.js';
|
|
11
|
+
import { rustModuleFiles, rustPublicUses } from './cargo-module-files.js';
|
|
12
|
+
const MAX_FILES = 100_000;
|
|
13
|
+
const table = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
14
|
+
/** A target is identified by its entry file, not its package directory. */
|
|
15
|
+
export function cargoTargetRoots(manifest, content, files, workspaceEditions) {
|
|
16
|
+
let data;
|
|
17
|
+
try {
|
|
18
|
+
data = parse(content);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (!table(data.package))
|
|
24
|
+
return table(data.workspace) ? [] : undefined;
|
|
25
|
+
const pkg = data.package;
|
|
26
|
+
if (typeof pkg.name !== 'string')
|
|
27
|
+
return undefined;
|
|
28
|
+
if (pkg.build !== undefined && typeof pkg.build !== 'string' && typeof pkg.build !== 'boolean')
|
|
29
|
+
return undefined;
|
|
30
|
+
const dir = path.posix.dirname(manifest);
|
|
31
|
+
let edition = pkg.edition ?? '2015';
|
|
32
|
+
if (table(edition) && edition.workspace === true) {
|
|
33
|
+
let workspace = typeof pkg.workspace === 'string' ? path.posix.join(dir, pkg.workspace, '.') : dir;
|
|
34
|
+
while (!workspaceEditions?.has(workspace) &&
|
|
35
|
+
typeof pkg.workspace !== 'string' &&
|
|
36
|
+
workspace !== '.') {
|
|
37
|
+
workspace = path.posix.dirname(workspace);
|
|
38
|
+
}
|
|
39
|
+
edition = workspaceEditions?.get(workspace);
|
|
40
|
+
}
|
|
41
|
+
if (typeof edition !== 'string' || !['2015', '2018', '2021', '2024'].includes(edition))
|
|
42
|
+
return undefined;
|
|
43
|
+
const relative = (file) => path.posix.normalize(path.posix.join(dir, file));
|
|
44
|
+
const roots = new Set();
|
|
45
|
+
for (const [kind, folder] of [
|
|
46
|
+
['lib', 'src'],
|
|
47
|
+
['bin', 'src/bin'],
|
|
48
|
+
['test', 'tests'],
|
|
49
|
+
['bench', 'benches'],
|
|
50
|
+
['example', 'examples'],
|
|
51
|
+
]) {
|
|
52
|
+
const autoKey = {
|
|
53
|
+
lib: 'autolib',
|
|
54
|
+
bin: 'autobins',
|
|
55
|
+
test: 'autotests',
|
|
56
|
+
bench: 'autobenches',
|
|
57
|
+
example: 'autoexamples',
|
|
58
|
+
}[kind];
|
|
59
|
+
if (pkg[autoKey] !== undefined && typeof pkg[autoKey] !== 'boolean')
|
|
60
|
+
return undefined;
|
|
61
|
+
const discovered = new Map();
|
|
62
|
+
if (kind === 'lib') {
|
|
63
|
+
if (files.has(relative('src/lib.rs')))
|
|
64
|
+
discovered.set(pkg.name, relative('src/lib.rs'));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (kind === 'bin' && files.has(relative('src/main.rs'))) {
|
|
68
|
+
discovered.set(pkg.name, relative('src/main.rs'));
|
|
69
|
+
}
|
|
70
|
+
const prefix = `${relative(folder)}/`;
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
if (!file.startsWith(prefix))
|
|
73
|
+
continue;
|
|
74
|
+
const tail = file.slice(prefix.length);
|
|
75
|
+
const match = /^([^/]+)\.rs$/.exec(tail) ?? /^([^/]+)\/main\.rs$/.exec(tail);
|
|
76
|
+
if (match)
|
|
77
|
+
discovered.set(match[1], file);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const explicit = data[kind] === undefined ? [] : kind === 'lib' ? [data[kind]] : data[kind];
|
|
81
|
+
if (!Array.isArray(explicit))
|
|
82
|
+
return undefined;
|
|
83
|
+
// Cargo 2015's opt-in discovery rule is PER target kind: an explicit
|
|
84
|
+
// binary does not disable integration tests, examples, benches or the lib.
|
|
85
|
+
const legacy = edition === '2015' && explicit.length > 0;
|
|
86
|
+
const overridden = new Set();
|
|
87
|
+
for (const entry of explicit) {
|
|
88
|
+
if (!table(entry))
|
|
89
|
+
return undefined;
|
|
90
|
+
const name = kind === 'lib' ? pkg.name : entry.name;
|
|
91
|
+
if (typeof name !== 'string')
|
|
92
|
+
return undefined;
|
|
93
|
+
if (entry.path !== undefined && typeof entry.path !== 'string')
|
|
94
|
+
return undefined;
|
|
95
|
+
if (typeof entry.path === 'string' && path.posix.isAbsolute(entry.path))
|
|
96
|
+
return undefined;
|
|
97
|
+
const file = typeof entry.path === 'string' ? relative(entry.path) : discovered.get(name);
|
|
98
|
+
if (!file || !files.has(file))
|
|
99
|
+
return undefined;
|
|
100
|
+
roots.add(file);
|
|
101
|
+
overridden.add(name);
|
|
102
|
+
}
|
|
103
|
+
if (pkg[autoKey] === true || (pkg[autoKey] !== false && !legacy)) {
|
|
104
|
+
for (const [name, file] of discovered)
|
|
105
|
+
if (!overridden.has(name))
|
|
106
|
+
roots.add(file);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Build scripts are crates too, even when located outside src/.
|
|
110
|
+
if (typeof pkg.build === 'string') {
|
|
111
|
+
const file = relative(pkg.build);
|
|
112
|
+
if (!files.has(file))
|
|
113
|
+
return undefined;
|
|
114
|
+
if (roots.has(file))
|
|
115
|
+
return undefined; // Multiple target roles need separate identities.
|
|
116
|
+
roots.add(file);
|
|
117
|
+
}
|
|
118
|
+
else if (pkg.build !== false && files.has(relative('build.rs'))) {
|
|
119
|
+
if (roots.has(relative('build.rs')))
|
|
120
|
+
return undefined;
|
|
121
|
+
roots.add(relative('build.rs'));
|
|
122
|
+
}
|
|
123
|
+
return [...roots];
|
|
124
|
+
}
|
|
125
|
+
class RustCargoTargets {
|
|
126
|
+
targetsByFile;
|
|
127
|
+
rootImports;
|
|
128
|
+
publicUsesByFile;
|
|
129
|
+
constructor(targetsByFile, rootImports, publicUsesByFile) {
|
|
130
|
+
this.targetsByFile = targetsByFile;
|
|
131
|
+
this.rootImports = rootImports;
|
|
132
|
+
this.publicUsesByFile = publicUsesByFile;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Exact public-use evidence, independent of the capture's coarse reexport kind. */
|
|
136
|
+
export function rustCargoPubliclyReexports(config, file, module, target, kind, name) {
|
|
137
|
+
return (config instanceof RustCargoTargets &&
|
|
138
|
+
config.publicUsesByFile.get(file)?.has(JSON.stringify([module, target, kind, name])) === true);
|
|
139
|
+
}
|
|
140
|
+
/** Positive evidence that this import names this library's ROOT, not a module
|
|
141
|
+
* elsewhere (or a binary/test entry point that cannot be imported as a lib). */
|
|
142
|
+
export function rustImportNamesCargoRoot(config, caller, candidate, importedModule) {
|
|
143
|
+
if (!(config instanceof RustCargoTargets))
|
|
144
|
+
return false;
|
|
145
|
+
const segments = importedModule.split('::').filter(Boolean);
|
|
146
|
+
if (segments.length !== 1)
|
|
147
|
+
return false;
|
|
148
|
+
for (const target of config.targetsByFile.get(caller) ?? []) {
|
|
149
|
+
if (config.rootImports.get(target)?.get(segments[0])?.has(candidate))
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
/** Every known membership must identify this file as the entry point. A file
|
|
155
|
+
* shared as a module in another target does not have a single root role. */
|
|
156
|
+
export function rustIsExclusiveCargoRoot(config, file) {
|
|
157
|
+
if (!(config instanceof RustCargoTargets))
|
|
158
|
+
return false;
|
|
159
|
+
const targets = config.targetsByFile.get(file);
|
|
160
|
+
return targets?.size === 1 && targets.has(file);
|
|
161
|
+
}
|
|
162
|
+
/** Establish crate identity before the existing module-path plausibility test. */
|
|
163
|
+
export function rustImportReachesCargoTarget(config, caller, candidate, importedModule) {
|
|
164
|
+
if (!(config instanceof RustCargoTargets))
|
|
165
|
+
return false;
|
|
166
|
+
const name = importedModule.split('::').filter(Boolean)[0];
|
|
167
|
+
if (!name)
|
|
168
|
+
return false;
|
|
169
|
+
const candidates = config.targetsByFile.get(candidate);
|
|
170
|
+
for (const target of config.targetsByFile.get(caller) ?? []) {
|
|
171
|
+
for (const imported of config.rootImports.get(target)?.get(name) ?? []) {
|
|
172
|
+
if (candidates?.has(imported))
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
/** Import names are target/package-relative. A dependency alias in another
|
|
179
|
+
* package must not authorize a guess here merely because its spelling matches. */
|
|
180
|
+
function cargoRootImports(manifests, targets) {
|
|
181
|
+
const libraries = new Map();
|
|
182
|
+
for (const [manifest, data] of manifests) {
|
|
183
|
+
if (!table(data.package))
|
|
184
|
+
continue;
|
|
185
|
+
const lib = table(data.lib) ? data.lib : undefined;
|
|
186
|
+
if (!lib && data.package.autolib === false)
|
|
187
|
+
continue;
|
|
188
|
+
const root = path.posix.join(path.posix.dirname(manifest), typeof lib?.path === 'string' ? lib.path : 'src/lib.rs');
|
|
189
|
+
const name = lib?.name ?? data.package.name;
|
|
190
|
+
if (typeof name === 'string' && targets.get(manifest)?.includes(root)) {
|
|
191
|
+
libraries.set(manifest, {
|
|
192
|
+
root,
|
|
193
|
+
name: name.replaceAll('-', '_'),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const result = new Map();
|
|
198
|
+
for (const [manifest, data] of manifests) {
|
|
199
|
+
if (!table(data.package))
|
|
200
|
+
continue;
|
|
201
|
+
const dir = path.posix.dirname(manifest);
|
|
202
|
+
let workspace = typeof data.package.workspace === 'string'
|
|
203
|
+
? path.posix.join(dir, data.package.workspace, '.')
|
|
204
|
+
: dir;
|
|
205
|
+
while (!table(manifests.get(path.posix.join(workspace, 'Cargo.toml'))?.workspace) &&
|
|
206
|
+
typeof data.package.workspace !== 'string' &&
|
|
207
|
+
workspace !== '.')
|
|
208
|
+
workspace = path.posix.dirname(workspace);
|
|
209
|
+
const workspaceData = manifests.get(path.posix.join(workspace, 'Cargo.toml'))?.workspace;
|
|
210
|
+
const workspaceDeps = table(workspaceData) && table(workspaceData.dependencies) ? workspaceData.dependencies : {};
|
|
211
|
+
const sections = [
|
|
212
|
+
data,
|
|
213
|
+
...(table(data.target) ? Object.values(data.target).filter(table) : []),
|
|
214
|
+
];
|
|
215
|
+
for (const target of targets.get(manifest) ?? []) {
|
|
216
|
+
let imports = result.get(target);
|
|
217
|
+
if (!imports)
|
|
218
|
+
result.set(target, (imports = new Map()));
|
|
219
|
+
const add = (name, root) => {
|
|
220
|
+
let roots = imports.get(name);
|
|
221
|
+
if (!roots)
|
|
222
|
+
imports.set(name, (roots = new Set()));
|
|
223
|
+
roots.add(root);
|
|
224
|
+
};
|
|
225
|
+
const own = libraries.get(manifest);
|
|
226
|
+
const buildRoot = path.posix.join(dir, typeof data.package.build === 'string' ? data.package.build : 'build.rs');
|
|
227
|
+
const isBuild = data.package.build !== false && target === buildRoot;
|
|
228
|
+
if (own && !isBuild)
|
|
229
|
+
add(own.name, own.root);
|
|
230
|
+
for (const section of sections) {
|
|
231
|
+
// Libraries/binaries can also compile as unit-test targets, so retain
|
|
232
|
+
// dev dependencies across cfg modes. Build scripts have their own
|
|
233
|
+
// dependency namespace and cannot import the package's own library.
|
|
234
|
+
const kinds = isBuild ? ['build-dependencies'] : ['dependencies', 'dev-dependencies'];
|
|
235
|
+
for (const kind of kinds) {
|
|
236
|
+
const deps = section[kind];
|
|
237
|
+
if (!table(deps))
|
|
238
|
+
continue;
|
|
239
|
+
for (const [key, declared] of Object.entries(deps)) {
|
|
240
|
+
const inherited = table(declared) && declared.workspace === true;
|
|
241
|
+
const dep = inherited ? workspaceDeps[key] : declared;
|
|
242
|
+
if (!table(dep) || typeof dep.path !== 'string')
|
|
243
|
+
continue;
|
|
244
|
+
const dependency = libraries.get(path.posix.join(inherited ? workspace : dir, dep.path, 'Cargo.toml'));
|
|
245
|
+
if (!dependency)
|
|
246
|
+
continue;
|
|
247
|
+
// Cargo uses the dependency key whenever `package` is explicit,
|
|
248
|
+
// even if it equals the package name and [lib].name differs.
|
|
249
|
+
const renamed = typeof dep.package === 'string';
|
|
250
|
+
add(renamed ? key.replaceAll('-', '_') : dependency.name, dependency.root);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
/** Undefined means no complete membership proof; never interpret it as disjoint. */
|
|
259
|
+
export function rustFilesShareCargoTarget(config, caller, candidate) {
|
|
260
|
+
if (!(config instanceof RustCargoTargets))
|
|
261
|
+
return undefined;
|
|
262
|
+
const callers = config.targetsByFile.get(caller);
|
|
263
|
+
const candidates = config.targetsByFile.get(candidate);
|
|
264
|
+
if (!callers || !candidates)
|
|
265
|
+
return undefined;
|
|
266
|
+
return [...callers].some((root) => candidates.has(root));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Static, bounded, one-shot provider loader. No cargo/rustc, build scripts,
|
|
270
|
+
* repository wrappers, or network. Parse only files reachable from Cargo roots.
|
|
271
|
+
* Unknown expansion anywhere can add shared membership, so it invalidates the
|
|
272
|
+
* negative proof for this snapshot rather than producing a partial veto.
|
|
273
|
+
*/
|
|
274
|
+
export async function loadRustCargoTargets(repoPath) {
|
|
275
|
+
try {
|
|
276
|
+
const root = await fs.realpath(repoPath);
|
|
277
|
+
const files = new Set();
|
|
278
|
+
const manifests = [];
|
|
279
|
+
for await (const entry of glob.iterate('**/Cargo.toml', {
|
|
280
|
+
// Cargo metadata must include targets the graph scanner omits (notably
|
|
281
|
+
// src/bin). An omitted target can share a source file with another crate.
|
|
282
|
+
cwd: root,
|
|
283
|
+
nodir: true,
|
|
284
|
+
follow: false,
|
|
285
|
+
posix: true,
|
|
286
|
+
dot: true,
|
|
287
|
+
ignore: [
|
|
288
|
+
'**/.git/**',
|
|
289
|
+
'**/node_modules/**',
|
|
290
|
+
'**/.gitnexus/**',
|
|
291
|
+
'**/target/debug/**',
|
|
292
|
+
'**/target/release/**',
|
|
293
|
+
'**/target/incremental/**',
|
|
294
|
+
'**/target/doc/**',
|
|
295
|
+
'**/target/tmp/**',
|
|
296
|
+
'**/target/.fingerprint/**',
|
|
297
|
+
'**/target/CACHEDIR.TAG',
|
|
298
|
+
],
|
|
299
|
+
})) {
|
|
300
|
+
if (files.size >= MAX_FILES)
|
|
301
|
+
return undefined;
|
|
302
|
+
files.add(entry);
|
|
303
|
+
manifests.push(entry);
|
|
304
|
+
}
|
|
305
|
+
if (manifests.length === 0)
|
|
306
|
+
return undefined;
|
|
307
|
+
// Artifact-layout pruning (`target/debug`, `target/release`, …) must not
|
|
308
|
+
// erase a source path whose segment is named `target` (`src/target/mod.rs`,
|
|
309
|
+
// `[lib] path = "target/entry.rs"`, `src/bin/target/main.rs`). Re-scan
|
|
310
|
+
// Cargo's auto-target slots without that filter.
|
|
311
|
+
for (const manifest of manifests) {
|
|
312
|
+
for await (const entry of glob.iterate([
|
|
313
|
+
'src/lib.rs',
|
|
314
|
+
'src/main.rs',
|
|
315
|
+
'src/bin/*.rs',
|
|
316
|
+
'src/bin/*/main.rs',
|
|
317
|
+
'tests/*.rs',
|
|
318
|
+
'tests/*/main.rs',
|
|
319
|
+
'benches/*.rs',
|
|
320
|
+
'benches/*/main.rs',
|
|
321
|
+
'examples/*.rs',
|
|
322
|
+
'examples/*/main.rs',
|
|
323
|
+
'build.rs',
|
|
324
|
+
], {
|
|
325
|
+
cwd: path.join(root, path.posix.dirname(manifest)),
|
|
326
|
+
nodir: true,
|
|
327
|
+
follow: false,
|
|
328
|
+
posix: true,
|
|
329
|
+
})) {
|
|
330
|
+
if (files.size >= MAX_FILES)
|
|
331
|
+
return undefined;
|
|
332
|
+
files.add(path.posix.join(path.posix.dirname(manifest), entry));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const read = async (file) => {
|
|
336
|
+
const requested = path.resolve(root, file);
|
|
337
|
+
const absolute = await fs.realpath(requested);
|
|
338
|
+
const rel = path.relative(root, absolute);
|
|
339
|
+
if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
340
|
+
throw new Error('Cargo module outside repository');
|
|
341
|
+
}
|
|
342
|
+
if (absolute !== requested)
|
|
343
|
+
throw new Error('Cargo module alias has unknown membership');
|
|
344
|
+
// The shared reader validates its opened descriptor and bounds streamed
|
|
345
|
+
// bytes; the outer realpath check is a separate containment/alias guard.
|
|
346
|
+
const content = await readRepoControlFile(root, file);
|
|
347
|
+
if (content === null)
|
|
348
|
+
throw new Error('Cargo source disappeared');
|
|
349
|
+
return content;
|
|
350
|
+
};
|
|
351
|
+
const contents = new Map();
|
|
352
|
+
const manifestData = new Map();
|
|
353
|
+
const workspaceEditions = new Map();
|
|
354
|
+
for (const manifest of manifests) {
|
|
355
|
+
const content = await read(manifest);
|
|
356
|
+
contents.set(manifest, content);
|
|
357
|
+
const data = parse(content);
|
|
358
|
+
// A dependency alias can replace a std/core/alloc extern-prelude entry.
|
|
359
|
+
// In that case the module walker cannot identify standard macros safely.
|
|
360
|
+
const sections = [
|
|
361
|
+
data,
|
|
362
|
+
...(table(data.target) ? Object.values(data.target).filter(table) : []),
|
|
363
|
+
];
|
|
364
|
+
if (sections.some((section) => ['dependencies', 'dev-dependencies', 'build-dependencies'].some((kind) => table(section[kind]) &&
|
|
365
|
+
['std', 'core', 'alloc'].some((name) => Object.hasOwn(section[kind], name)))))
|
|
366
|
+
return undefined;
|
|
367
|
+
manifestData.set(manifest, data);
|
|
368
|
+
if (table(data.workspace) &&
|
|
369
|
+
table(data.workspace.package) &&
|
|
370
|
+
typeof data.workspace.package.edition === 'string') {
|
|
371
|
+
workspaceEditions.set(path.posix.dirname(manifest), data.workspace.package.edition);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Manifest discovery prunes artifacts, but explicit source paths and mod
|
|
375
|
+
// declarations are authoritative candidates even beneath a `target` folder.
|
|
376
|
+
// Probe only those literal paths; never crawl the artifact tree recursively.
|
|
377
|
+
const discover = async (candidates) => {
|
|
378
|
+
const patterns = [...candidates].map((file) => {
|
|
379
|
+
if (path.posix.isAbsolute(file) ||
|
|
380
|
+
file === '..' ||
|
|
381
|
+
file.startsWith('../') ||
|
|
382
|
+
file.includes('\\'))
|
|
383
|
+
throw new Error('Cargo source outside repository');
|
|
384
|
+
if (!file.endsWith('.rs') ||
|
|
385
|
+
file
|
|
386
|
+
.split('/')
|
|
387
|
+
.some((part) => ['.git', '.gitnexus', 'node_modules'].includes(part.toLowerCase())))
|
|
388
|
+
throw new Error('Cargo source excluded from inventory');
|
|
389
|
+
return escape(file);
|
|
390
|
+
});
|
|
391
|
+
for await (const entry of glob.iterate(patterns, {
|
|
392
|
+
cwd: root,
|
|
393
|
+
nodir: true,
|
|
394
|
+
follow: false,
|
|
395
|
+
posix: true,
|
|
396
|
+
dot: true,
|
|
397
|
+
})) {
|
|
398
|
+
if (files.size >= MAX_FILES)
|
|
399
|
+
throw new Error('Cargo file limit');
|
|
400
|
+
files.add(entry);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
for (const [manifest, data] of manifestData) {
|
|
404
|
+
const explicit = [
|
|
405
|
+
data.lib,
|
|
406
|
+
...['bin', 'test', 'bench', 'example'].flatMap((kind) => Array.isArray(data[kind]) ? data[kind] : []),
|
|
407
|
+
];
|
|
408
|
+
const paths = explicit
|
|
409
|
+
.filter(table)
|
|
410
|
+
.map((entry) => entry.path)
|
|
411
|
+
.filter((value) => typeof value === 'string');
|
|
412
|
+
if (table(data.package) && typeof data.package.build === 'string')
|
|
413
|
+
paths.push(data.package.build);
|
|
414
|
+
await discover(paths.map((file) => path.posix.join(path.posix.dirname(manifest), file)));
|
|
415
|
+
}
|
|
416
|
+
const roots = new Set();
|
|
417
|
+
const targetsByManifest = new Map();
|
|
418
|
+
for (const [manifest, content] of contents) {
|
|
419
|
+
const targets = cargoTargetRoots(manifest, content, files, workspaceEditions);
|
|
420
|
+
if (targets === undefined)
|
|
421
|
+
return undefined;
|
|
422
|
+
targetsByManifest.set(manifest, targets);
|
|
423
|
+
for (const target of targets)
|
|
424
|
+
roots.add(target);
|
|
425
|
+
}
|
|
426
|
+
const parser = new Parser();
|
|
427
|
+
parser.setLanguage(getLanguageGrammar(SupportedLanguages.Rust));
|
|
428
|
+
const targetsByFile = new Map();
|
|
429
|
+
const publicUsesByFile = new Map();
|
|
430
|
+
// A file may be reached conventionally AND through #[path]. Those have
|
|
431
|
+
// different submodule bases, so cache and visit both contexts separately.
|
|
432
|
+
const childrenByFile = new Map();
|
|
433
|
+
let visits = 0;
|
|
434
|
+
for (const target of roots) {
|
|
435
|
+
const pending = [{ file: target, ownsDirectory: true }];
|
|
436
|
+
const visited = new Set();
|
|
437
|
+
while (pending.length > 0) {
|
|
438
|
+
if (++visits > MAX_FILES)
|
|
439
|
+
return undefined;
|
|
440
|
+
const { file, ownsDirectory } = pending.pop();
|
|
441
|
+
const key = `${file === target ? 'root' : ownsDirectory ? 'owned' : 'module'}:${file}`;
|
|
442
|
+
if (visited.has(key))
|
|
443
|
+
continue;
|
|
444
|
+
visited.add(key);
|
|
445
|
+
let owners = targetsByFile.get(file);
|
|
446
|
+
if (!owners)
|
|
447
|
+
targetsByFile.set(file, (owners = new Set()));
|
|
448
|
+
owners.add(target);
|
|
449
|
+
let children = childrenByFile.get(key);
|
|
450
|
+
if (!children) {
|
|
451
|
+
const tree = parseSourceSafe(parser, await read(file));
|
|
452
|
+
const missing = new Set();
|
|
453
|
+
let result = rustModuleFiles(tree.rootNode, file, ownsDirectory, files, missing, file === target);
|
|
454
|
+
if (result === undefined && missing.size > 0) {
|
|
455
|
+
await discover(missing);
|
|
456
|
+
result = rustModuleFiles(tree.rootNode, file, ownsDirectory, files, undefined, file === target);
|
|
457
|
+
}
|
|
458
|
+
if (result === undefined)
|
|
459
|
+
return undefined;
|
|
460
|
+
publicUsesByFile.set(file, rustPublicUses(tree.rootNode));
|
|
461
|
+
children = result;
|
|
462
|
+
childrenByFile.set(key, children);
|
|
463
|
+
}
|
|
464
|
+
pending.push(...children);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return new RustCargoTargets(targetsByFile, cargoRootImports(manifestData, targetsByManifest), publicUsesByFile);
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
// I/O, parse or containment failure cannot establish target separation.
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
@@ -7,15 +7,18 @@ import { syntheticCapture } from '../../utils/ast-helpers.js';
|
|
|
7
7
|
export function splitRustUseDeclaration(node) {
|
|
8
8
|
if (node.type !== 'use_declaration')
|
|
9
9
|
return [];
|
|
10
|
-
const isReexport =
|
|
10
|
+
const isReexport = hasUnrestrictedPub(node);
|
|
11
11
|
const argument = getUseArgument(node);
|
|
12
12
|
if (argument === null)
|
|
13
13
|
return [];
|
|
14
14
|
return decomposeUseArgument(argument, '', isReexport, node);
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
function hasUnrestrictedPub(node) {
|
|
17
17
|
for (let i = 0; i < node.childCount; i++) {
|
|
18
|
-
|
|
18
|
+
const child = node.child(i);
|
|
19
|
+
// `pub(crate)` / `pub(super)` / `pub(in …)` / `crate` are not the crate's
|
|
20
|
+
// public surface, so they must not walk as `pub use` re-export evidence.
|
|
21
|
+
if (child?.type === 'visibility_modifier' && child.text === 'pub')
|
|
19
22
|
return true;
|
|
20
23
|
}
|
|
21
24
|
return false;
|
|
@@ -106,7 +109,9 @@ function buildScopedPath(node) {
|
|
|
106
109
|
if (node.type === 'scoped_identifier') {
|
|
107
110
|
const parts = [];
|
|
108
111
|
collectScopedParts(node, parts);
|
|
109
|
-
|
|
112
|
+
// An absolute extern-prelude path bypasses a same-named local module.
|
|
113
|
+
// Keep that evidence when flattening the AST into an import specifier.
|
|
114
|
+
return `${node.text.startsWith('::') ? '::' : ''}${parts.join('::')}`;
|
|
110
115
|
}
|
|
111
116
|
return node.text;
|
|
112
117
|
}
|
|
@@ -136,7 +141,7 @@ function buildWildcardPath(node) {
|
|
|
136
141
|
continue;
|
|
137
142
|
if (child.type === 'scoped_identifier')
|
|
138
143
|
return buildScopedPath(child);
|
|
139
|
-
if (
|
|
144
|
+
if (['identifier', 'crate', 'self', 'super'].includes(child.type))
|
|
140
145
|
return child.text;
|
|
141
146
|
}
|
|
142
147
|
return '';
|
|
@@ -22,6 +22,8 @@ import type { ParsedFile, ScopeId, SymbolDefinition } from '../../../../_shared/
|
|
|
22
22
|
export declare function rustIsGlobalNameFallbackPlausible(ctx: {
|
|
23
23
|
readonly callerParsed: ParsedFile;
|
|
24
24
|
readonly candidate: SymbolDefinition;
|
|
25
|
+
readonly resolutionConfig?: unknown;
|
|
26
|
+
readonly parsedFileOf?: (filePath: string) => ParsedFile | undefined;
|
|
25
27
|
readonly site: {
|
|
26
28
|
readonly name: string;
|
|
27
29
|
readonly rawQualifiedName?: string;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* `a::b`). `crate::` names the root; `self::` and `super::` resolve relative
|
|
19
19
|
* to the caller's module before comparison.
|
|
20
20
|
*/
|
|
21
|
+
import { rustFilesShareCargoTarget, rustImportNamesCargoRoot, rustIsExclusiveCargoRoot, rustImportReachesCargoTarget, rustCargoPubliclyReexports, } from './cargo-targets.js';
|
|
21
22
|
import { modulePathReaches, stripExtension, } from '../../scope-resolution/utils/name-fallback-visibility.js';
|
|
22
23
|
/** File-stems that name their PARENT directory as the module, not themselves. */
|
|
23
24
|
const RUST_DIRECTORY_MODULE_STEMS = new Set(['mod', 'lib', 'main']);
|
|
@@ -26,6 +27,16 @@ const RUST_DIRECTORY_MODULE_STEMS = new Set(['mod', 'lib', 'main']);
|
|
|
26
27
|
const RUST_CRATE_ROOT_DIRS = new Set(['src', 'tests', 'benches', 'examples']);
|
|
27
28
|
/** Path prefixes of a `use` that name a root rather than a module segment. */
|
|
28
29
|
const RUST_USE_ROOT_PREFIXES = new Set(['crate', '$crate']);
|
|
30
|
+
const RUST_TYPE_NAMESPACE_KINDS = new Set([
|
|
31
|
+
'Namespace',
|
|
32
|
+
'Class',
|
|
33
|
+
'Struct',
|
|
34
|
+
'Enum',
|
|
35
|
+
'Trait',
|
|
36
|
+
'Interface',
|
|
37
|
+
'TypeAlias',
|
|
38
|
+
'Union',
|
|
39
|
+
]);
|
|
29
40
|
// One scope lookup per immutable parsed-file snapshot, not per fallback site.
|
|
30
41
|
// Weak keys release both the snapshot and its index at the end of ingestion.
|
|
31
42
|
const scopeLookupByFile = new WeakMap();
|
|
@@ -92,12 +103,64 @@ export function rustIsGlobalNameFallbackPlausible(ctx) {
|
|
|
92
103
|
if (ctx.site.rawQualifiedName !== undefined)
|
|
93
104
|
return true;
|
|
94
105
|
const candidateModule = rustModulePathOf(ctx.candidate.filePath);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
const sharesTarget = rustFilesShareCargoTarget(ctx.resolutionConfig, ctx.callerParsed.filePath, ctx.candidate.filePath);
|
|
107
|
+
const separateRoot = sharesTarget === false &&
|
|
108
|
+
rustIsExclusiveCargoRoot(ctx.resolutionConfig, ctx.candidate.filePath);
|
|
109
|
+
// Cargo membership does not establish cross-file lexical visibility.
|
|
110
|
+
// Root candidates must also pass the import checks below; same-file and
|
|
111
|
+
// explicitly qualified calls have already been handled above.
|
|
100
112
|
const candidateName = rustSimpleNameOf(ctx.candidate);
|
|
113
|
+
const exportModules = new Set([(ctx.candidate.namespacePrefix ?? '').replaceAll('.', '::')]);
|
|
114
|
+
const candidateParsed = sharesTarget === false ? ctx.parsedFileOf?.(ctx.candidate.filePath) : undefined;
|
|
115
|
+
if (candidateParsed !== undefined) {
|
|
116
|
+
const moduleByScope = new Map();
|
|
117
|
+
const moduleScopes = new Set();
|
|
118
|
+
for (const scope of candidateParsed.scopes) {
|
|
119
|
+
const parent = scope.parent === null ? '' : (moduleByScope.get(scope.parent) ?? '');
|
|
120
|
+
const own = scope.kind === 'Namespace'
|
|
121
|
+
? scope.ownedDefs.find((def) => def.type === 'Namespace')?.qualifiedName
|
|
122
|
+
: undefined;
|
|
123
|
+
moduleByScope.set(scope.id, [parent, own].filter(Boolean).join('::'));
|
|
124
|
+
if (scope.kind === 'Namespace' || scope.kind === 'Module')
|
|
125
|
+
moduleScopes.add(scope.id);
|
|
126
|
+
}
|
|
127
|
+
// Follow same-file re-exports without confusing the defining module with
|
|
128
|
+
// the module an importer sees. Each iteration adds a known module scope,
|
|
129
|
+
// so cycles terminate. Cargo's AST snapshot supplies public visibility,
|
|
130
|
+
// which the coarse parsed reexport/wildcard kind does not preserve.
|
|
131
|
+
let changed = true;
|
|
132
|
+
while (changed) {
|
|
133
|
+
changed = false;
|
|
134
|
+
for (const imp of candidateParsed.parsedImports) {
|
|
135
|
+
if (imp.declaredAtScope === undefined || !moduleScopes.has(imp.declaredAtScope))
|
|
136
|
+
continue;
|
|
137
|
+
if (imp.kind !== 'wildcard' &&
|
|
138
|
+
(imp.kind !== 'reexport' ||
|
|
139
|
+
imp.localName !== candidateName ||
|
|
140
|
+
imp.importedName !== candidateName))
|
|
141
|
+
continue;
|
|
142
|
+
if (imp.targetRaw.startsWith('::'))
|
|
143
|
+
continue;
|
|
144
|
+
const owner = moduleByScope.get(imp.declaredAtScope);
|
|
145
|
+
if (!rustCargoPubliclyReexports(ctx.resolutionConfig, ctx.candidate.filePath, owner, imp.targetRaw, imp.kind, imp.kind === 'wildcard' ? '*' : imp.localName))
|
|
146
|
+
continue;
|
|
147
|
+
const parts = imp.targetRaw.split('::').filter(Boolean);
|
|
148
|
+
if (imp.kind !== 'wildcard')
|
|
149
|
+
parts.pop();
|
|
150
|
+
const base = parts[0] === 'self' || parts[0] === 'super' ? owner.split('::').filter(Boolean) : [];
|
|
151
|
+
if (parts[0] === 'crate' || parts[0] === 'self')
|
|
152
|
+
parts.shift();
|
|
153
|
+
while (parts[0] === 'super') {
|
|
154
|
+
base.pop();
|
|
155
|
+
parts.shift();
|
|
156
|
+
}
|
|
157
|
+
if (exportModules.has([...base, ...parts].join('::')) && !exportModules.has(owner)) {
|
|
158
|
+
exportModules.add(owner);
|
|
159
|
+
changed = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
101
164
|
// Imports are lexical evidence, not a file-wide allowlist. Legacy/synthetic
|
|
102
165
|
// imports without a scope receipt retain the previous conservative behavior.
|
|
103
166
|
let visibleScopes;
|
|
@@ -117,24 +180,95 @@ export function rustIsGlobalNameFallbackPlausible(ctx) {
|
|
|
117
180
|
current = scope.parent;
|
|
118
181
|
}
|
|
119
182
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
183
|
+
const visibleImports = ctx.callerParsed.parsedImports.filter((imp) => imp.declaredAtScope === undefined ||
|
|
184
|
+
visibleScopes === undefined ||
|
|
185
|
+
visibleScopes.has(imp.declaredAtScope));
|
|
186
|
+
const scopeRanks = new Map([...(visibleScopes ?? [])].map((scope, rank) => [scope, rank]));
|
|
187
|
+
const namesCandidateRoot = (module, entryOnly) => {
|
|
188
|
+
const pending = [module];
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
while (pending.length > 0) {
|
|
191
|
+
const name = pending.pop();
|
|
192
|
+
const parts = name.split('::').filter(Boolean);
|
|
193
|
+
const head = parts[0];
|
|
194
|
+
const bindingName = name.startsWith('::') ? name : head;
|
|
195
|
+
if (!bindingName || seen.has(bindingName))
|
|
196
|
+
continue;
|
|
197
|
+
seen.add(bindingName);
|
|
198
|
+
const aliases = visibleImports.filter((imported) => 'localName' in imported && imported.localName === bindingName);
|
|
199
|
+
const rank = (imported) => imported.declaredAtScope === undefined
|
|
200
|
+
? Infinity
|
|
201
|
+
: (scopeRanks.get(imported.declaredAtScope) ?? Infinity);
|
|
202
|
+
const nearest = aliases.length > 0 ? Math.min(...aliases.map(rank)) : Infinity;
|
|
203
|
+
let localTypeRank = Infinity;
|
|
204
|
+
for (const [scopeId, depth] of scopeRanks) {
|
|
205
|
+
const bindings = scopeLookupByFile
|
|
206
|
+
.get(ctx.callerParsed)
|
|
207
|
+
?.get(scopeId)
|
|
208
|
+
?.bindings.get(bindingName);
|
|
209
|
+
if (bindings?.some((binding) => binding.origin === 'local' && RUST_TYPE_NAMESPACE_KINDS.has(binding.def.type))) {
|
|
210
|
+
localTypeRank = depth;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// A local module/type shadows the extern prelude, but a nearer import
|
|
215
|
+
// can shadow that declaration. Value-namespace functions do not block it.
|
|
216
|
+
if (localTypeRank !== Infinity && localTypeRank <= nearest)
|
|
217
|
+
continue;
|
|
218
|
+
if (aliases.length > 0) {
|
|
219
|
+
const destinations = new Set(aliases
|
|
220
|
+
.filter((imported) => rank(imported) === nearest)
|
|
221
|
+
.map((imported) => imported.targetRaw));
|
|
222
|
+
if (destinations.size !== 1)
|
|
223
|
+
continue;
|
|
224
|
+
const destination = [...destinations][0];
|
|
225
|
+
if (destination !== bindingName) {
|
|
226
|
+
pending.push([destination, ...parts.slice(1)].join('::'));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// A root FILE can also contain inline modules. Its Cargo identity names
|
|
231
|
+
// the crate, while the scope model supplies the member's module suffix.
|
|
232
|
+
if (rustImportNamesCargoRoot(ctx.resolutionConfig, ctx.callerParsed.filePath, ctx.candidate.filePath, head)) {
|
|
233
|
+
if (exportModules.has(parts.slice(1).join('::')))
|
|
234
|
+
return 'root';
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (!entryOnly &&
|
|
238
|
+
rustImportReachesCargoTarget(ctx.resolutionConfig, ctx.callerParsed.filePath, ctx.candidate.filePath, name))
|
|
239
|
+
return 'module';
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
};
|
|
243
|
+
for (const imp of visibleImports) {
|
|
244
|
+
let importedRoot = false;
|
|
125
245
|
// `crate::` is the caller's crate. A same trailing module in another
|
|
126
246
|
// workspace crate is a different item and cannot authorize the guess.
|
|
127
247
|
if (imp.targetRaw === 'crate' || imp.targetRaw.startsWith('crate::')) {
|
|
248
|
+
if (sharesTarget === false)
|
|
249
|
+
continue;
|
|
128
250
|
const callerRoot = rustCrateRootOf(ctx.callerParsed.filePath);
|
|
129
251
|
const candidateRoot = rustCrateRootOf(ctx.candidate.filePath);
|
|
130
|
-
if (
|
|
252
|
+
if (sharesTarget === undefined &&
|
|
253
|
+
callerRoot !== '' &&
|
|
254
|
+
candidateRoot !== '' &&
|
|
255
|
+
callerRoot !== candidateRoot)
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (sharesTarget === false) {
|
|
259
|
+
const module = imp.kind === 'wildcard'
|
|
260
|
+
? imp.targetRaw
|
|
261
|
+
: imp.targetRaw.slice(0, Math.max(0, imp.targetRaw.lastIndexOf('::')));
|
|
262
|
+
const reached = namesCandidateRoot(module, separateRoot);
|
|
263
|
+
if (!reached)
|
|
131
264
|
continue;
|
|
265
|
+
importedRoot = reached === 'root';
|
|
132
266
|
}
|
|
133
267
|
const usePath = rustUsePathOf(imp.targetRaw, ctx.callerParsed.filePath);
|
|
134
268
|
// Only a glob introduces every bare item of a module. A named import must
|
|
135
269
|
// match both the candidate's original name and the call's local spelling.
|
|
136
270
|
if (imp.kind === 'wildcard') {
|
|
137
|
-
if (modulePathReaches(usePath, candidateModule))
|
|
271
|
+
if (importedRoot || candidateModule === '' || modulePathReaches(usePath, candidateModule))
|
|
138
272
|
return true;
|
|
139
273
|
continue;
|
|
140
274
|
}
|
|
@@ -147,9 +281,14 @@ export function rustIsGlobalNameFallbackPlausible(ctx) {
|
|
|
147
281
|
// An alias authorizes only the local spelling checked above.
|
|
148
282
|
if (importedNameOf(imp) !== candidateName)
|
|
149
283
|
continue;
|
|
284
|
+
// A different target may import the library crate's root exports. Even
|
|
285
|
+
// when that root has no path segment to compare, a named import must name
|
|
286
|
+
// THIS callable: `use std::fmt` cannot revive a rejected `crate::helper`.
|
|
287
|
+
const parent = usePath.slice(0, Math.max(0, usePath.lastIndexOf('::')));
|
|
288
|
+
if (importedRoot || candidateModule === '')
|
|
289
|
+
return true;
|
|
150
290
|
if (modulePathReaches(usePath, candidateModule))
|
|
151
291
|
return true;
|
|
152
|
-
const parent = usePath.slice(0, Math.max(0, usePath.lastIndexOf('::')));
|
|
153
292
|
if (parent !== '' && modulePathReaches(parent, candidateModule))
|
|
154
293
|
return true;
|
|
155
294
|
}
|
|
@@ -10,6 +10,7 @@ import { isClassLike, findClassBindingInScope, resolveAmbiguousInheritanceBaseVi
|
|
|
10
10
|
import { resolveDefGraphId } from '../../scope-resolution/graph-bridge/ids.js';
|
|
11
11
|
import { generateId } from '../../../../lib/utils.js';
|
|
12
12
|
import { rustIsGlobalNameFallbackPlausible } from './name-fallback-visibility.js';
|
|
13
|
+
import { loadRustCargoTargets } from './cargo-targets.js';
|
|
13
14
|
/**
|
|
14
15
|
* Emit Rust `S IMPLEMENTS T` edges from `impl T for S` trait implementations.
|
|
15
16
|
*
|
|
@@ -131,6 +132,7 @@ export const rustScopeResolver = {
|
|
|
131
132
|
language: SupportedLanguages.Rust,
|
|
132
133
|
languageProvider: rustProvider,
|
|
133
134
|
importEdgeReason: 'rust-scope: use',
|
|
135
|
+
loadResolutionConfig: loadRustCargoTargets,
|
|
134
136
|
resolveImportTarget: (targetRaw, fromFile, allFilePaths, resolutionConfig) => resolveRustImportTarget(targetRaw, fromFile, allFilePaths, resolutionConfig),
|
|
135
137
|
mergeBindings: (existing, incoming, scopeId) => rustMergeBindings(existing, incoming, scopeId),
|
|
136
138
|
arityCompatibility: (callsite, def) => rustArityCompatibility(def, callsite),
|
|
@@ -819,6 +819,8 @@ export interface ScopeResolver {
|
|
|
819
819
|
readonly isGlobalNameFallbackPlausible?: (ctx: {
|
|
820
820
|
readonly callerParsed: ParsedFile;
|
|
821
821
|
readonly candidate: SymbolDefinition;
|
|
822
|
+
/** Opaque workspace metadata from this provider's loadResolutionConfig. */
|
|
823
|
+
readonly resolutionConfig?: unknown;
|
|
822
824
|
readonly parsedFileOf: (filePath: string) => ParsedFile | undefined;
|
|
823
825
|
/**
|
|
824
826
|
* Raw source of any parsed file, for languages whose visibility rule needs
|
|
@@ -37,6 +37,7 @@ export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: Scop
|
|
|
37
37
|
/** Per-language veto on a name guess — see
|
|
38
38
|
* `ScopeResolver.isGlobalNameFallbackPlausible`. */
|
|
39
39
|
readonly isGlobalNameFallbackPlausible?: ScopeResolver['isGlobalNameFallbackPlausible'];
|
|
40
|
+
readonly resolutionConfig?: unknown;
|
|
40
41
|
/** Raw source lookup handed to `isGlobalNameFallbackPlausible` (optional). */
|
|
41
42
|
readonly sourceTextOf?: (filePath: string) => string | undefined;
|
|
42
43
|
/** When true, `Type(...)` constructor calls link to the Class def
|
|
@@ -469,6 +469,7 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
|
|
|
469
469
|
options.isGlobalNameFallbackPlausible?.({
|
|
470
470
|
callerParsed: parsed,
|
|
471
471
|
candidate: vetoCandidate,
|
|
472
|
+
resolutionConfig: options.resolutionConfig,
|
|
472
473
|
parsedFileOf: parsedFileByPath(),
|
|
473
474
|
sourceTextOf: options.sourceTextOf,
|
|
474
475
|
site: {
|
|
@@ -695,6 +695,7 @@ export function runScopeResolution(input, provider) {
|
|
|
695
695
|
allowGlobalFallback: provider.allowGlobalFreeCallFallback === true,
|
|
696
696
|
language: provider.language,
|
|
697
697
|
isGlobalNameFallbackPlausible: provider.isGlobalNameFallbackPlausible,
|
|
698
|
+
resolutionConfig,
|
|
698
699
|
sourceTextOf: provider.isGlobalNameFallbackPlausible !== undefined
|
|
699
700
|
? (filePath) => getFileContents().get(filePath)
|
|
700
701
|
: undefined,
|
|
@@ -758,7 +758,13 @@ import { copyV8CacheIfPresent, tryLoadV8Cache, writeV8CacheFile } from './v8-sid
|
|
|
758
758
|
// #3190. Old durable ParsedFiles lack the facts needed for scoped binding;
|
|
759
759
|
// invalidate both stores so warm indexing actually applies the correction.
|
|
760
760
|
// origin/main took 98 for #3219; 99 is the next free value.
|
|
761
|
-
|
|
761
|
+
// v100 (#3253): Rust import captures preserve the leading `::` that selects
|
|
762
|
+
// the extern prelude. Old warm captures erase it and cannot distinguish an
|
|
763
|
+
// absolute library import from a same-named local module. Reparse both stores.
|
|
764
|
+
// v101 (#3294 review): Rust bare-keyword glob imports retain crate/self/super
|
|
765
|
+
// instead of an empty target path; restricted pub(...) imports are no longer
|
|
766
|
+
// captured as unrestricted reexports. Re-extract both facts on warm indexes.
|
|
767
|
+
const SCHEMA_BUMP = 101;
|
|
762
768
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
763
769
|
try {
|
|
764
770
|
// package.json sits at gitnexus/package.json — two levels up from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.6.13-rc.
|
|
3
|
+
"version": "1.6.13-rc.8",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"pino": "^10.3.1",
|
|
93
93
|
"pino-pretty": "^13.1.3",
|
|
94
94
|
"proxy-addr": "^2.0.7",
|
|
95
|
+
"smol-toml": "^1.8.0",
|
|
95
96
|
"tree-sitter": "0.21.1",
|
|
96
97
|
"tree-sitter-c-sharp": "0.23.1",
|
|
97
98
|
"tree-sitter-cpp": "0.23.2",
|
|
@@ -305,6 +305,9 @@ const NATIVE_ADDON_SMOKE = [
|
|
|
305
305
|
// Filesystem behavior tests — exercise operations that vary across
|
|
306
306
|
// platforms (CRLF, symlinks, permissions, temp dirs)
|
|
307
307
|
const FILESYSTEM = [
|
|
308
|
+
// Cargo membership uses path normalization, descriptor validation, symlinks,
|
|
309
|
+
// and Rust native parsing (including long Windows source strings).
|
|
310
|
+
'test/unit/scope-resolution/rust-cargo-targets.test.ts',
|
|
308
311
|
// The durable ParsedFile store's prune tolerates a chunk directory it cannot
|
|
309
312
|
// delete (#3204). The failures that motivate it — held handles, read-only
|
|
310
313
|
// mounts — are Windows- and macOS-flavored, and the permission-based case
|