Repository: rust-analyzer/rust-analyzer
Pull request #10915 (2021-12-03 15:32:42)

feat: Resolve builtin-attr and tools in ide layer
We now properly tag builtin attributes as builtin attributes(instead of heuristically) as well as the builtin tool modules in highlighting. (Support for registerered tools and attributes is still missing)

And we now show hover messages for builtin attributes, using the macro DSL rustc has for defining these and its templates(https://github.com/rust-lang/rust/blob/3e21768a0a3fc84befd1cbe825ae6849e9941b73/compiler/rustc_feature/src/builtin_attrs.rs#L250):
![Code_UBDFEjQitc](https://user-images.githubusercontent.com/3757771/144661639-dbd324df-807c-4cc0-b860-4953a98b9fa5.png)
![Code_bAoRusIdQd](https://user-images.githubusercontent.com/3757771/144661637-e9276cc8-e975-420e-ab32-29fa7732a21f.png)



Patch:
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index e0dc921c9f..4d758c7df7 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -103,6 +103,7 @@ pub use {
     hir_def::{
         adt::StructKind,
         attr::{Attr, Attrs, AttrsWithOwner, Documentation},
+        builtin_attr::AttributeTemplate,
         find_path::PrefixKind,
         import_map,
         item_scope::ItemScope,
@@ -2023,6 +2024,40 @@ impl Local {
     }
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct BuiltinAttr(usize);
+
+impl BuiltinAttr {
+    pub(crate) fn by_name(name: &str) -> Option<Self> {
+        // FIXME: def maps registered attrs?
+        hir_def::builtin_attr::find_builtin_attr_idx(name).map(Self)
+    }
+
+    pub fn name(&self, _: &dyn HirDatabase) -> &str {
+        // FIXME: Return a `Name` here
+        hir_def::builtin_attr::INERT_ATTRIBUTES[self.0].name
+    }
+
+    pub fn template(&self, _: &dyn HirDatabase) -> AttributeTemplate {
+        hir_def::builtin_attr::INERT_ATTRIBUTES[self.0].template
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct ToolModule(usize);
+
+impl ToolModule {
+    pub(crate) fn by_name(name: &str) -> Option<Self> {
+        // FIXME: def maps registered tools
+        hir_def::builtin_attr::TOOL_MODULES.iter().position(|&tool| tool == name).map(Self)
+    }
+
+    pub fn name(&self, _: &dyn HirDatabase) -> &str {
+        // FIXME: Return a `Name` here
+        hir_def::builtin_attr::TOOL_MODULES[self.0]
+    }
+}
+
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub struct Label {
     pub(crate) parent: DefWithBodyId,
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 27ba42ce1a..ed1b2f64fd 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -25,9 +25,9 @@ use crate::{
     db::HirDatabase,
     semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
     source_analyzer::{resolve_hir_path, resolve_hir_path_as_macro, SourceAnalyzer},
-    Access, AssocItem, Callable, ConstParam, Crate, Field, Function, HasSource, HirFileId, Impl,
-    InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef, Name, Path, ScopeDef, Trait,
-    Type, TypeAlias, TypeParam, VariantDef,
+    Access, AssocItem, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource,
+    HirFileId, Impl, InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef, Name, Path,
+    ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
 };
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -43,6 +43,8 @@ pub enum PathResolution {
     SelfType(Impl),
     Macro(MacroDef),
     AssocItem(AssocItem),
+    BuiltinAttr(BuiltinAttr),
+    ToolModule(ToolModule),
 }
 
 impl PathResolution {
@@ -63,9 +65,11 @@ impl PathResolution {
             PathResolution::Def(ModuleDef::TypeAlias(alias)) => {
                 Some(TypeNs::TypeAliasId((*alias).into()))
             }
-            PathResolution::Local(_) | PathResolution::Macro(_) | PathResolution::ConstParam(_) => {
-                None
-            }
+            PathResolution::BuiltinAttr(_)
+            | PathResolution::ToolModule(_)
+            | PathResolution::Local(_)
+            | PathResolution::Macro(_)
+            | PathResolution::ConstParam(_) => None,
             PathResolution::TypeParam(param) => Some(TypeNs::GenericParam((*param).into())),
             PathResolution::SelfType(impl_def) => Some(TypeNs::SelfType((*impl_def).into())),
             PathResolution::AssocItem(AssocItem::Const(_) | AssocItem::Function(_)) => None,
@@ -334,10 +338,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.resolve_path(path)
     }
 
-    pub fn resolve_path_as_macro(&self, path: &ast::Path) -> Option<MacroDef> {
-        self.imp.resolve_path_as_macro(path)
-    }
-
     pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
         self.imp.resolve_extern_crate(extern_crate)
     }
@@ -860,12 +860,6 @@ impl<'db> SemanticsImpl<'db> {
         self.analyze(path.syntax()).resolve_path(self.db, path)
     }
 
-    // FIXME: This shouldn't exist, but is currently required to always resolve attribute paths in
-    // the IDE layer due to namespace collisions
-    fn resolve_path_as_macro(&self, path: &ast::Path) -> Option<MacroDef> {
-        self.analyze(path.syntax()).resolve_path_as_macro(self.db, path)
-    }
-
     fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
         let krate = self.scope(extern_crate.syntax()).krate()?;
         krate.dependencies(self.db).into_iter().find_map(|dep| {
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 23fcd02b57..762f172c2a 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -29,8 +29,9 @@ use syntax::{
 };
 
 use crate::{
-    db::HirDatabase, semantics::PathResolution, Adt, BuiltinType, Const, Field, Function, Local,
-    MacroDef, ModuleDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, Variant,
+    db::HirDatabase, semantics::PathResolution, Adt, BuiltinAttr, BuiltinType, Const, Field,
+    Function, Local, MacroDef, ModuleDef, Static, Struct, ToolModule, Trait, Type, TypeAlias,
+    TypeParam, Variant,
 };
 use base_db::CrateId;
 
@@ -246,24 +247,14 @@ impl SourceAnalyzer {
         }
     }
 
-    pub(crate) fn resolve_path_as_macro(
-        &self,
-        db: &dyn HirDatabase,
-        path: &ast::Path,
-    ) -> Option<MacroDef> {
-        // This must be a normal source file rather than macro file.
-        let hygiene = Hygiene::new(db.upcast(), self.file_id);
-        let ctx = body::LowerCtx::with_hygiene(db.upcast(), &hygiene);
-        let hir_path = Path::from_src(path.clone(), &ctx)?;
-        resolve_hir_path_as_macro(db, &self.resolver, &hir_path)
-    }
-
     pub(crate) fn resolve_path(
         &self,
         db: &dyn HirDatabase,
         path: &ast::Path,
     ) -> Option<PathResolution> {
-        let parent = || path.syntax().parent();
+        let parent = path.syntax().parent();
+        let parent = || parent.clone();
+
         let mut prefer_value_ns = false;
         if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) {
             let expr_id = self.expr_id(db, &path_expr.into())?;
@@ -318,29 +309,62 @@ impl SourceAnalyzer {
         let ctx = body::LowerCtx::with_hygiene(db.upcast(), &hygiene);
         let hir_path = Path::from_src(path.clone(), &ctx)?;
 
-        // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we are
+        // Case where path is a qualifier of a use tree, e.g. foo::bar::{Baz, Qux} where we are
         // trying to resolve foo::bar.
-        if let Some(outer_path) = parent().and_then(ast::Path::cast) {
-            if let Some(qualifier) = outer_path.qualifier() {
-                if path == &qualifier {
+        if let Some(use_tree) = parent().and_then(ast::UseTree::cast) {
+            if let Some(qualifier) = use_tree.path() {
+                if path == &qualifier && use_tree.coloncolon_token().is_some() {
                     return resolve_hir_path_qualifier(db, &self.resolver, &hir_path);
                 }
             }
         }
-        // Case where path is a qualifier of a use tree, e.g. foo::bar::{Baz, Qux} where we are
+
+        let is_path_of_attr = path
+            .top_path()
+            .syntax()
+            .ancestors()
+            .nth(2) // Path -> Meta -> Attr
+            .map_or(false, |it| ast::Attr::can_cast(it.kind()));
+
+        // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we are
         // trying to resolve foo::bar.
-        if let Some(use_tree) = parent().and_then(ast::UseTree::cast) {
-            if let Some(qualifier) = use_tree.path() {
-                if path == &qualifier && use_tree.coloncolon_token().is_some() {
+        if let Some(outer_path) = path.parent_path() {
+            if let Some(qualifier) = outer_path.qualifier() {
+                if path == &qualifier {
                     return resolve_hir_path_qualifier(db, &self.resolver, &hir_path);
                 }
             }
+        } else if is_path_of_attr {
+            let res = resolve_hir_path_as_macro(db, &self.resolver, &hir_path);
+            return match res {
+                Some(_) => res.map(PathResolution::Macro),
+                None => path.as_single_name_ref().and_then(|name_ref| {
+                    if let builtin @ Some(_) = BuiltinAttr::by_name(&name_ref.text()) {
+                        builtin.map(PathResolution::BuiltinAttr)
+                    } else if let tool @ Some(_) = ToolModule::by_name(&name_ref.text()) {
+                        tool.map(PathResolution::ToolModule)
+                    } else {
+                        None
+                    }
+                }),
+            };
         }
 
-        if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) {
+        let res = if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) {
             resolve_hir_path_qualifier(db, &self.resolver, &hir_path)
         } else {
             resolve_hir_path_(db, &self.resolver, &hir_path, prefer_value_ns)
+        };
+        match res {
+            Some(_) => res,
+            // this labels any path that starts with a tool module as the tool itself, this is technically wrong
+            // but there is no benefit in differentiating these two cases for the time being
+            None if is_path_of_attr => path
+                .first_segment()
+                .and_then(|seg| seg.name_ref())
+                .and_then(|name_ref| ToolModule::by_name(&name_ref.text()))
+                .map(PathResolution::ToolModule),
+            None => None,
         }
     }
 
diff --git a/crates/hir_def/src/builtin_attr.rs b/crates/hir_def/src/builtin_attr.rs
index 6cd185ceeb..3f43111fb1 100644
--- a/crates/hir_def/src/builtin_attr.rs
+++ b/crates/hir_def/src/builtin_attr.rs
@@ -2,35 +2,100 @@
 //!
 //! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`.
 //!
-//! It was last synchronized with upstream commit 835150e70288535bc57bb624792229b9dc94991d.
+//! It was last synchronized with upstream commit ae90dcf0207c57c3034f00b07048d63f8b2363c8.
 //!
 //! The macros were adjusted to only expand to the attribute name, since that is all we need to do
 //! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to
 //! ease updating.
 
+use once_cell::sync::OnceCell;
+use rustc_hash::FxHashMap;
+
 /// Ignored attribute namespaces used by tools.
 pub const TOOL_MODULES: &[&str] = &["rustfmt", "clippy"];
 
-type BuiltinAttribute = &'static str;
+pub struct BuiltinAttribute {
+    pub name: &'static str,
+    pub template: AttributeTemplate,
+}
+
+/// A template that the attribute input must match.
+/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now.
+#[derive(Clone, Copy)]
+pub struct AttributeTemplate {
+    pub word: bool,
+    pub list: Option<&'static str>,
+    pub name_value_str: Option<&'static str>,
+}
+
+pub fn find_builtin_attr_idx(name: &str) -> Option<usize> {
+    static BUILTIN_LOOKUP_TABLE: OnceCell<FxHashMap<&'static str, usize>> = OnceCell::new();
+    BUILTIN_LOOKUP_TABLE
+        .get_or_init(|| {
+            INERT_ATTRIBUTES.iter().map(|attr| attr.name).enumerate().map(|(a, b)| (b, a)).collect()
+        })
+        .get(name)
+        .copied()
+}
+
+// impl AttributeTemplate {
+//     const DEFAULT: AttributeTemplate =
+//         AttributeTemplate { word: false, list: None, name_value_str: None };
+// }
+
+/// A convenience macro for constructing attribute templates.
+/// E.g., `template!(Word, List: "description")` means that the attribute
+/// supports forms `#[attr]` and `#[attr(description)]`.
+macro_rules! template {
+    (Word) => { template!(@ true, None, None) };
+    (List: $descr: expr) => { template!(@ false, Some($descr), None) };
+    (NameValueStr: $descr: expr) => { template!(@ false, None, Some($descr)) };
+    (Word, List: $descr: expr) => { template!(@ true, Some($descr), None) };
+    (Word, NameValueStr: $descr: expr) => { template!(@ true, None, Some($descr)) };
+    (List: $descr1: expr, NameValueStr: $descr2: expr) => {
+        template!(@ false, Some($descr1), Some($descr2))
+    };
+    (Word, List: $descr1: expr, NameValueStr: $descr2: expr) => {
+        template!(@ true, Some($descr1), Some($descr2))
+    };
+    (@ $word: expr, $list: expr, $name_value_str: expr) => {
+        AttributeTemplate {
+            word: $word, list: $list, name_value_str: $name_value_str
+        }
+    };
+}
 
 macro_rules! ungated {
     ($attr:ident, $typ:expr, $tpl:expr $(,)?) => {
-        stringify!($attr)
+        BuiltinAttribute { name: stringify!($attr), template: $tpl }
     };
 }
 
 macro_rules! gated {
-    ($attr:ident $($rest:tt)*) => {
-        stringify!($attr)
+    ($attr:ident, $typ:expr, $tpl:expr, $gate:ident, $msg:expr $(,)?) => {
+        BuiltinAttribute { name: stringify!($attr), template: $tpl }
+    };
+    ($attr:ident, $typ:expr, $tpl:expr, $msg:expr $(,)?) => {
+        BuiltinAttribute { name: stringify!($attr), template: $tpl }
     };
 }
 
 macro_rules! rustc_attr {
-    (TEST, $attr:ident $($rest:tt)*) => {
-        stringify!($attr)
+    (TEST, $attr:ident, $typ:expr, $tpl:expr $(,)?) => {
+        rustc_attr!(
+            $attr,
+            $typ,
+            $tpl,
+            concat!(
+                "the `#[",
+                stringify!($attr),
+                "]` attribute is just used for rustc unit tests \
+                and will never be stable",
+            ),
+        )
     };
-    ($attr:ident $($rest:tt)*) => {
-        stringify!($attr)
+    ($attr:ident, $typ:expr, $tpl:expr, $msg:expr $(,)?) => {
+        BuiltinAttribute { name: stringify!($attr), template: $tpl }
     };
 }
 
@@ -158,8 +223,8 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
 
     // Plugins:
     // XXX Modified for use in rust-analyzer
-    gated!(plugin_registrar),
-    gated!(plugin),
+    gated!(plugin_registrar, Normal, template!(Word), experimental!()),
+    gated!(plugin, CrateLevel, template!(Word), experimental!()),
 
     // Testing:
     gated!(allow_fail, Normal, template!(Word), experimental!(allow_fail)),
@@ -195,6 +260,12 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
     ),
 
     gated!(cmse_nonsecure_entry, AssumedUsed, template!(Word), experimental!(cmse_nonsecure_entry)),
+    // RFC 2632
+    gated!(
+        default_method_body_is_const, AssumedUsed, template!(Word), const_trait_impl,
+        "`default_method_body_is_const` is a temporary placeholder for declaring default bodies \
+        as `const`, which may be removed or renamed in the future."
+    ),
 
     // ==========================================================================
     // Internal attributes: Stability, deprecation, and unsafe:
@@ -258,10 +329,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
     ),
     gated!(panic_runtime, AssumedUsed, template!(Word), experimental!(panic_runtime)),
     gated!(needs_panic_runtime, AssumedUsed, template!(Word), experimental!(needs_panic_runtime)),
-    gated!(
-        unwind, AssumedUsed, template!(List: "allowed|aborts"), unwind_attributes,
-        experimental!(unwind),
-    ),
     gated!(
         compiler_builtins, AssumedUsed, template!(Word),
         "the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate \
@@ -287,7 +354,11 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
     // Internal attributes, Macro related:
     // ==========================================================================
 
-    rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word, NameValueStr: "name"), IMPL_DETAIL),
+    rustc_attr!(
+        rustc_builtin_macro, AssumedUsed,
+        template!(Word, List: "name, /*opt*/ attributes(name1, name2, ...)"),
+        IMPL_DETAIL,
+    ),
     rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), INTERNAL_UNSTABLE),
     rustc_attr!(
         rustc_macro_transparency, AssumedUsed,
@@ -344,7 +415,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
         lang, Normal, template!(NameValueStr: "name"), lang_items,
         "language items are subject to change",
     ),
-    gated!(rustc_diagnostic_item), // XXX modified in rust-analyzer
+    gated!(rustc_diagnostic_item, Normal, template!(NameValueStr: "name"), experimental!()), // XXX Modified for use in rust-analyzer
     gated!(
         // Used in resolve:
         prelude_import, AssumedUsed, template!(Word),
@@ -428,6 +499,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
     rustc_attr!(TEST, rustc_dump_program_clauses, AssumedUsed, template!(Word)),
     rustc_attr!(TEST, rustc_dump_env_program_clauses, AssumedUsed, template!(Word)),
     rustc_attr!(TEST, rustc_object_lifetime_default, AssumedUsed, template!(Word)),
+    rustc_attr!(TEST, rustc_dump_vtable, AssumedUsed, template!(Word)),
     rustc_attr!(TEST, rustc_dummy, Normal, template!(Word /* doesn't matter*/)),
     gated!(
         omit_gdb_pretty_printer_section, AssumedUsed, template!(Word),
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index d7a35caf29..ad4a4aa752 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1798,14 +1798,18 @@ impl ModCollector<'_, '_> {
 
             let registered = self.def_collector.registered_tools.iter().map(SmolStr::as_str);
             let is_tool = builtin_attr::TOOL_MODULES.iter().copied().chain(registered).any(pred);
+            // FIXME: tool modules can be shadowed by actual modules
             if is_tool {
                 return true;
             }
 
             if segments.len() == 1 {
                 let registered = self.def_collector.registered_attrs.iter().map(SmolStr::as_str);
-                let is_inert =
-                    builtin_attr::INERT_ATTRIBUTES.iter().copied().chain(registered).any(pred);
+                let is_inert = builtin_attr::INERT_ATTRIBUTES
+                    .iter()
+                    .map(|it| it.name)
+                    .chain(registered)
+                    .any(pred);
                 return is_inert;
             }
         }
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index d5dca95fba..617f63d358 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -181,7 +181,9 @@ pub(crate) fn resolve_doc_path_for_def(
         Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
         Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
         Definition::Field(it) => it.resolve_doc_path(db, link, ns),
-        Definition::BuiltinType(_)
+        Definition::BuiltinAttr(_)
+        | Definition::ToolModule(_)
+        | Definition::BuiltinType(_)
         | Definition::SelfType(_)
         | Definition::Local(_)
         | Definition::GenericParam(_)
@@ -492,9 +494,11 @@ fn filename_and_frag_for_def(
             // FIXME fragment numbering
             return Some((adt, file, Some(String::from("impl"))));
         }
-        Definition::Local(_) => return None,
-        Definition::GenericParam(_) => return None,
-        Definition::Label(_) => return None,
+        Definition::Local(_)
+        | Definition::GenericParam(_)
+        | Definition::Label(_)
+        | Definition::BuiltinAttr(_)
+        | Definition::ToolModule(_) => return None,
     };
 
     Some((def, res, None))
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index dd4a961d31..c493e3e2fb 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -2,7 +2,7 @@
 use std::fmt::Display;
 
 use either::Either;
-use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
+use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
 use ide_db::{
     base_db::SourceDatabase,
     defs::Definition,
@@ -369,11 +369,32 @@ pub(super) fn definition(
         }
         Definition::GenericParam(it) => label_and_docs(db, it),
         Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))),
+        // FIXME: We should be able to show more info about these
+        Definition::BuiltinAttr(it) => return render_builtin_attr(db, it),
+        Definition::ToolModule(it) => return Some(Markup::fenced_block(&it.name(db))),
     };
 
     markup(docs.filter(|_| config.documentation.is_some()).map(Into::into), label, mod_path)
 }
 
+fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
+    let name = attr.name(db);
+    let desc = format!("#[{}]", name);
+
+    let AttributeTemplate { word, list, name_value_str } = attr.template(db);
+    let mut docs = "Valid forms are:".to_owned();
+    if word {
+        format_to!(docs, "\n - #\\[{}]", name);
+    }
+    if let Some(list) = list {
+        format_to!(docs, "\n - #\\[{}({})]", name, list);
+    }
+    if let Some(name_value_str) = name_value_str {
+        format_to!(docs, "\n - #\\[{} = {}]", name, name_value_str);
+    }
+    markup(Some(docs.replace('*', "\\*")), desc, None)
+}
+
 fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
 where
     D: HasAttrs + HirDisplay,
diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs
index c93c471f49..9d8c742fc4 100644
--- a/crates/ide/src/moniker.rs
+++ b/crates/ide/src/moniker.rs
@@ -1,7 +1,7 @@
 //! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
 //! for LSIF and LSP.
 
-use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
+use hir::{db::DefDatabase, Crate, Name, Semantics};
 use ide_db::{
     base_db::{CrateOrigin, FileId, FileLoader, FilePosition},
     defs::Definition,
@@ -106,34 +106,9 @@ pub(crate) fn def_to_moniker(
     let krate = module.krate();
     let mut path = vec![];
     path.extend(module.path_to_root(db).into_iter().filter_map(|x| x.name(db)));
-
-    // Handle associated items within a trait
-    if let Some(assoc) = def.as_assoc_item(db) {
-        let container = assoc.container(db);
-        match container {
-            AssocItemContainer::Trait(trait_) => {
-                // Because different traits can have functions with the same name,
-                // we have to include the trait name as part of the moniker for uniqueness.
-                path.push(trait_.name(db));
-            }
-            AssocItemContainer::Impl(impl_) => {
-                // Because a struct can implement multiple traits, for implementations
-                // we add both the struct name and the trait name to the path
-                if let Some(adt) = impl_.self_ty(db).as_adt() {
-                    path.push(adt.name(db));
-                }
-
-                if let Some(trait_) = impl_.trait_(db) {
-                    path.push(trait_.name(db));
-                }
-            }
-        }
-    }
-
     if let Definition::Field(it) = def {
         path.push(it.parent_def(db).name(db));
     }
-
     path.push(def.name(db)?);
     Some(MonikerResult {
         identifier: MonikerIdentifier {
@@ -217,80 +192,6 @@ pub mod module {
         );
     }
 
-    #[test]
-    fn moniker_for_trait() {
-        check_moniker(
-            r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
-pub mod module {
-    pub trait MyTrait {
-        pub fn func$0() {}
-    }
-}
-"#,
-            "foo::module::MyTrait::func",
-            r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
-            MonikerKind::Export,
-        );
-    }
-
-    #[test]
-    fn moniker_for_trait_constant() {
-        check_moniker(
-            r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
-pub mod module {
-    pub trait MyTrait {
-        const MY_CONST$0: u8;
-    }
-}
-"#,
-            "foo::module::MyTrait::MY_CONST",
-            r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
-            MonikerKind::Export,
-        );
-    }
-
-    #[test]
-    fn moniker_for_trait_type() {
-        check_moniker(
-            r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
-pub mod module {
-    pub trait MyTrait {
-        type MyType$0;
-    }
-}
-"#,
-            "foo::module::MyTrait::MyType",
-            r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
-            MonikerKind::Export,
-        );
-    }
-
-    #[test]
-    fn moniker_for_trait_impl_function() {
-        check_moniker(
-            r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
-pub mod module {
-    pub trait MyTrait {
-        pub fn func() {}
-    }
-
-    struct MyStruct {}
-
-    impl MyTrait for MyStruct {
-        pub fn func$0() {}
-    }
-}
-"#,
-            "foo::module::MyStruct::MyTrait::func",
-            r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
-            MonikerKind::Export,
-        );
-    }
-
     #[test]
     fn moniker_for_field() {
         check_moniker(
diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs
index 7deb6cae38..49ff3625ca 100644
--- a/crates/ide/src/navigation_target.rs
+++ b/crates/ide/src/navigation_target.rs
@@ -214,6 +214,8 @@ impl TryToNav for Definition {
             Definition::Trait(it) => it.try_to_nav(db),
             Definition::TypeAlias(it) => it.try_to_nav(db),
             Definition::BuiltinType(_) => None,
+            Definition::ToolModule(_) => None,
+            Definition::BuiltinAttr(_) => None,
         }
     }
 }
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index cf98bc32fa..77d202cc39 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -299,6 +299,7 @@ pub func() {
             r#"
 //- minicore:derive
 #[rustc_builtin_macro]
+//^^^^^^^^^^^^^^^^^^^
 pub macro Copy {}
         //^^^^
 #[derive(Copy)]
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 8b613fde54..56e07da0b9 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -115,6 +115,7 @@ pub struct HlRange {
 // parameter:: Emitted for non-self function parameters.
 // property:: Emitted for struct and union fields.
 // selfKeyword:: Emitted for the self function parameter and self path-specifier.
+// toolModule:: Emitted for tool modules.
 // typeParameter:: Emitted for type parameters.
 // unresolvedReference:: Emitted for unresolved references, names that rust-analyzer can't find the definition of.
 // variable:: Emitted for locals, constants and statics.
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index b8177b4ab6..34c4ccd4c7 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -208,22 +208,14 @@ fn node(
             },
             // Highlight references like the definitions they resolve to
             ast::NameRef(name_ref) => {
-                if node.ancestors().any(|it| it.kind() == ATTR) {
-
-                    // FIXME: We highlight paths in attributes slightly differently to work around this module
-                    // currently not knowing about tool attributes and rustc builtin attributes as
-                    // we do not want to resolve those to functions that may be defined in scope.
-                    highlight_name_ref_in_attr(sema, name_ref)
-                } else {
-                    highlight_name_ref(
-                        sema,
-                        krate,
-                        bindings_shadow_count,
-                        &mut binding_hash,
-                        syntactic_name_ref_highlighting,
-                        name_ref,
-                    )
-                }
+                highlight_name_ref(
+                    sema,
+                    krate,
+                    bindings_shadow_count,
+                    &mut binding_hash,
+                    syntactic_name_ref_highlighting,
+                    name_ref,
+                )
             },
             ast::Lifetime(lifetime) => {
                 match NameClass::classify_lifetime(sema, &lifetime) {
@@ -243,28 +235,6 @@ fn node(
     Some((highlight, binding_hash))
 }
 
-fn highlight_name_ref_in_attr(sema: &Semantics<RootDatabase>, name_ref: ast::NameRef) -> Highlight {
-    match NameRefClass::classify(sema, &name_ref) {
-        Some(name_class) => match name_class {
-            NameRefClass::Definition(Definition::Module(_))
-                if name_ref
-                    .syntax()
-                    .ancestors()
-                    .find_map(ast::Path::cast)
-                    .map_or(false, |it| it.parent_path().is_some()) =>
-            {
-                HlTag::Symbol(SymbolKind::Module)
-            }
-            NameRefClass::Definition(Definition::Macro(m)) if m.kind() == hir::MacroKind::Attr => {
-                HlTag::Symbol(SymbolKind::Macro)
-            }
-            _ => HlTag::BuiltinAttr,
-        },
-        None => HlTag::BuiltinAttr,
-    }
-    .into()
-}
-
 fn highlight_name_ref(
     sema: &Semantics<RootDatabase>,
     krate: Option<hir::Crate>,
@@ -542,6 +512,8 @@ fn highlight_def(
             h
         }
         Definition::Label(_) => Highlight::new(HlTag::Symbol(SymbolKind::Label)),
+        Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)),
+        Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)),
     };
 
     let famous_defs = FamousDefs(sema, krate);
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 686fd5baa7..a3b05ee263 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -263,6 +263,8 @@ fn module_def_to_hl_tag(def: Definition) -> HlTag {
             hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
         },
         Definition::Label(_) => SymbolKind::Label,
+        Definition::BuiltinAttr(_) => SymbolKind::BuiltinAttr,
+        Definition::ToolModule(_) => SymbolKind::ToolModule,
     };
     HlTag::Symbol(symbol)
 }
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index 92c7fcab76..3e0a78392a 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -20,7 +20,6 @@ pub enum HlTag {
 
     Attribute,
     BoolLiteral,
-    BuiltinAttr,
     BuiltinType,
     ByteLiteral,
     CharLiteral,
@@ -125,30 +124,31 @@ impl HlTag {
     fn as_str(self) -> &'static str {
         match self {
             HlTag::Symbol(symbol) => match symbol {
+                SymbolKind::BuiltinAttr => "builtin_attr",
                 SymbolKind::Const => "constant",
-                SymbolKind::Static => "static",
+                SymbolKind::ConstParam => "const_param",
                 SymbolKind::Enum => "enum",
-                SymbolKind::Variant => "enum_variant",
-                SymbolKind::Struct => "struct",
-                SymbolKind::Union => "union",
                 SymbolKind::Field => "field",
+                SymbolKind::Function => "function",
+                SymbolKind::Impl => "self_type",
+                SymbolKind::Label => "label",
+                SymbolKind::LifetimeParam => "lifetime",
+                SymbolKind::Local => "variable",
+                SymbolKind::Macro => "macro",
                 SymbolKind::Module => "module",
+                SymbolKind::SelfParam => "self_keyword",
+                SymbolKind::Static => "static",
+                SymbolKind::Struct => "struct",
+                SymbolKind::ToolModule => "tool_module",
                 SymbolKind::Trait => "trait",
-                SymbolKind::Function => "function",
                 SymbolKind::TypeAlias => "type_alias",
                 SymbolKind::TypeParam => "type_param",
-                SymbolKind::ConstParam => "const_param",
-                SymbolKind::LifetimeParam => "lifetime",
-                SymbolKind::Macro => "macro",
-                SymbolKind::Local => "variable",
-                SymbolKind::Label => "label",
+                SymbolKind::Union => "union",
                 SymbolKind::ValueParam => "value_param",
-                SymbolKind::SelfParam => "self_keyword",
-                SymbolKind::Impl => "self_type",
+                SymbolKind::Variant => "enum_variant",
             },
             HlTag::Attribute => "attribute",
             HlTag::BoolLiteral => "bool_literal",
-            HlTag::BuiltinAttr => "builtin_attr",
             HlTag::BuiltinType => "builtin_type",
             HlTag::ByteLiteral => "byte_literal",
             HlTag::CharLiteral => "char_literal",
diff --git a/crates/ide_assists/src/handlers/add_return_type.rs b/crates/ide_assists/src/handlers/add_return_type.rs
index 2c5b61eddb..84983597eb 100644
--- a/crates/ide_assists/src/handlers/add_return_type.rs
+++ b/crates/ide_assists/src/handlers/add_return_type.rs
@@ -1,5 +1,5 @@
 use hir::HirDisplay;
-use syntax::{ast, AstNode, SyntaxKind, SyntaxToken, TextRange, TextSize};
+use syntax::{ast, AstNode, TextRange, TextSize};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
 
@@ -33,9 +33,8 @@ pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<
         tail_expr.syntax().text_range(),
         |builder| {
             match builder_edit_pos {
-                InsertOrReplace::Insert(insert_pos, needs_whitespace) => {
-                    let preceeding_whitespace = if needs_whitespace { " " } else { "" };
-                    builder.insert(insert_pos, &format!("{}-> {} ", preceeding_whitespace, ty))
+                InsertOrReplace::Insert(insert_pos) => {
+                    builder.insert(insert_pos, &format!("-> {} ", ty))
                 }
                 InsertOrReplace::Replace(text_range) => {
                     builder.replace(text_range, &format!("-> {}", ty))
@@ -51,16 +50,13 @@ pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<
 }
 
 enum InsertOrReplace {
-    Insert(TextSize, bool),
+    Insert(TextSize),
     Replace(TextRange),
 }
 
 /// Check the potentially already specified return type and reject it or turn it into a builder command
 /// if allowed.
-fn ret_ty_to_action(
-    ret_ty: Option<ast::RetType>,
-    insert_after: SyntaxToken,
-) -> Option<InsertOrReplace> {
+fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
     match ret_ty {
         Some(ret_ty) => match ret_ty.ty() {
             Some(ast::Type::InferType(_)) | None => {
@@ -74,17 +70,7 @@ fn ret_ty_to_action(
                 None
             }
         },
-        None => {
-            let insert_after_pos = insert_after.text_range().end();
-            let (insert_pos, needs_whitespace) = match insert_after.next_token() {
-                Some(it) if it.kind() == SyntaxKind::WHITESPACE => {
-                    (insert_after_pos + TextSize::from(1), false)
-                }
-                _ => (insert_after_pos, true),
-            };
-
-            Some(InsertOrReplace::Insert(insert_pos, needs_whitespace))
-        }
+        None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
     }
 }
 
@@ -96,10 +82,8 @@ enum FnType {
 fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
     let (fn_type, tail_expr, return_type_range, action) =
         if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
-            let rpipe = closure.param_list()?.syntax().last_token()?;
-            let rpipe_pos = rpipe.text_range().end();
-
-            let action = ret_ty_to_action(closure.ret_type(), rpipe)?;
+            let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
+            let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
 
             let body = closure.body()?;
             let body_start = body.syntax().first_token()?.text_range().start();
@@ -112,10 +96,8 @@ fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrRepla
             (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
         } else {
             let func = ctx.find_node_at_offset::<ast::Fn>()?;
-
-            let rparen = func.param_list()?.r_paren_token()?;
-            let rparen_pos = rparen.text_range().end();
-            let action = ret_ty_to_action(func.ret_type(), rparen)?;
+            let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
+            let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
 
             let body = func.body()?;
             let stmt_list = body.stmt_list()?;
@@ -214,19 +196,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn infer_return_type_no_whitespace() {
-        check_assist(
-            add_return_type,
-            r#"fn foo(){
-    45$0
-}"#,
-            r#"fn foo() -> i32 {
-    45
-}"#,
-        );
-    }
-
     #[test]
     fn infer_return_type_nested() {
         check_assist(
@@ -311,19 +280,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn infer_return_type_closure_no_whitespace() {
-        check_assist(
-            add_return_type,
-            r#"fn foo() {
-    |x: i32|{ x$0 };
-}"#,
-            r#"fn foo() {
-    |x: i32| -> i32 { x };
-}"#,
-        );
-    }
-
     #[test]
     fn infer_return_type_closure_wrap() {
         cov_mark::check!(wrap_closure_non_block_expr);
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index ebe227d260..b3f404d905 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -232,6 +232,7 @@ impl CompletionItemKind {
     pub(crate) fn tag(&self) -> &'static str {
         match self {
             CompletionItemKind::SymbolKind(kind) => match kind {
+                SymbolKind::BuiltinAttr => "ba",
                 SymbolKind::Const => "ct",
                 SymbolKind::ConstParam => "cp",
                 SymbolKind::Enum => "en",
@@ -246,6 +247,7 @@ impl CompletionItemKind {
                 SymbolKind::SelfParam => "sp",
                 SymbolKind::Static => "sc",
                 SymbolKind::Struct => "st",
+                SymbolKind::ToolModule => "tm",
                 SymbolKind::Trait => "tt",
                 SymbolKind::TypeAlias => "ta",
                 SymbolKind::TypeParam => "tp",
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index 1d7f4392dd..ef2f0f940f 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -7,9 +7,9 @@
 
 use arrayvec::ArrayVec;
 use hir::{
-    Adt, AsAssocItem, AssocItem, BuiltinType, Const, Field, Function, GenericParam, HasVisibility,
-    Impl, ItemInNs, Label, Local, MacroDef, Module, ModuleDef, Name, PathResolution, Semantics,
-    Static, Trait, TypeAlias, Variant, Visibility,
+    Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Field, Function, GenericParam,
+    HasVisibility, Impl, ItemInNs, Label, Local, MacroDef, Module, ModuleDef, Name, PathResolution,
+    Semantics, Static, ToolModule, Trait, TypeAlias, Variant, Visibility,
 };
 use stdx::impl_from;
 use syntax::{
@@ -37,6 +37,8 @@ pub enum Definition {
     Local(Local),
     GenericParam(GenericParam),
     Label(Label),
+    BuiltinAttr(BuiltinAttr),
+    ToolModule(ToolModule),
 }
 
 impl Definition {
@@ -48,10 +50,9 @@ impl Definition {
             Some(parent) => parent,
             None => return Default::default(),
         };
+        // resolve derives if possible
         if let Some(ident) = ast::Ident::cast(token.clone()) {
-            let attr = parent
-                .ancestors()
-                .find_map(ast::TokenTree::cast)
+            let attr = ast::TokenTree::cast(parent.clone())
                 .and_then(|tt| tt.parent_meta())
                 .and_then(|meta| meta.parent_attr());
             if let Some(attr) = attr {
@@ -128,7 +129,9 @@ impl Definition {
             Definition::Local(it) => it.module(db),
             Definition::GenericParam(it) => it.module(db),
             Definition::Label(it) => it.module(db),
-            Definition::BuiltinType(_) => return None,
+            Definition::BuiltinAttr(_) | Definition::BuiltinType(_) | Definition::ToolModule(_) => {
+                return None
+            }
         };
         Some(module)
     }
@@ -146,7 +149,9 @@ impl Definition {
             Definition::Variant(it) => it.visibility(db),
             Definition::BuiltinType(_) => Visibility::Public,
             Definition::Macro(_) => return None,
-            Definition::SelfType(_)
+            Definition::BuiltinAttr(_)
+            | Definition::ToolModule(_)
+            | Definition::SelfType(_)
             | Definition::Local(_)
             | Definition::GenericParam(_)
             | Definition::Label(_) => return None,
@@ -171,6 +176,8 @@ impl Definition {
             Definition::Local(it) => it.name(db)?,
             Definition::GenericParam(it) => it.name(db),
             Definition::Label(it) => it.name(db),
+            Definition::BuiltinAttr(_) => return None, // FIXME
+            Definition::ToolModule(_) => return None,  // FIXME
         };
         Some(name)
     }
@@ -450,30 +457,7 @@ impl NameRefClass {
                     }
                 }
             }
-            let top_path = path.top_path();
-            let is_attribute_path = top_path
-                .syntax()
-                .ancestors()
-                .find_map(ast::Attr::cast)
-                .map(|attr| attr.path().as_ref() == Some(&top_path));
-            return match is_attribute_path {
-                Some(true) if path == top_path => sema
-                    .resolve_path_as_macro(&path)
-                    .filter(|mac| mac.kind() == hir::MacroKind::Attr)
-                    .map(Definition::Macro)
-                    .map(NameRefClass::Definition),
-                // in case of the path being a qualifier, don't resolve to anything but a module
-                Some(true) => match sema.resolve_path(&path)? {
-                    PathResolution::Def(ModuleDef::Module(module)) => {
-                        cov_mark::hit!(name_ref_classify_attr_path_qualifier);
-                        Some(NameRefClass::Definition(Definition::Module(module)))
-                    }
-                    _ => None,
-                },
-                // inside attribute, but our path isn't part of the attribute's path(might be in its expression only)
-                Some(false) => None,
-                None => sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition),
-            };
+            return sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition);
         }
 
         let extern_crate = ast::ExternCrate::cast(parent)?;
@@ -566,6 +550,8 @@ impl From<PathResolution> for Definition {
             PathResolution::Macro(def) => Definition::Macro(def),
             PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
             PathResolution::ConstParam(par) => Definition::GenericParam(par.into()),
+            PathResolution::BuiltinAttr(attr) => Definition::BuiltinAttr(attr),
+            PathResolution::ToolModule(tool) => Definition::ToolModule(tool),
         }
     }
 }
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 1250008984..67f23536bd 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -145,6 +145,7 @@ fn line_index(db: &dyn LineIndexDatabase, file_id: FileId) -> Arc<LineIndex> {
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub enum SymbolKind {
+    BuiltinAttr,
     Const,
     ConstParam,
     Enum,
@@ -159,6 +160,7 @@ pub enum SymbolKind {
     SelfParam,
     Static,
     Struct,
+    ToolModule,
     Trait,
     TypeAlias,
     TypeParam,
diff --git a/crates/ide_db/src/path_transform.rs b/crates/ide_db/src/path_transform.rs
index b8b1307a3c..1b4f793a50 100644
--- a/crates/ide_db/src/path_transform.rs
+++ b/crates/ide_db/src/path_transform.rs
@@ -164,7 +164,9 @@ impl<'a> Ctx<'a> {
             | hir::PathResolution::ConstParam(_)
             | hir::PathResolution::SelfType(_)
             | hir::PathResolution::Macro(_)
-            | hir::PathResolution::AssocItem(_) => (),
+            | hir::PathResolution::AssocItem(_)
+            | hir::PathResolution::BuiltinAttr(_)
+            | hir::PathResolution::ToolModule(_) => (),
         }
         Some(())
     }
diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs
index 678153c6e1..60160f9553 100644
--- a/crates/ide_db/src/rename.rs
+++ b/crates/ide_db/src/rename.rs
@@ -115,8 +115,6 @@ impl Definition {
             Definition::Static(it) => name_range(it, sema),
             Definition::Trait(it) => name_range(it, sema),
             Definition::TypeAlias(it) => name_range(it, sema),
-            Definition::BuiltinType(_) => return None,
-            Definition::SelfType(_) => return None,
             Definition::Local(local) => {
                 let src = local.source(sema.db);
                 let name = match &src.value {
@@ -146,6 +144,10 @@ impl Definition {
                 let lifetime = src.value.lifetime()?;
                 src.with_value(lifetime.syntax()).original_file_range_opt(sema.db)
             }
+            Definition::BuiltinType(_) => return None,
+            Definition::SelfType(_) => return None,
+            Definition::BuiltinAttr(_) => return None,
+            Definition::ToolModule(_) => return None,
         };
         return res;
 
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index 3117b75728..8e1ccfb397 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -65,6 +65,7 @@ define_semantic_token_types![
     (SELF_KEYWORD, "selfKeyword"),
     (SEMICOLON, "semicolon"),
     (TYPE_ALIAS, "typeAlias"),
+    (TOOL_MODULE, "toolModule"),
     (UNION, "union"),
     (UNRESOLVED_REFERENCE, "unresolvedReference"),
 ];
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 8e77df9598..579ad22bac 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -50,8 +50,8 @@ pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
         SymbolKind::Enum => lsp_types::SymbolKind::ENUM,
         SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER,
         SymbolKind::Trait => lsp_types::SymbolKind::INTERFACE,
-        SymbolKind::Macro => lsp_types::SymbolKind::FUNCTION,
-        SymbolKind::Module => lsp_types::SymbolKind::MODULE,
+        SymbolKind::Macro | SymbolKind::BuiltinAttr => lsp_types::SymbolKind::FUNCTION,
+        SymbolKind::Module | SymbolKind::ToolModule => lsp_types::SymbolKind::MODULE,
         SymbolKind::TypeAlias | SymbolKind::TypeParam => lsp_types::SymbolKind::TYPE_PARAMETER,
         SymbolKind::Field => lsp_types::SymbolKind::FIELD,
         SymbolKind::Static => lsp_types::SymbolKind::CONSTANT,
@@ -128,6 +128,8 @@ pub(crate) fn completion_item_kind(
             SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT,
             SymbolKind::ValueParam => lsp_types::CompletionItemKind::VALUE,
             SymbolKind::Variant => lsp_types::CompletionItemKind::ENUM_MEMBER,
+            SymbolKind::BuiltinAttr => lsp_types::CompletionItemKind::FUNCTION,
+            SymbolKind::ToolModule => lsp_types::CompletionItemKind::MODULE,
         },
     }
 }
@@ -499,10 +501,11 @@ fn semantic_token_type_and_modifiers(
             SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS,
             SymbolKind::Trait => lsp_types::SemanticTokenType::INTERFACE,
             SymbolKind::Macro => lsp_types::SemanticTokenType::MACRO,
+            SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
+            SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE,
         },
         HlTag::Attribute => semantic_tokens::ATTRIBUTE,
         HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
-        HlTag::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
         HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
         HlTag::ByteLiteral | HlTag::NumericLiteral => lsp_types::SemanticTokenType::NUMBER,
         HlTag::CharLiteral => semantic_tokens::CHAR,
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index a75188c953..e3372574b3 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -16,6 +16,8 @@
                 "vscode-languageclient": "8.0.0-next.2"
             },
             "devDependencies": {
+                "@types/glob": "^7.1.4",
+                "@types/mocha": "^8.2.3",
                 "@types/node": "~14.17.5",
                 "@types/node-fetch": "^2.5.11",
                 "@types/vscode": "^1.57.0",
@@ -23,8 +25,10 @@
                 "@typescript-eslint/parser": "^4.28.2",
                 "esbuild": "^0.12.19",
                 "eslint": "^7.30.0",
+                "glob": "^7.1.6",
+                "mocha": "^9.0.2",
                 "tslib": "^2.3.0",
-                "typescript": "^4.3.5",
+                "typescript": "^4.4.4",
                 "typescript-formatter": "^7.2.2",
                 "vsce": "^1.95.1",
                 "vscode-test": "^1.5.1"
@@ -219,12 +223,34 @@
                 "node": ">= 6"
             }
         },
+        "node_modules/@types/glob": {
+            "version": "7.1.4",
+            "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz",
+            "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==",
+            "dev": true,
+            "dependencies": {
+                "@types/minimatch": "*",
+                "@types/node": "*"
+            }
+        },
         "node_modules/@types/json-schema": {
             "version": "7.0.8",
             "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
             "integrity": "sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==",
             "dev": true
         },
+        "node_modules/@types/minimatch": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
+            "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==",
+            "dev": true
+        },
+        "node_modules/@types/mocha": {
+            "version": "8.2.3",
+            "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz",
+            "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==",
+            "dev": true
+        },
         "node_modules/@types/node": {
             "version": "14.17.5",
             "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.5.tgz",
@@ -403,6 +429,12 @@
                 "url": "https://opencollective.com/typescript-eslint"
             }
         },
+        "node_modules/@ungap/promise-all-settled": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
+            "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
+            "dev": true
+        },
         "node_modules/acorn": {
             "version": "7.4.1",
             "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -484,6 +516,19 @@
                 "url": "https://github.com/chalk/ansi-styles?sponsor=1"
             }
         },
+        "node_modules/anymatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+            "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+            "dev": true,
+            "dependencies": {
+                "normalize-path": "^3.0.0",
+                "picomatch": "^2.0.4"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
         "node_modules/argparse": {
             "version": "1.0.10",
             "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -551,6 +596,15 @@
                 "chainsaw": "~0.1.0"
             }
         },
+        "node_modules/binary-extensions": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+            "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/bluebird": {
             "version": "3.4.7",
             "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
@@ -584,6 +638,12 @@
                 "node": ">=8"
             }
         },
+        "node_modules/browser-stdout": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+            "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+            "dev": true
+        },
         "node_modules/buffer-crc32": {
             "version": "0.2.13",
             "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -633,6 +693,18 @@
                 "node": ">=6"
             }
         },
+        "node_modules/camelcase": {
+            "version": "6.2.0",
+            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
+            "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/chainsaw": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
@@ -695,6 +767,38 @@
                 "url": "https://github.com/sponsors/fb55"
             }
         },
+        "node_modules/chokidar": {
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
+            "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
+            "dev": true,
+            "dependencies": {
+                "anymatch": "~3.1.2",
+                "braces": "~3.0.2",
+                "glob-parent": "~5.1.2",
+                "is-binary-path": "~2.1.0",
+                "is-glob": "~4.0.1",
+                "normalize-path": "~3.0.0",
+                "readdirp": "~3.6.0"
+            },
+            "engines": {
+                "node": ">= 8.10.0"
+            },
+            "optionalDependencies": {
+                "fsevents": "~2.3.2"
+            }
+        },
+        "node_modules/cliui": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+            "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+            "dev": true,
+            "dependencies": {
+                "string-width": "^4.2.0",
+                "strip-ansi": "^6.0.0",
+                "wrap-ansi": "^7.0.0"
+            }
+        },
         "node_modules/color-convert": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1297,6 +1401,18 @@
                 }
             }
         },
+        "node_modules/decamelize": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+            "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/deep-is": {
             "version": "0.1.3",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -1326,6 +1442,15 @@
             "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=",
             "dev": true
         },
+        "node_modules/diff": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+            "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.3.1"
+            }
+        },
         "node_modules/dir-glob": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -1488,6 +1613,15 @@
                 "esbuild": "bin/esbuild"
             }
         },
+        "node_modules/escalade": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+            "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/escape-string-regexp": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -1793,6 +1927,31 @@
                 "node": ">=8"
             }
         },
+        "node_modules/find-up": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+            "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+            "dev": true,
+            "dependencies": {
+                "locate-path": "^6.0.0",
+                "path-exists": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/flat": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+            "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+            "dev": true,
+            "bin": {
+                "flat": "cli.js"
+            }
+        },
         "node_modules/flat-cache": {
             "version": "3.0.4",
             "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -1832,6 +1991,20 @@
             "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
             "dev": true
         },
+        "node_modules/fsevents": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+            "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+            "dev": true,
+            "hasInstallScript": true,
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+            }
+        },
         "node_modules/fstream": {
             "version": "1.0.12",
             "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
@@ -1871,6 +2044,15 @@
             "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
             "dev": true
         },
