Repository: rust-analyzer/rust-analyzer
Pull request #5159 (2020-07-01 08:44:34)

Don't fail expect tests in rewrite mode
bors r+


Patch:
diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs
index dd7b96aab2..a5e26faded 100644
--- a/crates/expect/src/lib.rs
+++ b/crates/expect/src/lib.rs
@@ -2,7 +2,7 @@
 //! https://github.com/rust-analyzer/rust-analyzer/pull/5101
 use std::{
     collections::HashMap,
-    env, fmt, fs,
+    env, fmt, fs, mem,
     ops::Range,
     panic,
     path::{Path, PathBuf},
@@ -14,7 +14,7 @@ use once_cell::sync::Lazy;
 use stdx::{lines_with_ends, trim_indent};
 
 const HELP: &str = "
-You can update all `expect![[]]` tests by:
+You can update all `expect![[]]` tests by running:
 
     env UPDATE_EXPECT=1 cargo test
 
@@ -25,24 +25,48 @@ fn update_expect() -> bool {
     env::var("UPDATE_EXPECT").is_ok()
 }
 
-/// expect![[""]]
+/// expect![[r#"inline snapshot"#]]
 #[macro_export]
 macro_rules! expect {
-    [[$lit:literal]] => {$crate::Expect {
-        file: file!(),
-        line: line!(),
-        column: column!(),
-        data: $lit,
+    [[$data:literal]] => {$crate::Expect {
+        position: $crate::Position {
+            file: file!(),
+            line: line!(),
+            column: column!(),
+        },
+        data: $data,
     }};
     [[]] => { $crate::expect![[""]] };
 }
 
+/// expect_file!["/crates/foo/test_data/bar.html"]
+#[macro_export]
+macro_rules! expect_file {
+    [$path:literal] => {$crate::ExpectFile { path: $path }};
+}
+
 #[derive(Debug)]
 pub struct Expect {
+    pub position: Position,
+    pub data: &'static str,
+}
+
+#[derive(Debug)]
+pub struct ExpectFile {
+    pub path: &'static str,
+}
+
+#[derive(Debug)]
+pub struct Position {
     pub file: &'static str,
     pub line: u32,
     pub column: u32,
-    pub data: &'static str,
+}
+
+impl fmt::Display for Position {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}:{}:{}", self.file, self.line, self.column)
+    }
 }
 
 impl Expect {
@@ -51,7 +75,7 @@ impl Expect {
         if &trimmed == actual {
             return;
         }
-        Runtime::fail(self, &trimmed, actual);
+        Runtime::fail_expect(self, &trimmed, actual);
     }
     pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) {
         let actual = format!("{:#?}\n", actual);
@@ -69,7 +93,7 @@ impl Expect {
         let mut target_line = None;
         let mut line_start = 0;
         for (i, line) in lines_with_ends(file).enumerate() {
-            if i == self.line as usize - 1 {
+            if i == self.position.line as usize - 1 {
                 let pat = "expect![[";
                 let offset = line.find(pat).unwrap();
                 let literal_start = line_start + offset + pat.len();
@@ -87,6 +111,25 @@ impl Expect {
     }
 }
 
+impl ExpectFile {
+    pub fn assert_eq(&self, actual: &str) {
+        let expected = self.read();
+        if actual == expected {
+            return;
+        }
+        Runtime::fail_file(self, &expected, actual);
+    }
+    fn read(&self) -> String {
+        fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n")
+    }
+    fn write(&self, contents: &str) {
+        fs::write(self.abs_path(), contents).unwrap()
+    }
+    fn abs_path(&self) -> PathBuf {
+        workspace_root().join(self.path)
+    }
+}
+
 #[derive(Default)]
 struct Runtime {
     help_printed: bool,
@@ -95,27 +138,39 @@ struct Runtime {
 static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default);
 
 impl Runtime {
-    fn fail(expect: &Expect, expected: &str, actual: &str) {
+    fn fail_expect(expect: &Expect, expected: &str, actual: &str) {
         let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
-        let mut updated = "";
         if update_expect() {
-            updated = " (updated)";
+            println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position);
             rt.per_file
-                .entry(expect.file)
+                .entry(expect.position.file)
                 .or_insert_with(|| FileRuntime::new(expect))
                 .update(expect, actual);
+            return;
         }
-        let print_help = !rt.help_printed && !update_expect();
-        rt.help_printed = true;
+        rt.panic(expect.position.to_string(), expected, actual);
+    }
+
+    fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) {
+        let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
+        if update_expect() {
+            println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path);
+            expect.write(actual);
+            return;
+        }
+        rt.panic(expect.path.to_string(), expected, actual);
+    }
 
+    fn panic(&mut self, position: String, expected: &str, actual: &str) {
+        let print_help = !mem::replace(&mut self.help_printed, true);
         let help = if print_help { HELP } else { "" };
 
         let diff = Changeset::new(actual, expected, "\n");
 
         println!(
             "\n
-\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m{}
-   \x1b[1m\x1b[34m-->\x1b[0m {}:{}:{}
+\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m
+   \x1b[1m\x1b[34m-->\x1b[0m {}
 {}
 \x1b[1mExpect\x1b[0m:
 ----
@@ -132,7 +187,7 @@ impl Runtime {
 {}
 ----
 ",
-            updated, expect.file, expect.line, expect.column, help, expected, actual, diff
+            position, help, expected, actual, diff
         );
         // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise.
         panic::resume_unwind(Box::new(()));
@@ -147,7 +202,7 @@ struct FileRuntime {
 
 impl FileRuntime {
     fn new(expect: &Expect) -> FileRuntime {
-        let path = workspace_root().join(expect.file);
+        let path = workspace_root().join(expect.position.file);
         let original_text = fs::read_to_string(&path).unwrap();
         let patchwork = Patchwork::new(original_text.clone());
         FileRuntime { path, original_text, patchwork }
diff --git a/crates/ra_hir_expand/src/diagnostics.rs b/crates/ra_hir_expand/src/diagnostics.rs
index 545cff9bd1..99209c6e8c 100644
--- a/crates/ra_hir_expand/src/diagnostics.rs
+++ b/crates/ra_hir_expand/src/diagnostics.rs
@@ -28,7 +28,7 @@ pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
 
 pub trait AstDiagnostic {
     type AST;
-    fn ast(&self, db: &dyn AstDatabase) -> Self::AST;
+    fn ast(&self, db: &impl AstDatabase) -> Self::AST;
 }
 
 impl dyn Diagnostic {
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 0289911de0..a59efb3476 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -32,7 +32,7 @@ impl Diagnostic for NoSuchField {
 impl AstDiagnostic for NoSuchField {
     type AST = ast::RecordField;
 
-    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
+    fn ast(&self, db: &impl AstDatabase) -> Self::AST {
         let root = db.parse_or_expand(self.source().file_id).unwrap();
         let node = self.source().value.to_node(&root);
         ast::RecordField::cast(node).unwrap()
@@ -65,7 +65,7 @@ impl Diagnostic for MissingFields {
 impl AstDiagnostic for MissingFields {
     type AST = ast::RecordFieldList;
 
-    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
+    fn ast(&self, db: &impl AstDatabase) -> Self::AST {
         let root = db.parse_or_expand(self.source().file_id).unwrap();
         let node = self.source().value.to_node(&root);
         ast::RecordFieldList::cast(node).unwrap()
@@ -135,7 +135,7 @@ impl Diagnostic for MissingOkInTailExpr {
 impl AstDiagnostic for MissingOkInTailExpr {
     type AST = ast::Expr;
 
-    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
+    fn ast(&self, db: &impl AstDatabase) -> Self::AST {
         let root = db.parse_or_expand(self.file).unwrap();
         let node = self.source().value.to_node(&root);
         ast::Expr::cast(node).unwrap()
@@ -163,7 +163,7 @@ impl Diagnostic for BreakOutsideOfLoop {
 impl AstDiagnostic for BreakOutsideOfLoop {
     type AST = ast::Expr;
 
-    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
+    fn ast(&self, db: &impl AstDatabase) -> Self::AST {
         let root = db.parse_or_expand(self.file).unwrap();
         let node = self.source().value.to_node(&root);
         ast::Expr::cast(node).unwrap()
@@ -191,7 +191,7 @@ impl Diagnostic for MissingUnsafe {
 impl AstDiagnostic for MissingUnsafe {
     type AST = ast::Expr;
 
-    fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
+    fn ast(&self, db: &impl AstDatabase) -> Self::AST {
         let root = db.parse_or_expand(self.source().file_id).unwrap();
         let node = self.source().value.to_node(&root);
         ast::Expr::cast(node).unwrap()
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/test_data/highlight_doctest.html
similarity index 100%
rename from crates/ra_ide/src/snapshots/highlight_doctest.html
rename to crates/ra_ide/test_data/highlight_doctest.html
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/test_data/highlight_injection.html
similarity index 100%
rename from crates/ra_ide/src/snapshots/highlight_injection.html
rename to crates/ra_ide/test_data/highlight_injection.html
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/test_data/highlight_strings.html
similarity index 100%
rename from crates/ra_ide/src/snapshots/highlight_strings.html
rename to crates/ra_ide/test_data/highlight_strings.html
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/test_data/highlight_unsafe.html
similarity index 100%
rename from crates/ra_ide/src/snapshots/highlight_unsafe.html
rename to crates/ra_ide/test_data/highlight_unsafe.html
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/test_data/highlighting.html
similarity index 100%
rename from crates/ra_ide/src/snapshots/highlighting.html
rename to crates/ra_ide/test_data/highlighting.html
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/test_data/rainbow_highlighting.html
similarity index 100%
rename from crates/ra_ide/src/snapshots/rainbow_highlighting.html
rename to crates/ra_ide/test_data/rainbow_highlighting.html
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
index 422e15ee6a..e148f4564b 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -9,11 +9,10 @@ mod replacing;
 #[cfg(test)]
 mod tests;
 
-pub use crate::matching::Match;
-use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason};
+use crate::matching::Match;
 use hir::Semantics;
 use ra_db::{FileId, FileRange};
-use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange};
+use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode};
 use ra_text_edit::TextEdit;
 use rustc_hash::FxHashMap;
 
@@ -27,7 +26,7 @@ pub struct SsrRule {
 }
 
 #[derive(Debug)]
-pub struct SsrPattern {
+struct SsrPattern {
     raw: parsing::RawSearchPattern,
     /// Placeholders keyed by the stand-in ident that we use in Rust source code.
     placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
@@ -46,7 +45,7 @@ pub struct SsrError(String);
 
 #[derive(Debug, Default)]
 pub struct SsrMatches {
-    pub matches: Vec<Match>,
+    matches: Vec<Match>,
 }
 
 /// Searches a crate for pattern matches and possibly replaces them with something else.
@@ -65,12 +64,6 @@ impl<'db> MatchFinder<'db> {
         self.rules.push(rule);
     }
 
-    /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
-    /// intend to do replacement, use `add_rule` instead.
-    pub fn add_search_pattern(&mut self, pattern: SsrPattern) {
-        self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() })
-    }
-
     pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> {
         let matches = self.find_matches_in_file(file_id);
         if matches.matches.is_empty() {
@@ -81,7 +74,7 @@ impl<'db> MatchFinder<'db> {
         }
     }
 
-    pub fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches {
+    fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches {
         let file = self.sema.parse(file_id);
         let code = file.syntax();
         let mut matches = SsrMatches::default();
@@ -89,32 +82,6 @@ impl<'db> MatchFinder<'db> {
         matches
     }
 
-    /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
-    /// them, while recording reasons why they don't match. This API is useful for command
-    /// line-based debugging where providing a range is difficult.
-    pub fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> {
-        use ra_db::SourceDatabaseExt;
-        let file = self.sema.parse(file_id);
-        let mut res = Vec::new();
-        let file_text = self.sema.db.file_text(file_id);
-        let mut remaining_text = file_text.as_str();
-        let mut base = 0;
-        let len = snippet.len() as u32;
-        while let Some(offset) = remaining_text.find(snippet) {
-            let start = base + offset as u32;
-            let end = start + len;
-            self.output_debug_for_nodes_at_range(
-                file.syntax(),
-                FileRange { file_id, range: TextRange::new(start.into(), end.into()) },
-                &None,
-                &mut res,
-            );
-            remaining_text = &remaining_text[offset + snippet.len()..];
-            base = end;
-        }
-        res
-    }
-
     fn find_matches(
         &self,
         code: &SyntaxNode,
@@ -161,59 +128,6 @@ impl<'db> MatchFinder<'db> {
             self.find_matches(&child, restrict_range, matches_out);
         }
     }
-
-    fn output_debug_for_nodes_at_range(
-        &self,
-        node: &SyntaxNode,
-        range: FileRange,
-        restrict_range: &Option<FileRange>,
-        out: &mut Vec<MatchDebugInfo>,
-    ) {
-        for node in node.children() {
-            let node_range = self.sema.original_range(&node);
-            if node_range.file_id != range.file_id || !node_range.range.contains_range(range.range)
-            {
-                continue;
-            }
-            if node_range.range == range.range {
-                for rule in &self.rules {
-                    let pattern =
-                        rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone());
-                    out.push(MatchDebugInfo {
-                        matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
-                            .map_err(|e| MatchFailureReason {
-                                reason: e.reason.unwrap_or_else(|| {
-                                    "Match failed, but no reason was given".to_owned()
-                                }),
-                            }),
-                        pattern,
-                        node: node.clone(),
-                    });
-                }
-            } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) {
-                if let Some(expanded) = self.sema.expand(&macro_call) {
-                    if let Some(tt) = macro_call.token_tree() {
-                        self.output_debug_for_nodes_at_range(
-                            &expanded,
-                            range,
-                            &Some(self.sema.original_range(tt.syntax())),
-                            out,
-                        );
-                    }
-                }
-            } else {
-                self.output_debug_for_nodes_at_range(&node, range, restrict_range, out);
-            }
-        }
-    }
-}
-
-pub struct MatchDebugInfo {
-    node: SyntaxNode,
-    /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item,
-    /// etc. Will be absent if the pattern can't be parsed as that kind.
-    pattern: Result<SyntaxNode, MatchFailureReason>,
-    matched: Result<Match, MatchFailureReason>,
 }
 
 impl std::fmt::Display for SsrError {
@@ -222,70 +136,4 @@ impl std::fmt::Display for SsrError {
     }
 }
 
-impl std::fmt::Debug for MatchDebugInfo {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "========= PATTERN ==========\n")?;
-        match &self.pattern {
-            Ok(pattern) => {
-                write!(f, "{:#?}", pattern)?;
-            }
-            Err(err) => {
-                write!(f, "{}", err.reason)?;
-            }
-        }
-        write!(
-            f,
-            "\n============ AST ===========\n\
-            {:#?}\n============================\n",
-            self.node
-        )?;
-        match &self.matched {
-            Ok(_) => write!(f, "Node matched")?,
-            Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?,
-        }
-        Ok(())
-    }
-}
-
-impl SsrPattern {
-    fn tree_for_kind_with_reason(
-        &self,
-        kind: SyntaxKind,
-    ) -> Result<&SyntaxNode, MatchFailureReason> {
-        record_match_fails_reasons_scope(true, || self.tree_for_kind(kind))
-            .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() })
-    }
-}
-
-impl SsrMatches {
-    /// Returns `self` with any nested matches removed and made into top-level matches.
-    pub fn flattened(self) -> SsrMatches {
-        let mut out = SsrMatches::default();
-        self.flatten_into(&mut out);
-        out
-    }
-
-    fn flatten_into(self, out: &mut SsrMatches) {
-        for mut m in self.matches {
-            for p in m.placeholder_values.values_mut() {
-                std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out);
-            }
-            out.matches.push(m);
-        }
-    }
-}
-
-impl Match {
-    pub fn matched_text(&self) -> String {
-        self.matched_node.text().to_string()
-    }
-}
-
 impl std::error::Error for SsrError {}
-
-#[cfg(test)]
-impl MatchDebugInfo {
-    pub(crate) fn match_failure_reason(&self) -> Option<&str> {
-        self.matched.as_ref().err().map(|r| r.reason.as_str())
-    }
-}
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index 53d802e775..54413a151e 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -8,7 +8,9 @@ use crate::{
 use hir::Semantics;
 use ra_db::FileRange;
 use ra_syntax::ast::{AstNode, AstToken};
-use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken};
+use ra_syntax::{
+    ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
+};
 use rustc_hash::FxHashMap;
 use std::{cell::Cell, iter::Peekable};
 
@@ -42,8 +44,8 @@ macro_rules! fail_match {
 
 /// Information about a match that was found.
 #[derive(Debug)]
-pub struct Match {
-    pub(crate) range: FileRange,
+pub(crate) struct Match {
+    pub(crate) range: TextRange,
     pub(crate) matched_node: SyntaxNode,
     pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
     pub(crate) ignored_comments: Vec<ast::Comment>,
@@ -133,7 +135,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
         match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?;
         match_state.validate_range(&sema.original_range(code))?;
         match_state.match_out = Some(Match {
-            range: sema.original_range(code),
+            range: sema.original_range(code).range,
             matched_node: code.clone(),
             placeholder_values: FxHashMap::default(),
             ignored_comments: Vec::new(),
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs
index e43cc51674..70ce1c185b 100644
--- a/crates/ra_ssr/src/replacing.rs
+++ b/crates/ra_ssr/src/replacing.rs
@@ -21,10 +21,8 @@ fn matches_to_edit_at_offset(
 ) -> TextEdit {
     let mut edit_builder = ra_text_edit::TextEditBuilder::default();
     for m in &matches.matches {
-        edit_builder.replace(
-            m.range.range.checked_sub(relative_start).unwrap(),
-            render_replace(m, file_src),
-        );
+        edit_builder
+            .replace(m.range.checked_sub(relative_start).unwrap(), render_replace(m, file_src));
     }
     edit_builder.finish()
 }
diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs
index 8c0f4df8b8..8a0b101174 100644
--- a/crates/rust-analyzer/src/bin/args.rs
+++ b/crates/rust-analyzer/src/bin/args.rs
@@ -5,7 +5,7 @@
 
 use anyhow::{bail, Result};
 use pico_args::Arguments;
-use ra_ssr::{SsrPattern, SsrRule};
+use ra_ssr::SsrRule;
 use rust_analyzer::cli::{BenchWhat, Position, Verbosity};
 
 use std::{fmt::Write, path::PathBuf};
@@ -50,10 +50,6 @@ pub(crate) enum Command {
     Ssr {
         rules: Vec<SsrRule>,
     },
-    StructuredSearch {
-        debug_snippet: Option<String>,
-        patterns: Vec<SsrPattern>,
-    },
     ProcMacro,
     RunServer,
     Version,
@@ -298,7 +294,6 @@ EXAMPLE:
     rust-analyzer ssr '$a.foo($b) ==> bar($a, $b)'
 
 FLAGS:
-    --debug <snippet>   Prints debug information for any nodes with source exactly equal to <snippet>
     -h, --help          Prints help information
 
 ARGS:
@@ -312,34 +307,6 @@ ARGS:
                 }
                 Command::Ssr { rules }
             }
-            "search" => {
-                if matches.contains(["-h", "--help"]) {
-                    eprintln!(
-                        "\
-rust-analyzer search
-
-USAGE:
-    rust-analyzer search [FLAGS] [PATTERN...]
-
-EXAMPLE:
-    rust-analyzer search '$a.foo($b)'
-
-FLAGS:
-    --debug <snippet>   Prints debug information for any nodes with source exactly equal to <snippet>
-    -h, --help          Prints help information
-
-ARGS:
-    <PATTERN>           A structured search pattern"
-                    );
-                    return Ok(Err(HelpPrinted));
-                }
-                let debug_snippet = matches.opt_value_from_str("--debug")?;
-                let mut patterns = Vec::new();
-                while let Some(rule) = matches.free_from_str()? {
-                    patterns.push(rule);
-                }
-                Command::StructuredSearch { patterns, debug_snippet }
-            }
             _ => {
                 print_subcommands();
                 return Ok(Err(HelpPrinted));
@@ -367,7 +334,6 @@ SUBCOMMANDS:
     diagnostics
     proc-macro
     parse
-    search
     ssr
     symbols"
     )
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index eec76d4156..0f55c3ee2b 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -65,9 +65,6 @@ fn main() -> Result<()> {
         args::Command::Ssr { rules } => {
             cli::apply_ssr_rules(rules)?;
         }
-        args::Command::StructuredSearch { patterns, debug_snippet } => {
-            cli::search_for_patterns(patterns, debug_snippet)?;
-        }
         args::Command::Version => println!("rust-analyzer {}", env!("REV")),
     }
     Ok(())
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs
index 6863f100b6..13e3d75be1 100644
--- a/crates/rust-analyzer/src/cli.rs
+++ b/crates/rust-analyzer/src/cli.rs
@@ -18,7 +18,7 @@ pub use analysis_bench::{analysis_bench, BenchWhat, Position};
 pub use analysis_stats::analysis_stats;
 pub use diagnostics::diagnostics;
 pub use load_cargo::load_cargo;
-pub use ssr::{apply_ssr_rules, search_for_patterns};
+pub use ssr::apply_ssr_rules;
 
 #[derive(Clone, Copy)]
 pub enum Verbosity {
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 4fb829ea5c..a5265ac158 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -2,7 +2,7 @@
 
 use crate::cli::{load_cargo::load_cargo, Result};
 use ra_ide::SourceFileEdit;
-use ra_ssr::{MatchFinder, SsrPattern, SsrRule};
+use ra_ssr::{MatchFinder, SsrRule};
 
 pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
     use ra_db::SourceDatabaseExt;
@@ -31,41 +31,3 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
     }
     Ok(())
 }
-
-/// Searches for `patterns`, printing debug information for any nodes whose text exactly matches
-/// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful
-/// for much else.
-pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> {
-    use ra_db::SourceDatabaseExt;
-    use ra_ide_db::symbol_index::SymbolsDatabase;
-    let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
-    let db = host.raw_database();
-    let mut match_finder = MatchFinder::new(db);
-    for pattern in patterns {
-        match_finder.add_search_pattern(pattern);
-    }
-    for &root in db.local_roots().iter() {
-        let sr = db.source_root(root);
-        for file_id in sr.iter() {
-            if let Some(debug_snippet) = &debug_snippet {
-                for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) {
-                    println!("{:#?}", debug_info);
-                }
-            } else {
-                let matches = match_finder.find_matches_in_file(file_id);
-                if !matches.matches.is_empty() {
-                    let matches = matches.flattened().matches;
-                    if let Some(path) = vfs.file_path(file_id).as_path() {
-                        println!("{} matches in '{}'", matches.len(), path.to_string_lossy());
-                    }
-                    // We could possibly at some point do something more useful than just printing
-                    // the matched text. For now though, that's the easiest thing to do.
-                    for m in matches {
-                        println!("{}", m.matched_text());
-                    }
-                }
-            }
-        }
-    }
-    Ok(())
-}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 25bcd80af8..607a95682a 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -415,7 +415,7 @@ pub(crate) fn handle_runnables(
             let source_file = snap.analysis.parse(file_id)?;
             algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset)
                 .and_then(|it| it.path()?.segment()?.name_ref())
-                .map_or(false, |it| it.text() == "expect")
+                .map_or(false, |it| it.text() == "expect" || it.text() == "expect_file")
         }
         None => false,
     };
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 4f94c2dc5d..3b4a316820 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -5,35 +5,35 @@
     "requires": true,
     "dependencies": {
         "@babel/code-frame": {
-            "version": "7.10.3",
-            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.3.tgz",
-            "integrity": "sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==",
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
+            "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
             "dev": true,
             "requires": {
-                "@babel/highlight": "^7.10.3"
+                "@babel/highlight": "^7.8.3"
             }
         },
         "@babel/helper-validator-identifier": {
-            "version": "7.10.3",
-            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz",
-            "integrity": "sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw==",
+            "version": "7.9.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
+            "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==",
             "dev": true
         },
         "@babel/highlight": {
-            "version": "7.10.3",
-            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz",
-            "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==",
+            "version": "7.9.0",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz",
+            "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==",
             "dev": true,
             "requires": {
-                "@babel/helper-validator-identifier": "^7.10.3",
+                "@babel/helper-validator-identifier": "^7.9.0",
                 "chalk": "^2.0.0",
                 "js-tokens": "^4.0.0"
             }
         },
         "@rollup/plugin-commonjs": {
-            "version": "13.0.0",
-            "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz",
-            "integrity": "sha512-Anxc3qgkAi7peAyesTqGYidG5GRim9jtg8xhmykNaZkImtvjA7Wsqep08D2mYsqw1IF7rA3lYfciLgzUSgRoqw==",
+            "version": "12.0.0",
+            "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-12.0.0.tgz",
+            "integrity": "sha512-8+mDQt1QUmN+4Y9D3yCG8AJNewuTSLYPJVzKKUZ+lGeQrI+bV12Tc5HCyt2WdlnG6ihIL/DPbKRJlB40DX40mw==",
             "dev": true,
             "requires": {
                 "@rollup/pluginutils": "^3.0.8",
@@ -46,9 +46,9 @@
             }
         },
         "@rollup/plugin-node-resolve": {
-            "version": "8.1.0",
-            "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.1.0.tgz",
-            "integrity": "sha512-ovq7ZM3JJYUUmEjjO+H8tnUdmQmdQudJB7xruX8LFZ1W2q8jXdPUS6SsIYip8ByOApu4RR7729Am9WhCeCMiHA==",
+            "version": "8.0.0",
+            "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.0.0.tgz",
+            "integrity": "sha512-5poJCChrkVggXXND/sQ7yNqwjUNT4fP31gpRWCnSNnlXuUXTCMHT33xZrTGxgjm5Rl18MHj7iEzlCT8rYWwQSA==",
             "dev": true,
             "requires": {
                 "@rollup/pluginutils": "^3.0.8",
@@ -89,12 +89,19 @@
             "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
             "dev": true
         },
+        "@types/events": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+            "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
+            "dev": true
+        },
         "@types/glob": {
-            "version": "7.1.2",
-            "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz",
-            "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==",
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+            "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
             "dev": true,
             "requires": {
+                "@types/events": "*",
                 "@types/minimatch": "*",
                 "@types/node": "*"
             }
@@ -118,9 +125,9 @@
             "dev": true
         },
         "@types/node": {
-            "version": "12.7.12",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz",
-            "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==",
+            "version": "14.0.5",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.5.tgz",
+            "integrity": "sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA==",
             "dev": true
         },
         "@types/node-fetch": {
@@ -149,46 +156,18 @@
             "dev": true
         },
         "@typescript-eslint/eslint-plugin": {
-            "version": "3.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.4.0.tgz",
-            "integrity": "sha512-wfkpiqaEVhZIuQRmudDszc01jC/YR7gMSxa6ulhggAe/Hs0KVIuo9wzvFiDbG3JD5pRFQoqnf4m7REDsUvBnMQ==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.0.tgz",
+            "integrity": "sha512-lcZ0M6jD4cqGccYOERKdMtg+VWpoq3NSnWVxpc/AwAy0zhkUYVioOUZmfNqiNH8/eBNGhCn6HXd6mKIGRgNc1Q==",
             "dev": true,
             "requires": {
-                "@typescript-eslint/experimental-utils": "3.4.0",
-                "debug": "^4.1.1",
+                "@typescript-eslint/experimental-utils": "3.0.0",
                 "functional-red-black-tree": "^1.0.1",
                 "regexpp": "^3.0.0",
                 "semver": "^7.3.2",
                 "tsutils": "^3.17.1"
             },
             "dependencies": {
-                "@typescript-eslint/experimental-utils": {
-                    "version": "3.4.0",
-                    "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.4.0.tgz",
-                    "integrity": "sha512-rHPOjL43lOH1Opte4+dhC0a/+ks+8gOBwxXnyrZ/K4OTAChpSjP76fbI8Cglj7V5GouwVAGaK+xVwzqTyE/TPw==",
-                    "dev": true,
-                    "requires": {
-                        "@types/json-schema": "^7.0.3",
-                        "@typescript-eslint/typescript-estree": "3.4.0",
-                        "eslint-scope": "^5.0.0",
-                        "eslint-utils": "^2.0.0"
-                    }
-                },
-                "@typescript-eslint/typescript-estree": {
-                    "version": "3.4.0",
-                    "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.4.0.tgz",
-                    "integrity": "sha512-zKwLiybtt4uJb4mkG5q2t6+W7BuYx2IISiDNV+IY68VfoGwErDx/RfVI7SWL4gnZ2t1A1ytQQwZ+YOJbHHJ2rw==",
-                    "dev": true,
-                    "requires": {
-                        "debug": "^4.1.1",
-                        "eslint-visitor-keys": "^1.1.0",
-                        "glob": "^7.1.6",
-                        "is-glob": "^4.0.1",
-                        "lodash": "^4.17.15",
-                        "semver": "^7.3.2",
-                        "tsutils": "^3.17.1"
-                    }
-                },
                 "semver": {
                     "version": "7.3.2",
                     "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
@@ -198,33 +177,33 @@
             }
         },
         "@typescript-eslint/experimental-utils": {
-            "version": "3.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.4.0.tgz",
-            "integrity": "sha512-rHPOjL43lOH1Opte4+dhC0a/+ks+8gOBwxXnyrZ/K4OTAChpSjP76fbI8Cglj7V5GouwVAGaK+xVwzqTyE/TPw==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.0.tgz",
+            "integrity": "sha512-BN0vmr9N79M9s2ctITtChRuP1+Dls0x/wlg0RXW1yQ7WJKPurg6X3Xirv61J2sjPif4F8SLsFMs5Nzte0WYoTQ==",
             "dev": true,
             "requires": {
                 "@types/json-schema": "^7.0.3",
-                "@typescript-eslint/typescript-estree": "3.4.0",
+                "@typescript-eslint/typescript-estree": "3.0.0",
                 "eslint-scope": "^5.0.0",
                 "eslint-utils": "^2.0.0"
             }
         },
         "@typescript-eslint/parser": {
-            "version": "3.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.4.0.tgz",
-            "integrity": "sha512-ZUGI/de44L5x87uX5zM14UYcbn79HSXUR+kzcqU42gH0AgpdB/TjuJy3m4ezI7Q/jk3wTQd755mxSDLhQP79KA==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.0.0.tgz",
+            "integrity": "sha512-8RRCA9KLxoFNO0mQlrLZA0reGPd/MsobxZS/yPFj+0/XgMdS8+mO8mF3BDj2ZYQj03rkayhSJtF1HAohQ3iylw==",
             "dev": true,
             "requires": {
                 "@types/eslint-visitor-keys": "^1.0.0",
-                "@typescript-eslint/experimental-utils": "3.4.0",
-                "@typescript-eslint/typescript-estree": "3.4.0",
+                "@typescript-eslint/experimental-utils": "3.0.0",
+                "@typescript-eslint/typescript-estree": "3.0.0",
                 "eslint-visitor-keys": "^1.1.0"
             }
         },
         "@typescript-eslint/typescript-estree": {
-            "version": "3.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.4.0.tgz",
-            "integrity": "sha512-zKwLiybtt4uJb4mkG5q2t6+W7BuYx2IISiDNV+IY68VfoGwErDx/RfVI7SWL4gnZ2t1A1ytQQwZ+YOJbHHJ2rw==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.0.tgz",
+            "integrity": "sha512-nevQvHyNghsfLrrByzVIH4ZG3NROgJ8LZlfh3ddwPPH4CH7W4GAiSx5qu+xHuX5pWsq6q/eqMc1io840ZhAnUg==",
             "dev": true,
             "requires": {
                 "debug": "^4.1.1",
@@ -245,9 +224,9 @@
             }
         },
         "acorn": {
-            "version": "7.3.1",
-            "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
-            "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==",
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz",
+            "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==",
             "dev": true
         },
         "acorn-jsx": {
@@ -283,6 +262,23 @@
             "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
             "dev": true
         },
+        "ansi-escapes": {
+            "version": "4.3.1",
+            "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
+            "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
+            "dev": true,
+            "requires": {
+                "type-fest": "^0.11.0"
+            },
+            "dependencies": {
+                "type-fest": {
+                    "version": "0.11.0",
+                    "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
+                    "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
+                    "dev": true
+                }
+            }
+        },
         "ansi-regex": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
@@ -317,18 +313,6 @@
                 "sprintf-js": "~1.0.2"
             }
         },
-        "array.prototype.map": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz",
-            "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==",
-            "dev": true,
-            "requires": {
-                "define-properties": "^1.1.3",
-                "es-abstract": "^1.17.0-next.1",
-                "es-array-method-boxes-properly": "^1.0.0",
-                "is-string": "^1.0.4"
-            }
-        },
         "astral-regex": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@@ -360,9 +344,9 @@
             "dev": true
         },
         "binary-extensions": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
-            "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+            "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
             "dev": true
         },
         "boolbase": {
@@ -431,6 +415,12 @@
                 "supports-color": "^5.3.0"
             }
         },
+        "chardet": {
+            "version": "0.7.0",
+            "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+            "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+            "dev": true
+        },
         "cheerio": {
             "version": "1.0.0-rc.3",
             "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz",
@@ -446,21 +436,36 @@
             }
         },
         "chokidar": {
-            "version": "3.3.1",
-            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
-            "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz",
+            "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==",
             "dev": true,
             "requires": {
                 "anymatch": "~3.1.1",
                 "braces": "~3.0.2",
-                "fsevents": "~2.1.2",
+                "fsevents": "~2.1.1",
                 "glob-parent": "~5.1.0",
                 "is-binary-path": "~2.1.0",
                 "is-glob": "~4.0.1",
                 "normalize-path": "~3.0.0",
-                "readdirp": "~3.3.0"
+                "readdirp": "~3.2.0"
             }
         },
+        "cli-cursor": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+            "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+            "dev": true,
+            "requires": {
+                "restore-cursor": "^3.1.0"
+            }
+        },
+        "cli-width": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
+            "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
+            "dev": true
+        },
         "cliui": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -478,6 +483,29 @@
                     "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
                     "dev": true
                 },
+                "emoji-regex": {
+                    "version": "7.0.3",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+                    "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+                    "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": "3.1.0",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+                    "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+                    "dev": true,
+                    "requires": {
+                        "emoji-regex": "^7.0.1",
+                        "is-fullwidth-code-point": "^2.0.0",
+                        "strip-ansi": "^5.1.0"
+                    }
+                },
                 "strip-ansi": {
                     "version": "5.2.0",
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@@ -538,9 +566,9 @@
             "dev": true
         },
         "cross-spawn": {
-            "version": "7.0.3",
-            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-            "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+            "version": "7.0.2",
+            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz",
+            "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==",
             "dev": true,
             "requires": {
                 "path-key": "^3.1.0",
@@ -621,9 +649,9 @@
             "dev": true
         },
         "diff": {
-            "version": "4.0.2",
-            "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
-            "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+            "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
             "dev": true
         },
         "doctrine": {
@@ -691,20 +719,11 @@
             }
         },
         "emoji-regex": {
-            "version": "7.0.3",
-            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
-            "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+            "version": "8.0.0",
+            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
             "dev": true
         },
-        "enquirer": {
-            "version": "2.3.5",
-            "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz",
-            "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==",
-            "dev": true,
-            "requires": {
-                "ansi-colors": "^3.2.1"
-            }
-        },
         "entities": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
@@ -712,43 +731,22 @@
             "dev": true
         },
         "es-abstract": {
-            "version": "1.17.6",
-            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
-            "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+            "version": "1.17.5",
+            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
+            "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
             "dev": true,
             "requires": {
                 "es-to-primitive": "^1.2.1",
                 "function-bind": "^1.1.1",
                 "has": "^1.0.3",
                 "has-symbols": "^1.0.1",
-                "is-callable": "^1.2.0",
-                "is-regex": "^1.1.0",
+                "is-callable": "^1.1.5",
+                "is-regex": "^1.0.5",
                 "object-inspect": "^1.7.0",
                 "object-keys": "^1.1.1",
                 "object.assign": "^4.1.0",
-                "string.prototype.trimend": "^1.0.1",
-                "string.prototype.trimstart": "^1.0.1"
-            }
-        },
-        "es-array-method-boxes-properly": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
-            "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
-            "dev": true
-        },
-        "es-get-iterator": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz",
-            "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==",
-            "dev": true,
-            "requires": {
-                "es-abstract": "^1.17.4",
-                "has-symbols": "^1.0.1",
-                "is-arguments": "^1.0.4",
-                "is-map": "^2.0.1",
-                "is-set": "^2.0.1",
-                "is-string": "^1.0.5",
-                "isarray": "^2.0.5"
+                "string.prototype.trimleft": "^2.1.1",
+                "string.prototype.trimright": "^2.1.1"
             }
         },
         "es-to-primitive": {
@@ -784,9 +782,9 @@
             "dev": true
         },
         "eslint": {
-            "version": "7.3.1",
-            "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.3.1.tgz",
-            "integrity": "sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==",
+            "version": "7.1.0",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.1.0.tgz",
+            "integrity": "sha512-DfS3b8iHMK5z/YLSme8K5cge168I8j8o1uiVmFCgnnjxZQbCGyraF8bMl7Ju4yfBmCuxD7shOF7eqGkcuIHfsA==",
             "dev": true,
             "requires": {
                 "@babel/code-frame": "^7.0.0",
@@ -795,11 +793,10 @@
                 "cross-spawn": "^7.0.2",
                 "debug": "^4.0.1",
                 "doctrine": "^3.0.0",
-                "enquirer": "^2.3.5",
-                "eslint-scope": "^5.1.0",
+                "eslint-scope": "^5.0.0",
                 "eslint-utils": "^2.0.0",
-                "eslint-visitor-keys": "^1.2.0",
-                "espree": "^7.1.0",
+                "eslint-visitor-keys": "^1.1.0",
+                "espree": "^7.0.0",
                 "esquery": "^1.2.0",
                 "esutils": "^2.0.2",
                 "file-entry-cache": "^5.0.1",
@@ -809,6 +806,7 @@
                 "ignore": "^4.0.6",
                 "import-fresh": "^3.0.0",
                 "imurmurhash": "^0.1.4",
+                "inquirer": "^7.0.0",
                 "is-glob": "^4.0.0",
                 "js-yaml": "^3.13.1",
                 "json-stable-stringify-without-jsonify": "^1.0.1",
@@ -838,9 +836,9 @@
                     }
                 },
                 "chalk": {
-                    "version": "4.1.0",
-                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
-                    "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
+                    "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
                     "dev": true,
                     "requires": {
                         "ansi-styles": "^4.1.0",
@@ -862,22 +860,6 @@
                     "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
                     "dev": true
                 },
-                "eslint-scope": {
-                    "version": "5.1.0",
-                    "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
-                    "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
-                    "dev": true,
-                    "requires": {
-                        "esrecurse": "^4.1.0",
-                        "estraverse": "^4.1.1"
-                    }
-                },
-                "eslint-visitor-keys": {
-                    "version": "1.3.0",
-                    "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
-                    "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
-                    "dev": true
-                },
                 "has-flag": {
                     "version": "4.0.0",
                     "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -927,22 +909,14 @@
             "dev": true
         },
         "espree": {
-            "version": "7.1.0",
-            "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz",
-            "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==",
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/espree/-/espree-7.0.0.tgz",
+            "integrity": "sha512-/r2XEx5Mw4pgKdyb7GNLQNsu++asx/dltf/CI8RFi9oGHxmQFgvLbc5Op4U6i8Oaj+kdslhJtVlEZeAqH5qOTw==",
             "dev": true,
             "requires": {
-                "acorn": "^7.2.0",
+                "acorn": "^7.1.1",
                 "acorn-jsx": "^5.2.0",
-                "eslint-visitor-keys": "^1.2.0"
-            },
-            "dependencies": {
-                "eslint-visitor-keys": {
-                    "version": "1.3.0",
-                    "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
-                    "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
-                    "dev": true
-                }
+                "eslint-visitor-keys": "^1.1.0"
             }
         },
         "esprima": {
@@ -995,10 +969,21 @@
             "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
             "dev": true
         },
+        "external-editor": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+            "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+            "dev": true,
+            "requires": {
+                "chardet": "^0.7.0",
+                "iconv-lite": "^0.4.24",
+                "tmp": "^0.0.33"
+            }
+        },
         "fast-deep-equal": {
-            "version": "3.1.3",
-            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+            "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
             "dev": true
         },
         "fast-json-stable-stringify": {
@@ -1022,6 +1007,15 @@
                 "pend": "~1.2.0"
             }
         },
+        "figures": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+            "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+            "dev": true,
+            "requires": {
+                "escape-string-regexp": "^1.0.5"
+            }
+        },
         "file-entry-cache": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
@@ -1041,13 +1035,12 @@
             }
         },
         "find-up": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-            "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+            "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
             "dev": true,
             "requires": {
-                "locate-path": "^5.0.0",
-                "path-exists": "^4.0.0"
+                "locate-path": "^3.0.0"
             }
         },
         "flat": {
@@ -1245,6 +1238,15 @@
                 }
             }
         },
+        "iconv-lite": {
+            "version": "0.4.24",
+            "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+            "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+            "dev": true,
+            "requires": {
+                "safer-buffer": ">= 2.1.2 < 3"
+            }
+        },
         "ignore": {
             "version": "4.0.6",
             "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -1283,11 +1285,78 @@
             "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
             "dev": true
         },
-        "is-arguments": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
-            "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
-            "dev": true
+        "inquirer": {
+            "version": "7.1.0",
+            "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
+            "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==",
+            "dev": true,
+            "requires": {
+                "ansi-escapes": "^4.2.1",
+                "chalk": "^3.0.0",
+                "cli-cursor": "^3.1.0",
+                "cli-width": "^2.0.0",
+                "external-editor": "^3.0.3",
+                "figures": "^3.0.0",
+                "lodash": "^4.17.15",
+                "mute-stream": "0.0.8",
+                "run-async": "^2.4.0",
+                "rxjs": "^6.5.3",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0",
+                "through": "^2.3.6"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "4.2.1",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+                    "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+                    "dev": true,
+                    "requires": {
+                        "@types/color-name": "^1.1.1",
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "chalk": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+                    "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+                    "dev": true,
+                    "requires": {
+                        "ansi-styles": "^4.1.0",
+                        "supports-color": "^7.1.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "dev": true,
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+                    "dev": true
+                },
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+                    "dev": true
+                },
+                "supports-color": {
+                    "version": "7.1.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+                    "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
         },
         "is-binary-path": {
             "version": "2.1.0",
@@ -1305,9 +1374,9 @@
             "dev": true
         },
         "is-callable": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
-            "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+            "version": "1.1.5",
+            "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+            "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
             "dev": true
         },
         "is-date-object": {
@@ -1323,9 +1392,9 @@
             "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=",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+            "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
             "dev": true
         },
         "is-glob": {
@@ -1337,12 +1406,6 @@
                 "is-extglob": "^2.1.1"
             }
         },
-        "is-map": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz",
-            "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==",
-            "dev": true
-        },
         "is-module": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
@@ -1356,35 +1419,23 @@
             "dev": true
         },
         "is-reference": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
-            "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
+            "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
             "dev": true,
             "requires": {
-                "@types/estree": "*"
+                "@types/estree": "0.0.39"
             }
         },
         "is-regex": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
-            "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==",
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+            "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
             "dev": true,
             "requires": {
-                "has-symbols": "^1.0.1"
+                "has": "^1.0.3"
             }
         },
-        "is-set": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz",
-            "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==",
-            "dev": true
-        },
-        "is-string": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
-            "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
-            "dev": true
-        },
         "is-symbol": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
@@ -1394,34 +1445,12 @@
                 "has-symbols": "^1.0.1"
             }
         },
