use super::super::super::command::{
BodyOptions, FindOptions, JqQueryOptions, QueryKind, QueryOptions,
};
use super::super::{
DispatchResult, GlobalOptions, load_or_create_query_snapshot_for_roots, load_or_create_snapshot,
};
pub fn handle_find_where_symbol_command(
opts: &FindOptions,
global: &GlobalOptions,
) -> DispatchResult {
use crate::query::query_where_symbol;
let target = opts
.query
.clone()
.or_else(|| opts.queries.first().cloned())
.unwrap_or_default();
if target.is_empty() {
eprintln!("Error: Query cannot be empty");
return DispatchResult::Exit(1);
}
let roots = vec![std::path::PathBuf::from(".")];
let query_global = query_global_options(global);
let snapshot = match load_or_create_query_snapshot_for_roots(&roots, &query_global) {
Ok(s) => s,
Err(e) => {
eprintln!("[loct][error] {}", e);
return DispatchResult::Exit(1);
}
};
let result = query_where_symbol(&snapshot, &target);
if global.json {
match serde_json::to_string_pretty(&result) {
Ok(json) => println!("{}", json),
Err(e) => {
eprintln!("[loct][error] Failed to serialize results: {}", e);
return DispatchResult::Exit(1);
}
}
} else {
println!("where-symbol '{}':", result.target);
if result.results.is_empty() {
println!(" (no results)");
} else {
for m in &result.results {
if let Some(line) = m.line {
print!(" {}:{}", m.file, line);
} else {
print!(" {}", m.file);
}
if let Some(ref ctx) = m.context {
print!(" - {}", ctx);
}
println!();
}
}
}
DispatchResult::Exit(0)
}
pub fn handle_body_command(opts: &BodyOptions, global: &GlobalOptions) -> DispatchResult {
use crate::body::query_symbol_body;
let roots = vec![std::path::PathBuf::from(".")];
let query_global = query_global_options(global);
let snapshot = match load_or_create_query_snapshot_for_roots(&roots, &query_global) {
Ok(s) => s,
Err(e) => {
eprintln!("[loct][error] {}", e);
return DispatchResult::Exit(1);
}
};
let result = query_symbol_body(&snapshot, &opts.symbol, opts.line_cap);
if global.json {
match serde_json::to_string_pretty(&result) {
Ok(json) => println!("{}", json),
Err(e) => {
eprintln!("[loct][error] Failed to serialize results: {}", e);
return DispatchResult::Exit(1);
}
}
return DispatchResult::Exit(if result.bodies.is_empty() { 1 } else { 0 });
}
if result.bodies.is_empty() {
println!("body '{}': (no source body found)", result.symbol);
println!(
" hint: run `loct query where-symbol {}` to locate the symbol first.",
result.symbol
);
return DispatchResult::Exit(1);
}
if result.bodies.len() > 1 {
println!(
"body '{}': multiple exact definitions found; choose one:",
result.symbol
);
for body in &result.bodies {
println!(
" {}:{}-{} [{}]",
body.file, body.start_line, body.end_line, body.language
);
}
println!(" hint: use a qualified symbol when available, e.g. Type::method.");
return DispatchResult::Exit(1);
}
for body in &result.bodies {
println!(
"── {} [{}] {}:{}-{} ──",
body.symbol, body.language, body.file, body.start_line, body.end_line
);
println!("{}", body.source);
if body.truncated {
println!(
" … truncated: showing {} of {} lines (cap {}). Use --max-lines to widen.",
body.end_line - body.start_line + 1,
body.total_lines,
body.line_cap
);
}
println!();
}
DispatchResult::Exit(0)
}
pub fn handle_query_command(opts: &QueryOptions, global: &GlobalOptions) -> DispatchResult {
use crate::query::{
SwiftTypeResolutionStatus, classify_swift_type_references, query_component_of,
query_where_symbol, query_who_imports,
};
let roots = vec![std::path::PathBuf::from(".")];
let query_global = query_global_options(global);
let snapshot = match load_or_create_query_snapshot_for_roots(&roots, &query_global) {
Ok(s) => s,
Err(e) => {
eprintln!("[loct][error] {}", e);
return DispatchResult::Exit(1);
}
};
if matches!(opts.kind, QueryKind::SwiftTypes) {
let source = match std::fs::read_to_string(&opts.target) {
Ok(source) => source,
Err(e) => {
eprintln!("[loct][error] Failed to read {}: {}", opts.target, e);
return DispatchResult::Exit(1);
}
};
let result = classify_swift_type_references(&snapshot, &opts.target, &source);
if global.json {
match serde_json::to_string_pretty(&result) {
Ok(json) => println!("{}", json),
Err(e) => {
eprintln!("[loct][error] Failed to serialize results: {}", e);
return DispatchResult::Exit(1);
}
}
} else {
println!("swift-types '{}':", result.target);
if result.references.is_empty() {
println!(" (no type-position references)");
} else {
for reference in &result.references {
match reference.status {
SwiftTypeResolutionStatus::Resolved => {
if let Some(definition) = &reference.definition {
let line = definition
.line
.map(|line| format!(":{}", line))
.unwrap_or_default();
print!(
" {}: RESOLVED -> {}{}",
reference.name, definition.file, line
);
if let Some(ctx) = &definition.context {
print!(" - {}", ctx);
}
println!(" (ref line {})", reference.line);
} else {
println!(
" {}: RESOLVED (ref line {})",
reference.name, reference.line
);
}
}
SwiftTypeResolutionStatus::External => {
println!(
" {}: EXTERNAL (Swift/Foundation/SwiftUI allowlist, ref line {})",
reference.name, reference.line
);
}
SwiftTypeResolutionStatus::Unresolved => {
let symbol_id = reference.symbol_id.as_deref().unwrap_or("");
println!(
" {}: UNRESOLVED {} (ref line {})",
reference.name, symbol_id, reference.line
);
}
}
}
}
}
return DispatchResult::Exit(0);
}
let result = match opts.kind {
QueryKind::WhoImports => query_who_imports(&snapshot, &opts.target),
QueryKind::WhereSymbol => query_where_symbol(&snapshot, &opts.target),
QueryKind::ComponentOf => query_component_of(&snapshot, &opts.target),
QueryKind::SwiftTypes => unreachable!("swift-types handled before QueryResult path"),
};
if global.json {
match serde_json::to_string_pretty(&result) {
Ok(json) => println!("{}", json),
Err(e) => {
eprintln!("[loct][error] Failed to serialize results: {}", e);
return DispatchResult::Exit(1);
}
}
} else {
println!("{} '{}':", result.kind, result.target);
if result.results.is_empty() {
println!(" (no results)");
} else {
for m in &result.results {
if let Some(line) = m.line {
print!(" {}:{}", m.file, line);
} else {
print!(" {}", m.file);
}
if let Some(ref ctx) = m.context {
print!(" - {}", ctx);
}
println!();
}
}
}
DispatchResult::Exit(0)
}
fn query_global_options(global: &GlobalOptions) -> GlobalOptions {
let mut scoped = global.clone();
if !scoped.verbose {
scoped.quiet = true;
}
scoped
}
pub fn handle_jq_query_command(opts: &JqQueryOptions, global: &GlobalOptions) -> DispatchResult {
use crate::jaq_query::{JaqExecutor, format_output};
use std::path::Path;
let snapshot = if let Some(ref explicit_path) = opts.snapshot_path {
use crate::snapshot::Snapshot;
let snapshot_path = match Snapshot::find_latest_snapshot(Some(explicit_path.as_ref())) {
Ok(p) => p,
Err(e) => {
eprintln!("[loct][error] {}", e);
eprintln!("[loct][hint] Specified snapshot path not found.");
return DispatchResult::Exit(1);
}
};
match std::fs::read_to_string(&snapshot_path)
.map_err(std::io::Error::other)
.and_then(|content| {
serde_json::from_str::<crate::snapshot::Snapshot>(&content)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}) {
Ok(s) => s,
Err(e) => {
eprintln!("[loct][error] Failed to load snapshot: {}", e);
return DispatchResult::Exit(1);
}
}
} else {
match load_or_create_snapshot(Path::new("."), global) {
Ok(s) => s,
Err(e) => {
eprintln!("[loct][error] {}", e);
return DispatchResult::Exit(1);
}
}
};
let snapshot_json = match serde_json::to_value(&snapshot) {
Ok(v) => v,
Err(e) => {
eprintln!("[loct][error] Failed to serialize snapshot: {}", e);
return DispatchResult::Exit(1);
}
};
let executor = JaqExecutor::new();
let results = match executor.execute(
&opts.filter,
&snapshot_json,
&opts.string_args,
&opts.json_args,
) {
Ok(r) => r,
Err(e) => {
eprintln!("[loct][error] Filter execution failed: {}", e);
return DispatchResult::Exit(1);
}
};
for result in &results {
let output = format_output(result, opts.raw_output, opts.compact_output);
println!("{}", output);
}
if opts.exit_status {
if results.is_empty() {
return DispatchResult::Exit(1);
}
let all_false_or_null = results
.iter()
.all(|v| v.is_null() || (v.as_bool().is_some() && !v.as_bool().unwrap()));
if all_false_or_null {
return DispatchResult::Exit(1);
}
}
DispatchResult::Exit(0)
}