+        "node_modules/get-caller-file": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+            "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+            "dev": true,
+            "engines": {
+                "node": "6.* || 8.* || >= 10.*"
+            }
+        },
         "node_modules/get-intrinsic": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
@@ -1967,6 +2149,15 @@
             "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
             "dev": true
         },
+        "node_modules/growl": {
+            "version": "1.10.5",
+            "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+            "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+            "dev": true,
+            "engines": {
+                "node": ">=4.x"
+            }
+        },
         "node_modules/has": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -2000,6 +2191,15 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/he": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+            "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+            "dev": true,
+            "bin": {
+                "he": "bin/he"
+            }
+        },
         "node_modules/htmlparser2": {
             "version": "6.1.0",
             "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -2114,6 +2314,18 @@
                 "node": ">=12"
             }
         },
+        "node_modules/is-binary-path": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+            "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+            "dev": true,
+            "dependencies": {
+                "binary-extensions": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/is-extglob": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2153,6 +2365,27 @@
                 "node": ">=0.12.0"
             }
         },
+        "node_modules/is-plain-obj": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+            "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/is-unicode-supported": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+            "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/isarray": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -2233,6 +2466,21 @@
             "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=",
             "dev": true
         },
+        "node_modules/locate-path": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+            "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+            "dev": true,
+            "dependencies": {
+                "p-locate": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/lodash": {
             "version": "4.17.21",
             "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -2257,6 +2505,22 @@
             "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
             "dev": true
         },
