about summary refs log tree commit diff
path: root/src/librustdoc/clean/cfg.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/clean/cfg.rs')
-rw-r--r--src/librustdoc/clean/cfg.rs889
1 files changed, 889 insertions, 0 deletions
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
new file mode 100644
index 00000000000..da8c3a5cf20
--- /dev/null
+++ b/src/librustdoc/clean/cfg.rs
@@ -0,0 +1,889 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Representation of a `#[doc(cfg(...))]` attribute.
+
+// FIXME: Once RFC #1868 is implemented, switch to use those structures instead.
+
+use std::mem;
+use std::fmt::{self, Write};
+use std::ops;
+use std::ascii::AsciiExt;
+
+use syntax::symbol::Symbol;
+use syntax::ast::{MetaItem, MetaItemKind, NestedMetaItem, NestedMetaItemKind, LitKind};
+use syntax::parse::ParseSess;
+use syntax::feature_gate::Features;
+
+use syntax_pos::Span;
+
+use html::escape::Escape;
+
+#[derive(Clone, RustcEncodable, RustcDecodable, Debug, PartialEq)]
+pub enum Cfg {
+    /// Accepts all configurations.
+    True,
+    /// Denies all configurations.
+    False,
+    /// A generic configration option, e.g. `test` or `target_os = "linux"`.
+    Cfg(Symbol, Option<Symbol>),
+    /// Negate a configuration requirement, i.e. `not(x)`.
+    Not(Box<Cfg>),
+    /// Union of a list of configuration requirements, i.e. `any(...)`.
+    Any(Vec<Cfg>),
+    /// Intersection of a list of configuration requirements, i.e. `all(...)`.
+    All(Vec<Cfg>),
+}
+
+#[derive(PartialEq, Debug)]
+pub struct InvalidCfgError {
+    pub msg: &'static str,
+    pub span: Span,
+}
+
+impl Cfg {
+    /// Parses a `NestedMetaItem` into a `Cfg`.
+    fn parse_nested(nested_cfg: &NestedMetaItem) -> Result<Cfg, InvalidCfgError> {
+        match nested_cfg.node {
+            NestedMetaItemKind::MetaItem(ref cfg) => Cfg::parse(cfg),
+            NestedMetaItemKind::Literal(ref lit) => Err(InvalidCfgError {
+                msg: "unexpected literal",
+                span: lit.span,
+            }),
+        }
+    }
+
+    /// Parses a `MetaItem` into a `Cfg`.
+    ///
+    /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g. `unix` or
+    /// `target_os = "redox"`.
+    ///
+    /// If the content is not properly formatted, it will return an error indicating what and where
+    /// the error is.
+    pub fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
+        let name = cfg.name();
+        match cfg.node {
+            MetaItemKind::Word => Ok(Cfg::Cfg(name, None)),
+            MetaItemKind::NameValue(ref lit) => match lit.node {
+                LitKind::Str(value, _) => Ok(Cfg::Cfg(name, Some(value))),
+                _ => Err(InvalidCfgError {
+                    // FIXME: if the main #[cfg] syntax decided to support non-string literals,
+                    // this should be changed as well.
+                    msg: "value of cfg option should be a string literal",
+                    span: lit.span,
+                }),
+            },
+            MetaItemKind::List(ref items) => {
+                let mut sub_cfgs = items.iter().map(Cfg::parse_nested);
+                match &*name.as_str() {
+                    "all" => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
+                    "any" => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
+                    "not" => if sub_cfgs.len() == 1 {
+                        Ok(!sub_cfgs.next().unwrap()?)
+                    } else {
+                        Err(InvalidCfgError {
+                            msg: "expected 1 cfg-pattern",
+                            span: cfg.span,
+                        })
+                    },
+                    _ => Err(InvalidCfgError {
+                        msg: "invalid predicate",
+                        span: cfg.span,
+                    }),
+                }
+            }
+        }
+    }
+
+    /// Checks whether the given configuration can be matched in the current session.
+    ///
+    /// Equivalent to `attr::cfg_matches`.
+    // FIXME: Actually make use of `features`.
+    pub fn matches(&self, parse_sess: &ParseSess, features: Option<&Features>) -> bool {
+        match *self {
+            Cfg::False => false,
+            Cfg::True => true,
+            Cfg::Not(ref child) => !child.matches(parse_sess, features),
+            Cfg::All(ref sub_cfgs) => {
+                sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(parse_sess, features))
+            },
+            Cfg::Any(ref sub_cfgs) => {
+                sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
+            },
+            Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
+        }
+    }
+
+    /// Whether the configuration consists of just `Cfg` or `Not`.
+    fn is_simple(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
+            Cfg::All(..) | Cfg::Any(..) => false,
+        }
+    }
+
+    /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
+    fn is_all(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
+            Cfg::Any(..) => false,
+        }
+    }
+
+    /// Renders the configuration for human display, as a short HTML description.
+    pub(crate) fn render_short_html(&self) -> String {
+        let mut msg = Html(self).to_string();
+        if self.should_capitalize_first_letter() {
+            if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
+                msg[i .. i+1].make_ascii_uppercase();
+            }
+        }
+        msg
+    }
+
+    /// Renders the configuration for long display, as a long HTML description.
+    pub(crate) fn render_long_html(&self) -> String {
+        let mut msg = format!("This is supported on <strong>{}</strong>", Html(self));
+        if self.should_append_only_to_description() {
+            msg.push_str(" only");
+        }
+        msg.push('.');
+        msg
+    }
+
+    fn should_capitalize_first_letter(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True | Cfg::Not(..) => true,
+            Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
+                sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
+            },
+            Cfg::Cfg(name, _) => match &*name.as_str() {
+                "debug_assertions" | "target_endian" => true,
+                _ => false,
+            },
+        }
+    }
+
+    fn should_append_only_to_description(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True => false,
+            Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
+            Cfg::Not(ref child) => match **child {
+                Cfg::Cfg(..) => true,
+                _ => false,
+            }
+        }
+    }
+}
+
+impl ops::Not for Cfg {
+    type Output = Cfg;
+    fn not(self) -> Cfg {
+        match self {
+            Cfg::False => Cfg::True,
+            Cfg::True => Cfg::False,
+            Cfg::Not(cfg) => *cfg,
+            s => Cfg::Not(Box::new(s)),
+        }
+    }
+}
+
+impl ops::BitAndAssign for Cfg {
+    fn bitand_assign(&mut self, other: Cfg) {
+        match (self, other) {
+            (&mut Cfg::False, _) | (_, Cfg::True) => {},
+            (s, Cfg::False) => *s = Cfg::False,
+            (s @ &mut Cfg::True, b) => *s = b,
+            (&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => a.append(b),
+            (&mut Cfg::All(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
+            (s, Cfg::All(mut a)) => {
+                let b = mem::replace(s, Cfg::True);
+                a.push(b);
+                *s = Cfg::All(a);
+            },
+            (s, b) => {
+                let a = mem::replace(s, Cfg::True);
+                *s = Cfg::All(vec![a, b]);
+            },
+        }
+    }
+}
+
+impl ops::BitAnd for Cfg {
+    type Output = Cfg;
+    fn bitand(mut self, other: Cfg) -> Cfg {
+        self &= other;
+        self
+    }
+}
+
+impl ops::BitOrAssign for Cfg {
+    fn bitor_assign(&mut self, other: Cfg) {
+        match (self, other) {
+            (&mut Cfg::True, _) | (_, Cfg::False) => {},
+            (s, Cfg::True) => *s = Cfg::True,
+            (s @ &mut Cfg::False, b) => *s = b,
+            (&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => a.append(b),
+            (&mut Cfg::Any(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
+            (s, Cfg::Any(mut a)) => {
+                let b = mem::replace(s, Cfg::True);
+                a.push(b);
+                *s = Cfg::Any(a);
+            },
+            (s, b) => {
+                let a = mem::replace(s, Cfg::True);
+                *s = Cfg::Any(vec![a, b]);
+            },
+        }
+    }
+}
+
+impl ops::BitOr for Cfg {
+    type Output = Cfg;
+    fn bitor(mut self, other: Cfg) -> Cfg {
+        self |= other;
+        self
+    }
+}
+
+struct Html<'a>(&'a Cfg);
+
+fn write_with_opt_paren<T: fmt::Display>(
+    fmt: &mut fmt::Formatter,
+    has_paren: bool,
+    obj: T,
+) -> fmt::Result {
+    if has_paren {
+        fmt.write_char('(')?;
+    }
+    obj.fmt(fmt)?;
+    if has_paren {
+        fmt.write_char(')')?;
+    }
+    Ok(())
+}
+
+
+impl<'a> fmt::Display for Html<'a> {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        match *self.0 {
+            Cfg::Not(ref child) => match **child {
+                Cfg::Any(ref sub_cfgs) => {
+                    let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
+                        " nor "
+                    } else {
+                        ", nor "
+                    };
+                    for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+                        fmt.write_str(if i == 0 { "neither " } else { separator })?;
+                        write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg))?;
+                    }
+                    Ok(())
+                }
+                ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple)),
+                ref c => write!(fmt, "not ({})", Html(c)),
+            },
+
+            Cfg::Any(ref sub_cfgs) => {
+                let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
+                    " or "
+                } else {
+                    ", or "
+                };
+                for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+                    if i != 0 {
+                        fmt.write_str(separator)?;
+                    }
+                    write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg))?;
+                }
+                Ok(())
+            },
+
+            Cfg::All(ref sub_cfgs) => {
+                for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+                    if i != 0 {
+                        fmt.write_str(" and ")?;
+                    }
+                    write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg))?;
+                }
+                Ok(())
+            },
+
+            Cfg::True => fmt.write_str("everywhere"),
+            Cfg::False => fmt.write_str("nowhere"),
+
+            Cfg::Cfg(name, value) => {
+                let n = &*name.as_str();
+                let human_readable = match (n, value) {
+                    ("unix", None) => "Unix",
+                    ("windows", None) => "Windows",
+                    ("debug_assertions", None) => "debug-assertions enabled",
+                    ("target_os", Some(os)) => match &*os.as_str() {
+                        "android" => "Android",
+                        "bitrig" => "Bitrig",
+                        "dragonfly" => "DragonFly BSD",
+                        "emscripten" => "Emscripten",
+                        "freebsd" => "FreeBSD",
+                        "fuchsia" => "Fuchsia",
+                        "haiku" => "Haiku",
+                        "ios" => "iOS",
+                        "l4re" => "L4Re",
+                        "linux" => "Linux",
+                        "macos" => "macOS",
+                        "nacl" => "NaCl",
+                        "netbsd" => "NetBSD",
+                        "openbsd" => "OpenBSD",
+                        "redox" => "Redox",
+                        "solaris" => "Solaris",
+                        "windows" => "Windows",
+                        _ => "",
+                    },
+                    ("target_arch", Some(arch)) => match &*arch.as_str() {
+                        "aarch64" => "AArch64",
+                        "arm" => "ARM",
+                        "asmjs" => "asm.js",
+                        "mips" => "MIPS",
+                        "mips64" => "MIPS-64",
+                        "msp430" => "MSP430",
+                        "powerpc" => "PowerPC",
+                        "powerpc64" => "PowerPC-64",
+                        "s390x" => "s390x",
+                        "sparc64" => "SPARC64",
+                        "wasm32" => "WebAssembly",
+                        "x86" => "x86",
+                        "x86_64" => "x86-64",
+                        _ => "",
+                    },
+                    ("target_vendor", Some(vendor)) => match &*vendor.as_str() {
+                        "apple" => "Apple",
+                        "pc" => "PC",
+                        "rumprun" => "Rumprun",
+                        "sun" => "Sun",
+                        _ => ""
+                    },
+                    ("target_env", Some(env)) => match &*env.as_str() {
+                        "gnu" => "GNU",
+                        "msvc" => "MSVC",
+                        "musl" => "musl",
+                        "newlib" => "Newlib",
+                        "uclibc" => "uClibc",
+                        _ => "",
+                    },
+                    ("target_endian", Some(endian)) => return write!(fmt, "{}-endian", endian),
+                    ("target_pointer_width", Some(bits)) => return write!(fmt, "{}-bit", bits),
+                    _ => "",
+                };
+                if !human_readable.is_empty() {
+                    fmt.write_str(human_readable)
+                } else if let Some(v) = value {
+                    write!(fmt, "<code>{}=\"{}\"</code>", Escape(n), Escape(&*v.as_str()))
+                } else {
+                    write!(fmt, "<code>{}</code>", Escape(n))
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::Cfg;
+
+    use syntax::symbol::Symbol;
+    use syntax::ast::*;
+    use syntax::codemap::dummy_spanned;
+    use syntax_pos::DUMMY_SP;
+
+    fn word_cfg(s: &str) -> Cfg {
+        Cfg::Cfg(Symbol::intern(s), None)
+    }
+
+    fn name_value_cfg(name: &str, value: &str) -> Cfg {
+        Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value)))
+    }
+
+    #[test]
+    fn test_cfg_not() {
+        assert_eq!(!Cfg::False, Cfg::True);
+        assert_eq!(!Cfg::True, Cfg::False);
+        assert_eq!(!word_cfg("test"), Cfg::Not(Box::new(word_cfg("test"))));
+        assert_eq!(
+            !Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
+            Cfg::Not(Box::new(Cfg::All(vec![word_cfg("a"), word_cfg("b")])))
+        );
+        assert_eq!(
+            !Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
+            Cfg::Not(Box::new(Cfg::Any(vec![word_cfg("a"), word_cfg("b")])))
+        );
+        assert_eq!(!Cfg::Not(Box::new(word_cfg("test"))), word_cfg("test"));
+    }
+
+    #[test]
+    fn test_cfg_and() {
+        let mut x = Cfg::False;
+        x &= Cfg::True;
+        assert_eq!(x, Cfg::False);
+
+        x = word_cfg("test");
+        x &= Cfg::False;
+        assert_eq!(x, Cfg::False);
+
+        x = word_cfg("test2");
+        x &= Cfg::True;
+        assert_eq!(x, word_cfg("test2"));
+
+        x = Cfg::True;
+        x &= word_cfg("test3");
+        assert_eq!(x, word_cfg("test3"));
+
+        x &= word_cfg("test4");
+        assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")]));
+
+        x &= word_cfg("test5");
+        assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
+
+        x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]);
+        assert_eq!(x, Cfg::All(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+        ]));
+
+        let mut y = Cfg::Any(vec![word_cfg("a"), word_cfg("b")]);
+        y &= x;
+        assert_eq!(y, Cfg::All(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+            Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
+        ]));
+
+        assert_eq!(
+            word_cfg("a") & word_cfg("b") & word_cfg("c"),
+            Cfg::All(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
+        );
+    }
+
+    #[test]
+    fn test_cfg_or() {
+        let mut x = Cfg::True;
+        x |= Cfg::False;
+        assert_eq!(x, Cfg::True);
+
+        x = word_cfg("test");
+        x |= Cfg::True;
+        assert_eq!(x, Cfg::True);
+
+        x = word_cfg("test2");
+        x |= Cfg::False;
+        assert_eq!(x, word_cfg("test2"));
+
+        x = Cfg::False;
+        x |= word_cfg("test3");
+        assert_eq!(x, word_cfg("test3"));
+
+        x |= word_cfg("test4");
+        assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")]));
+
+        x |= word_cfg("test5");
+        assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
+
+        x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]);
+        assert_eq!(x, Cfg::Any(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+        ]));
+
+        let mut y = Cfg::All(vec![word_cfg("a"), word_cfg("b")]);
+        y |= x;
+        assert_eq!(y, Cfg::Any(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+            Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
+        ]));
+
+        assert_eq!(
+            word_cfg("a") | word_cfg("b") | word_cfg("c"),
+            Cfg::Any(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
+        );
+    }
+
+    #[test]
+    fn test_parse_ok() {
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::Word,
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::NameValue(dummy_spanned(LitKind::Str(
+                Symbol::intern("done"),
+                StrStyle::Cooked,
+            ))),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(name_value_cfg("all", "done")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("any"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("any"),
+                    node: MetaItemKind::List(vec![
+                        dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                            name: Symbol::intern("a"),
+                            node: MetaItemKind::Word,
+                            span: DUMMY_SP,
+                        })),
+                        dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                            name: Symbol::intern("all"),
+                            node: MetaItemKind::List(vec![
+                                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                                    name: Symbol::intern("b"),
+                                    node: MetaItemKind::Word,
+                                    span: DUMMY_SP,
+                                })),
+                                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                                    name: Symbol::intern("c"),
+                                    node: MetaItemKind::Word,
+                                    span: DUMMY_SP,
+                                })),
+                            ]),
+                            span: DUMMY_SP,
+                        })),
+                    ]),
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c")))));
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("c"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c")));
+    }
+
+    #[test]
+    fn test_parse_err() {
+        let mi = MetaItem {
+            name: Symbol::intern("foo"),
+            node: MetaItemKind::NameValue(dummy_spanned(LitKind::Bool(false))),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("foo"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("foo"),
+                    node: MetaItemKind::List(vec![]),
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("any"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("foo"),
+                    node: MetaItemKind::List(vec![]),
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("foo"),
+                    node: MetaItemKind::List(vec![]),
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+    }
+
+    #[test]
+    fn test_render_short_html() {
+        assert_eq!(
+            word_cfg("unix").render_short_html(),
+            "Unix"
+        );
+        assert_eq!(
+            name_value_cfg("target_os", "macos").render_short_html(),
+            "macOS"
+        );
+        assert_eq!(
+            name_value_cfg("target_pointer_width", "16").render_short_html(),
+            "16-bit"
+        );
+        assert_eq!(
+            name_value_cfg("target_endian", "little").render_short_html(),
+            "Little-endian"
+        );
+        assert_eq!(
+            (!word_cfg("windows")).render_short_html(),
+            "Non-Windows"
+        );
+        assert_eq!(
+            (word_cfg("unix") & word_cfg("windows")).render_short_html(),
+            "Unix and Windows"
+        );
+        assert_eq!(
+            (word_cfg("unix") | word_cfg("windows")).render_short_html(),
+            "Unix or Windows"
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions")
+            ).render_short_html(),
+            "Unix and Windows and debug-assertions enabled"
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")
+            ).render_short_html(),
+            "Unix or Windows or debug-assertions enabled"
+        );
+        assert_eq!(
+            (
+                !(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
+            ).render_short_html(),
+            "Neither Unix nor Windows nor debug-assertions enabled"
+        );
+        assert_eq!(
+            (
+                (word_cfg("unix") & name_value_cfg("target_arch", "x86_64")) |
+                (word_cfg("windows") & name_value_cfg("target_pointer_width", "64"))
+            ).render_short_html(),
+            "Unix and x86-64, or Windows and 64-bit"
+        );
+        assert_eq!(
+            (!(word_cfg("unix") & word_cfg("windows"))).render_short_html(),
+            "Not (Unix and Windows)"
+        );
+        assert_eq!(
+            (
+                (word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix")
+            ).render_short_html(),
+            "(Debug-assertions enabled or Windows) and Unix"
+        );
+    }
+
+    #[test]
+    fn test_render_long_html() {
+        assert_eq!(
+            word_cfg("unix").render_long_html(),
+            "This is supported on <strong>Unix</strong> only."
+        );
+        assert_eq!(
+            name_value_cfg("target_os", "macos").render_long_html(),
+            "This is supported on <strong>macOS</strong> only."
+        );
+        assert_eq!(
+            name_value_cfg("target_pointer_width", "16").render_long_html(),
+            "This is supported on <strong>16-bit</strong> only."
+        );
+        assert_eq!(
+            name_value_cfg("target_endian", "little").render_long_html(),
+            "This is supported on <strong>little-endian</strong> only."
+        );
+        assert_eq!(
+            (!word_cfg("windows")).render_long_html(),
+            "This is supported on <strong>non-Windows</strong> only."
+        );
+        assert_eq!(
+            (word_cfg("unix") & word_cfg("windows")).render_long_html(),
+            "This is supported on <strong>Unix and Windows</strong> only."
+        );
+        assert_eq!(
+            (word_cfg("unix") | word_cfg("windows")).render_long_html(),
+            "This is supported on <strong>Unix or Windows</strong> only."
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions")
+            ).render_long_html(),
+            "This is supported on <strong>Unix and Windows and debug-assertions enabled</strong> \
+                only."
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")
+            ).render_long_html(),
+            "This is supported on <strong>Unix or Windows or debug-assertions enabled</strong> \
+                only."
+        );
+        assert_eq!(
+            (
+                !(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
+            ).render_long_html(),
+            "This is supported on <strong>neither Unix nor Windows nor debug-assertions \
+                enabled</strong>."
+        );
+        assert_eq!(
+            (
+                (word_cfg("unix") & name_value_cfg("target_arch", "x86_64")) |
+                (word_cfg("windows") & name_value_cfg("target_pointer_width", "64"))
+            ).render_long_html(),
+            "This is supported on <strong>Unix and x86-64, or Windows and 64-bit</strong> only."
+        );
+        assert_eq!(
+            (!(word_cfg("unix") & word_cfg("windows"))).render_long_html(),
+            "This is supported on <strong>not (Unix and Windows)</strong>."
+        );
+        assert_eq!(
+            (
+                (word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix")
+            ).render_long_html(),
+            "This is supported on <strong>(debug-assertions enabled or Windows) and Unix</strong> \
+                only."
+        );
+    }
+}
\ No newline at end of file