flowscope_core/analyzer/helpers/
naming.rs

1use crate::types::CanonicalName;
2
3pub fn extract_simple_name(name: &str) -> String {
4    let mut parts = split_qualified_identifiers(name);
5    parts.pop().unwrap_or_else(|| name.to_string())
6}
7
8pub fn split_qualified_identifiers(name: &str) -> Vec<String> {
9    let mut parts = Vec::new();
10    let mut current = String::new();
11    let mut chars = name.chars().peekable();
12    let mut active_quote: Option<char> = None;
13
14    while let Some(ch) = chars.next() {
15        if let Some(q) = active_quote {
16            current.push(ch);
17            if ch == q {
18                if matches!(q, '"' | '\'' | '`') {
19                    if let Some(next) = chars.peek() {
20                        if *next == q {
21                            current.push(chars.next().unwrap());
22                            continue;
23                        }
24                    }
25                }
26                active_quote = None;
27            } else if q == ']' && ch == ']' {
28                active_quote = None;
29            }
30            continue;
31        }
32
33        match ch {
34            '"' | '\'' | '`' => {
35                active_quote = Some(ch);
36                current.push(ch);
37            }
38            '[' => {
39                active_quote = Some(']');
40                current.push(ch);
41            }
42            '.' => {
43                if !current.is_empty() {
44                    parts.push(current.trim().to_string());
45                    current.clear();
46                }
47            }
48            _ => current.push(ch),
49        }
50    }
51
52    if !current.is_empty() {
53        parts.push(current.trim().to_string());
54    }
55
56    if parts.is_empty() && !name.is_empty() {
57        vec![name.trim().to_string()]
58    } else {
59        parts
60    }
61}
62
63pub fn is_quoted_identifier(part: &str) -> bool {
64    let trimmed = part.trim();
65    if trimmed.len() < 2 {
66        return false;
67    }
68    let first = trimmed.chars().next().unwrap();
69    let last = trimmed.chars().last().unwrap();
70    matches!(
71        (first, last),
72        ('"', '"') | ('`', '`') | ('[', ']') | ('\'', '\'')
73    )
74}
75
76pub fn unquote_identifier(part: &str) -> String {
77    let trimmed = part.trim();
78    if trimmed.len() < 2 {
79        return trimmed.to_string();
80    }
81
82    if is_quoted_identifier(trimmed) {
83        trimmed[1..trimmed.len() - 1].to_string()
84    } else {
85        trimmed.to_string()
86    }
87}
88
89pub fn parse_canonical_name(name: &str) -> CanonicalName {
90    let parts = split_qualified_identifiers(name);
91    match parts.len() {
92        0 => CanonicalName::table(None, None, String::new()),
93        1 => CanonicalName::table(None, None, parts[0].clone()),
94        2 => CanonicalName::table(None, Some(parts[0].clone()), parts[1].clone()),
95        3 => CanonicalName::table(
96            Some(parts[0].clone()),
97            Some(parts[1].clone()),
98            parts[2].clone(),
99        ),
100        _ => CanonicalName::table(None, None, name.to_string()),
101    }
102}