1use dashmap::DashMap;
2use serde::{Deserialize, Serialize};
3
4use crate::graph::GraphynGraph;
5use crate::ir::{Relationship, RelationshipKind, SymbolId};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
8pub enum AliasScope {
9 ImportAlias,
10 ReExport,
11 BarrelReExport,
12 DefaultImport,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
16pub struct AliasEntry {
17 pub alias_name: String,
18 pub defined_in_file: String,
19 pub scope: AliasScope,
20}
21
22#[derive(Default)]
23pub struct AliasResolver {
24 alias_to_canonical: DashMap<String, SymbolId>,
25}
26
27impl AliasResolver {
28 pub fn ingest_relationships(&self, graph: &GraphynGraph, relationships: &[Relationship]) {
29 for relationship in relationships {
30 if relationship.kind == RelationshipKind::Imports
31 || relationship.kind == RelationshipKind::ReExports
32 {
33 if let Some(alias) = &relationship.alias {
34 let scope = infer_scope(relationship, alias);
35 self.alias_to_canonical.insert(
36 make_alias_key(&relationship.file, alias),
37 relationship.to.clone(),
38 );
39 graph
40 .alias_chains
41 .entry(relationship.to.clone())
42 .and_modify(|entries| {
43 entries.push(AliasEntry {
44 alias_name: alias.clone(),
45 defined_in_file: relationship.file.clone(),
46 scope: scope.clone(),
47 });
48 entries.sort_by(|a, b| {
49 a.defined_in_file
50 .cmp(&b.defined_in_file)
51 .then(a.alias_name.cmp(&b.alias_name))
52 });
53 entries.dedup();
54 })
55 .or_insert_with(|| {
56 vec![AliasEntry {
57 alias_name: alias.clone(),
58 defined_in_file: relationship.file.clone(),
59 scope,
60 }]
61 });
62 }
63 }
64 }
65 }
66
67 pub fn canonicalize_relationship(&self, relationship: &Relationship) -> Relationship {
68 if relationship.kind == RelationshipKind::AccessesProperty {
69 if let Some(alias) = &relationship.alias {
70 if let Some(canonical_to) = self.resolve_alias_in_file(alias, &relationship.file) {
71 let mut normalized = relationship.clone();
72 normalized.to = canonical_to;
73 return normalized;
74 }
75 }
76 }
77
78 relationship.clone()
79 }
80
81 pub fn canonicalize_relationships(&self, relationships: &[Relationship]) -> Vec<Relationship> {
82 relationships
83 .iter()
84 .map(|relationship| self.canonicalize_relationship(relationship))
85 .collect()
86 }
87
88 pub fn resolve_alias_in_file(&self, alias: &str, file: &str) -> Option<SymbolId> {
89 self.alias_to_canonical
90 .get(&make_alias_key(file, alias))
91 .map(|v| v.value().clone())
92 }
93}
94
95fn infer_scope(relationship: &Relationship, alias: &str) -> AliasScope {
96 if relationship.kind == RelationshipKind::ReExports {
97 if relationship.context.contains("export *") {
98 return AliasScope::BarrelReExport;
99 }
100 return AliasScope::ReExport;
101 }
102 if relationship.context.contains("default") {
103 return AliasScope::DefaultImport;
104 }
105 if relationship.context.contains(" as ") || relationship.to.rsplit("::").next() != Some(alias) {
106 return AliasScope::ImportAlias;
107 }
108 AliasScope::ImportAlias
109}
110
111fn make_alias_key(file: &str, alias: &str) -> String {
112 format!("{file}::{alias}")
113}