graphyn_core/
symbol_id.rs1use crate::ir::{Language, Symbol, SymbolId, SymbolKind};
28
29const UNRESOLVED_IMPORT_PREFIX: &str = "unresolved_import";
30const UNRESOLVED_LOCAL_TYPE_PREFIX: &str = "unresolved_local_type";
31const EXTERNAL_PREFIX: &str = "ext";
32
33pub fn kind_suffix(kind: &SymbolKind) -> &'static str {
35 match kind {
36 SymbolKind::Class => "class",
37 SymbolKind::Interface => "interface",
38 SymbolKind::TypeAlias => "type_alias",
39 SymbolKind::Function => "function",
40 SymbolKind::Method => "method",
41 SymbolKind::Property => "property",
42 SymbolKind::Variable => "variable",
43 SymbolKind::Module => "module",
44 SymbolKind::Enum => "enum",
45 SymbolKind::EnumVariant => "enum_variant",
46 SymbolKind::ExternalPackage => "package",
47 }
48}
49
50pub fn make_symbol_id(file: &str, name: &str, kind: &SymbolKind) -> SymbolId {
52 format!("{file}::{name}::{}", kind_suffix(kind))
53}
54
55pub fn module_symbol_id(file: &str) -> SymbolId {
60 make_symbol_id(file, "module", &SymbolKind::Module)
61}
62
63pub fn module_symbol(file: &str, language: Language) -> Symbol {
65 Symbol {
66 id: module_symbol_id(file),
67 name: "module".to_string(),
68 kind: SymbolKind::Module,
69 language,
70 file: file.to_string(),
71 line_start: 1,
72 line_end: 1,
73 signature: None,
74 }
75}
76
77pub fn parse_symbol_id(id: &str) -> Option<(&str, &str, &str)> {
83 let first = id.find("::")?;
84 let last = id.rfind("::")?;
85 if last <= first {
86 return None;
87 }
88 Some((&id[..first], &id[first + 2..last], &id[last + 2..]))
89}
90
91pub fn symbol_name_of(id: &str) -> Option<&str> {
93 parse_symbol_id(id).map(|(_, name, _)| name)
94}
95
96pub fn unresolved_import_id(module: &str, symbol: &str) -> SymbolId {
103 format!("{UNRESOLVED_IMPORT_PREFIX}|{module}|{symbol}")
104}
105
106pub const IMPORT_ALL: &str = "*";
108
109pub fn parse_unresolved_import_id(raw: &str) -> Option<(&str, &str)> {
111 let rest = raw.strip_prefix(UNRESOLVED_IMPORT_PREFIX)?.strip_prefix('|')?;
112 let cut = rest.rfind('|')?;
115 Some((&rest[..cut], &rest[cut + 1..]))
116}
117
118pub fn unresolved_local_type_id(type_name: &str) -> SymbolId {
120 format!("{UNRESOLVED_LOCAL_TYPE_PREFIX}|{type_name}")
121}
122
123pub fn parse_unresolved_local_type_id(raw: &str) -> Option<&str> {
125 raw.strip_prefix(UNRESOLVED_LOCAL_TYPE_PREFIX)?
126 .strip_prefix('|')
127}
128
129pub fn is_placeholder(id: &str) -> bool {
135 id.starts_with(UNRESOLVED_IMPORT_PREFIX) || id.starts_with(UNRESOLVED_LOCAL_TYPE_PREFIX)
136}
137
138pub fn external_package_id(package: &str) -> SymbolId {
146 format!("{EXTERNAL_PREFIX}::{package}::package")
147}
148
149pub fn is_external_package(id: &str) -> bool {
151 id.starts_with("ext::") && id.ends_with("::package")
152}
153
154pub fn parse_external_package_id(id: &str) -> Option<&str> {
156 id.strip_prefix("ext::")?.strip_suffix("::package")
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn resolved_ids_round_trip() {
165 let id = make_symbol_id("src/models/user.rs", "UserPayload", &SymbolKind::Class);
166 assert_eq!(id, "src/models/user.rs::UserPayload::class");
167 assert_eq!(
168 parse_symbol_id(&id),
169 Some(("src/models/user.rs", "UserPayload", "class"))
170 );
171 }
172
173 #[test]
174 fn symbol_names_containing_the_separator_survive_a_round_trip() {
175 let id = make_symbol_id("src/lib.rs", "Display::fmt", &SymbolKind::Method);
177 assert_eq!(
178 parse_symbol_id(&id),
179 Some(("src/lib.rs", "Display::fmt", "method"))
180 );
181 }
182
183 #[test]
184 fn import_placeholders_round_trip_paths_containing_colons() {
185 let id = unresolved_import_id("crate::models::user_payload", "UserPayload");
188 assert_eq!(
189 parse_unresolved_import_id(&id),
190 Some(("crate::models::user_payload", "UserPayload"))
191 );
192 }
193
194 #[test]
195 fn import_placeholders_round_trip_go_style_module_paths() {
196 let id = unresolved_import_id("github.com/test/app/models", IMPORT_ALL);
197 assert_eq!(
198 parse_unresolved_import_id(&id),
199 Some(("github.com/test/app/models", "*"))
200 );
201 }
202
203 #[test]
204 fn local_type_placeholders_round_trip() {
205 let id = unresolved_local_type_id("ResponseModel");
206 assert_eq!(parse_unresolved_local_type_id(&id), Some("ResponseModel"));
207 assert!(is_placeholder(&id));
208 }
209
210 #[test]
211 fn resolved_ids_are_not_placeholders() {
212 let id = make_symbol_id("src/a.rs", "Alpha", &SymbolKind::Class);
213 assert!(!is_placeholder(&id));
214 assert!(!is_placeholder(&external_package_id("serde")));
215 }
216
217 #[test]
218 fn external_package_ids_round_trip() {
219 let id = external_package_id("serde");
220 assert!(is_external_package(&id));
221 assert_eq!(parse_external_package_id(&id), Some("serde"));
222 }
223}