mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-15 01:29:02 +08:00
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.
The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
isRustNameInScope uses it, so a block's `use` shadows the file's own item
and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
item its path names, and a call to a name a local binds (rust-scopes'
bindings, nearer than any `use`) to nothing, unless the local is a `let`
holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
module the path is written in (rustPathTarget), with today's crate-root
fallback; resolveRustModuleFile, collectRustUseBindings and
rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
"use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
`categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
(ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
file no manifest covers starts it from the nearest lib.rs or main.rs
above it, as the directory walk did.
Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3a, 7 repos, the same sites on the kernel and on wasm:
tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
called or passed as values, 6 `Result`s std's, 1 import through a path
attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
serde +155 / -207 (192 calls to a closure named `test`); dioxus
+2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
changed site's crate checked against the manifests' dependencies;
express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
3.6 KiB
TypeScript
74 lines
3.6 KiB
TypeScript
/**
|
|
* The root a Rust path's `crate::` names, as the manifest says: a `[[bin]]`
|
|
* or `[lib]` with a `path` of its own (ripgrep's `crates/core/main.rs`), and
|
|
* a package whose `name` its manifest writes after an array (axum-core's
|
|
* `categories = [ … ]` first).
|
|
*/
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { CodeGraph } from '../src';
|
|
|
|
const roots: string[] = [];
|
|
const graphs: CodeGraph[] = [];
|
|
|
|
async function index(files: Record<string, string>): Promise<CodeGraph> {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-crate-roots-'));
|
|
roots.push(root);
|
|
for (const [relative, content] of Object.entries(files)) {
|
|
fs.mkdirSync(path.dirname(path.join(root, relative)), { recursive: true });
|
|
fs.writeFileSync(path.join(root, relative), content);
|
|
}
|
|
const cg = await CodeGraph.init(root, { index: true });
|
|
graphs.push(cg);
|
|
return cg;
|
|
}
|
|
|
|
let bin: CodeGraph;
|
|
let member: CodeGraph;
|
|
|
|
beforeAll(async () => {
|
|
bin = await index({
|
|
'Cargo.toml': '[package]\nname = "ripgrep"\nversion = "0.1.0"\nedition = "2021"\n\n[[bin]]\nbench = false\npath = "crates/core/main.rs"\nname = "rg"\n',
|
|
'crates/core/main.rs': 'mod flags;\nmod messages;\n\nfn main() {\n let _ = messages::errored();\n}\n',
|
|
'crates/core/messages.rs': 'pub fn errored() -> bool {\n false\n}\n',
|
|
'crates/core/flags/mod.rs': 'pub mod parse;\npub mod doc;\n\npub enum Category {\n Input,\n}\n',
|
|
'crates/core/flags/parse.rs': 'pub fn lookup() -> usize {\n 1\n}\n',
|
|
'crates/core/flags/doc.rs': 'use crate::flags::Category;\n\npub fn generate() -> usize {\n crate::flags::parse::lookup()\n}\n',
|
|
// Namesakes the paths must not reach.
|
|
'crates/core/flags/doc/parse.rs': 'pub fn lookup() -> usize {\n 2\n}\n',
|
|
'crates/core/other.rs': 'pub fn errored() -> bool {\n true\n}\npub enum Category {\n Output,\n}\n',
|
|
});
|
|
member = await index({
|
|
'Cargo.toml': '[workspace]\nmembers = ["core-lib"]\n',
|
|
'core-lib/Cargo.toml': '[package]\ncategories = ["asynchronous", "network-programming"]\ndescription = "Core types"\nedition = "2021"\nname = "core-lib"\nversion = "0.1.0"\n',
|
|
'core-lib/src/lib.rs': 'pub mod body;\npub mod other;\n',
|
|
'core-lib/src/body.rs': 'pub struct Body;\n\n#[cfg(test)]\nmod tests {\n use crate::body::Body;\n}\n',
|
|
'core-lib/src/other.rs': 'pub struct Body;\n',
|
|
});
|
|
});
|
|
|
|
afterAll(() => {
|
|
for (const cg of graphs) cg.close();
|
|
for (const root of roots) fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
const describeEdges = (cg: CodeGraph, file: string, kind: string, name?: string): string[] => {
|
|
const ids = cg.getNodesInFile(file).filter((n) => name === undefined || n.name === name).map((n) => n.id);
|
|
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === kind)
|
|
.map((e) => cg.getNode(e.target)!).map((t) => `${t.kind} ${t.qualifiedName} ${t.filePath}:${t.startLine}`);
|
|
};
|
|
|
|
describe('the crate a Rust path starts from', () => {
|
|
it('is the binary a manifest places with a path of its own', () => {
|
|
expect(describeEdges(bin, 'crates/core/flags/doc.rs', 'calls', 'generate')).toEqual(['function lookup crates/core/flags/parse.rs:1']);
|
|
expect(describeEdges(bin, 'crates/core/flags/doc.rs', 'imports')).toEqual(['enum Category crates/core/flags/mod.rs:4']);
|
|
expect(describeEdges(bin, 'crates/core/main.rs', 'calls', 'main')).toEqual(['function errored crates/core/messages.rs:1']);
|
|
});
|
|
|
|
it('is a package whose manifest writes its name after an array', () => {
|
|
expect(describeEdges(member, 'core-lib/src/body.rs', 'imports')).toEqual(['struct Body core-lib/src/body.rs:1']);
|
|
});
|
|
});
|