gitnexus 1.6.10-rc.76 → 1.6.10-rc.78
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.
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
const REF_PREFIX_RE = /^&\s*(mut\s+)?/;
|
|
2
2
|
const PTR_PREFIX_RE = /^\*\s*(const|mut)?\s*/;
|
|
3
|
+
const DYN_PREFIX_RE = /^dyn\s+/;
|
|
3
4
|
const ENUM_VARIANT_NAMES = new Set(['Some', 'None', 'Ok', 'Err']);
|
|
5
|
+
// `dyn Trait`, `&dyn Trait`, `Box<dyn Trait>` all name a trait object whose
|
|
6
|
+
// receiver-dispatch target is the trait itself (#2604) — strip the `dyn`
|
|
7
|
+
// keyword and any auto-trait/lifetime bound list (`dyn Trait + Send`) down to
|
|
8
|
+
// the principal trait name. Reference/pointer sigils are stripped by the
|
|
9
|
+
// caller first; wrapper unwrapping (Box<T> etc.) runs before this so the
|
|
10
|
+
// unwrapped inner text still gets the same treatment.
|
|
11
|
+
function stripDynBound(t) {
|
|
12
|
+
if (!DYN_PREFIX_RE.test(t))
|
|
13
|
+
return t;
|
|
14
|
+
t = t.replace(DYN_PREFIX_RE, '');
|
|
15
|
+
const plus = t.indexOf('+');
|
|
16
|
+
if (plus !== -1)
|
|
17
|
+
t = t.slice(0, plus);
|
|
18
|
+
return t.trim();
|
|
19
|
+
}
|
|
4
20
|
// ─── interpretImport ──────────────────────────────────────────────────────
|
|
5
21
|
export function interpretRustImport(captures) {
|
|
6
22
|
const kind = captures['@import.kind']?.text;
|
|
@@ -104,6 +120,7 @@ export function normalizeRustTypeName(text) {
|
|
|
104
120
|
if (inner !== null)
|
|
105
121
|
t = inner;
|
|
106
122
|
}
|
|
123
|
+
t = stripDynBound(t);
|
|
107
124
|
const bracket = t.indexOf('<');
|
|
108
125
|
if (bracket !== -1)
|
|
109
126
|
t = t.slice(0, bracket);
|
|
@@ -168,6 +185,7 @@ function normalizeRustReturnType(text) {
|
|
|
168
185
|
}
|
|
169
186
|
}
|
|
170
187
|
}
|
|
188
|
+
t = stripDynBound(t);
|
|
171
189
|
const bracket = t.indexOf('<');
|
|
172
190
|
if (bracket !== -1)
|
|
173
191
|
t = t.slice(0, bracket);
|
|
@@ -9,6 +9,7 @@ const RUST_SCOPE_QUERY = `
|
|
|
9
9
|
(enum_item) @scope.class
|
|
10
10
|
(union_item) @scope.class
|
|
11
11
|
(function_item) @scope.function
|
|
12
|
+
(function_signature_item) @scope.function
|
|
12
13
|
(closure_expression) @scope.function
|
|
13
14
|
(block) @scope.block
|
|
14
15
|
(if_expression) @scope.block
|
|
@@ -54,6 +55,14 @@ const RUST_SCOPE_QUERY = `
|
|
|
54
55
|
(function_item
|
|
55
56
|
name: (identifier) @declaration.name) @declaration.function
|
|
56
57
|
|
|
58
|
+
;; Declarations — trait method signature (required method, no body,
|
|
59
|
+
;; e.g. fn foo(self) -> T; inside a trait body). Without this, an abstract
|
|
60
|
+
;; trait method is invisible to scope resolution — never owned by its
|
|
61
|
+
;; trait's Class scope, so a dyn Trait receiver can never dispatch to
|
|
62
|
+
;; it (#2604).
|
|
63
|
+
(function_signature_item
|
|
64
|
+
name: (identifier) @declaration.name) @declaration.function
|
|
65
|
+
|
|
57
66
|
;; Declarations — struct fields
|
|
58
67
|
(field_declaration
|
|
59
68
|
name: (field_identifier) @declaration.name
|
|
@@ -3549,28 +3549,13 @@ export class LocalBackend {
|
|
|
3549
3549
|
if (oldName === new_name) {
|
|
3550
3550
|
return { error: 'New name is the same as the current name.' };
|
|
3551
3551
|
}
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
};
|
|
3560
|
-
// The definition itself
|
|
3561
|
-
if (sym.filePath && sym.startLine) {
|
|
3562
|
-
try {
|
|
3563
|
-
const content = await fs.readFile(assertSafePath(sym.filePath), 'utf-8');
|
|
3564
|
-
const lines = content.split('\n');
|
|
3565
|
-
const lineIdx = sym.startLine - 1;
|
|
3566
|
-
if (lineIdx >= 0 && lineIdx < lines.length && lines[lineIdx].includes(oldName)) {
|
|
3567
|
-
const defRegex = new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g');
|
|
3568
|
-
addEdit(sym.filePath, sym.startLine, lines[lineIdx].trim(), lines[lineIdx].replace(defRegex, new_name).trim(), 'graph');
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
catch (e) {
|
|
3572
|
-
logQueryError('rename:read-definition', e);
|
|
3573
|
-
}
|
|
3552
|
+
const escapedOldName = oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3553
|
+
// Classify each file to rewrite by how it was discovered. Definition and
|
|
3554
|
+
// graph-ref files carry graph confidence; files found only by text search
|
|
3555
|
+
// carry text_search confidence. A graph-classified file is never downgraded.
|
|
3556
|
+
const fileConfidence = new Map();
|
|
3557
|
+
if (sym.filePath) {
|
|
3558
|
+
fileConfidence.set(sym.filePath, 'graph');
|
|
3574
3559
|
}
|
|
3575
3560
|
// All incoming refs from graph (callers, importers, etc.)
|
|
3576
3561
|
const allIncoming = [
|
|
@@ -3579,31 +3564,12 @@ export class LocalBackend {
|
|
|
3579
3564
|
...(lookupResult.incoming.extends || []),
|
|
3580
3565
|
...(lookupResult.incoming.implements || []),
|
|
3581
3566
|
];
|
|
3582
|
-
let graphEdits = changes.size > 0 ? 1 : 0; // count definition edit
|
|
3583
3567
|
for (const ref of allIncoming) {
|
|
3584
|
-
if (
|
|
3585
|
-
|
|
3586
|
-
try {
|
|
3587
|
-
const content = await fs.readFile(assertSafePath(ref.filePath), 'utf-8');
|
|
3588
|
-
const lines = content.split('\n');
|
|
3589
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3590
|
-
if (lines[i].includes(oldName)) {
|
|
3591
|
-
addEdit(ref.filePath, i + 1, lines[i].trim(), lines[i]
|
|
3592
|
-
.replace(new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g'), new_name)
|
|
3593
|
-
.trim(), 'graph');
|
|
3594
|
-
graphEdits++;
|
|
3595
|
-
break; // one edit per file from graph refs
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3599
|
-
catch (e) {
|
|
3600
|
-
logQueryError('rename:read-ref', e);
|
|
3568
|
+
if (ref.filePath) {
|
|
3569
|
+
fileConfidence.set(ref.filePath, 'graph');
|
|
3601
3570
|
}
|
|
3602
3571
|
}
|
|
3603
|
-
//
|
|
3604
|
-
let astSearchEdits = 0;
|
|
3605
|
-
const graphFiles = new Set([sym.filePath, ...allIncoming.map((r) => r.filePath)].filter(Boolean));
|
|
3606
|
-
// Simple text search across the repo for the old name (in files not already covered by graph)
|
|
3572
|
+
// Text search for files the graph might have missed entirely.
|
|
3607
3573
|
try {
|
|
3608
3574
|
const { execFileSync } = await import('child_process');
|
|
3609
3575
|
const rgArgs = [
|
|
@@ -3629,61 +3595,97 @@ export class LocalBackend {
|
|
|
3629
3595
|
.filter((f) => f.length > 0);
|
|
3630
3596
|
for (const file of files) {
|
|
3631
3597
|
const normalizedFile = file.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
const content = await fs.readFile(assertSafePath(normalizedFile), 'utf-8');
|
|
3636
|
-
const lines = content.split('\n');
|
|
3637
|
-
const regex = new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g');
|
|
3638
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3639
|
-
regex.lastIndex = 0;
|
|
3640
|
-
if (regex.test(lines[i])) {
|
|
3641
|
-
regex.lastIndex = 0;
|
|
3642
|
-
addEdit(normalizedFile, i + 1, lines[i].trim(), lines[i].replace(regex, new_name).trim(), 'text_search');
|
|
3643
|
-
astSearchEdits++;
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
catch (e) {
|
|
3648
|
-
logQueryError('rename:text-search-read', e);
|
|
3598
|
+
// Never downgrade a graph-classified file to text_search.
|
|
3599
|
+
if (!fileConfidence.has(normalizedFile)) {
|
|
3600
|
+
fileConfidence.set(normalizedFile, 'text_search');
|
|
3649
3601
|
}
|
|
3650
3602
|
}
|
|
3651
3603
|
}
|
|
3652
3604
|
catch (e) {
|
|
3653
3605
|
logQueryError('rename:ripgrep', e);
|
|
3654
3606
|
}
|
|
3655
|
-
//
|
|
3656
|
-
|
|
3657
|
-
|
|
3607
|
+
// Enumerate every `\boldName\b` line in each file to rewrite, so the previewed
|
|
3608
|
+
// file set is exactly the set the apply loop below rewrites. A file with no
|
|
3609
|
+
// matching line is dropped (apply would write nothing to it). `wordTest`
|
|
3610
|
+
// (non-global) probes each line; `wordReplace` (global) rewrites it and is
|
|
3611
|
+
// reused by the apply loop — compiled once each rather than once per line,
|
|
3612
|
+
// and one escaping formula serves both passes.
|
|
3613
|
+
const wordTest = new RegExp(`\\b${escapedOldName}\\b`);
|
|
3614
|
+
const wordReplace = new RegExp(`\\b${escapedOldName}\\b`, 'g');
|
|
3615
|
+
const changes = new Map();
|
|
3616
|
+
for (const [filePath, confidence] of fileConfidence) {
|
|
3617
|
+
try {
|
|
3618
|
+
const content = await fs.readFile(assertSafePath(filePath), 'utf-8');
|
|
3619
|
+
const lines = content.split('\n');
|
|
3620
|
+
const edits = [];
|
|
3621
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3622
|
+
if (!wordTest.test(lines[i])) {
|
|
3623
|
+
continue;
|
|
3624
|
+
}
|
|
3625
|
+
edits.push({
|
|
3626
|
+
line: i + 1,
|
|
3627
|
+
old_text: lines[i].trim(),
|
|
3628
|
+
new_text: lines[i].replace(wordReplace, new_name).trim(),
|
|
3629
|
+
confidence,
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
if (edits.length > 0) {
|
|
3633
|
+
changes.set(filePath, { file_path: filePath, edits });
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
catch (e) {
|
|
3637
|
+
logQueryError('rename:enumerate', e);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
// Step 4: Apply or preview.
|
|
3658
3641
|
const failedFiles = [];
|
|
3659
3642
|
if (!dry_run) {
|
|
3660
|
-
|
|
3661
|
-
for (const change of allChanges) {
|
|
3643
|
+
for (const change of changes.values()) {
|
|
3662
3644
|
try {
|
|
3663
3645
|
const fullPath = assertSafePath(change.file_path);
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
content = content.replace(regex, new_name);
|
|
3667
|
-
await fs.writeFile(fullPath, content, 'utf-8');
|
|
3646
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
3647
|
+
await fs.writeFile(fullPath, content.replace(wordReplace, new_name), 'utf-8');
|
|
3668
3648
|
}
|
|
3669
3649
|
catch (e) {
|
|
3670
|
-
// A swallowed write failure must not be reported as
|
|
3671
|
-
//
|
|
3672
|
-
// with the unwritten files listed, rather than masquerading as done.
|
|
3650
|
+
// A swallowed write failure must not be reported as success (#2283):
|
|
3651
|
+
// record the file so the result degrades to 'partial'.
|
|
3673
3652
|
logQueryError('rename:apply-edit', e);
|
|
3674
3653
|
failedFiles.push(change.file_path);
|
|
3675
3654
|
}
|
|
3676
3655
|
}
|
|
3656
|
+
// A file whose write threw did not land, so drop its edits from the
|
|
3657
|
+
// reported result — total_edits/changes must describe what actually
|
|
3658
|
+
// reached disk, not what was attempted (#2605: the report matches reality
|
|
3659
|
+
// even on partial failure). failed_files still names every dropped file.
|
|
3660
|
+
for (const f of failedFiles) {
|
|
3661
|
+
changes.delete(f);
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
// Counts derive from the reported set (dry-run: every enumerated file;
|
|
3665
|
+
// apply: only files that landed), so the graph/text_search split always
|
|
3666
|
+
// sums to total_edits and never overstates a partial apply.
|
|
3667
|
+
const reported = Array.from(changes.values());
|
|
3668
|
+
let graphEdits = 0;
|
|
3669
|
+
let astSearchEdits = 0;
|
|
3670
|
+
for (const change of reported) {
|
|
3671
|
+
for (const edit of change.edits) {
|
|
3672
|
+
if (edit.confidence === 'graph') {
|
|
3673
|
+
graphEdits++;
|
|
3674
|
+
}
|
|
3675
|
+
else {
|
|
3676
|
+
astSearchEdits++;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3677
3679
|
}
|
|
3678
3680
|
return {
|
|
3679
3681
|
status: failedFiles.length > 0 ? 'partial' : 'success',
|
|
3680
3682
|
old_name: oldName,
|
|
3681
3683
|
new_name,
|
|
3682
|
-
files_affected:
|
|
3683
|
-
total_edits:
|
|
3684
|
+
files_affected: reported.length,
|
|
3685
|
+
total_edits: graphEdits + astSearchEdits,
|
|
3684
3686
|
graph_edits: graphEdits,
|
|
3685
3687
|
text_search_edits: astSearchEdits,
|
|
3686
|
-
changes:
|
|
3688
|
+
changes: reported,
|
|
3687
3689
|
applied: !dry_run,
|
|
3688
3690
|
...(failedFiles.length > 0 && { failed_files: failedFiles }),
|
|
3689
3691
|
};
|
|
@@ -411,8 +411,15 @@ export interface RepoMeta {
|
|
|
411
411
|
* `Record` node and its `HAS_METHOD` edges for every unchanged record file
|
|
412
412
|
* (same v7 contract: new nodes/edges the incremental path would otherwise
|
|
413
413
|
* never backfill); force a full re-analyze instead.
|
|
414
|
-
|
|
415
|
-
|
|
414
|
+
* v11: Rust abstract trait methods (`fn foo(&self) -> T;`, no body) now get a
|
|
415
|
+
* scope + declaration capture (#2604): RUST_SCOPE_QUERY had no
|
|
416
|
+
* `function_signature_item` pattern, so a `&dyn Trait` receiver could never
|
|
417
|
+
* dispatch a CALLS edge to the trait's own method. Same v7/v10 contract: the
|
|
418
|
+
* incremental write set only covers changed files, so a top-up against a
|
|
419
|
+
* pre-v11 index would keep silently missing these CALLS edges for every
|
|
420
|
+
* unchanged Rust trait file; force a full re-analyze instead.
|
|
421
|
+
*/
|
|
422
|
+
export declare const INCREMENTAL_SCHEMA_VERSION = 11;
|
|
416
423
|
export interface IndexedRepo {
|
|
417
424
|
repoPath: string;
|
|
418
425
|
storagePath: string;
|
|
@@ -132,8 +132,15 @@ export const cloneDirBelongsToEntry = (cloneDir, entryPath) => registryPathEqual
|
|
|
132
132
|
* `Record` node and its `HAS_METHOD` edges for every unchanged record file
|
|
133
133
|
* (same v7 contract: new nodes/edges the incremental path would otherwise
|
|
134
134
|
* never backfill); force a full re-analyze instead.
|
|
135
|
+
* v11: Rust abstract trait methods (`fn foo(&self) -> T;`, no body) now get a
|
|
136
|
+
* scope + declaration capture (#2604): RUST_SCOPE_QUERY had no
|
|
137
|
+
* `function_signature_item` pattern, so a `&dyn Trait` receiver could never
|
|
138
|
+
* dispatch a CALLS edge to the trait's own method. Same v7/v10 contract: the
|
|
139
|
+
* incremental write set only covers changed files, so a top-up against a
|
|
140
|
+
* pre-v11 index would keep silently missing these CALLS edges for every
|
|
141
|
+
* unchanged Rust trait file; force a full re-analyze instead.
|
|
135
142
|
*/
|
|
136
|
-
export const INCREMENTAL_SCHEMA_VERSION =
|
|
143
|
+
export const INCREMENTAL_SCHEMA_VERSION = 11;
|
|
137
144
|
const GITNEXUS_DIR = '.gitnexus';
|
|
138
145
|
const GITNEXUS_EXCLUDE_ENTRY = `${GITNEXUS_DIR}/`;
|
|
139
146
|
export const INDEX_METADATA_FILE = 'gitnexus.json';
|
package/package.json
CHANGED