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