-        "isarray": {
-            "version": "2.0.5",
-            "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
-            "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
-            "dev": true
-        },
         "isexe": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
             "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
             "dev": true
         },
-        "iterate-iterator": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz",
-            "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==",
-            "dev": true
-        },
-        "iterate-value": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz",
-            "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==",
-            "dev": true,
-            "requires": {
-                "es-get-iterator": "^1.0.2",
-                "iterate-iterator": "^1.0.1"
-            }
-        },
         "js-tokens": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1476,12 +1505,13 @@
             }
         },
         "locate-path": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-            "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+            "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
             "dev": true,
             "requires": {
-                "p-locate": "^4.1.0"
+                "p-locate": "^3.0.0",
+                "path-exists": "^3.0.0"
             }
         },
         "lodash": {
@@ -1566,6 +1596,12 @@
                 "mime-db": "1.44.0"
             }
         },
+        "mimic-fn": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+            "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+            "dev": true
+        },
         "minimatch": {
             "version": "3.0.4",
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -1591,44 +1627,37 @@
             }
         },
         "mocha": {
-            "version": "8.0.1",
-            "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.0.1.tgz",
-            "integrity": "sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg==",
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz",
+            "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==",
             "dev": true,
             "requires": {
-                "ansi-colors": "4.1.1",
+                "ansi-colors": "3.2.3",
                 "browser-stdout": "1.3.1",
-                "chokidar": "3.3.1",
+                "chokidar": "3.3.0",
                 "debug": "3.2.6",
-                "diff": "4.0.2",
+                "diff": "3.5.0",
                 "escape-string-regexp": "1.0.5",
-                "find-up": "4.1.0",
-                "glob": "7.1.6",
+                "find-up": "3.0.0",
+                "glob": "7.1.3",
                 "growl": "1.10.5",
                 "he": "1.2.0",
                 "js-yaml": "3.13.1",
                 "log-symbols": "3.0.0",
                 "minimatch": "3.0.4",
-                "ms": "2.1.2",
+                "mkdirp": "0.5.5",
+                "ms": "2.1.1",
+                "node-environment-flags": "1.0.6",
                 "object.assign": "4.1.0",
-                "promise.allsettled": "1.0.2",
-                "serialize-javascript": "3.0.0",
-                "strip-json-comments": "3.0.1",
-                "supports-color": "7.1.0",
-                "which": "2.0.2",
+                "strip-json-comments": "2.0.1",
+                "supports-color": "6.0.0",
+                "which": "1.3.1",
                 "wide-align": "1.1.3",
-                "workerpool": "6.0.0",
                 "yargs": "13.3.2",
                 "yargs-parser": "13.1.2",
                 "yargs-unparser": "1.6.0"
             },
             "dependencies": {
-                "ansi-colors": {
-                    "version": "4.1.1",
-                    "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
-                    "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
-                    "dev": true
-                },
                 "debug": {
                     "version": "3.2.6",
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
@@ -1638,11 +1667,19 @@
                         "ms": "^2.1.1"
                     }
                 },
-                "has-flag": {
-                    "version": "4.0.0",
-                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-                    "dev": true
+                "glob": {
+                    "version": "7.1.3",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+                    "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+                    "dev": true,
+                    "requires": {
+                        "fs.realpath": "^1.0.0",
+                        "inflight": "^1.0.4",
+                        "inherits": "2",
+                        "minimatch": "^3.0.4",
+                        "once": "^1.3.0",
+                        "path-is-absolute": "^1.0.0"
+                    }
                 },
                 "js-yaml": {
                     "version": "3.13.1",
@@ -1654,19 +1691,34 @@
                         "esprima": "^4.0.0"
                     }
                 },
+                "ms": {
+                    "version": "2.1.1",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+                    "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+                    "dev": true
+                },
                 "strip-json-comments": {
-                    "version": "3.0.1",
-                    "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
-                    "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+                    "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
                     "dev": true
                 },
                 "supports-color": {
-                    "version": "7.1.0",
-                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
-                    "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+                    "version": "6.0.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+                    "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^4.0.0"
+                        "has-flag": "^3.0.0"
+                    }
+                },
+                "which": {
+                    "version": "1.3.1",
+                    "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+                    "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+                    "dev": true,
+                    "requires": {
+                        "isexe": "^2.0.0"
                     }
                 }
             }
