Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,11 @@ Selectors are resolved after graph construction:
- Directory selectors match every project file node under that directory.
- Export selectors match one exported-symbol node.

`somepath` and `somepaths` run breadth-first search from the resolved source
nodes. They keep one visited bit and one parent edge per node, so dependency
cycles cannot create infinite walks. `somepath` stops after the first matched
target. `somepaths` keeps traversing and reconstructs one shortest path for each
reachable matched target.
`somepath` runs breadth-first search from the resolved source nodes. It keeps
one visited bit and one parent edge per node, so dependency cycles cannot create
infinite walks. It keeps traversing after the first matched target and
reconstructs one shortest path for each reachable matched target. File and
export selectors usually match one target; directory selectors can match many.

`allpaths` intentionally does not enumerate every simple path. It computes the
set of nodes reachable forward from the sources, the set of nodes that can reach
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The public Rust API is intentionally narrow:
returns a fix report.
- `codescythe::doctor(cwd, config_path)` returns config-risk diagnostics.
- `codescythe::query(cwd, config_path, request)` returns dependency paths or a
path subgraph for `somepath`, `somepaths`, and `allpaths`.
path subgraph for `somepath` and `allpaths`.
- `codescythe::render_query_mermaid(result)` and
`codescythe::render_query_svg(result)` render query results as diagrams.

Expand Down
41 changes: 28 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Use `query` to inspect dependency paths through the same source graph:

```sh
codescythe query somepath src/main.ts src/module.ts
codescythe query somepaths src/main.ts src/features/
codescythe query somepath src/main.ts src/features/
codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --json
codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output mermaid
codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output svg > graph.svg
Expand All @@ -149,10 +149,9 @@ Selectors can point at files, directories, or exported symbols written as
`<file>:<symbol>`. Relative selectors are resolved from the analysis root chosen
by `-C` or `--config`.

- `somepath` returns one shortest path from the source selector to any matched
target.
- `somepaths` returns one shortest path per reachable matched target, which is
useful for file-to-folder queries.
- `somepath` returns one shortest path per reachable matched target. File and
export targets usually match one target, while directory targets can match
many.
- `allpaths` returns the subgraph of every node and edge that lies on a path
from the source selector to the target selector.

Expand All @@ -161,15 +160,19 @@ file/export nodes and typed import or re-export edges. Mermaid output renders
the same query graph as a `flowchart LR` diagram, and SVG output renders that
Mermaid source with the pure-Rust `mermaid-rs-renderer` crate.

`somepath` and `somepaths` use breadth-first search with visited nodes, while
`allpaths` intersects forward reachability from the source with reverse
reachability from the target. That makes dependency cycles finite without
enumerating every possible walk through the graph.
`somepath` uses breadth-first search with visited nodes, while `allpaths`
intersects forward reachability from the source with reverse reachability from
the target. That makes dependency cycles finite without enumerating every
possible walk through the graph.

Fixture-backed Mermaid examples:

```sh
codescythe query somepath -C tests/fixtures/test-file-usage --output mermaid src/main.ts src/module.ts:used
codescythe query somepath \
-C tests/fixtures/test-file-usage \
--output mermaid \
src/main.ts \
src/module.ts:used
```

```mermaid
Expand All @@ -180,7 +183,11 @@ flowchart LR
```

```sh
codescythe query somepaths -C tests/fixtures/oxc-resolution --output mermaid app/index.ts app/
codescythe query somepath \
-C tests/fixtures/oxc-resolution \
--output mermaid \
app/index.ts \
app/
```

```mermaid
Expand All @@ -201,7 +208,11 @@ flowchart LR
```

```sh
codescythe query allpaths -C tests/fixtures/knip-export-basics --output mermaid index.ts my-namespace.ts:y
codescythe query allpaths \
-C tests/fixtures/knip-export-basics \
--output mermaid \
index.ts \
my-namespace.ts:y
```

```mermaid
Expand All @@ -216,7 +227,11 @@ flowchart LR
```

```sh
codescythe query somepath -C tests/fixtures/runfiles-fixture --output mermaid workspace/frontend/apps/client/platform/platformRuntime.ts protobuf/generated/client.ts:client
codescythe query somepath \
-C tests/fixtures/runfiles-fixture \
--output mermaid \
workspace/frontend/apps/client/platform/platformRuntime.ts \
protobuf/generated/client.ts:client
```

```mermaid
Expand Down
11 changes: 4 additions & 7 deletions crates/codescythe/analyze/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use super::*;
#[serde(rename_all = "camelCase")]
pub enum QueryKind {
Somepath,
Somepaths,
Allpaths,
}

