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};
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
30#[allow(
32 clippy::too_many_lines,
33 reason = "one ordered pass emitting every section of the refs model in the fixed order the text output promises"
34)]
35pub fn render_refs_text(model: &query::RefsModel) -> String {
36 let mut out: Vec<String> = Vec::new();
37 out.push(format!("{} ({})", model.id, model.kind));
38 out.push(format!(
39 "def: {}",
40 model
41 .sites
42 .iter()
43 .map(|s| format!("{}:{}", s.file, s.line))
44 .collect::<Vec<_>>()
45 .join(" ")
46 ));
47
48 out.push("inbound:".to_string());
49 ref_kind_block(
50 &mut out,
51 "inherits",
52 Some(&model.inbound.inherits),
53 |r: &query::InboundRow| {
54 format!(
55 "{}:{} inherits{}{}",
56 r.file,
57 r.line,
58 heuristic_suffix(r.heuristic, r.tier),
59 source_suffix(&r.source)
60 )
61 },
62 );
63 ref_kind_block(
64 &mut out,
65 "uses-type",
66 Some(&model.inbound.uses_type),
67 |r: &query::InboundRow| {
68 format!(
69 "{}:{} uses-type{}{}",
70 r.file,
71 r.line,
72 heuristic_suffix(r.heuristic, r.tier),
73 source_suffix(&r.source)
74 )
75 },
76 );
77 ref_kind_block(
78 &mut out,
79 "uses-member",
80 Some(&model.inbound.uses_member),
81 |r: &query::InboundRow| {
82 format!(
83 "{}:{} uses-member{}{}",
84 r.file,
85 r.line,
86 heuristic_suffix(r.heuristic, r.tier),
87 source_suffix(&r.source)
88 )
89 },
90 );
91 ref_kind_block_if_any(
92 &mut out,
93 "implements",
94 &model.inbound.implements,
95 |r: &query::InboundRow| {
96 format!(
97 "{}:{} implements{}",
98 r.file,
99 r.line,
100 source_suffix(&r.source)
101 )
102 },
103 );
104 ref_kind_block_if_any(
105 &mut out,
106 "overrides",
107 &model.inbound.overrides,
108 |r: &query::InboundRow| {
109 format!(
110 "{}:{} overrides{}",
111 r.file,
112 r.line,
113 source_suffix(&r.source)
114 )
115 },
116 );
117 let inbound_dropped = model.inbound.inherits.dropped
123 + model.inbound.uses_type.dropped
124 + model.inbound.uses_member.dropped
125 + model.inbound.implements.dropped
126 + model.inbound.overrides.dropped;
127 if inbound_dropped != 0 {
128 out.push(format!(" +{inbound_dropped} more"));
129 }
130 if let Some(m) = &model.member_refs {
131 out.push(member_refs_line(m));
132 }
133
134 if let Some(ob) = &model.outbound {
135 out.push("outbound:".to_string());
136 ref_kind_block(
137 &mut out,
138 "inherits",
139 Some(&ob.inherits),
140 |r: &query::OutboundRow| {
141 format!(
142 "{}:{} inherits -> {}{}{}",
143 r.file,
144 r.line,
145 r.to_file,
146 heuristic_suffix(r.heuristic, r.tier),
147 source_suffix(&r.source)
148 )
149 },
150 );
151 ref_kind_block(
152 &mut out,
153 "uses-type",
154 Some(&ob.uses_type),
155 |r: &query::OutboundRow| {
156 format!(
157 "{}:{} uses-type -> {}{}{}",
158 r.file,
159 r.line,
160 r.to_file,
161 heuristic_suffix(r.heuristic, r.tier),
162 source_suffix(&r.source)
163 )
164 },
165 );
166 ref_kind_block(
167 &mut out,
168 "uses-member",
169 Some(&ob.uses_member),
170 |r: &query::OutboundRow| {
171 format!(
172 "{}:{} uses-member -> {}{}{}",
173 r.file,
174 r.line,
175 r.to_file,
176 heuristic_suffix(r.heuristic, r.tier),
177 source_suffix(&r.source)
178 )
179 },
180 );
181 ref_kind_block_if_any(
182 &mut out,
183 "implements",
184 &ob.implements,
185 |r: &query::OutboundRow| {
186 format!(
187 "{}:{} implements -> {}{}",
188 r.file,
189 r.line,
190 r.to_file,
191 source_suffix(&r.source)
192 )
193 },
194 );
195 ref_kind_block_if_any(
196 &mut out,
197 "overrides",
198 &ob.overrides,
199 |r: &query::OutboundRow| {
200 format!(
201 "{}:{} overrides -> {}{}",
202 r.file,
203 r.line,
204 r.to_file,
205 source_suffix(&r.source)
206 )
207 },
208 );
209 ref_kind_block(
210 &mut out,
211 "imports",
212 Some(&ob.imports),
213 |r: &query::ImportRow| {
214 format!(
215 "{}:{} imports -> {}{}",
216 r.file,
217 r.line,
218 r.target,
219 source_suffix(&r.source)
220 )
221 },
222 );
223 let outbound_dropped = ob.inherits.dropped
229 + ob.uses_type.dropped
230 + ob.uses_member.dropped
231 + ob.implements.dropped
232 + ob.overrides.dropped
233 + ob.imports.dropped;
234 if outbound_dropped != 0 {
235 out.push(format!(" +{outbound_dropped} more, use --all"));
236 }
237 }
238
239 let amb_in = &model.ambiguous.inbound;
240 let amb_out = &model.ambiguous.outbound;
241 if amb_in.total != 0 || amb_out.total != 0 {
242 out.push("ambiguous:".to_string());
243 let amb_row = |r: &query::AmbiguousRow| {
244 format!(
245 "{}:{} {} raw=\"{}\" candidates={}",
246 r.file, r.line, r.origin, r.raw, r.candidate_count
247 )
248 };
249 if amb_in.total != 0 {
250 ref_kind_block(&mut out, "inbound", Some(amb_in), amb_row);
251 }
252 if amb_out.total != 0 {
253 ref_kind_block(&mut out, "outbound", Some(amb_out), amb_row);
254 }
255 }
256
257 if model.manifest_gap != 0 {
258 out.push(format!(
259 "manifest gap: {} graph file(s) not in manifest",
260 model.manifest_gap
261 ));
262 }
263 out.join("\n")
264}
265
266#[allow(
268 clippy::too_many_lines,
269 reason = "one ordered pass mirroring render_refs_text's section order in the compact shape"
270)]
271pub fn render_refs_compact(model: &query::RefsModel) -> String {
272 let mut out: Vec<String> = Vec::new();
273 out.push(format!("{} ({})", model.id, model.kind));
274 out.push(format!(
275 "def: {}",
276 model
277 .sites
278 .iter()
279 .map(|s| format!("{}:{}", s.file, s.line))
280 .collect::<Vec<_>>()
281 .join(" ")
282 ));
283
284 let file_of_ib: fn(&query::InboundRow) -> &str = |r| r.file.as_str();
300 let line_ib =
301 |r: &query::InboundRow| format!("{}{}", r.line, compact_marker(r.heuristic, r.tier));
302 compact_block(
303 &mut out,
304 "in:inherits",
305 Some(&model.inbound.inherits),
306 file_of_ib,
307 line_ib,
308 );
309 compact_block(
310 &mut out,
311 "in:uses-type",
312 Some(&model.inbound.uses_type),
313 file_of_ib,
314 line_ib,
315 );
316 compact_block(
317 &mut out,
318 "in:uses-member",
319 Some(&model.inbound.uses_member),
320 file_of_ib,
321 line_ib,
322 );
323 compact_block(
324 &mut out,
325 "in:implements",
326 Some(&model.inbound.implements),
327 file_of_ib,
328 line_ib,
329 );
330 compact_block(
331 &mut out,
332 "in:overrides",
333 Some(&model.inbound.overrides),
334 file_of_ib,
335 line_ib,
336 );
337
338 if let Some(ob) = &model.outbound {
339 let file_of_ob: fn(&query::OutboundRow) -> &str = |r| r.file.as_str();
340 let line_ob =
341 |r: &query::OutboundRow| format!("{}{}", r.line, compact_marker(r.heuristic, r.tier));
342 compact_block(
343 &mut out,
344 "out:inherits",
345 Some(&ob.inherits),
346 file_of_ob,
347 line_ob,
348 );
349 compact_block(
350 &mut out,
351 "out:uses-type",
352 Some(&ob.uses_type),
353 file_of_ob,
354 line_ob,
355 );
356 compact_block(
357 &mut out,
358 "out:uses-member",
359 Some(&ob.uses_member),
360 file_of_ob,
361 line_ob,
362 );
363 compact_block(
364 &mut out,
365 "out:implements",
366 Some(&ob.implements),
367 file_of_ob,
368 line_ob,
369 );
370 compact_block(
371 &mut out,
372 "out:overrides",
373 Some(&ob.overrides),
374 file_of_ob,
375 line_ob,
376 );
377
378 let file_of_imp: fn(&query::ImportRow) -> &str = |r| r.file.as_str();
379 let line_imp = |r: &query::ImportRow| r.line.to_string();
380 compact_block(
381 &mut out,
382 "out:imports",
383 Some(&ob.imports),
384 file_of_imp,
385 line_imp,
386 );
387 }
388
389 let amb_in = &model.ambiguous.inbound;
390 let amb_out = &model.ambiguous.outbound;
391 let file_of_amb: fn(&query::AmbiguousRow) -> &str = |r| r.file.as_str();
392 let line_amb =
393 |r: &query::AmbiguousRow| format!("{}(candidates={})", r.line, r.candidate_count);
394 compact_block(&mut out, "amb:in", Some(amb_in), file_of_amb, line_amb);
395 compact_block(&mut out, "amb:out", Some(amb_out), file_of_amb, line_amb);
396
397 let ob = model.outbound.as_ref();
402 let ob_sum = |f: fn(&query::OutboundTables) -> usize| ob.map_or(0, f);
403 let edges = model.inbound.inherits.total
404 + model.inbound.uses_type.total
405 + model.inbound.uses_member.total
406 + model.inbound.implements.total
407 + model.inbound.overrides.total
408 + ob_sum(|o| {
409 o.inherits.total
410 + o.uses_type.total
411 + o.uses_member.total
412 + o.implements.total
413 + o.overrides.total
414 + o.imports.total
415 });
416 let shown = model.inbound.inherits.rows.len()
417 + model.inbound.uses_type.rows.len()
418 + model.inbound.uses_member.rows.len()
419 + model.inbound.implements.rows.len()
420 + model.inbound.overrides.rows.len()
421 + ob_sum(|o| {
422 o.inherits.rows.len()
423 + o.uses_type.rows.len()
424 + o.uses_member.rows.len()
425 + o.implements.rows.len()
426 + o.overrides.rows.len()
427 + o.imports.rows.len()
428 });
429 let dropped = model.inbound.inherits.dropped
430 + model.inbound.uses_type.dropped
431 + model.inbound.uses_member.dropped
432 + model.inbound.implements.dropped
433 + model.inbound.overrides.dropped
434 + ob_sum(|o| {
435 o.inherits.dropped
436 + o.uses_type.dropped
437 + o.uses_member.dropped
438 + o.implements.dropped
439 + o.overrides.dropped
440 + o.imports.dropped
441 });
442 let ambiguous = amb_in.total + amb_out.total;
443 let gap = if model.manifest_gap != 0 {
444 format!(" gap={}", model.manifest_gap)
445 } else {
446 String::new()
447 };
448 if let Some(m) = &model.member_refs {
452 let named = m
453 .members
454 .iter()
455 .map(|e| format!("{}={}", e.name, e.count))
456 .collect::<Vec<_>>()
457 .join(",");
458 let more = if m.dropped != 0 {
459 format!(",+{}", m.dropped)
460 } else {
461 String::new()
462 };
463 out.push(format!("mem: {named}{more}"));
464 }
465 out.push(format!(
466 "summary: edges={edges} shown={shown} dropped={dropped} ambiguous={ambiguous}{gap}"
467 ));
468 out.join("\n")
469}