@@ -1689,6 +1741,24 @@
             "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
             "dev": true
         },
+        "node-environment-flags": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
+            "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==",
+            "dev": true,
+            "requires": {
+                "object.getownpropertydescriptors": "^2.0.3",
+                "semver": "^5.7.0"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "5.7.1",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+                    "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+                    "dev": true
+                }
+            }
+        },
         "node-fetch": {
             "version": "2.6.0",
             "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
@@ -1710,9 +1780,9 @@
             }
         },
         "object-inspect": {
-            "version": "1.8.0",
-            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
-            "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==",
+            "version": "1.7.0",
+            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
+            "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
             "dev": true
         },
         "object-keys": {
@@ -1733,6 +1803,16 @@
                 "object-keys": "^1.0.11"
             }
         },
+        "object.getownpropertydescriptors": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz",
+            "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==",
+            "dev": true,
+            "requires": {
+                "define-properties": "^1.1.3",
+                "es-abstract": "^1.17.0-next.1"
+            }
+        },
         "once": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1742,6 +1822,15 @@
                 "wrappy": "1"
             }
         },
+        "onetime": {
+            "version": "5.1.0",
+            "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
+            "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
+            "dev": true,
+            "requires": {
+                "mimic-fn": "^2.1.0"
+            }
+        },
         "optionator": {
             "version": "0.9.1",
             "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -1794,12 +1883,12 @@
             }
         },
         "p-locate": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-            "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+            "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
             "dev": true,
             "requires": {
-                "p-limit": "^2.2.0"
+                "p-limit": "^2.0.0"
             }
         },
         "p-try": {
@@ -1844,9 +1933,9 @@
             }
         },
         "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==",
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+            "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
             "dev": true
         },
         "path-is-absolute": {
@@ -1891,19 +1980,6 @@
             "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
             "dev": true
         },