+        "node_modules/log-symbols": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+            "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+            "dev": true,
+            "dependencies": {
+                "chalk": "^4.1.0",
+                "is-unicode-supported": "^0.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/lru-cache": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -2380,6 +2644,89 @@
                 "mkdirp": "bin/cmd.js"
             }
         },
+        "node_modules/mocha": {
+            "version": "9.0.2",
+            "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.2.tgz",
+            "integrity": "sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA==",
+            "dev": true,
+            "dependencies": {
+                "@ungap/promise-all-settled": "1.1.2",
+                "ansi-colors": "4.1.1",
+                "browser-stdout": "1.3.1",
+                "chokidar": "3.5.2",
+                "debug": "4.3.1",
+                "diff": "5.0.0",
+                "escape-string-regexp": "4.0.0",
+                "find-up": "5.0.0",
+                "glob": "7.1.7",
+                "growl": "1.10.5",
+                "he": "1.2.0",
+                "js-yaml": "4.1.0",
+                "log-symbols": "4.1.0",
+                "minimatch": "3.0.4",
+                "ms": "2.1.3",
+                "nanoid": "3.1.23",
+                "serialize-javascript": "6.0.0",
+                "strip-json-comments": "3.1.1",
+                "supports-color": "8.1.1",
+                "which": "2.0.2",
+                "wide-align": "1.1.3",
+                "workerpool": "6.1.5",
+                "yargs": "16.2.0",
+                "yargs-parser": "20.2.4",
+                "yargs-unparser": "2.0.0"
+            },
+            "bin": {
+                "_mocha": "bin/_mocha",
+                "mocha": "bin/mocha"
+            },
+            "engines": {
+                "node": ">= 12.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/mochajs"
+            }
+        },
+        "node_modules/mocha/node_modules/argparse": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+            "dev": true
+        },
+        "node_modules/mocha/node_modules/js-yaml": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+            "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+            "dev": true,
+            "dependencies": {
+                "argparse": "^2.0.1"
+            },
+            "bin": {
+                "js-yaml": "bin/js-yaml.js"
+            }
+        },
+        "node_modules/mocha/node_modules/ms": {
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+            "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+            "dev": true
+        },
+        "node_modules/mocha/node_modules/supports-color": {
+            "version": "8.1.1",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/supports-color?sponsor=1"
+            }
+        },
         "node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -2391,6 +2738,18 @@
             "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
             "dev": true
         },
