1use crate::query;
2
3use super::blocks::{compact_block, ref_kind_block, ref_kind_block_if_any};
4use super::markers::{compact_marker, heuristic_suffix, source_suffix, BUS_HOP_UNVERIFIED};
5
6fn member_refs_line(m: &query::MemberRefs) -> String {
13 let named = m
14 .members
15 .iter()
16 .map(|e| format!("{} {}", e.name, e.count))
17 .collect::<Vec<_>>()
18 .join(", ");
19 let more = if m.dropped != 0 {
20 format!(" +{} more", m.dropped)
21 } else {
22 String::new()
23 };
24 format!(
25 "member refs: {} across {} member(s): {named}{more}",
26 m.total, m.member_count
27 )
28}
29
30pub(super) fn bus_hop_line(r: &query::BusHopRow) -> String {
39 format!(
40 "{}:{} {} message={} handler={} handlerFile={} evidence={} handlers={} \
41 ({BUS_HOP_UNVERIFIED})",
42 r.file,
43 r.line,
44 r.direction.as_str(),
45 r.message,
46 r.to,
47 r.to_file,
48 r.evidence,
49 r.message_handlers
50 )
51}
52
53#[allow(
55 clippy::too_many_lines,
56 reason = "one ordered pass emitting every section of the refs model in the fixed order the text output promises"
57)]
58pub fn render_refs_text(model: &query::RefsModel) -> String {
59 let mut out: Vec<String> = Vec::new();
60 out.push(format!("{} ({})", model.id, model.kind));
61 out.push(format!(
62 "def: {}",
63 model
64 .sites
65 .iter()
66 .map(|s| format!("{}:{}", s.file, s.line))
67 .collect::<Vec<_>>()
68 .join(" ")
69 ));
70
71 out.push("inbound:".to_string());
72 ref_kind_block(
73 &mut out,
74 "inherits",
75 Some(&model.inbound.inherits),
76 |r: &query::InboundRow| {
77 format!(
78 "{}:{} inherits{}{}",
79 r.file,
80 r.line,
81 heuristic_suffix(r.heuristic, r.tier),
82 source_suffix(&r.source)
83 )
84 },
85 );
86 ref_kind_block(
87 &mut out,
88 "uses-type",
89 Some(&model.inbound.uses_type),
90 |r: &query::InboundRow| {
91 format!(
92 "{}:{} uses-type{}{}",
93 r.file,
94 r.line,
95 heuristic_suffix(r.heuristic, r.tier),
96 source_suffix(&r.source)
97 )
98 },
99 );
100 ref_kind_block(
101 &mut out,
102 "uses-member",
103 Some(&model.inbound.uses_member),
104 |r: &query::InboundRow| {
105 format!(
106 "{}:{} uses-member{}{}",
107 r.file,
108 r.line,
109 heuristic_suffix(r.heuristic, r.tier),
110 source_suffix(&r.source)
111 )
112 },
113 );
114 ref_kind_block_if_any(
115 &mut out,
116 "implements",
117 &model.inbound.implements,
118 |r: &query::InboundRow| {
119 format!(
120 "{}:{} implements{}",
121 r.file,
122 r.line,
123 source_suffix(&r.source)
124 )
125 },
126 );
127 ref_kind_block_if_any(
128 &mut out,
129 "overrides",
130 &model.inbound.overrides,
131 |r: &query::InboundRow| {
132 format!(
133 "{}:{} overrides{}",
134 r.file,
135 r.line,
136 source_suffix(&r.source)
137 )
138 },
139 );
140 let inbound_dropped = model.inbound.inherits.dropped
146 + model.inbound.uses_type.dropped
147 + model.inbound.uses_member.dropped
148 + model.inbound.implements.dropped
149 + model.inbound.overrides.dropped;
150 if inbound_dropped != 0 {
151 out.push(format!(" +{inbound_dropped} more"));
152 }
153 if let Some(m) = &model.member_refs {
154 out.push(member_refs_line(m));
155 }
156 ref_kind_block_if_any(&mut out, "bus-hop", &model.bus, bus_hop_line);
161
162 if let Some(ob) = &model.outbound {
163 out.push("outbound:".to_string());
164 ref_kind_block(
165 &mut out,
166 "inherits",
167 Some(&ob.inherits),
168 |r: &query::OutboundRow| {
169 format!(
170 "{}:{} inherits -> {}{}{}",
171 r.file,
172 r.line,
173 r.to_file,
174 heuristic_suffix(r.heuristic, r.tier),
175 source_suffix(&r.source)
176 )
177 },
178 );
179 ref_kind_block(
180 &mut out,
181 "uses-type",
182 Some(&ob.uses_type),
183 |r: &query::OutboundRow| {
184 format!(
185 "{}:{} uses-type -> {}{}{}",
186 r.file,
187 r.line,
188 r.to_file,
189 heuristic_suffix(r.heuristic, r.tier),
190 source_suffix(&r.source)
191 )
192 },
193 );
194 ref_kind_block(
195 &mut out,
196 "uses-member",
197 Some(&ob.uses_member),
198 |r: &query::OutboundRow| {
199 format!(
200 "{}:{} uses-member -> {}{}{}",
201 r.file,
202 r.line,
203 r.to_file,
204 heuristic_suffix(r.heuristic, r.tier),
205 source_suffix(&r.source)
206 )
207 },
208 );
209 ref_kind_block_if_any(
210 &mut out,
211 "implements",
212 &ob.implements,
213 |r: &query::OutboundRow| {
214 format!(
215 "{}:{} implements -> {}{}",
216 r.file,
217 r.line,
218 r.to_file,
219 source_suffix(&r.source)
220 )
221 },
222 );
223 ref_kind_block_if_any(
224 &mut out,
225 "overrides",
226 &ob.overrides,
227 |r: &query::OutboundRow| {
228 format!(
229 "{}:{} overrides -> {}{}",
230 r.file,
231 r.line,
232 r.to_file,
233 source_suffix(&r.source)
234 )
235 },
236 );
237 ref_kind_block(
238 &mut out,
239 "imports",
240 Some(&ob.imports),
241 |r: &query::ImportRow| {
242 format!(
243 "{}:{} imports -> {}{}",
244 r.file,
245 r.line,
246 r.target,
247 source_suffix(&r.source)
248 )
249 },
250 );
251 let outbound_dropped = ob.inherits.dropped
257 + ob.uses_type.dropped
258 + ob.uses_member.dropped
259 + ob.implements.dropped
260 + ob.overrides.dropped
261 + ob.imports.dropped;
262 if outbound_dropped != 0 {
263 out.push(format!(" +{outbound_dropped} more, use --all"));
264 }
265 }
266
267 let amb_in = &model.ambiguous.inbound;
268 let amb_out = &model.ambiguous.outbound;
269 if amb_in.total != 0 || amb_out.total != 0 {
270 out.push("ambiguous:".to_string());
271 let amb_row = |r: &query::AmbiguousRow| {
272 format!(
273 "{}:{} {} raw=\"{}\" candidates={}",
274 r.file, r.line, r.origin, r.raw, r.candidate_count
275 )
276 };
277 if amb_in.total != 0 {
278 ref_kind_block(&mut out, "inbound", Some(amb_in), amb_row);
279 }
280 if amb_out.total != 0 {
281 ref_kind_block(&mut out, "outbound", Some(amb_out), amb_row);
282 }
283 }
284
285 if model.manifest_gap != 0 {
286 out.push(format!(
287 "manifest gap: {} graph file(s) not in manifest",
288 model.manifest_gap
289 ));
290 }
291 out.join("\n")
292}
293
294#[allow(
296 clippy::too_many_lines,
297 reason = "one ordered pass mirroring render_refs_text's section order in the compact shape"
298)]
299pub fn render_refs_compact(model: &query::RefsModel) -> String {
300 let mut out: Vec<String> = Vec::new();
301 out.push(format!("{} ({})", model.id, model.kind));
302 out.push(format!(
303 "def: {}",
304 model
305 .sites
306 .iter()
307 .map(|s| format!("{}:{}", s.file, s.line))
308 .collect::<Vec<_>>()
309 .join(" ")
310 ));
311
312 let file_of_ib: fn(&query::InboundRow) -> &str = |r| r.file.as_str();
328 let line_ib =
329 |r: &query::InboundRow| format!("{}{}", r.line, compact_marker(r.heuristic, r.tier));
330 compact_block(
331 &mut out,
332 "in:inherits",
333 Some(&model.inbound.inherits),
334 file_of_ib,
335 line_ib,
336 );
337 compact_block(
338 &mut out,
339 "in:uses-type",
340 Some(&model.inbound.uses_type),
341 file_of_ib,
342 line_ib,
343 );
344 compact_block(
345 &mut out,
346 "in:uses-member",
347 Some(&model.inbound.uses_member),
348 file_of_ib,
349 line_ib,
350 );
351 compact_block(
352 &mut out,
353 "in:implements",
354 Some(&model.inbound.implements),
355 file_of_ib,
356 line_ib,
357 );
358 compact_block(
359 &mut out,
360 "in:overrides",
361 Some(&model.inbound.overrides),
362 file_of_ib,
363 line_ib,
364 );
365 let file_of_bus: fn(&query::BusHopRow) -> &str = |r| r.file.as_str();
371 let line_bus = |r: &query::BusHopRow| format!("{}{}?", r.line, &r.direction.as_str()[..1]);
372 compact_block(
373 &mut out,
374 "bus-hop (? = possible route, rerun without --compact for the full row)",
375 Some(&model.bus),
376 file_of_bus,
377 line_bus,
378 );
379
380 if let Some(ob) = &model.outbound {
381 let file_of_ob: fn(&query::OutboundRow) -> &str = |r| r.file.as_str();
382 let line_ob =
383 |r: &query::OutboundRow| format!("{}{}", r.line, compact_marker(r.heuristic, r.tier));
384 compact_block(
385 &mut out,
386 "out:inherits",
387 Some(&ob.inherits),
388 file_of_ob,
389 line_ob,
390 );
391 compact_block(
392 &mut out,
393 "out:uses-type",
394 Some(&ob.uses_type),
395 file_of_ob,
396 line_ob,
397 );
398 compact_block(
399 &mut out,
400 "out:uses-member",
401 Some(&ob.uses_member),
402 file_of_ob,
403 line_ob,
404 );
405 compact_block(
406 &mut out,
407 "out:implements",
408 Some(&ob.implements),
409 file_of_ob,
410 line_ob,
411 );
412 compact_block(
413 &mut out,
414 "out:overrides",
415 Some(&ob.overrides),
416 file_of_ob,
417 line_ob,
418 );
419
420 let file_of_imp: fn(&query::ImportRow) -> &str = |r| r.file.as_str();
421 let line_imp = |r: &query::ImportRow| r.line.to_string();
422 compact_block(
423 &mut out,
424 "out:imports",
425 Some(&ob.imports),
426 file_of_imp,
427 line_imp,
428 );
429 }
430
431 let amb_in = &model.ambiguous.inbound;
432 let amb_out = &model.ambiguous.outbound;
433 let file_of_amb: fn(&query::AmbiguousRow) -> &str = |r| r.file.as_str();
434 let line_amb =
435 |r: &query::AmbiguousRow| format!("{}(candidates={})", r.line, r.candidate_count);
436 compact_block(&mut out, "amb:in", Some(amb_in), file_of_amb, line_amb);
437 compact_block(&mut out, "amb:out", Some(amb_out), file_of_amb, line_amb);
438
439 let ob = model.outbound.as_ref();
444 let ob_sum = |f: fn(&query::OutboundTables) -> usize| ob.map_or(0, f);
445 let edges = model.inbound.inherits.total
446 + model.inbound.uses_type.total
447 + model.inbound.uses_member.total
448 + model.inbound.implements.total
449 + model.inbound.overrides.total
450 + model.bus.total
451 + ob_sum(|o| {
452 o.inherits.total
453 + o.uses_type.total
454 + o.uses_member.total
455 + o.implements.total
456 + o.overrides.total
457 + o.imports.total
458 });
459 let shown = model.inbound.inherits.rows.len()
460 + model.inbound.uses_type.rows.len()
461 + model.inbound.uses_member.rows.len()
462 + model.inbound.implements.rows.len()
463 + model.inbound.overrides.rows.len()
464 + model.bus.rows.len()
465 + ob_sum(|o| {
466 o.inherits.rows.len()
467 + o.uses_type.rows.len()
468 + o.uses_member.rows.len()
469 + o.implements.rows.len()
470 + o.overrides.rows.len()
471 + o.imports.rows.len()
472 });
473 let dropped = model.inbound.inherits.dropped
474 + model.inbound.uses_type.dropped
475 + model.inbound.uses_member.dropped
476 + model.inbound.implements.dropped
477 + model.inbound.overrides.dropped
478 + model.bus.dropped
479 + ob_sum(|o| {
480 o.inherits.dropped
481 + o.uses_type.dropped
482 + o.uses_member.dropped
483 + o.implements.dropped
484 + o.overrides.dropped
485 + o.imports.dropped
486 });
487 let ambiguous = amb_in.total + amb_out.total;
488 let gap = if model.manifest_gap != 0 {
489 format!(" gap={}", model.manifest_gap)
490 } else {
491 String::new()
492 };
493 if let Some(m) = &model.member_refs {
497 let named = m
498 .members
499 .iter()
500 .map(|e| format!("{}={}", e.name, e.count))
501 .collect::<Vec<_>>()
502 .join(",");
503 let more = if m.dropped != 0 {
504 format!(",+{}", m.dropped)
505 } else {
506 String::new()
507 };
508 out.push(format!("mem: {named}{more}"));
509 }
510 out.push(format!(
511 "summary: edges={edges} shown={shown} dropped={dropped} ambiguous={ambiguous}{gap}"
512 ));
513 out.join("\n")
514}