-        "promise.allsettled": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz",
-            "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==",
-            "dev": true,
-            "requires": {
-                "array.prototype.map": "^1.0.1",
-                "define-properties": "^1.1.3",
-                "es-abstract": "^1.17.0-next.1",
-                "function-bind": "^1.1.1",
-                "iterate-value": "^1.0.0"
-            }
-        },
         "pseudomap": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -1937,12 +2013,12 @@
             }
         },
         "readdirp": {
-            "version": "3.3.0",
-            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
-            "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz",
+            "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==",
             "dev": true,
             "requires": {
-                "picomatch": "^2.0.7"
+                "picomatch": "^2.0.4"
             }
         },
         "regexpp": {
@@ -1978,6 +2054,16 @@
             "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
             "dev": true
         },
+        "restore-cursor": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+            "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+            "dev": true,
+            "requires": {
+                "onetime": "^5.1.0",
+                "signal-exit": "^3.0.2"
+            }
+        },
         "rimraf": {
             "version": "2.6.3",
             "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
@@ -1988,31 +2074,54 @@
             }
         },
         "rollup": {
-            "version": "2.18.1",
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.18.1.tgz",
-            "integrity": "sha512-w4X77ADA+WTGlapC8Z6yggdJtODw3SBl6R2LSkA7ZW5MtdkgcB7sfaSD1UWyx8diXbMcGIb0eI9gCx/dyqOgNQ==",
+            "version": "2.10.9",
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.10.9.tgz",
+            "integrity": "sha512-dY/EbjiWC17ZCUSyk14hkxATAMAShkMsD43XmZGWjLrgFj15M3Dw2kEkA9ns64BiLFm9PKN6vTQw8neHwK74eg==",
             "dev": true,
             "requires": {
                 "fsevents": "~2.1.2"
             }
         },