+        "node_modules/nanoid": {
+            "version": "3.1.23",
+            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
+            "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
+            "dev": true,
+            "bin": {
+                "nanoid": "bin/nanoid.cjs"
+            },
+            "engines": {
+                "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+            }
+        },
         "node_modules/natural-compare": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -2405,6 +2764,15 @@
                 "node": "4.x || >=6.0.0"
             }
         },
+        "node_modules/normalize-path": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+            "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/nth-check": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
@@ -2480,6 +2848,36 @@
                 "os-tmpdir": "^1.0.0"
             }
         },
+        "node_modules/p-limit": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+            "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+            "dev": true,
+            "dependencies": {
+                "yocto-queue": "^0.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/p-locate": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+            "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+            "dev": true,
+            "dependencies": {
+                "p-limit": "^3.0.2"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/parent-module": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -2525,6 +2923,15 @@
                 "parse5": "^6.0.1"
             }
         },
+        "node_modules/path-exists": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+            "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/path-is-absolute": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -2644,6 +3051,15 @@
                 }
             ]
         },
+        "node_modules/randombytes": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+            "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+            "dev": true,
+            "dependencies": {
+                "safe-buffer": "^5.1.0"
+            }
+        },
         "node_modules/read": {
             "version": "1.0.7",
             "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
@@ -2677,6 +3093,18 @@
             "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
             "dev": true
         },
