Skip to main content

fallow_core/plugins/
used_export_rule.rs

1//! Used-export rules: the export names that a plugin marks as used in the
2//! files that a path rule matches.
3
4use super::{AggregatedPluginResult, CompiledPathRule, PathRule, PluginResult};
5
6impl PluginResult {
7    pub(super) fn push_used_export_rule(
8        &mut self,
9        pattern: impl Into<String>,
10        exports: impl IntoIterator<Item = impl Into<String>>,
11    ) {
12        self.used_exports
13            .push(UsedExportRule::new(pattern, exports));
14    }
15
16    /// Credit `exports` of the entry files that `pattern` matches, under
17    /// `includeEntryExports` only.
18    pub(super) fn push_entry_export_credit(
19        &mut self,
20        pattern: impl Into<String>,
21        exports: impl IntoIterator<Item = impl Into<String>>,
22    ) {
23        self.entry_export_credits
24            .push(UsedExportRule::new(pattern, exports));
25    }
26}
27
28/// A used-export rule bound to a file-pattern rule.
29#[derive(Debug, Clone, Default, PartialEq, Eq)]
30pub struct UsedExportRule {
31    pub(crate) path: PathRule,
32    pub(crate) exports: Vec<String>,
33}
34
35impl UsedExportRule {
36    #[must_use]
37    pub(crate) fn new(
38        pattern: impl Into<String>,
39        exports: impl IntoIterator<Item = impl Into<String>>,
40    ) -> Self {
41        Self {
42            path: PathRule::new(pattern),
43            exports: exports.into_iter().map(Into::into).collect(),
44        }
45    }
46
47    #[must_use]
48    pub(super) fn from_static(pattern: &'static str, exports: &'static [&'static str]) -> Self {
49        Self::new(pattern, exports.iter().copied())
50    }
51
52    #[must_use]
53    pub(super) fn with_excluded_globs<I, S>(mut self, patterns: I) -> Self
54    where
55        I: IntoIterator<Item = S>,
56        S: Into<String>,
57    {
58        self.path = self.path.with_excluded_globs(patterns);
59        self
60    }
61
62    #[must_use]
63    pub(super) fn with_excluded_regexes<I, S>(mut self, patterns: I) -> Self
64    where
65        I: IntoIterator<Item = S>,
66        S: Into<String>,
67    {
68        self.path = self.path.with_excluded_regexes(patterns);
69        self
70    }
71
72    #[must_use]
73    pub(super) fn with_excluded_segment_regexes<I, S>(mut self, patterns: I) -> Self
74    where
75        I: IntoIterator<Item = S>,
76        S: Into<String>,
77    {
78        self.path = self.path.with_excluded_segment_regexes(patterns);
79        self
80    }
81
82    #[must_use]
83    pub(super) fn prefixed(&self, ws_prefix: &str) -> Self {
84        Self {
85            path: self.path.prefixed(ws_prefix),
86            exports: self.exports.clone(),
87        }
88    }
89}
90
91/// A used-export rule tagged with the plugin that contributed it.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct PluginUsedExportRule {
94    pub(crate) plugin_name: String,
95    pub(crate) rule: UsedExportRule,
96}
97
98impl PluginUsedExportRule {
99    #[must_use]
100    pub(crate) fn new(plugin_name: impl Into<String>, rule: UsedExportRule) -> Self {
101        Self {
102            plugin_name: plugin_name.into(),
103            rule,
104        }
105    }
106
107    #[must_use]
108    pub(super) fn prefixed(&self, ws_prefix: &str) -> Self {
109        Self {
110            plugin_name: self.plugin_name.clone(),
111            rule: self.rule.prefixed(ws_prefix),
112        }
113    }
114}
115
116/// The export credit for the config files of active plugins.
117///
118/// A tool loads its config file and reads some of its exports. Under
119/// `includeEntryExports`, these exports count as used. The rules come from the
120/// config patterns of each active plugin (the `default` export) and from the
121/// anchored `config_file_exports` of each plugin (the export names that the
122/// tool reads, or `*` for every export).
123///
124/// The credit has its own matcher set. Without `includeEntryExports`, fallow
125/// skips an entry point as a whole, and the plugin `used_exports` matchers
126/// decide whether to analyse it. This credit never feeds that decision, so
127/// output without `includeEntryExports` does not change.
128pub struct ConfigExportCredit<'a> {
129    matchers: Vec<(CompiledPathRule, &'a [String])>,
130}
131
132impl<'a> ConfigExportCredit<'a> {
133    /// Compile the credit. It is empty unless `include_entry_exports` is on.
134    pub fn new(
135        plugin_result: Option<&'a AggregatedPluginResult>,
136        include_entry_exports: bool,
137    ) -> Self {
138        let matchers = plugin_result
139            .filter(|_| include_entry_exports)
140            .map(|pr| {
141                pr.config_export_credits
142                    .iter()
143                    .filter_map(|rule| {
144                        CompiledPathRule::for_used_export_rule(
145                            &rule.rule.path,
146                            "config export credit pattern",
147                        )
148                        .map(|matcher| (matcher, rule.rule.exports.as_slice()))
149                    })
150                    .collect()
151            })
152            .unwrap_or_default();
153        Self { matchers }
154    }
155
156    /// Whether no config pattern gets the credit.
157    pub fn is_empty(&self) -> bool {
158        self.matchers.is_empty()
159    }
160
161    /// The credited export names of each rule that matches the
162    /// project-relative `path`.
163    pub fn exports_for(&self, path: &str) -> Vec<&'a [String]> {
164        self.matchers
165            .iter()
166            .filter(|(matcher, _)| matcher.matches(path))
167            .map(|(_, exports)| *exports)
168            .collect()
169    }
170
171    /// Whether the export lists from [`Self::exports_for`] credit
172    /// `export_name`. The comparison is by name, so
173    /// `export { config as default }` matches `default`.
174    pub fn credits(credited: &[&[String]], export_name: &str) -> bool {
175        credited
176            .iter()
177            .any(|exports| exports.iter().any(|e| e == "*" || e == export_name))
178    }
179}