+        "run-async": {
+            "version": "2.4.1",
+            "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+            "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+            "dev": true
+        },
+        "rxjs": {
+            "version": "6.5.5",
+            "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
+            "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
+            "dev": true,
+            "requires": {
+                "tslib": "^1.9.0"
+            },
+            "dependencies": {
+                "tslib": {
+                    "version": "1.13.0",
+                    "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
+                    "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
+                    "dev": true
+                }
+            }
+        },
         "safe-buffer": {
             "version": "5.2.0",
             "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
             "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
             "dev": true
         },
+        "safer-buffer": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+            "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+            "dev": true
+        },
         "semver": {
             "version": "6.3.0",
             "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
             "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
         },
-        "serialize-javascript": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz",
-            "integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==",
-            "dev": true
-        },
         "set-blocking": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -2040,6 +2149,12 @@
             "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
             "dev": true
         },
+        "signal-exit": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+            "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
+            "dev": true
+        },
         "slice-ansi": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
@@ -2049,6 +2164,14 @@
                 "ansi-styles": "^3.2.0",
                 "astral-regex": "^1.0.0",
                 "is-fullwidth-code-point": "^2.0.0"
+            },
+            "dependencies": {
+                "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
+                }
             }
         },
         "sourcemap-codec": {
@@ -2064,31 +2187,14 @@
             "dev": true
         },
         "string-width": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-            "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+            "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
             "dev": true,
             "requires": {
-                "emoji-regex": "^7.0.1",
-                "is-fullwidth-code-point": "^2.0.0",
-                "strip-ansi": "^5.1.0"
-            },
-            "dependencies": {
-                "ansi-regex": {
-                    "version": "4.1.0",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
-                    "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
-                    "dev": true
-                },
-                "strip-ansi": {
-                    "version": "5.2.0",
-                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
-                    "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
-                    "dev": true,
-                    "requires": {
-                        "ansi-regex": "^4.1.0"
-                    }
-                }
+                "emoji-regex": "^8.0.0",
+                "is-fullwidth-code-point": "^3.0.0",
+                "strip-ansi": "^6.0.0"
             }
         },
         "string.prototype.trimend": {
@@ -2101,6 +2207,28 @@
                 "es-abstract": "^1.17.5"
             }
         },
