Skip to main content

browserslist/queries/
mod.rs

1use std::{borrow::Cow, fmt::Display};
2
3use crate::{
4    data::caniuse,
5    error::Error,
6    opts::Opts,
7    parser::{QueryAtom, Stats, VersionRange},
8    semver::Version,
9};
10
11mod browser_accurate;
12mod browser_bounded_range;
13mod browser_unbounded_range;
14mod cover;
15mod cover_by_region;
16mod current_node;
17mod dead;
18mod defaults;
19mod electron_accurate;
20mod electron_bounded_range;
21mod electron_unbounded_range;
22mod firefox_esr;
23mod last_n_browsers;
24mod last_n_electron;
25mod last_n_electron_major;
26mod last_n_major_browsers;
27mod last_n_node;
28mod last_n_node_major;
29mod last_n_x_browsers;
30mod last_n_x_major_browsers;
31mod maintained_node;
32mod node_accurate;
33mod node_bounded_range;
34mod node_unbounded_range;
35mod op_mini;
36mod percentage;
37mod percentage_by_region;
38mod phantom;
39mod since;
40mod supports;
41mod unreleased_browsers;
42mod unreleased_electron;
43mod unreleased_x_browsers;
44mod years;
45
46/// Representation of browser name (or `node`) and its version.
47///
48/// When converting it to string, it will be formatted as the output of
49/// [browserslist](https://github.com/browserslist/browserslist). For example:
50///
51/// ```
52/// use browserslist::{Opts, resolve};
53///
54/// let distrib = &resolve(&["firefox 93"], &Opts::default()).unwrap()[0];
55///
56/// assert_eq!(distrib.name(), "firefox");
57/// assert_eq!(distrib.version(), "93");
58/// ```
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct Distrib(Cow<'static, str>, Cow<'static, str>);
61
62impl Distrib {
63    #[inline]
64    fn new<N: Into<Cow<'static, str>>, S: Into<Cow<'static, str>>>(name: N, version: S) -> Self {
65        Self(name.into(), version.into())
66    }
67
68    #[inline]
69    /// Return browser name, or `node`.
70    ///
71    /// ```
72    /// use browserslist::{Opts, resolve};
73    ///
74    /// let distrib = &resolve(&["firefox 93"], &Opts::default()).unwrap()[0];
75    ///
76    /// assert_eq!(distrib.name(), "firefox");
77    /// ```
78    #[must_use]
79    pub fn name(&self) -> &str {
80        &self.0
81    }
82
83    #[inline]
84    /// Return version string.
85    ///
86    /// ```
87    /// use browserslist::{Opts, resolve};
88    ///
89    /// let distrib = &resolve(&["firefox 93"], &Opts::default()).unwrap()[0];
90    ///
91    /// assert_eq!(distrib.version(), "93");
92    /// ```
93    #[must_use]
94    pub fn version(&self) -> &str {
95        &self.1
96    }
97}
98
99impl Display for Distrib {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        write!(f, "{} {}", self.0, self.1)
102    }
103}
104
105pub type QueryResult = Result<Vec<Distrib>, Error>;
106
107pub fn query(atom: QueryAtom, opts: &Opts) -> QueryResult {
108    match atom {
109        QueryAtom::Last { count, major, name: Some(name) }
110            if name.eq_ignore_ascii_case("electron") =>
111        {
112            let count = count as usize;
113            if major {
114                last_n_electron_major::last_n_electron_major(count)
115            } else {
116                last_n_electron::last_n_electron(count)
117            }
118        }
119        QueryAtom::Last { count, major, name: Some(name) } if name.eq_ignore_ascii_case("node") => {
120            let count = count as usize;
121            if major {
122                last_n_node_major::last_n_node_major(count)
123            } else {
124                last_n_node::last_n_node(count)
125            }
126        }
127        QueryAtom::Last { count, major, name: Some(name) } => {
128            let count = count as usize;
129            if major {
130                last_n_x_major_browsers::last_n_x_major_browsers(count, name, opts)
131            } else {
132                last_n_x_browsers::last_n_x_browsers(count, name, opts)
133            }
134        }
135        QueryAtom::Last { count, major, name: None } => {
136            let count = count as usize;
137            if major {
138                last_n_major_browsers::last_n_major_browsers(count, opts)
139            } else {
140                last_n_browsers::last_n_browsers(count, opts)
141            }
142        }
143        QueryAtom::Unreleased(Some(name)) if name.eq_ignore_ascii_case("electron") => {
144            unreleased_electron::unreleased_electron()
145        }
146        QueryAtom::Unreleased(Some(name)) => {
147            unreleased_x_browsers::unreleased_x_browsers(name, opts)
148        }
149        QueryAtom::Unreleased(None) => unreleased_browsers::unreleased_browsers(opts),
150        QueryAtom::Years(count) => years::years(count, opts),
151        QueryAtom::Since { year, month, day } => since::since(year, month, day, opts),
152        QueryAtom::Percentage { comparator, popularity, stats: Stats::Global } => {
153            percentage::percentage(comparator, popularity)
154        }
155        QueryAtom::Percentage { comparator, popularity, stats: Stats::Region(region) } => {
156            percentage_by_region::percentage_by_region(comparator, popularity, region)
157        }
158        QueryAtom::Cover { coverage, stats: Stats::Global } => cover::cover(coverage),
159        QueryAtom::Cover { coverage, stats: Stats::Region(region) } => {
160            cover_by_region::cover_by_region(coverage, region)
161        }
162        QueryAtom::Supports(name, kind) => supports::supports(name, kind, opts),
163        QueryAtom::Electron(VersionRange::Bounded(from, to)) => {
164            electron_bounded_range::electron_bounded_range(from, to)
165        }
166        QueryAtom::Electron(VersionRange::Unbounded(comparator, version)) => {
167            electron_unbounded_range::electron_unbounded_range(comparator, version)
168        }
169        QueryAtom::Electron(VersionRange::Accurate(version)) => {
170            electron_accurate::electron_accurate(version)
171        }
172        QueryAtom::Node(VersionRange::Bounded(from, to)) => {
173            node_bounded_range::node_bounded_range(from, to)
174        }
175        QueryAtom::Node(VersionRange::Unbounded(comparator, version)) => {
176            node_unbounded_range::node_unbounded_range(comparator, version)
177        }
178        QueryAtom::Node(VersionRange::Accurate(version)) => {
179            node_accurate::node_accurate(version, opts)
180        }
181        QueryAtom::Browser(name, VersionRange::Bounded(from, to)) => {
182            browser_bounded_range::browser_bounded_range(name, from, to, opts)
183        }
184        QueryAtom::Browser(name, VersionRange::Unbounded(comparator, version)) => {
185            browser_unbounded_range::browser_unbounded_range(name, comparator, version, opts)
186        }
187        QueryAtom::Browser(name, VersionRange::Accurate(version)) => {
188            browser_accurate::browser_accurate(name, version, opts)
189        }
190        QueryAtom::FirefoxESR => firefox_esr::firefox_esr(),
191        QueryAtom::OperaMini => op_mini::op_mini(),
192        QueryAtom::CurrentNode => current_node::current_node(),
193        QueryAtom::MaintainedNode => maintained_node::maintained_node(),
194        QueryAtom::Phantom(is_later_version) => phantom::phantom(is_later_version),
195        QueryAtom::Defaults => defaults::defaults(opts),
196        QueryAtom::Dead => dead::dead(opts),
197        QueryAtom::Unknown(query) => Err(Error::UnknownQuery(query.into())),
198    }
199}
200
201pub fn count_filter_versions(name: &str, mobile_to_desktop: bool, count: usize) -> usize {
202    let jump = match name {
203        "android" => {
204            if mobile_to_desktop {
205                return count;
206            } else {
207                let last_released = &caniuse::get_browser_stat("android", mobile_to_desktop)
208                    .unwrap()
209                    .1
210                    .version_list
211                    .iter()
212                    .filter(|version| version.release_date().is_some())
213                    .map(|version| version.version())
214                    .next_back()
215                    .unwrap()
216                    .parse::<f32>()
217                    .unwrap();
218                (last_released - caniuse::ANDROID_EVERGREEN_FIRST) as usize
219            }
220        }
221        "op_mob" => {
222            let latest = caniuse::get_browser_stat("android", mobile_to_desktop)
223                .unwrap()
224                .1
225                .version_list
226                .last()
227                .unwrap();
228            (latest.version().parse::<Version>().unwrap().major() - caniuse::OP_MOB_BLINK_FIRST + 1)
229                as usize
230        }
231        _ => return count,
232    };
233    if count <= jump { 1 } else { count + 1 - jump }
234}
235
236/// Find the minimum major version that covers `count` distinct major versions
237/// by iterating in reverse. Returns `0` if fewer than `count` majors exist.
238pub fn find_minimum_major(stat: &caniuse::BrowserStat, count: usize) -> usize {
239    let mut seen = 0usize;
240    let mut last_major = None;
241    for version in stat.version_list.iter().rev().filter(|v| v.release_date().is_some()) {
242        let major = version.version().split('.').next().unwrap();
243        if last_major != Some(major) {
244            seen += 1;
245            if seen > count {
246                break;
247            }
248            // SAFETY: major borrows from version which borrows from stat (lives long enough within this scope)
249            last_major = Some(major);
250        }
251    }
252    last_major.and_then(|m| m.parse().ok()).unwrap_or(0)
253}