1use super::encode::quoted;
2use crate::model::{
3 CloneEvidence, CloneFamily, CloneKind, CloneLocation, ClonePair, CloneReport, CloneStatistics,
4};
5use std::fmt::Write;
6
7pub const JSON_SCHEMA: &str = "https://weavatrix.com/schemas/clone-report/v1";
8pub const JSON_SCHEMA_DOCUMENT: &str = include_str!("../../schemas/clone-report-v1.schema.json");
9
10#[must_use]
11pub fn to_json(report: &CloneReport) -> String {
12 let mut output = String::new();
13 output.push_str("{\"schema\":");
14 quoted(&mut output, JSON_SCHEMA);
15 output.push_str(",\"version\":1,\"pairs\":[");
16 let mut pairs = report.pairs.iter().collect::<Vec<_>>();
17 pairs.sort_unstable_by(|left, right| left.id.cmp(&right.id));
18 join(&mut output, pairs, pair);
19 output.push_str("],\"families\":[");
20 let mut families = report.families.iter().collect::<Vec<_>>();
21 families.sort_unstable_by(|left, right| left.id.cmp(&right.id));
22 join(&mut output, families, family);
23 output.push_str("],\"statistics\":");
24 statistics(&mut output, &report.statistics);
25 output.push('}');
26 output
27}
28
29fn pair(output: &mut String, pair: &ClonePair) {
30 output.push_str("{\"id\":");
31 quoted(output, &pair.id);
32 output.push_str(",\"kind\":");
33 quoted(
34 output,
35 match pair.kind {
36 CloneKind::Type1 => "type1",
37 CloneKind::Type2 => "type2",
38 CloneKind::Type3 => "type3",
39 },
40 );
41 let _ = write!(
42 output,
43 ",\"similarityPermille\":{},\"left\":",
44 pair.similarity.permille()
45 );
46 location(output, &pair.left);
47 output.push_str(",\"right\":");
48 location(output, &pair.right);
49 output.push_str(",\"evidence\":");
50 evidence(output, &pair.evidence);
51 output.push('}');
52}
53
54fn location(output: &mut String, location: &CloneLocation) {
55 output.push_str("{\"fragmentId\":");
56 quoted(output, &location.fragment_id);
57 output.push_str(",\"path\":");
58 quoted(output, &location.path);
59 let span = location.span;
60 let _ = write!(
61 output,
62 ",\"span\":{{\"startByte\":{},\"endByte\":{},\"startLine\":{},\"endLine\":{}}}}}",
63 span.start_byte, span.end_byte, span.start_line, span.end_line
64 );
65}
66
67fn evidence(output: &mut String, value: &CloneEvidence) {
68 let _ = write!(
69 output,
70 concat!(
71 "{{\"strictEqual\":{},\"renamedEqual\":{},",
72 "\"sharedFingerprints\":{},\"fingerprintJaccardPermille\":{},",
73 "\"fingerprintContainmentPermille\":{},\"editDistance\":{},",
74 "\"comparedTokens\":{}}}"
75 ),
76 value.strict_equal,
77 value.renamed_equal,
78 value.shared_fingerprints,
79 value.fingerprint_jaccard.permille(),
80 value.fingerprint_containment.permille(),
81 value.edit_distance,
82 value.compared_tokens
83 );
84}
85
86fn family(output: &mut String, family: &CloneFamily) {
87 output.push_str("{\"id\":");
88 quoted(output, &family.id);
89 output.push_str(",\"members\":[");
90 let mut members = family.members.iter().collect::<Vec<_>>();
91 members.sort_unstable();
92 join(output, members, location);
93 output.push_str("],\"pairIds\":[");
94 let mut pair_ids = family.pair_ids.iter().collect::<Vec<_>>();
95 pair_ids.sort_unstable();
96 join(output, pair_ids, |output, id| quoted(output, id));
97 output.push_str("]}");
98}
99
100fn statistics(output: &mut String, value: &CloneStatistics) {
101 let _ = write!(
102 output,
103 concat!(
104 "{{\"sourceFiles\":{},\"sourceTokens\":{},\"inputFragments\":{},",
105 "\"analyzedFragments\":{},\"skippedSmallFragments\":{},\"tokens\":{},",
106 "\"fingerprints\":{},\"candidatePairs\":{},\"exactBlockCandidates\":{},",
107 "\"verifiedPairs\":{},\"suppressedBuckets\":{},",
108 "\"suppressedExactBuckets\":{}}}"
109 ),
110 value.source_files,
111 value.source_tokens,
112 value.input_fragments,
113 value.analyzed_fragments,
114 value.skipped_small_fragments,
115 value.tokens,
116 value.fingerprints,
117 value.candidate_pairs,
118 value.exact_block_candidates,
119 value.verified_pairs,
120 value.suppressed_buckets,
121 value.suppressed_exact_buckets
122 );
123}
124
125fn join<T>(
126 output: &mut String,
127 values: impl IntoIterator<Item = T>,
128 mut render: impl FnMut(&mut String, T),
129) {
130 let mut first = true;
131 for value in values {
132 if !first {
133 output.push(',');
134 }
135 first = false;
136 render(output, value);
137 }
138}