browserslist/
lib.rs

1#![allow(clippy::float_cmp)]
2#![deny(clippy::if_not_else)]
3#![deny(clippy::needless_borrow)]
4#![deny(clippy::unimplemented)]
5#![warn(missing_docs)]
6
7//! **browserslist-rs** is a Rust-based implementation of [Browserslist](https://github.com/browserslist/browserslist).
8//!
9//! ## Introduction
10//!
11//! This library bundles Can I Use data, Electron versions list and Node.js releases list,
12//! so it won't and doesn't need to access any data files.
13//!
14//! Except several non-widely/non-frequently used features,
15//! this library works as same as the JavaScript-based
16//! implementation [Browserslist](https://github.com/browserslist/browserslist).
17//!
18//! ## Usage
19//!
20//! It provides a simple API for querying which accepts a sequence of strings and options [`Opts`],
21//! then returns the result.
22//!
23//! ```
24//! use browserslist::{Distrib, Opts, resolve, Error};
25//!
26//! let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
27//! assert_eq!(distribs[0].name(), "ie");
28//! assert_eq!(distribs[0].version(), "6");
29//! assert_eq!(distribs[1].name(), "ie");
30//! assert_eq!(distribs[1].version(), "5.5");
31//!
32//! assert_eq!(
33//!     resolve(["yuru 1.0"], &Opts::default()),
34//!     Err(Error::BrowserNotFound(String::from("yuru")))
35//! );
36//! ```
37//!
38//! The result isn't a list of strings, instead, it's a tuple struct called [`Distrib`].
39//! If you need to retrieve something like JavaScript-based implementation of
40//! [Browserslist](https://github.com/browserslist/browserslist),
41//! you can convert them to strings:
42//!
43//! ```
44//! use browserslist::{Distrib, Opts, resolve, Error};
45//!
46//! let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
47//! assert_eq!(
48//!     distribs.into_iter().map(|d| d.to_string()).collect::<Vec<_>>(),
49//!     vec![String::from("ie 6"), String::from("ie 5.5")]
50//! );
51//! ```
52//!
53//! ## WebAssembly
54//!
55//! This crate can be compiled as WebAssembly, without configuring any features manually.
56//!
57//! Please note that browser and Deno can run WebAssembly,
58//! but those environments aren't Node.js,
59//! so you will receive an error when querying `current node` in those environments.
60
61use parser::parse_browserslist_query;
62use std::cmp::Ordering;
63pub use {error::Error, opts::Opts, queries::Distrib};
64
65#[cfg(not(target_arch = "wasm32"))]
66mod config;
67mod error;
68mod opts;
69mod parser;
70mod queries;
71mod semver;
72#[cfg(test)]
73mod test;
74
75/// Resolve browserslist queries.
76///
77/// This is a low-level API.
78/// If you want to load queries from configuration file and
79/// resolve them automatically,
80/// use the higher-level API [`execute`] instead.
81///
82/// ```
83/// use browserslist::{Distrib, Opts, resolve};
84///
85/// let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
86/// assert_eq!(distribs[0].name(), "ie");
87/// assert_eq!(distribs[0].version(), "6");
88/// assert_eq!(distribs[1].name(), "ie");
89/// assert_eq!(distribs[1].version(), "5.5");
90/// ```
91pub fn resolve<I, S>(queries: I, opts: &Opts) -> Result<Vec<Distrib>, Error>
92where
93    S: AsRef<str>,
94    I: IntoIterator<Item = S>,
95{
96    let query = queries
97        .into_iter()
98        .enumerate()
99        .fold(String::new(), |mut s, (i, query)| {
100            if i > 0 {
101                s.push_str(", ");
102            }
103            s.push_str(query.as_ref());
104            s
105        });
106
107    let mut distribs = parse_browserslist_query(&query)?
108        .1
109        .into_iter()
110        .enumerate()
111        .try_fold(vec![], |mut distribs, (i, current)| {
112            if i == 0 && current.negated {
113                return Err(Error::NotAtFirst(current.raw.to_string()));
114            }
115
116            let mut dist = queries::query(current.atom, opts)?;
117            if current.negated {
118                distribs.retain(|distrib| !dist.contains(distrib));
119            } else if current.is_and {
120                distribs.retain(|distrib| dist.contains(distrib));
121            } else {
122                distribs.append(&mut dist);
123            }
124
125            Ok::<_, Error>(distribs)
126        })?;
127
128    distribs.sort_by(|a, b| match a.name().cmp(b.name()) {
129        Ordering::Equal => {
130            let version_a = a.version().split('-').next().unwrap();
131            let version_b = b.version().split('-').next().unwrap();
132            version_b
133                .parse::<semver::Version>()
134                .unwrap_or_default()
135                .cmp(&version_a.parse().unwrap_or_default())
136        }
137        ord => ord,
138    });
139    distribs.dedup();
140
141    Ok(distribs)
142}
143
144#[cfg(not(target_arch = "wasm32"))]
145/// Load queries from configuration with environment information,
146/// then resolve those queries.
147///
148/// If you want to resolve custom queries (not from configuration file),
149/// use the lower-level API [`resolve`] instead.
150///
151/// ```
152/// use browserslist::{Opts, execute};
153///
154/// // when no config found, it use `defaults` query
155/// assert!(!execute(&Opts::default()).unwrap().is_empty());
156/// ```
157pub fn execute(opts: &Opts) -> Result<Vec<Distrib>, Error> {
158    resolve(config::load(opts)?, opts)
159}