+        "string.prototype.trimleft": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
+            "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
+            "dev": true,
+            "requires": {
+                "define-properties": "^1.1.3",
+                "es-abstract": "^1.17.5",
+                "string.prototype.trimstart": "^1.0.0"
+            }
+        },
+        "string.prototype.trimright": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
+            "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
+            "dev": true,
+            "requires": {
+                "define-properties": "^1.1.3",
+                "es-abstract": "^1.17.5",
+                "string.prototype.trimend": "^1.0.0"
+            }
+        },
         "string.prototype.trimstart": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
@@ -2154,6 +2282,46 @@
                 "lodash": "^4.17.14",
                 "slice-ansi": "^2.1.0",
                 "string-width": "^3.0.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+                    "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+                    "dev": true
+                },
+                "emoji-regex": {
+                    "version": "7.0.3",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+                    "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+                    "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": "3.1.0",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+                    "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+                    "dev": true,
+                    "requires": {
+                        "emoji-regex": "^7.0.1",
+                        "is-fullwidth-code-point": "^2.0.0",
+                        "strip-ansi": "^5.1.0"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                    "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+                    "dev": true,
+                    "requires": {
+                        "ansi-regex": "^4.1.0"
+                    }
+                }
             }
         },
         "text-table": {
@@ -2162,6 +2330,21 @@
             "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
             "dev": true
         },
