icb_server/display_name.rs
1//! Conversions from Clang USR strings to display‑friendly identifiers.
2//!
3//! A USR (Unified Symbol Resolution) uniquely identifies a declaration in the
4//! Clang AST. Its structure encodes namespaces, classes, function signatures
5//! and template parameters. This module extracts the last, human‑significant
6//! segment of the USR and strips template suffix clutter.
7//!
8//! Additionally, the module provides [`cleanup_node_names`] which normalises
9//! both `name` and `usr` fields of every node in a [`CodePropertyGraph`].
10//! This is applied once after graph construction or cache loading.
11
12use icb_common::NodeKind;
13use icb_graph::graph::CodePropertyGraph;
14
15/// Returns a readable name from a raw identifier that may be a Clang USR.
16///
17/// # USR format
18///
19/// USRs follow the pattern `c:…@…@name#…`. The last `@`‑delimited segment
20/// contains the symbol’s base name, optionally followed by `#…` encoding
21/// template arguments and qualifiers.
22///
23/// # Strategy
24///
25/// 1. If `raw` does **not** contain `@`, it is already a plain name and is
26/// returned unchanged.
27/// 2. Otherwise, the substring after the last `@` is taken.
28/// 3. Everything from the first `#` in that substring is discarded.
29///
30/// # Examples
31///
32/// ```rust
33/// # use icb_server::display_name::readable_name;
34/// assert_eq!(readable_name("c:@F@main"), "main");
35/// assert_eq!(readable_name("c:@S@MyClass@F@method"), "method");
36/// assert_eq!(
37/// readable_name("c:@S@MyClass@F@MyClass#&1$@S@MyClass#"),
38/// "MyClass"
39/// );
40/// assert_eq!(readable_name("already_clean"), "already_clean");
41/// assert_eq!(readable_name(""), "");
42/// ```
43pub fn readable_name(raw: &str) -> String {
44 if !raw.contains('@') {
45 return raw.to_string();
46 }
47
48 let after_at = raw.rsplit('@').next().unwrap_or(raw);
49 let before_hash = after_at.split('#').next().unwrap_or(after_at);
50 before_hash.to_string()
51}
52
53/// Walks all graph nodes and replaces USR‑encoded names with their
54/// human‑readable equivalents.
55///
56/// Both the `name` field (the primary display name) and, for functions and
57/// classes, the `usr` field are cleaned. A `usr` value starting with `"c:"`
58/// is considered a raw USR and is converted; other values (e.g. file paths)
59/// are left unchanged.
60pub fn cleanup_node_names(cpg: &mut CodePropertyGraph) {
61 for node in cpg.graph.node_weights_mut() {
62 // Clean the primary display name
63 if let Some(ref name) = node.name {
64 let cleaned = readable_name(name);
65 if cleaned != *name {
66 node.name = Some(cleaned);
67 }
68 }
69
70 // For functions and classes, also clean the `usr` field if it
71 // appears to be a raw USR (starts with "c:").
72 if node.kind == NodeKind::Function || node.kind == NodeKind::Class {
73 if let Some(ref usr) = node.usr {
74 if usr.starts_with("c:") {
75 let cleaned = readable_name(usr);
76 if cleaned != *usr {
77 node.usr = Some(cleaned);
78 }
79 }
80 }
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_simple_function() {
91 assert_eq!(readable_name("c:@F@main"), "main");
92 }
93
94 #[test]
95 fn test_class_method() {
96 assert_eq!(readable_name("c:@S@MyClass@F@method"), "method");
97 }
98
99 #[test]
100 fn test_constructor_template() {
101 assert_eq!(
102 readable_name("c:@S@MyClass@F@MyClass#&1$@S@MyClass#"),
103 "MyClass"
104 );
105 }
106
107 #[test]
108 fn test_already_clean() {
109 assert_eq!(readable_name("helper"), "helper");
110 }
111
112 #[test]
113 fn test_empty_string() {
114 assert_eq!(readable_name(""), "");
115 }
116
117 #[test]
118 fn test_only_at() {
119 assert_eq!(readable_name("@"), "");
120 }
121}