+        "node_modules/readdirp": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+            "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+            "dev": true,
+            "dependencies": {
+                "picomatch": "^2.2.1"
+            },
+            "engines": {
+                "node": ">=8.10.0"
+            }
+        },
         "node_modules/regexpp": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
@@ -2689,6 +3117,15 @@
                 "url": "https://github.com/sponsors/mysticatea"
             }
         },
+        "node_modules/require-directory": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+            "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/require-from-string": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -2765,6 +3202,26 @@
             "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
             "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
         },
+        "node_modules/safe-buffer": {
+            "version": "5.2.1",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ]
+        },
         "node_modules/safer-buffer": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -2784,6 +3241,15 @@
                 "node": ">=10"
             }
         },
+        "node_modules/serialize-javascript": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+            "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+            "dev": true,
+            "dependencies": {
+                "randombytes": "^2.1.0"
+            }
+        },
         "node_modules/setimmediate": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -3351,6 +3817,58 @@
                 "node": ">= 8"
             }
         },
+        "node_modules/wide-align": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+            "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+            "dev": true,
+            "dependencies": {
+                "string-width": "^1.0.2 || 2"
+            }
+        },
+        "node_modules/wide-align/node_modules/ansi-regex": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+            "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/wide-align/node_modules/is-fullwidth-code-point": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+            "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/wide-align/node_modules/string-width": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+            "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+            "dev": true,
+            "dependencies": {
+                "is-fullwidth-code-point": "^2.0.0",
+                "strip-ansi": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/wide-align/node_modules/strip-ansi": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+            "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+            "dev": true,
+            "dependencies": {
+                "ansi-regex": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/word-wrap": {
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@@ -3360,17 +3878,91 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/workerpool": {
+            "version": "6.1.5",
+            "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz",
+            "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==",
+            "dev": true
+        },
+        "node_modules/wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+            }
+        },
         "node_modules/wrappy": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
             "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
             "dev": true
         },
