From 038f66164e80620463ee3129206cce547cc6da1d Mon Sep 17 00:00:00 2001 From: Long Ho Date: Mon, 1 Jun 2026 10:10:02 -0400 Subject: [PATCH] feat(cli): scope query unresolved diagnostics --- README.md | 9 +++--- crates/codescythe_cli/e2e.rs | 42 ++++++++++++++++++++++++++ crates/codescythe_cli/main.rs | 56 +++++++++++++++++++++++++++++++---- docs/src/render.ts | 12 +++++++- 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c405974..c1a078b 100644 --- a/README.md +++ b/README.md @@ -159,10 +159,11 @@ Text output is optimized for terminal inspection, including the resolved selector kinds, match counts, and a reachability summary that helps explain no-path results. JSON output includes the same diagnostics, stable file/export nodes, and typed import or re-export edges. Pass `--include-unresolved` when -you also need unresolved imports discovered while building the source graph. -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. +you also need every unresolved import discovered while building the source +graph, or `--include-unresolved=related` to limit diagnostics to unresolved +imports from the query endpoints and returned path graph. 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` uses breadth-first search with visited nodes, while `allpaths` intersects forward reachability from the source with reverse reachability from diff --git a/crates/codescythe_cli/e2e.rs b/crates/codescythe_cli/e2e.rs index 8329229..1e87884 100644 --- a/crates/codescythe_cli/e2e.rs +++ b/crates/codescythe_cli/e2e.rs @@ -395,6 +395,11 @@ fn cli_query_includes_unresolved_imports_when_requested() { ) .unwrap(); fs::write(fixture.join("src/module.ts"), "export const used = 1;\n").unwrap(); + fs::write( + fixture.join("src/unrelated.ts"), + "import { unrelated } from '#generated/unrelated';\nconsole.log(unrelated);\n", + ) + .unwrap(); let default_output = Command::new(&cli) .args([ @@ -445,6 +450,43 @@ fn cli_query_includes_unresolved_imports_when_requested() { included_query["unresolved"][0]["specifier"], "#generated/missing" ); + assert_eq!( + included_query["unresolved"][1]["specifier"], + "#generated/unrelated" + ); + + let related_output = Command::new(&cli) + .args([ + "query", + "somepath", + "-C", + path_arg(&fixture), + "--json", + "--include-unresolved=related", + "src/main.ts", + "src/module.ts:used", + ]) + .output() + .expect("failed to run codescythe query with related unresolved imports"); + + assert!( + related_output.status.success(), + "{}", + output_text(&related_output) + ); + let related_query: Value = + serde_json::from_slice(&related_output.stdout).expect("query stdout should be JSON"); + assert_eq!( + related_query["unresolved"][0]["specifier"], + "#generated/missing" + ); + assert_eq!( + related_query["unresolved"] + .as_array() + .expect("unresolved should be an array") + .len(), + 1 + ); fs::remove_dir_all(&fixture).unwrap(); } diff --git a/crates/codescythe_cli/main.rs b/crates/codescythe_cli/main.rs index 1613806..5729847 100644 --- a/crates/codescythe_cli/main.rs +++ b/crates/codescythe_cli/main.rs @@ -87,8 +87,15 @@ struct QueryPathArgs { #[arg(long)] json: bool, - #[arg(long, help = "Include unresolved import diagnostics in JSON output")] - include_unresolved: bool, + #[arg( + long, + value_enum, + num_args = 0..=1, + require_equals = true, + default_missing_value = "all", + help = "Include unresolved import diagnostics in JSON output: all or related" + )] + include_unresolved: Option, #[arg(long, value_enum, default_value_t = QueryOutputFormat::Text)] output: QueryOutputFormat, @@ -102,6 +109,12 @@ enum QueryOutputFormat { Svg, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +enum QueryUnresolvedScope { + All, + Related, +} + fn main() -> ExitCode { match run() { Ok(has_issues) => { @@ -250,9 +263,7 @@ fn run_query_command( } else { args.output }; - if !args.include_unresolved { - result.unresolved.clear(); - } + filter_query_unresolved(&mut result, args.include_unresolved); match output { QueryOutputFormat::Json => { let started = start_profile_timer(); @@ -272,6 +283,41 @@ fn run_query_command( Ok(false) } +fn filter_query_unresolved( + result: &mut codescythe::QueryResult, + include_unresolved: Option, +) { + match include_unresolved { + Some(QueryUnresolvedScope::All) => {} + Some(QueryUnresolvedScope::Related) => { + let related_paths = query_related_paths(result); + result + .unresolved + .retain(|unresolved| related_paths.contains(&unresolved.importer)); + } + None => result.unresolved.clear(), + } +} + +fn query_related_paths(result: &codescythe::QueryResult) -> std::collections::BTreeSet { + let mut paths = result + .source_nodes + .iter() + .chain(&result.target_nodes) + .map(|node| node.path.clone()) + .collect::>(); + + if let Some(graph) = &result.graph { + paths.extend(graph.nodes.iter().map(|node| node.path.clone())); + return paths; + } + + for path in &result.paths { + paths.extend(path.nodes.iter().map(|node| node.path.clone())); + } + paths +} + #[cfg(feature = "profiling")] struct CliProfileTimer(Option); diff --git a/docs/src/render.ts b/docs/src/render.ts index 4756828..0e5f084 100644 --- a/docs/src/render.ts +++ b/docs/src/render.ts @@ -843,6 +843,14 @@ npx codescythe query allpaths \\ h('code', null, 'mermaid-rs-renderer'), '.', ), + h( + 'p', + null, + h('code', null, '--include-unresolved'), + ' adds every unresolved import discovered while building the graph. Use ', + h('code', null, '--include-unresolved=related'), + ' when you only want unresolved imports from the query endpoints and returned path graph files.', + ), h(CodeBlock, null, `From selector: src/main.ts (file selector, 1 matched node) To selector: src/module.ts:used (export selector, 1 matched node) src/main.ts @@ -1175,7 +1183,9 @@ npx codescythe --json --explain-export src/constants.ts:oldFlag`), null, 'Pass ', h('code', null, '--include-unresolved'), - ' to include the full unresolved import diagnostics array in JSON output.', + ' to include the full unresolved import diagnostics array in JSON output. Use ', + h('code', null, '--include-unresolved=related'), + ' to limit that array to imports from files that are part of the query endpoints or returned path graph.', ), ), h(