browserslist/queries/
supports.rs

1use super::{Distrib, QueryResult};
2use crate::{error::Error, parser::SupportKind, Opts};
3use browserslist_data::caniuse::{features::get_feature_stat, get_browser_stat, to_desktop_name};
4
5const Y: u8 = 1;
6const A: u8 = 2;
7
8pub(super) fn supports(name: &str, kind: Option<SupportKind>, opts: &Opts) -> QueryResult {
9    let include_partial = matches!(kind, Some(SupportKind::Partially) | None);
10
11    if let Some(feature) = get_feature_stat(name) {
12        let distribs = feature
13            .iter()
14            .filter_map(|(name, versions)| {
15                get_browser_stat(name, opts.mobile_to_desktop)
16                    .map(|(name, stat)| (name, stat, versions))
17            })
18            .flat_map(|(name, browser_stat, versions)| {
19                let desktop_name = opts
20                    .mobile_to_desktop
21                    .then_some(to_desktop_name(name))
22                    .flatten();
23                let check_desktop = desktop_name.is_some()
24                    && browser_stat
25                        .iter()
26                        .filter(|version| version.released)
27                        .filter_map(|latest_version| versions.get(latest_version.version()))
28                        .next_back()
29                        .is_some_and(|flags| is_supported(flags, include_partial));
30                browser_stat
31                    .iter()
32                    .filter_map(move |version| {
33                        versions
34                            .get(version.version())
35                            .or_else(|| match desktop_name {
36                                Some(desktop_name) if check_desktop => feature
37                                    .get(desktop_name)
38                                    .and_then(|versions| versions.get(version.version())),
39                                _ => None,
40                            })
41                            .and_then(|flags| {
42                                is_supported(flags, include_partial).then_some(version)
43                            })
44                    })
45                    .map(move |version| Distrib::new(name, version.version()))
46            })
47            .collect();
48        Ok(distribs)
49    } else {
50        Err(Error::UnknownBrowserFeature(name.to_string()))
51    }
52}
53
54fn is_supported(flags: u8, include_partial: bool) -> bool {
55    flags & Y != 0 || include_partial && flags & A != 0
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::{
62        opts::Opts,
63        test::{run_compare, should_failed},
64    };
65    use test_case::test_case;
66
67    #[test_case("supports objectrtc"; "case 1")]
68    #[test_case("supports    rtcpeerconnection"; "case 2")]
69    #[test_case("supports        arrow-functions"; "case 3")]
70    #[test_case("partially supports rtcpeerconnection"; "partially")]
71    #[test_case("fully     supports rtcpeerconnection"; "fully")]
72    fn default_options(query: &str) {
73        run_compare(query, &Opts::default(), None);
74    }
75
76    #[test_case("supports filesystem"; "case 1")]
77    #[test_case("supports  font-smooth"; "case 2")]
78    fn mobile_to_desktop(query: &str) {
79        run_compare(
80            query,
81            &Opts {
82                mobile_to_desktop: true,
83                ..Default::default()
84            },
85            None,
86        );
87    }
88
89    #[test]
90    fn invalid() {
91        assert_eq!(
92            should_failed("supports xxxyyyzzz", &Opts::default()),
93            Error::UnknownBrowserFeature(String::from("xxxyyyzzz"))
94        );
95    }
96}