+        "node_modules/y18n": {
+            "version": "5.0.8",
+            "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+            "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            }
+        },
         "node_modules/yallist": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
+        "node_modules/yargs": {
+            "version": "16.2.0",
+            "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+            "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+            "dev": true,
+            "dependencies": {
+                "cliui": "^7.0.2",
+                "escalade": "^3.1.1",
+                "get-caller-file": "^2.0.5",
+                "require-directory": "^2.1.1",
+                "string-width": "^4.2.0",
+                "y18n": "^5.0.5",
+                "yargs-parser": "^20.2.2"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/yargs-parser": {
+            "version": "20.2.4",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+            "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/yargs-unparser": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+            "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+            "dev": true,
+            "dependencies": {
+                "camelcase": "^6.0.0",
+                "decamelize": "^4.0.0",
+                "flat": "^5.0.2",
+                "is-plain-obj": "^2.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
         "node_modules/yauzl": {
             "version": "2.10.0",
             "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
@@ -3389,6 +3981,18 @@
             "dependencies": {
                 "buffer-crc32": "~0.2.3"
             }
+        },
+        "node_modules/yocto-queue": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+            "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
         }
     },
     "dependencies": {
@@ -3547,12 +4151,34 @@
             "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
             "dev": true
         },
+        "@types/glob": {
+            "version": "7.1.4",
+            "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz",
+            "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==",
+            "dev": true,
+            "requires": {
+                "@types/minimatch": "*",
+                "@types/node": "*"
+            }
+        },
         "@types/json-schema": {
             "version": "7.0.8",
             "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
             "integrity": "sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==",
             "dev": true
         },
+        "@types/minimatch": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
+            "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==",
+            "dev": true
+        },
+        "@types/mocha": {
+            "version": "8.2.3",
+            "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz",
+            "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==",
+            "dev": true
+        },
         "@types/node": {
             "version": "14.17.5",
             "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.5.tgz",
@@ -3657,6 +4283,12 @@
                 "eslint-visitor-keys": "^2.0.0"
             }
         },
+        "@ungap/promise-all-settled": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
+            "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
+            "dev": true
+        },
         "acorn": {
             "version": "7.4.1",
             "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -3711,6 +4343,16 @@
                 "color-convert": "^2.0.1"
             }
         },
+        "anymatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+            "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+            "dev": true,
+            "requires": {
+                "normalize-path": "^3.0.0",
+                "picomatch": "^2.0.4"
+            }
+        },
         "argparse": {
             "version": "1.0.10",
             "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -3769,6 +4411,12 @@
                 "chainsaw": "~0.1.0"
             }
         },
+        "binary-extensions": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+            "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+            "dev": true
+        },
         "bluebird": {
             "version": "3.4.7",
             "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
@@ -3799,6 +4447,12 @@
                 "fill-range": "^7.0.1"
             }
         },
+        "browser-stdout": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+            "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+            "dev": true
+        },
         "buffer-crc32": {
             "version": "0.2.13",
             "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -3833,6 +4487,12 @@
             "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
             "dev": true
         },
+        "camelcase": {
+            "version": "6.2.0",
+            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
+            "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
+            "dev": true
+        },
         "chainsaw": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
@@ -3880,6 +4540,33 @@
                 "domutils": "^2.7.0"
             }
         },
+        "chokidar": {
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
+            "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
+            "dev": true,
+            "requires": {
+                "anymatch": "~3.1.2",
+                "braces": "~3.0.2",
+                "fsevents": "~2.3.2",
+                "glob-parent": "~5.1.2",
+                "is-binary-path": "~2.1.0",
+                "is-glob": "~4.0.1",
+                "normalize-path": "~3.0.0",
+                "readdirp": "~3.6.0"
+            }
+        },
+        "cliui": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+            "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+            "dev": true,
+            "requires": {
+                "string-width": "^4.2.0",
+                "strip-ansi": "^6.0.0",
+                "wrap-ansi": "^7.0.0"
+            }
+        },
         "color-convert": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -4344,6 +5031,12 @@
                 "ms": "2.1.2"
             }
         },
+        "decamelize": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+            "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+            "dev": true
+        },
         "deep-is": {
             "version": "0.1.3",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -4370,6 +5063,12 @@
             "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=",
             "dev": true
         },
+        "diff": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+            "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+            "dev": true
+        },
         "dir-glob": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -4497,6 +5196,12 @@
             "integrity": "sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w==",
             "dev": true
         },
+        "escalade": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+            "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+            "dev": true
+        },
         "escape-string-regexp": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -4733,6 +5438,22 @@
                 "to-regex-range": "^5.0.1"
             }
         },
+        "find-up": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+            "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+            "dev": true,
+            "requires": {
+                "locate-path": "^6.0.0",
+                "path-exists": "^4.0.0"
+            }
+        },
+        "flat": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+            "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+            "dev": true
+        },
         "flat-cache": {
             "version": "3.0.4",
             "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -4766,6 +5487,13 @@
             "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
             "dev": true
         },
+        "fsevents": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+            "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+            "dev": true,
+            "optional": true
+        },
         "fstream": {
             "version": "1.0.12",
             "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
@@ -4801,6 +5529,12 @@
             "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
             "dev": true
         },
+        "get-caller-file": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+            "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+            "dev": true
+        },
         "get-intrinsic": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
@@ -4872,6 +5606,12 @@
             "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
             "dev": true
         },
+        "growl": {
+            "version": "1.10.5",
+            "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+            "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+            "dev": true
+        },
         "has": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -4893,6 +5633,12 @@
             "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
             "dev": true
         },
+        "he": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+            "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+            "dev": true
+        },
         "htmlparser2": {
             "version": "6.1.0",
             "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -4976,6 +5722,15 @@
             "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.1.tgz",
             "integrity": "sha512-Ujwccrj9FkGqjbY3iVoxD1VV+KdZZeENx0rphrtzmRXbFvkFO88L80BL/zeSIguX/7T+y8k04xqtgWgS5vxwxw=="
         },
+        "is-binary-path": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+            "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+            "dev": true,
+            "requires": {
+                "binary-extensions": "^2.0.0"
+            }
+        },
         "is-extglob": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -5003,6 +5758,18 @@
             "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
             "dev": true
         },
+        "is-plain-obj": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+            "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+            "dev": true
+        },
+        "is-unicode-supported": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+            "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+            "dev": true
+        },
         "isarray": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -5074,6 +5841,15 @@
             "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=",
             "dev": true
         },
+        "locate-path": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+            "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+            "dev": true,
+            "requires": {
+                "p-locate": "^5.0.0"
+            }
+        },
         "lodash": {
             "version": "4.17.21",
             "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -5098,6 +5874,16 @@
             "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
             "dev": true
         },
+        "log-symbols": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+            "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+            "dev": true,
+            "requires": {
+                "chalk": "^4.1.0",
+                "is-unicode-supported": "^0.1.0"
+            }
+        },
         "lru-cache": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -5193,6 +5979,71 @@
                 "minimist": "^1.2.5"
             }
         },
+        "mocha": {
+            "version": "9.0.2",
+            "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.2.tgz",
+            "integrity": "sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA==",
+            "dev": true,
+            "requires": {
+                "@ungap/promise-all-settled": "1.1.2",
+                "ansi-colors": "4.1.1",
+                "browser-stdout": "1.3.1",
+                "chokidar": "3.5.2",
+                "debug": "4.3.1",
+                "diff": "5.0.0",
+                "escape-string-regexp": "4.0.0",
+                "find-up": "5.0.0",
+                "glob": "7.1.7",
+                "growl": "1.10.5",
+                "he": "1.2.0",
+                "js-yaml": "4.1.0",
+                "log-symbols": "4.1.0",
+                "minimatch": "3.0.4",
+                "ms": "2.1.3",
+                "nanoid": "3.1.23",
+                "serialize-javascript": "6.0.0",
+                "strip-json-comments": "3.1.1",
+                "supports-color": "8.1.1",
+                "which": "2.0.2",
+                "wide-align": "1.1.3",
+                "workerpool": "6.1.5",
+                "yargs": "16.2.0",
+                "yargs-parser": "20.2.4",
+                "yargs-unparser": "2.0.0"
+            },
+            "dependencies": {
+                "argparse": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+                    "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+                    "dev": true
+                },
+                "js-yaml": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+                    "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+                    "dev": true,
+                    "requires": {
+                        "argparse": "^2.0.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.3",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+                    "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+                    "dev": true
+                },
+                "supports-color": {
+                    "version": "8.1.1",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+                    "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
         "ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5204,6 +6055,12 @@
             "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
             "dev": true
         },
+        "nanoid": {
+            "version": "3.1.23",
+            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
+            "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
+            "dev": true
+        },
         "natural-compare": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5215,6 +6072,12 @@
             "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
             "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
         },
+        "normalize-path": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+            "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+            "dev": true
+        },
         "nth-check": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
@@ -5275,6 +6138,24 @@
                 "os-tmpdir": "^1.0.0"
             }
         },