+        "through": {
+            "version": "2.3.8",
+            "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+            "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+            "dev": true
+        },
+        "tmp": {
+            "version": "0.0.33",
+            "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+            "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+            "dev": true,
+            "requires": {
+                "os-tmpdir": "~1.0.2"
+            }
+        },
         "to-regex-range": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2226,9 +2409,9 @@
             }
         },
         "typescript": {
-            "version": "3.9.5",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz",
-            "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==",
+            "version": "3.9.3",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
+            "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
             "dev": true
         },
         "typescript-formatter": {
@@ -2275,9 +2458,9 @@
             "dev": true
         },
         "v8-compile-cache": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
-            "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
+            "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
             "dev": true
         },
         "vsce": {
@@ -2354,9 +2537,9 @@
             "integrity": "sha512-tZFUSbyjUcrh+qQf13ALX4QDdOfDX0cVaBFgy7ktJ0VwS7AW/yRKgGPSxVqqP9OCMNPdqP57O5q47w2pEwfaUg=="
         },
         "vscode-test": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.4.0.tgz",
-            "integrity": "sha512-Jt7HNGvSE0+++Tvtq5wc4hiXLIr2OjDShz/gbAfM/mahQpy4rKBnmOK33D+MR67ATWviQhl+vpmU3p/qwSH/Pg==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.3.0.tgz",
+            "integrity": "sha512-LddukcBiSU2FVTDr3c1D8lwkiOvwlJdDL2hqVbn6gIz+rpTqUCkMZSKYm94Y1v0WXlHSDQBsXyY+tchWQgGVsw==",
             "dev": true,
             "requires": {
                 "http-proxy-agent": "^2.1.0",
@@ -2394,6 +2577,12 @@
                     "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",
@@ -2421,12 +2610,6 @@
             "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
             "dev": true
         },
-        "workerpool": {
-            "version": "6.0.0",
-            "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz",
-            "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==",
-            "dev": true
-        },
         "wrap-ansi": {
             "version": "5.1.0",
             "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
@@ -2444,6 +2627,29 @@
                     "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
                     "dev": true
                 },
+                "emoji-regex": {
+                    "version": "7.0.3",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+                    "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+                    "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": "3.1.0",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+                    "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+                    "dev": true,
+                    "requires": {
+                        "emoji-regex": "^7.0.1",
+                        "is-fullwidth-code-point": "^2.0.0",
+                        "strip-ansi": "^5.1.0"
+                    }
+                },
                 "strip-ansi": {
                     "version": "5.2.0",
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@@ -2500,39 +2706,43 @@
                 "yargs-parser": "^13.1.2"
             },
             "dependencies": {
-                "find-up": {
-                    "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
-                    "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
-                    "dev": true,
-                    "requires": {
-                        "locate-path": "^3.0.0"
-                    }
+                "ansi-regex": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+                    "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+                    "dev": true
                 },
-                "locate-path": {
-                    "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
-                    "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+                "emoji-regex": {
+                    "version": "7.0.3",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+                    "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+                    "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": "3.1.0",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+                    "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                     "dev": true,
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "emoji-regex": "^7.0.1",
+                        "is-fullwidth-code-point": "^2.0.0",
+                        "strip-ansi": "^5.1.0"
                     }
                 },
-                "p-locate": {
-                    "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
-                    "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+                "strip-ansi": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                    "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                     "dev": true,
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "ansi-regex": "^4.1.0"
                     }
-                },
-                "path-exists": {
-                    "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-                    "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
-                    "dev": true
                 }
             }
         },
diff --git a/editors/code/package.json b/editors/code/package.json
index fcb101fc6a..f542a490a9 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -39,24 +39,24 @@
         "vscode-languageclient": "7.0.0-next.1"
     },
     "devDependencies": {
-        "@rollup/plugin-commonjs": "^13.0.0",
-        "@rollup/plugin-node-resolve": "^8.1.0",
-        "@types/glob": "^7.1.2",
+        "@rollup/plugin-commonjs": "^12.0.0",
+        "@rollup/plugin-node-resolve": "^8.0.0",
+        "@types/glob": "^7.1.1",
         "@types/mocha": "^7.0.2",
-        "@types/node": "~12.7.0",
+        "@types/node": "^14.0.5",
         "@types/node-fetch": "^2.5.7",
         "@types/vscode": "^1.44.1",
-        "@typescript-eslint/eslint-plugin": "^3.4.0",
-        "@typescript-eslint/parser": "^3.4.0",
-        "eslint": "^7.3.1",
+        "@typescript-eslint/eslint-plugin": "^3.0.0",
+        "@typescript-eslint/parser": "^3.0.0",
+        "eslint": "^7.0.0",
         "glob": "^7.1.6",
-        "mocha": "^8.0.1",
-        "rollup": "^2.18.1",
+        "mocha": "^7.1.2",
+        "rollup": "^2.10.7",
         "tslib": "^2.0.0",
-        "typescript": "^3.9.5",
+        "typescript": "^3.9.3",
         "typescript-formatter": "^7.2.2",
         "vsce": "^1.75.0",
-        "vscode-test": "^1.4.0"
+        "vscode-test": "^1.3.0"
     },
     "activationEvents": [
         "onLanguage:rust",
diff --git a/editors/code/rollup.config.js b/editors/code/rollup.config.js
index 27abf75ac3..4b4c47f4a3 100644
--- a/editors/code/rollup.config.js
+++ b/editors/code/rollup.config.js
@@ -16,6 +16,7 @@ export default {
     external: [...nodeBuiltins, 'vscode'],
     output: {
         file: './out/src/main.js',
-        format: 'cjs'
+        format: 'cjs',
+        exports: 'named'
     }
 };


Test patch:
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index b7fad97197..aa7c887d67 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -1,6 +1,7 @@
 use std::fs;
 
-use test_utils::{assert_eq_text, project_dir, read_text};
+use expect::{expect_file, ExpectFile};
+use test_utils::project_dir;
 
 use crate::{mock_analysis::single_file, FileRange, TextRange};
 
@@ -91,7 +92,7 @@ impl<T> Option<T> {
 }
 "#
         .trim(),
-        "crates/ra_ide/src/snapshots/highlighting.html",
+        expect_file!["crates/ra_ide/test_data/highlighting.html"],
         false,
     );
 }
