Skip to main content

pgevolve_core/lint/
source_tree.rs

1//! [`SourceTree`] — `Catalog` plus per-object source locations.
2//!
3//! Layout-aware lint rules need to know which file each object came from.
4//! [`SourceTree`] couples the IR with that mapping.
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9use crate::identifier::{Identifier, QualifiedName};
10use crate::ir::catalog::Catalog;
11use crate::parse::SourceLocation;
12
13/// Identifier for one IR object in a [`SourceTree`].
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum ObjectKey {
16    /// A schema, identified by its name.
17    Schema(Identifier),
18    /// A table.
19    Table(QualifiedName),
20    /// An index.
21    Index(QualifiedName),
22    /// A sequence.
23    Sequence(QualifiedName),
24}
25
26impl ObjectKey {
27    /// Lowercase kind name (`schema` / `table` / `index` / `sequence`).
28    pub const fn kind_name(&self) -> &'static str {
29        match self {
30            Self::Schema(_) => "schema",
31            Self::Table(_) => "table",
32            Self::Index(_) => "index",
33            Self::Sequence(_) => "sequence",
34        }
35    }
36
37    /// Plural form used by some layout profiles (`schemas` / `tables` / etc.).
38    pub const fn kind_plural(&self) -> &'static str {
39        match self {
40            Self::Schema(_) => "schemas",
41            Self::Table(_) => "tables",
42            Self::Index(_) => "indexes",
43            Self::Sequence(_) => "sequences",
44        }
45    }
46
47    /// Schema name component:
48    /// - `Schema(s)` — `s` itself
49    /// - `Table(q) / Index(q) / Sequence(q)` — `q.schema`
50    pub const fn schema(&self) -> &Identifier {
51        match self {
52            Self::Schema(s) => s,
53            Self::Table(q) | Self::Index(q) | Self::Sequence(q) => &q.schema,
54        }
55    }
56
57    /// Bare-name component:
58    /// - `Schema(s)` — `s` itself
59    /// - other variants — `q.name`
60    pub const fn bare_name(&self) -> &Identifier {
61        match self {
62            Self::Schema(s) => s,
63            Self::Table(q) | Self::Index(q) | Self::Sequence(q) => &q.name,
64        }
65    }
66}
67
68impl std::fmt::Display for ObjectKey {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            Self::Schema(s) => write!(f, "schema:{s}"),
72            Self::Table(q) => write!(f, "table:{q}"),
73            Self::Index(q) => write!(f, "index:{q}"),
74            Self::Sequence(q) => write!(f, "sequence:{q}"),
75        }
76    }
77}
78
79/// `Catalog` plus a map from object identifiers to their source locations.
80#[derive(Debug, Clone)]
81pub struct SourceTree {
82    /// IR catalog parsed from the source tree.
83    pub catalog: Catalog,
84    /// One entry per IR object: where in the source tree the object was
85    /// declared.
86    pub object_locations: HashMap<ObjectKey, SourceLocation>,
87}
88
89impl SourceTree {
90    /// Construct from a parsed catalog and an object-keyed location map.
91    pub const fn new(
92        catalog: Catalog,
93        object_locations: HashMap<ObjectKey, SourceLocation>,
94    ) -> Self {
95        Self {
96            catalog,
97            object_locations,
98        }
99    }
100
101    /// Iterate all object keys.
102    pub fn objects(&self) -> impl Iterator<Item = &ObjectKey> {
103        self.object_locations.keys()
104    }
105
106    /// File path for `key`, if known.
107    pub fn file_of(&self, key: &ObjectKey) -> Option<&Path> {
108        self.object_locations.get(key).map(|l| l.file.as_path())
109    }
110
111    /// Group every object by the file path that declared it.
112    pub fn objects_by_file(&self) -> HashMap<PathBuf, Vec<ObjectKey>> {
113        let mut out: HashMap<PathBuf, Vec<ObjectKey>> = HashMap::new();
114        for (k, loc) in &self.object_locations {
115            out.entry(loc.file.clone()).or_default().push(k.clone());
116        }
117        for v in out.values_mut() {
118            v.sort_by_key(ToString::to_string);
119        }
120        out
121    }
122}