+        "p-limit": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+            "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+            "dev": true,
+            "requires": {
+                "yocto-queue": "^0.1.0"
+            }
+        },
+        "p-locate": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+            "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+            "dev": true,
+            "requires": {
+                "p-limit": "^3.0.2"
+            }
+        },
         "parent-module": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5316,6 +6197,12 @@
                 "parse5": "^6.0.1"
             }
         },
+        "path-exists": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+            "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+            "dev": true
+        },
         "path-is-absolute": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -5391,6 +6278,15 @@
             "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
             "dev": true
         },
+        "randombytes": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+            "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+            "dev": true,
+            "requires": {
+                "safe-buffer": "^5.1.0"
+            }
+        },
         "read": {
             "version": "1.0.7",
             "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
@@ -5423,12 +6319,27 @@
                 }
             }
         },
+        "readdirp": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+            "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+            "dev": true,
+            "requires": {
+                "picomatch": "^2.2.1"
+            }
+        },
         "regexpp": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
             "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
             "dev": true
         },
+        "require-directory": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+            "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+            "dev": true
+        },
         "require-from-string": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -5475,6 +6386,12 @@
             "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
             "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
         },
+        "safe-buffer": {
+            "version": "5.2.1",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+            "dev": true
+        },
         "safer-buffer": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -5488,6 +6405,15 @@
                 "lru-cache": "^6.0.0"
             }
         },
+        "serialize-javascript": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+            "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+            "dev": true,
+            "requires": {
+                "randombytes": "^2.1.0"
+            }
+        },
         "setimmediate": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -5941,23 +6867,121 @@
                 "isexe": "^2.0.0"
             }
         },
+        "wide-align": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+            "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+            "dev": true,
+            "requires": {
+                "string-width": "^1.0.2 || 2"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+                    "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+                    "dev": true
+                },
+                "is-fullwidth-code-point": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+                    "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+                    "dev": true
+                },
+                "string-width": {
+                    "version": "2.1.1",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+                    "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+                    "dev": true,
+                    "requires": {
+                        "is-fullwidth-code-point": "^2.0.0",
+                        "strip-ansi": "^4.0.0"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+                    "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+                    "dev": true,
+                    "requires": {
+                        "ansi-regex": "^3.0.0"
+                    }
+                }
+            }
+        },
         "word-wrap": {
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
             "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
             "dev": true
         },
+        "workerpool": {
+            "version": "6.1.5",
+            "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz",
+            "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==",
+            "dev": true
+        },
+        "wrap-ansi": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "dev": true,
+            "requires": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            }
+        },
         "wrappy": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
             "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
             "dev": true
         },
+        "y18n": {
+            "version": "5.0.8",
+            "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+            "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+            "dev": true
+        },
         "yallist": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
+        "yargs": {
+            "version": "16.2.0",
+            "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+            "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+            "dev": true,
+            "requires": {
+                "cliui": "^7.0.2",
+                "escalade": "^3.1.1",
+                "get-caller-file": "^2.0.5",
+                "require-directory": "^2.1.1",
+                "string-width": "^4.2.0",
+                "y18n": "^5.0.5",
+                "yargs-parser": "^20.2.2"
+            }
+        },
+        "yargs-parser": {
+            "version": "20.2.4",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+            "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+            "dev": true
+        },
+        "yargs-unparser": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+            "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+            "dev": true,
+            "requires": {
+                "camelcase": "^6.0.0",
+                "decamelize": "^4.0.0",
+                "flat": "^5.0.2",
+                "is-plain-obj": "^2.1.0"
+            }
+        },
         "yauzl": {
             "version": "2.10.0",
             "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
@@ -5976,6 +7000,12 @@
             "requires": {
                 "buffer-crc32": "~0.2.3"
             }
+        },
+        "yocto-queue": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+            "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+            "dev": true
         }
     }
 }
diff --git a/editors/code/package.json b/editors/code/package.json
index a5de30cfc0..d10cea8237 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -43,6 +43,8 @@
         "d3-graphviz": "^4.0.0"
     },
     "devDependencies": {
+        "@types/glob": "^7.1.4",
+        "@types/mocha": "^8.2.3",
         "@types/node": "~14.17.5",
         "@types/node-fetch": "^2.5.11",
         "@types/vscode": "^1.57.0",
@@ -50,6 +52,8 @@
         "@typescript-eslint/parser": "^4.28.2",
         "esbuild": "^0.12.19",
         "eslint": "^7.30.0",
+        "glob": "^7.1.6",
+        "mocha": "^9.0.2",
         "tslib": "^2.3.0",
         "typescript": "^4.3.5",
         "typescript-formatter": "^7.2.2",
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 58947118c9..81cf5d28b7 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -109,7 +109,7 @@ export class Config {
             .workspace
             .getConfiguration('http')
             .get<null | string>("proxy")! || process.env["https_proxy"] || process.env["HTTPS_PROXY"];
-        const strictSSL = vscode.workspace.getConfiguration("http").get<boolean>("proxyStrictSSL") ?? true;
+        const strictSSL = vscode.workspace.getConfiguration("http").get<boolean>("proxyStrictSSL") || true;
 
         return {
             proxy: proxy,


Test patch:
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 5718b9097c..f1d7d2791d 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -3678,7 +3678,6 @@ fn hover_clippy_lint() {
 
 #[test]
 fn hover_attr_path_qualifier() {
-    cov_mark::check!(name_ref_classify_attr_path_qualifier);
     check(
         r#"
 //- /foo.rs crate:foo
@@ -4278,3 +4277,46 @@ pub struct Foo;
         "#]],
     );
 }
+
+#[test]
+fn hover_inert_attr() {
+    check(
+        r#"
+#[doc$0 = ""]
+pub struct Foo;
+"#,
+        expect![[r##"
+            *doc*
+
+            ```rust
+            #[doc]
+            ```
+
+            ---
+
+            Valid forms are:
+
+            * \#\[doc(hidden|inline|...)\]
+            * \#\[doc = string\]
+        "##]],
+    );
+    check(
+        r#"
+#[allow$0()]
+pub struct Foo;
+"#,
+        expect![[r##"
+            *allow*
+
+            ```rust
+            #[allow]
+            ```
+
+            ---
+
+            Valid forms are:
+
+            * \#\[allow(lint1, lint2, ..., /\*opt\*/ reason = "...")\]
+        "##]],
+    );
+}
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 9c92bd3e74..1e778cc619 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -72,7 +72,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="comment documentation">/// # Examples</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```</span>
-    <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="builtin_attr attribute injected">allow</span><span class="parenthesis attribute injected">(</span><span class="none attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute attribute injected">]</span>
+    <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="builtin_attr attribute injected library">allow</span><span class="parenthesis attribute injected">(</span><span class="none attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute attribute injected">]</span>
     <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
     <span class="comment documentation">/// ```</span>
     <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function associated declaration public static">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="brace">{</span>
@@ -143,12 +143,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <span class="comment documentation">///</span>
 <span class="comment documentation">/// ```</span>
 <span class="comment documentation">/// </span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"false"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span><span class="attribute attribute"> </span><span class="none attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"false"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span><span class="attribute attribute"> </span><span class="none attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="attribute attribute">]</span>
 <span class="comment documentation">/// ```</span>
 <span class="comment documentation">///</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"alloc"</span><span class="comma attribute">,</span><span class="attribute attribute"> </span><span class="none attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"```rust"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span><span class="attribute attribute"> </span><span class="none attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"alloc"</span><span class="comma attribute">,</span><span class="attribute attribute"> </span><span class="none attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"```rust"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span><span class="attribute attribute"> </span><span class="none attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="none injected">alloc::</span><span class="macro injected">vec!</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
 <span class="comment documentation">/// ```</span>
 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">mix_and_match</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 893094b24a..b44fc817c7 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -45,14 +45,14 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
         <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>io<span class="colon">:</span><span class="colon">:</span>_print<span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>format_args_nl<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="brace">}</span><span class="parenthesis">)</span>
 <span class="brace">}</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">macro_export</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute attribute">]</span>
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">format_args</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">macro_export</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute attribute">]</span>
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">const_format_args</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">macro_export</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute attribute">]</span>
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">format_args_nl</span> <span class="brace">{</span><span class="brace">}</span>
 
 <span class="keyword">mod</span> <span class="module declaration">panic</span> <span class="brace">{</span>
@@ -77,12 +77,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="brace">}</span>
 <span class="brace">}</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">rustc_builtin_macro</span><span class="parenthesis attribute">(</span><span class="none attribute">std_panic</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">macro_export</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="parenthesis attribute">(</span><span class="none attribute">std_panic</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute attribute">]</span>
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">panic</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute attribute">]</span>
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">assert</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute attribute">]</span>
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">asm</span> <span class="brace">{</span><span class="brace">}</span>
 
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">toho</span> <span class="brace">{</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 2b86340efd..65dfbdf211 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -54,7 +54,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function associated declaration reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
 <span class="brace">}</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">repr</span><span class="parenthesis attribute">(</span><span class="none attribute">packed</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">repr</span><span class="parenthesis attribute">(</span><span class="none attribute">packed</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
 <span class="keyword">struct</span> <span class="struct declaration">Packed</span> <span class="brace">{</span>
     <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u16</span><span class="comma">,</span>
 <span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index d8c9827b5e..370f58080e 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -43,15 +43,16 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="brace">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="brace">}</span><span class="semicolon">;</span>
 <span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span><span class="brace">}</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="module attribute">proc_macros</span><span class="operator attribute">::</span><span class="macro attribute">identity</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">allow</span><span class="parenthesis attribute">(</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="module attribute crate_root library">proc_macros</span><span class="operator attribute">::</span><span class="macro attribute library">identity</span><span class="attribute attribute">]</span>
 <span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration public">ops</span> <span class="brace">{</span>
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_once"</span><span class="attribute attribute">]</span>
+    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_once"</span><span class="attribute attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnOnce</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
 
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_mut"</span><span class="attribute attribute">]</span>
+    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_mut"</span><span class="attribute attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnMut</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span><span class="colon">:</span> <span class="trait public">FnOnce</span><span class="angle">&lt;</span><span class="type_param">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
 
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn"</span><span class="attribute attribute">]</span>
+    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn"</span><span class="attribute attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">Fn</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span><span class="colon">:</span> <span class="trait public">FnMut</span><span class="angle">&lt;</span><span class="type_param">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
 <span class="brace">}</span>
 
@@ -86,7 +87,7 @@ proc_macros::<span class="macro">mirror!</span> <span class="brace">{</span>
     <span class="brace">}</span>
 <span class="brace">}</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="macro attribute">derive</span><span class="parenthesis attribute">(</span><span class="macro attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="macro attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="macro attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
 <span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="brace">{</span>
     <span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
 <span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index e74f39a865..63a1417c6a 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -16,6 +16,7 @@ fn test_highlighting() {
 use inner::{self as inner_mod};
 mod inner {}
 
+#[allow()]
 #[proc_macros::identity]
 pub mod ops {
     #[lang = "fn_once"]
diff --git a/editors/code/tests/unit/index.ts b/editors/code/tests/unit/index.ts
index 39ff36707f..b7d8d21448 100644
--- a/editors/code/tests/unit/index.ts
+++ b/editors/code/tests/unit/index.ts
@@ -1,80 +1,38 @@
 import * as path from 'path';
+import * as Mocha from 'mocha';
+import * as glob from 'glob';
+
+export function run(): Promise<void> {
+    // Create the mocha test
+    const mocha = new Mocha({
+        ui: 'tdd',
+        color: true
+    });
+
+    const testsRoot = __dirname;
+
+    return new Promise((resolve, reject) => {
+        glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
+            if (err) {
+                return reject(err);
+            }
 
-class Test {
-    readonly name: string;
-    readonly promise: Promise<void>;
-
-    constructor(name: string, promise: Promise<void>) {
-        this.name = name;
-        this.promise = promise;
-    }
-}
-
-class Suite {
-    tests: Test[];
-
-    constructor() {
-        this.tests = [];
-    }
-
-    public addTest(name: string, f: () => Promise<void>): void {
-        const test = new Test(name, f());
-        this.tests.push(test);
-    }
+            // Add files to the test suite
+            files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
 
-    public async run(): Promise<void> {
-        let failed = 0;
-        for (const test of this.tests) {
             try {
-                await test.promise;
-                ok(`  ✔ ${test.name}`);
-            } catch (e) {
-                error(`  ✖︎ ${test.name}\n  ${e.stack}`);
-                failed += 1;
+                // Run the mocha test
+                mocha.timeout(100000);
+                mocha.run(failures => {
+                    if (failures > 0) {
+                        reject(new Error(`${failures} tests failed.`));
+                    } else {
+                        resolve();
+                    }
+                });
+            } catch (err) {
+                reject(err);
             }
-        }
-        if (failed) {
-            const plural = failed > 1 ? "s" : "";
-            throw new Error(`${failed} failed test${plural}`);
-        }
-    }
-}
-
-export class Context {
-    public async suite(name: string, f: (ctx: Suite) => void): Promise<void> {
-        const ctx = new Suite();
-        f(ctx);
-        try {
-            ok(`⌛︎ ${name}`);
-            await ctx.run();
-            ok(`✔ ${name}`);
-        } catch (e) {
-            error(`✖︎ ${name}\n  ${e.stack}`);
-            throw e;
-        }
-    }
-}
-
-export async function run(): Promise<void> {
-    const context = new Context();
-    const testFiles = ["launch_config.test.js", "runnable_env.test.js"];
-    for (const testFile of testFiles) {
-        try {
-            const testModule = require(path.resolve(__dirname, testFile));
-            await testModule.getTests(context);
-        } catch (e) {
-            error(`${e}`);
-            throw e;
-        }
-    }
-}
-
-function ok(message: string): void {
-    // eslint-disable-next-line no-console
-    console.log(`\x1b[32m${message}\x1b[0m`);
-}
-
-function error(message: string): void {
-    // eslint-disable-next-line no-console
-    console.error(`\x1b[31m${message}\x1b[0m`);
+        });
+    });
 }
diff --git a/editors/code/tests/unit/launch_config.test.ts b/editors/code/tests/unit/launch_config.test.ts
index aa7a6be269..e53ae5ce9e 100644
--- a/editors/code/tests/unit/launch_config.test.ts
+++ b/editors/code/tests/unit/launch_config.test.ts
@@ -1,24 +1,24 @@
 import * as assert from 'assert';
 import { Cargo } from '../../src/toolchain';
-import { Context } from '.';
 
-export async function getTests(ctx: Context) {
-    await ctx.suite('Launch configuration/Lens', suite => {
-        suite.addTest('A binary', async () => {
+suite('Launch configuration', () => {
+
+    suite('Lens', () => {
+        test('A binary', async () => {
             const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]);
 
             assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
             assert.deepStrictEqual(args.filter, undefined);
         });
 
-        suite.addTest('One of Multiple Binaries', async () => {
+        test('One of Multiple Binaries', async () => {
             const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]);
 
             assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin1", "--message-format=json"]);
             assert.deepStrictEqual(args.filter, undefined);
         });
 
-        suite.addTest('A test', async () => {
+        test('A test', async () => {
             const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]);
 
             assert.deepStrictEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--no-run", "--message-format=json"]);
@@ -26,8 +26,8 @@ export async function getTests(ctx: Context) {
         });
     });
 
-    await ctx.suite('Launch configuration/QuickPick', suite => {
-        suite.addTest('A binary', async () => {
+    suite('QuickPick', () => {
+        test('A binary', async () => {
             const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]);
 
             assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
@@ -35,18 +35,18 @@ export async function getTests(ctx: Context) {
         });
 
 
-        suite.addTest('One of Multiple Binaries', async () => {
+        test('One of Multiple Binaries', async () => {
             const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]);
 
             assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin2", "--message-format=json"]);
             assert.deepStrictEqual(args.filter, undefined);
         });
 
-        suite.addTest('A test', async () => {
+        test('A test', async () => {
             const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]);
 
             assert.deepStrictEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--message-format=json", "--no-run"]);
             assert.notDeepStrictEqual(args.filter, undefined);
         });
     });
-}
+});
diff --git a/editors/code/tests/unit/runnable_env.test.ts b/editors/code/tests/unit/runnable_env.test.ts
index 085c96da92..d5f522dd11 100644
--- a/editors/code/tests/unit/runnable_env.test.ts
+++ b/editors/code/tests/unit/runnable_env.test.ts
@@ -1,7 +1,6 @@
 import * as assert from 'assert';
 import { prepareEnv } from '../../src/run';
 import { RunnableEnvCfg } from '../../src/config';
-import { Context } from '.';
 import * as ra from '../../src/lsp_ext';
 
 function makeRunnable(label: string): ra.Runnable {
@@ -21,101 +20,100 @@ function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<st
     return prepareEnv(runnable, config);
 }
 
-export async function getTests(ctx: Context) {
-    await ctx.suite('Runnable env', suite => {
-        suite.addTest('Global config works', async () => {
-            const binEnv = fakePrepareEnv("run project_name", { "GLOBAL": "g" });
-            assert.strictEqual(binEnv["GLOBAL"], "g");
-
-            const testEnv = fakePrepareEnv("test some::mod::test_name", { "GLOBAL": "g" });
-            assert.strictEqual(testEnv["GLOBAL"], "g");
-        });
-
-        suite.addTest('null mask works', async () => {
-            const config = [
-                {
-                    env: { DATA: "data" }
-                }
-            ];
-            const binEnv = fakePrepareEnv("run project_name", config);
-            assert.strictEqual(binEnv["DATA"], "data");
-
-            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
-            assert.strictEqual(testEnv["DATA"], "data");
-        });
-
-        suite.addTest('order works', async () => {
-            const config = [
-                {
-                    env: { DATA: "data" }
-                },
-                {
-                    env: { DATA: "newdata" }
-                }
-            ];
-            const binEnv = fakePrepareEnv("run project_name", config);
-            assert.strictEqual(binEnv["DATA"], "newdata");
-
-            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
-            assert.strictEqual(testEnv["DATA"], "newdata");
-        });
-
-        suite.addTest('mask works', async () => {
-            const config = [
-                {
-                    env: { DATA: "data" }
-                },
-                {
-                    mask: "^run",
-                    env: { DATA: "rundata" }
-                },
-                {
-                    mask: "special_test$",
-                    env: { DATA: "special_test" }
-                }
-            ];
-            const binEnv = fakePrepareEnv("run project_name", config);
-            assert.strictEqual(binEnv["DATA"], "rundata");
-
-            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
-            assert.strictEqual(testEnv["DATA"], "data");
-
-            const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config);
-            assert.strictEqual(specialTestEnv["DATA"], "special_test");
-        });
-
-        suite.addTest('exact test name works', async () => {
-            const config = [
-                {
-                    env: { DATA: "data" }
-                },
-                {
-                    mask: "some::mod::test_name",
-                    env: { DATA: "test special" }
-                }
-            ];
-            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
-            assert.strictEqual(testEnv["DATA"], "test special");
-
-            const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
-            assert.strictEqual(specialTestEnv["DATA"], "data");
-        });
-
-        suite.addTest('test mod name works', async () => {
-            const config = [
-                {
-                    env: { DATA: "data" }
-                },
-                {
-                    mask: "some::mod",
-                    env: { DATA: "mod special" }
-                }
-            ];
-            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
-            assert.strictEqual(testEnv["DATA"], "mod special");
-
-            const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
-            assert.strictEqual(specialTestEnv["DATA"], "mod special");
-        });
+suite('Runnable env', () => {
+    test('Global config works', () => {
+        const binEnv = fakePrepareEnv("run project_name", { "GLOBAL": "g" });
+        assert.strictEqual(binEnv["GLOBAL"], "g");
+
+        const testEnv = fakePrepareEnv("test some::mod::test_name", { "GLOBAL": "g" });
+        assert.strictEqual(testEnv["GLOBAL"], "g");
     });
-}
+
+    test('null mask works', () => {
+        const config = [
+            {
+                env: { DATA: "data" }
+            }
+        ];
+        const binEnv = fakePrepareEnv("run project_name", config);
+        assert.strictEqual(binEnv["DATA"], "data");
+
+        const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+        assert.strictEqual(testEnv["DATA"], "data");
+    });
+
+    test('order works', () => {
+        const config = [
+            {
+                env: { DATA: "data" }
+            },
+            {
+                env: { DATA: "newdata" }
+            }
+        ];
+        const binEnv = fakePrepareEnv("run project_name", config);
+        assert.strictEqual(binEnv["DATA"], "newdata");
+
+        const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+        assert.strictEqual(testEnv["DATA"], "newdata");
+    });
+
+    test('mask works', () => {
+        const config = [
+            {
+                env: { DATA: "data" }
+            },
+            {
+                mask: "^run",
+                env: { DATA: "rundata" }
+            },
+            {
+                mask: "special_test$",
+                env: { DATA: "special_test" }
+            }
+        ];
+        const binEnv = fakePrepareEnv("run project_name", config);
+        assert.strictEqual(binEnv["DATA"], "rundata");
+
+        const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+        assert.strictEqual(testEnv["DATA"], "data");
+
+        const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config);
+        assert.strictEqual(specialTestEnv["DATA"], "special_test");
+    });
+
+    test('exact test name works', () => {
+        const config = [
+            {
+                env: { DATA: "data" }
+            },
+            {
+                mask: "some::mod::test_name",
+                env: { DATA: "test special" }
+            }
+        ];
+        const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+        assert.strictEqual(testEnv["DATA"], "test special");
+
+        const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
+        assert.strictEqual(specialTestEnv["DATA"], "data");
+    });
+
+    test('test mod name works', () => {
+        const config = [
+            {
+                env: { DATA: "data" }
+            },
+            {
+                mask: "some::mod",
+                env: { DATA: "mod special" }
+            }
+        ];
+        const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+        assert.strictEqual(testEnv["DATA"], "mod special");
+
+        const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
+        assert.strictEqual(specialTestEnv["DATA"], "mod special");
+    });
+
+});