@@ -114,7 +115,7 @@ fn bar() {
 }
 "#
         .trim(),
-        "crates/ra_ide/src/snapshots/rainbow_highlighting.html",
+        expect_file!["crates/ra_ide/test_data/rainbow_highlighting.html"],
         true,
     );
 }
@@ -167,7 +168,7 @@ fn main() {
     );
 }"##
         .trim(),
-        "crates/ra_ide/src/snapshots/highlight_injection.html",
+        expect_file!["crates/ra_ide/test_data/highlight_injection.html"],
         false,
     );
 }
@@ -250,7 +251,7 @@ fn main() {
     println!("{ничоси}", ничоси = 92);
 }"#
         .trim(),
-        "crates/ra_ide/src/snapshots/highlight_strings.html",
+        expect_file!["crates/ra_ide/test_data/highlight_strings.html"],
         false,
     );
 }
@@ -278,7 +279,7 @@ fn main() {
 }
 "#
         .trim(),
-        "crates/ra_ide/src/snapshots/highlight_unsafe.html",
+        expect_file!["crates/ra_ide/test_data/highlight_unsafe.html"],
         false,
     );
 }
@@ -354,7 +355,7 @@ macro_rules! noop {
 }
 "#
         .trim(),
-        "crates/ra_ide/src/snapshots/highlight_doctest.html",
+        expect_file!["crates/ra_ide/test_data/highlight_doctest.html"],
         false,
     );
 }
@@ -362,11 +363,8 @@ macro_rules! noop {
 /// Highlights the code given by the `ra_fixture` argument, renders the
 /// result as HTML, and compares it with the HTML file given as `snapshot`.
 /// Note that the `snapshot` file is overwritten by the rendered HTML.
-fn check_highlighting(ra_fixture: &str, snapshot: &str, rainbow: bool) {
+fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
     let (analysis, file_id) = single_file(ra_fixture);
-    let dst_file = project_dir().join(snapshot);
     let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
-    let expected_html = &read_text(&dst_file);
-    fs::write(dst_file, &actual_html).unwrap();
-    assert_eq_text!(expected_html, actual_html);
+    expect.assert_eq(actual_html)
 }
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index c692c97e2e..57b2f50b25 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -1,5 +1,150 @@
-use crate::{MatchFinder, SsrRule};
-use ra_db::{FileId, SourceDatabaseExt};
+use crate::matching::MatchFailureReason;
+use crate::{matching, Match, MatchFinder, SsrMatches, SsrPattern, SsrRule};
+use matching::record_match_fails_reasons_scope;
+use ra_db::{FileId, FileRange, SourceDatabaseExt};
+use ra_syntax::ast::AstNode;
+use ra_syntax::{ast, SyntaxKind, SyntaxNode, TextRange};
+
+struct MatchDebugInfo {
+    node: SyntaxNode,
+    /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item,
+    /// etc. Will be absent if the pattern can't be parsed as that kind.
+    pattern: Result<SyntaxNode, MatchFailureReason>,
+    matched: Result<Match, MatchFailureReason>,
+}
+
+impl SsrPattern {
+    pub(crate) fn tree_for_kind_with_reason(
+        &self,
+        kind: SyntaxKind,
+    ) -> Result<&SyntaxNode, MatchFailureReason> {
+        record_match_fails_reasons_scope(true, || self.tree_for_kind(kind))
+            .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() })
+    }
+}
+
+impl std::fmt::Debug for MatchDebugInfo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "========= PATTERN ==========\n")?;
+        match &self.pattern {
+            Ok(pattern) => {
+                write!(f, "{:#?}", pattern)?;
+            }
+            Err(err) => {
+                write!(f, "{}", err.reason)?;
+            }
+        }
+        write!(
+            f,
+            "\n============ AST ===========\n\
+            {:#?}\n============================",
+            self.node
+        )?;
+        match &self.matched {
+            Ok(_) => write!(f, "Node matched")?,
+            Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?,
+        }
+        Ok(())
+    }
+}
+
+impl SsrMatches {
+    /// Returns `self` with any nested matches removed and made into top-level matches.
+    pub(crate) fn flattened(self) -> SsrMatches {
+        let mut out = SsrMatches::default();
+        self.flatten_into(&mut out);
+        out
+    }
+
+    fn flatten_into(self, out: &mut SsrMatches) {
+        for mut m in self.matches {
+            for p in m.placeholder_values.values_mut() {
+                std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out);
+            }
+            out.matches.push(m);
+        }
+    }
+}
+
+impl Match {
+    pub(crate) fn matched_text(&self) -> String {
+        self.matched_node.text().to_string()
+    }
+}
+
+impl<'db> MatchFinder<'db> {
+    /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
+    /// intend to do replacement, use `add_rule` instead.
+    fn add_search_pattern(&mut self, pattern: SsrPattern) {
+        self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() })
+    }
+
+    /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
+    /// them, while recording reasons why they don't match. This API is useful for command
+    /// line-based debugging where providing a range is difficult.
+    fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> {
+        let file = self.sema.parse(file_id);
+        let mut res = Vec::new();
+        let file_text = self.sema.db.file_text(file_id);
+        let mut remaining_text = file_text.as_str();
+        let mut base = 0;
+        let len = snippet.len() as u32;
+        while let Some(offset) = remaining_text.find(snippet) {
+            let start = base + offset as u32;
+            let end = start + len;
+            self.output_debug_for_nodes_at_range(
+                file.syntax(),
+                TextRange::new(start.into(), end.into()),
+                &None,
+                &mut res,
+            );
+            remaining_text = &remaining_text[offset + snippet.len()..];
+            base = end;
+        }
+        res
+    }
+
+    fn output_debug_for_nodes_at_range(
+        &self,
+        node: &SyntaxNode,
+        range: TextRange,
+        restrict_range: &Option<FileRange>,
+        out: &mut Vec<MatchDebugInfo>,
+    ) {
+        for node in node.children() {
+            if !node.text_range().contains_range(range) {
+                continue;
+            }
+            if node.text_range() == range {
+                for rule in &self.rules {
+                    let pattern =
+                        rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone());
+                    out.push(MatchDebugInfo {
+                        matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
+                            .map_err(|e| MatchFailureReason {
+                                reason: e.reason.unwrap_or_else(|| {
+                                    "Match failed, but no reason was given".to_owned()
+                                }),
+                            }),
+                        pattern,
+                        node: node.clone(),
+                    });
+                }
+            } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) {
+                if let Some(expanded) = self.sema.expand(&macro_call) {
+                    if let Some(tt) = macro_call.token_tree() {
+                        self.output_debug_for_nodes_at_range(
+                            &expanded,
+                            range,
+                            &Some(self.sema.original_range(tt.syntax())),
+                            out,
+                        );
+                    }
+                }
+            }
+        }
+    }
+}
 
 fn parse_error_text(query: &str) -> String {
     format!("{}", query.parse::<SsrRule>().unwrap_err())
@@ -115,19 +260,6 @@ fn assert_no_match(pattern: &str, code: &str) {
     assert_matches(pattern, code, &[]);
 }
 
-fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) {
-    let (db, file_id) = single_file(code);
-    let mut match_finder = MatchFinder::new(&db);
-    match_finder.add_search_pattern(pattern.parse().unwrap());
-    let mut reasons = Vec::new();
-    for d in match_finder.debug_where_text_equal(file_id, snippet) {
-        if let Some(reason) = d.match_failure_reason() {
-            reasons.push(reason.to_owned());
-        }
-    }
-    assert_eq!(reasons, vec![expected_reason]);
-}
-
 #[test]
 fn ssr_function_to_method() {
     assert_ssr_transform(
@@ -491,30 +623,3 @@ fn preserves_whitespace_within_macro_expansion() {
             fn f() {macro1!(4 - 3 - 1   *   2}"#,
     )
 }
-
-#[test]
-fn match_failure_reasons() {
-    let code = r#"
-        macro_rules! foo {
-            ($a:expr) => {
-                1 + $a + 2
-            };
-        }
-        fn f1() {
-            bar(1, 2);
-            foo!(5 + 43.to_string() + 5);
-        }
-        "#;
-    assert_match_failure_reason(
-        "bar($a, 3)",
-        code,
-        "bar(1, 2)",
-        r#"Pattern wanted token '3' (INT_NUMBER), but code had token '2' (INT_NUMBER)"#,
-    );
-    assert_match_failure_reason(
-        "42.to_string()",
-        code,
-        "43.to_string()",
-        r#"Pattern wanted token '42' (INT_NUMBER), but code had token '43' (INT_NUMBER)"#,
-    );
-}