Skip to main content

graphyn_core/
resolver.rs

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}