Expand Down Expand Up @@ -256,13 +255,12 @@ pub fn query_path(

let target_set = target_indexes.iter().copied().collect::<HashSet<_>>();
let paths = match request.kind {
QueryKind::Somepath => shortest_paths(&graph, &source_indexes, &target_set, true),
QueryKind::Somepaths => shortest_paths(&graph, &source_indexes, &target_set, false),
QueryKind::Somepath => shortest_paths(&graph, &source_indexes, &target_set),
QueryKind::Allpaths => Vec::new(),
};
let path_graph = match request.kind {
QueryKind::Allpaths => Some(allpaths_graph(&graph, &source_indexes, &target_set)),
QueryKind::Somepath | QueryKind::Somepaths => None,
QueryKind::Somepath => None,
};

Ok(QueryResult {
Expand Down Expand Up @@ -873,7 +871,6 @@ fn shortest_paths(
graph: &QueryGraphIndex,
sources: &[usize],
targets: &HashSet<usize>,
stop_after_first: bool,
) -> Vec<QueryPath> {
let mut queue = VecDeque::new();
let mut seen = vec![false; graph.nodes.len()];
Expand All @@ -887,8 +884,8 @@ fn shortest_paths(

let mut found = BTreeSet::<usize>::new();
while let Some(node) = queue.pop_front() {
if targets.contains(&node) && found.insert(node) && stop_after_first {
break;
if targets.contains(&node) {
found.insert(node);
}

for edge in &graph.outgoing[node] {
Expand Down
68 changes: 33 additions & 35 deletions crates/codescythe/analyze/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1519,7 +1519,7 @@ fn query_somepath_tracks_named_export_edges() {
}

#[test]
fn query_somepaths_returns_one_path_per_reachable_directory_file() {
fn query_somepath_returns_one_path_per_reachable_directory_file() {
let result = query_inline_project(
&[
(
Expand All @@ -1531,7 +1531,7 @@ fn query_somepaths_returns_one_path_per_reachable_directory_file() {
("src/deps/dead.ts", "export const dead = 1;\n"),
],
QueryRequest {
kind: QueryKind::Somepaths,
kind: QueryKind::Somepath,
from: "src/main.ts".to_string(),
to: "src/deps/".to_string(),
},
Expand Down Expand Up @@ -1599,39 +1599,37 @@ fn query_allpaths_returns_path_subgraph_without_dead_edges() {
}

#[test]
fn query_somepath_variants_handle_circular_dependencies() {
for kind in [QueryKind::Somepath, QueryKind::Somepaths] {
let result = query_inline_project(
cycle_query_files(),
QueryRequest {
kind,
from: "src/root.ts".to_string(),
to: "src/targets/sink.ts".to_string(),
},
);

assert_eq!(result.paths.len(), 1);
assert_eq!(
result.paths[0]
.nodes
.iter()
.map(query_node_label)
.collect::<Vec<_>>(),
vec![
"src/root.ts",
"src/cycle/a.ts:a",
"src/cycle/a.ts",
"src/cycle/b.ts:b",
"src/cycle/b.ts",
"src/targets/sink.ts:sink",
"src/targets/sink.ts",
]
);
assert!(
result.paths[0].edges.len() < 10,
"{kind:?} should terminate after a finite acyclic shortest path"
);
}
fn query_somepath_handles_circular_dependencies() {
let result = query_inline_project(
cycle_query_files(),
QueryRequest {
kind: QueryKind::Somepath,
from: "src/root.ts".to_string(),
to: "src/targets/sink.ts".to_string(),
},
);

assert_eq!(result.paths.len(), 1);
assert_eq!(
result.paths[0]
.nodes
.iter()
.map(query_node_label)
.collect::<Vec<_>>(),
vec![
"src/root.ts",
"src/cycle/a.ts:a",
"src/cycle/a.ts",
"src/cycle/b.ts:b",
"src/cycle/b.ts",
"src/targets/sink.ts:sink",
"src/targets/sink.ts",
]
);
assert!(
result.paths[0].edges.len() < 10,
"somepath should terminate after a finite acyclic shortest path"
);
}

#[test]
Expand Down
4 changes: 1 addition & 3 deletions crates/codescythe_cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ struct QueryArgs {
#[derive(Debug, Subcommand)]
enum QueryCommand {
Somepath(QueryPathArgs),
Somepaths(QueryPathArgs),
Allpaths(QueryPathArgs),
}

Expand Down Expand Up @@ -214,7 +213,6 @@ fn run_command(command: Command) -> Result<bool> {
fn run_query_command(args: QueryArgs) -> Result<bool> {
let (kind, args) = match args.command {
QueryCommand::Somepath(args) => (codescythe::QueryKind::Somepath, args),
QueryCommand::Somepaths(args) => (codescythe::QueryKind::Somepaths, args),
QueryCommand::Allpaths(args) => (codescythe::QueryKind::Allpaths, args),
};
let config = args.config.as_deref();
Expand Down Expand Up @@ -433,7 +431,7 @@ fn print_explain_export(analysis: &codescythe::Analysis) {

fn print_query_report(result: &codescythe::QueryResult) {
match result.kind {
codescythe::QueryKind::Somepath | codescythe::QueryKind::Somepaths => {
codescythe::QueryKind::Somepath => {
if result.paths.is_empty() {
println!(
"No path found from {} to {}",
Expand Down
2 changes: 1 addition & 1 deletion docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DOCS_TOOL_DATA = [
"//:node_modules/open-props",
"//:node_modules/react",
"//:node_modules/react-dom",
]
] + glob(["src/assets/query/*.svg"])

js_binary(
name = "render_docs",
Expand Down
1 change: 1 addition & 0 deletions docs/src/assets/query/knip-export-basics-allpaths.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading