Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mitsuhiko/insta
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.4.0
Choose a base ref
...
head repository: mitsuhiko/insta
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 1.5.0
Choose a head ref

Commits on Dec 31, 2020

  1. Removed unused dependency

    mitsuhiko committed Dec 31, 2020
    Copy the full SHA
    46f411c View commit details
  2. Copy the full SHA
    2f1ea9a View commit details
  3. Copy the full SHA
    d59c1a5 View commit details
  4. Copy the full SHA
    2390fa1 View commit details
  5. Copy the full SHA
    379dc1f View commit details
  6. a

    mitsuhiko committed Dec 31, 2020
    Copy the full SHA
    43fb3bf View commit details
  7. Copy the full SHA
    39210f9 View commit details
  8. vscode-insta 1.0.1

    mitsuhiko committed Dec 31, 2020
    Copy the full SHA
    eab9849 View commit details
  9. Copy the full SHA
    e3e34fe View commit details
  10. Fix vscode line comments

    mitsuhiko committed Dec 31, 2020
    Copy the full SHA
    24c447f View commit details

Commits on Jan 1, 2021

  1. Copy the full SHA
    799acf2 View commit details
  2. Copy the full SHA
    d2fa1fe View commit details
  3. Copy the full SHA
    6c6c949 View commit details
  4. Copy the full SHA
    1700267 View commit details
  5. Update readme for vscode

    mitsuhiko committed Jan 1, 2021
    Copy the full SHA
    c83bfd9 View commit details
  6. Copy the full SHA
    d591392 View commit details
  7. Bump vscode-insta to 1.0.3

    mitsuhiko committed Jan 1, 2021
    Copy the full SHA
    27a5dea View commit details
  8. Copy the full SHA
    046f249 View commit details
  9. vscode insta 1.0.4

    mitsuhiko committed Jan 1, 2021
    Copy the full SHA
    5de3cd4 View commit details

Commits on Jan 2, 2021

  1. Copy the full SHA
    ec6c8e3 View commit details
  2. Copy the full SHA
    fd5d186 View commit details
  3. vscode 1.0.5

    mitsuhiko committed Jan 2, 2021
    Copy the full SHA
    23910b4 View commit details
  4. Copy the full SHA
    8cd8125 View commit details
  5. Copy the full SHA
    42e6c5d View commit details

Commits on Jan 3, 2021

  1. Significant VS Code Improvements (#150)

    This adds a sidebar with pending snapshots and ability to accept/reject inline snapshots.
    
    This also requires updates to cargo-insta.
    mitsuhiko authored Jan 3, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3279c37 View commit details
  2. Update cargo-insta readme

    mitsuhiko committed Jan 3, 2021
    Copy the full SHA
    7652f0a View commit details
  3. Copy the full SHA
    5c03b86 View commit details
  4. Update changelog for 1.5.0

    mitsuhiko committed Jan 3, 2021
    Copy the full SHA
    59c58c1 View commit details
  5. 1.5.0

    mitsuhiko committed Jan 3, 2021
    Copy the full SHA
    f7b219f View commit details
10 changes: 5 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"editor.formatOnSave": true,
"rust.clippy_preference": "on",
"rust.cfg_test": true,
"rust.all_features": true,
"rust-analyzer.cargo.allFeatures": true
}
"rust-analyzer.checkOnSave.allFeatures": true,
"rust-analyzer.cargo.allFeatures": true,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.5.0

* Add `pending-snapshots` parameter to `cargo-insta`.
* `cargo-insta` now honors ignore files. This can be overridden
with `--no-ignore`.
* `cargo-insta` now supports the vscode extension.

## 1.4.0

* Add `--delete-unreferenced-snapshots` parameter to `cargo-insta`.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "insta"
version = "1.4.0"
version = "1.5.0"
license = "Apache-2.0"
authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
description = "A snapshot testing library for Rust"
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -36,6 +36,14 @@ Insta also supports inline snapshots which are stored right in your source file
instead of separate files. This is accomplished by the companion
[cargo-insta](https://crates.io/crates/cargo-insta) tool.

## Editor Support

For looking at `.snap` files there is a [vscode extension](https://github.com/mitsuhiko/insta/tree/master/vscode-insta)
which can syntax highlight snapshot files, review snapshots and more. It can be installed from the
marketplace: [view on marketplace](https://marketplace.visualstudio.com/items?itemName=mitsuhiko.insta).

![jump to definition](https://raw.githubusercontent.com/mitsuhiko/insta/master/vscode-insta/images/jump-to-definition.gif)

## License and Links

- [Issue Tracker](https://github.com/mitsuhiko/insta/issues)
12 changes: 2 additions & 10 deletions cargo-insta/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions cargo-insta/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-insta"
version = "1.4.0"
version = "1.5.0"
license = "Apache-2.0"
authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
description = "A review tool for the insta snapshot testing library for Rust"
@@ -11,15 +11,13 @@ edition = "2018"
readme = "README.md"

[dependencies]
insta = { version = "1.4.0", path = "..", features = ["redactions"] }
insta = { version = "1.5.0", path = "..", features = ["redactions"] }
console = "0.14.0"
clap = { version = "2.33.3", default-features = false }
difference = "2.0.0"
structopt = "0.3.20"
serde = { version = "1.0.117", features = ["derive"] }
serde_json = "1.0.59"
glob = "0.3.0"
walkdir = "2.3.1"
proc-macro2 = { version = "1.0.24", features = ["span-locations"] }
syn = { version = "1.0.50", features = ["full", "visit", "extra-traits"] }
ignore = "0.4.17"
40 changes: 39 additions & 1 deletion cargo-insta/README.md
Original file line number Diff line number Diff line change
@@ -11,7 +11,45 @@ $ cargo install cargo-insta
$ cargo insta --help
```

For more information see [the insta crate documentation](https://docs.rs/insta).
## Commands

`cargo-insta` provides a few different commands to interact with insta snapshots.

### `review`

This is the main command you are likely to use. It starts the interactive
review process. It takes similar arguments to the other commands and typically
does not require any. It will auto discover snapshots in the current workspace.
If you want to change the location you can use `--workspace-root` which
explicitly sets the path to the workspace or `--manifest-path` to set the
path to a specific `Cargo.toml`.

Once the review process is starting you can accept changes with `a`, reject
changes with `j` and skip with `s`.

### `test`

This is a special command that works exactly like `cargo test` but it will
force all snapshots assertions to pass. That way you can collect all snapshot
changes in one go and review them. You can also combine this with `--accept`
to just accept all changes, `--accept-unseen` to accept all previously unseen
snapshots or `--review` to start a review process afterwards. Additionally
this commands supports `--delete-unreferenced-snapshots` to automatically
delete all unreferenced snapshots after the test run.

### `reject`

Like `review` but automatically rejects all snapshots.

### `accept`

Like `accept` but automatically accepts all snapshots.

### `pending-snapshots`

A utility command to emit information about pending snapshots. This is useful
when you want to script `cargo-insta`. For instance this is now the visual
studio code extension interfaces with insta.

## License and Links

1 change: 1 addition & 0 deletions cargo-insta/integration-tests/src/main.rs
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ fn main() {
Command::new("../target/debug/cargo-insta")
.arg("test")
.arg("--accept")
.arg("--no-ignore")
.status()
.unwrap();

29 changes: 24 additions & 5 deletions cargo-insta/src/cargo.rs
Original file line number Diff line number Diff line change
@@ -5,9 +5,10 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::process;

use ignore::overrides::OverrideBuilder;
use ignore::{DirEntry, WalkBuilder};
use insta::{PendingInlineSnapshot, Snapshot};
use serde::Deserialize;
use walkdir::{DirEntry, WalkDir};

use crate::inline::FilePatcher;
use crate::utils::err_msg;
@@ -163,6 +164,10 @@ impl SnapshotContainer {
})
}

pub fn target_file(&self) -> &Path {
&self.target_path
}

pub fn snapshot_file(&self) -> Option<&Path> {
match self.kind {
SnapshotContainerKind::External => Some(&self.target_path),
@@ -239,10 +244,23 @@ fn is_hidden(entry: &DirEntry) -> bool {
pub fn find_snapshots<'a>(
root: PathBuf,
extensions: &'a [&'a str],
no_ignore: bool,
) -> impl Iterator<Item = Result<SnapshotContainer, Box<dyn Error>>> + 'a {
WalkDir::new(root.clone())
.into_iter()
.filter_entry(|e| e.file_type().is_file() || !is_hidden(e))
WalkBuilder::new(root.clone())
.hidden(false)
.standard_filters(!no_ignore)
.overrides(
// make sure pending snaps are never ignored
OverrideBuilder::new(&root)
.add("**/.*.pending-snap")
.unwrap()
.add("**/*.snap.new")
.unwrap()
.build()
.unwrap(),
)
.filter_entry(|e| e.file_type().map_or(false, |x| x.is_file()) || !is_hidden(e))
.build()
.filter_map(|e| e.ok())
.filter_map(move |e| {
let fname = e.file_name().to_string_lossy();
@@ -287,6 +305,7 @@ impl Package {
pub fn iter_snapshot_containers<'a>(
&self,
extensions: &'a [&'a str],
no_ignore: bool,
) -> impl Iterator<Item = Result<SnapshotContainer, Box<dyn Error>>> + 'a {
let mut roots = Vec::new();

@@ -328,7 +347,7 @@ impl Package {

reduced_roots
.into_iter()
.flat_map(move |root| find_snapshots(root, extensions))
.flat_map(move |root| find_snapshots(root, extensions, no_ignore))
}
}

110 changes: 105 additions & 5 deletions cargo-insta/src/cli.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,9 @@ use std::process;
use std::{env, fs};

use console::{set_colors_enabled, style, Key, Term};
use ignore::{Walk, WalkBuilder};
use insta::{print_snapshot_diff, Snapshot};
use serde::Serialize;
use structopt::clap::AppSettings;
use structopt::StructOpt;
use uuid::Uuid;
@@ -52,6 +54,9 @@ pub enum Command {
/// Run tests and then reviews
#[structopt(name = "test")]
Test(TestCommand),
/// Print a summary of all pending snapshots.
#[structopt(name = "pending-snapshots")]
PendingSnapshots(PendingSnapshotsCommand),
}

#[derive(StructOpt, Debug, Clone)]
@@ -69,13 +74,19 @@ pub struct TargetArgs {
/// Work on all packages in the workspace
#[structopt(long)]
pub all: bool,
/// Also walk into ignored paths.
#[structopt(long)]
pub no_ignore: bool,
}

#[derive(StructOpt, Debug)]
#[structopt(rename_all = "kebab-case")]
pub struct ProcessCommand {
#[structopt(flatten)]
pub target_args: TargetArgs,
/// Limits the operation to one or more snapshots.
#[structopt(long = "snapshot")]
pub snapshot_filter: Option<Vec<String>>,
/// Do not print to stdout.
#[structopt(short = "q", long)]
pub quiet: bool,
@@ -130,6 +141,19 @@ pub struct TestCommand {
pub delete_unreferenced_snapshots: bool,
}

#[derive(StructOpt, Debug)]
#[structopt(rename_all = "kebab-case")]
pub struct PendingSnapshotsCommand {
#[structopt(flatten)]
pub target_args: TargetArgs,
/// Changes the output from human readable to JSON.
#[structopt(long)]
pub as_json: bool,
/// Emits full instead of relative paths.
#[structopt(long)]
pub full_paths: bool,
}

#[allow(clippy::too_many_arguments)]
fn query_snapshot(
workspace_root: &Path,
@@ -195,10 +219,27 @@ fn handle_color(color: &str) -> Result<(), Box<dyn Error>> {
Ok(())
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case", tag = "type")]
enum SnapshotKey<'a> {
NamedSnapshot {
path: &'a Path,
},
InlineSnapshot {
path: &'a Path,
line: u32,
name: Option<&'a str>,
old_snapshot: Option<&'a str>,
new_snapshot: &'a str,
expression: Option<&'a str>,
},
}

struct LocationInfo<'a> {
workspace_root: PathBuf,
packages: Option<Vec<Package>>,
exts: Vec<&'a str>,
no_ignore: bool,
}

fn handle_target_args(target_args: &TargetArgs) -> Result<LocationInfo<'_>, Box<dyn Error>> {
@@ -237,6 +278,7 @@ fn handle_target_args(target_args: &TargetArgs) -> Result<LocationInfo<'_>, Box<
workspace_root: workspace_root.to_owned(),
packages: None,
exts,
no_ignore: target_args.no_ignore,
})
} else {
let metadata = get_package_metadata(manifest_path.as_ref().map(|x| x.as_path()))?;
@@ -245,6 +287,7 @@ fn handle_target_args(target_args: &TargetArgs) -> Result<LocationInfo<'_>, Box<
workspace_root: metadata.workspace_root().to_path_buf(),
packages: Some(packages),
exts,
no_ignore: target_args.no_ignore,
})
}
}
@@ -256,13 +299,16 @@ fn load_snapshot_containers<'a>(
match loc.packages {
Some(ref packages) => {
for package in packages.iter() {
for snapshot_container in package.iter_snapshot_containers(&loc.exts) {
for snapshot_container in package.iter_snapshot_containers(&loc.exts, loc.no_ignore)
{
snapshot_containers.push((snapshot_container?, Some(package)));
}
}
}
None => {
for snapshot_container in find_snapshots(loc.workspace_root.clone(), &loc.exts) {
for snapshot_container in
find_snapshots(loc.workspace_root.clone(), &loc.exts, loc.no_ignore)
{
snapshot_containers.push((snapshot_container?, None));
}
}
@@ -291,8 +337,22 @@ fn process_snapshots(cmd: ProcessCommand, op: Option<Operation>) -> Result<(), B
let mut num = 0;

for (snapshot_container, package) in snapshot_containers.iter_mut() {
let target_file = snapshot_container.target_file().to_path_buf();
let snapshot_file = snapshot_container.snapshot_file().map(|x| x.to_path_buf());
for snapshot_ref in snapshot_container.iter_snapshots() {
// if a filter is provided, check if the snapshot reference is included
if let Some(ref filter) = cmd.snapshot_filter {
let key = if let Some(line) = snapshot_ref.line {
format!("{}:{}", target_file.display(), line)
} else {
format!("{}", target_file.display())
};
if !filter.contains(&key) {
skipped.push(snapshot_ref.summary());
continue;
}
}

num += 1;
let op = match op {
Some(op) => op,
@@ -354,7 +414,7 @@ fn process_snapshots(cmd: ProcessCommand, op: Option<Operation>) -> Result<(), B
Ok(())
}

fn make_walker(loc: &LocationInfo) -> ignore::Walk {
fn make_deletion_walker(loc: &LocationInfo) -> Walk {
let roots: HashSet<_> = match loc.packages {
Some(ref packages) => packages
.iter()
@@ -367,7 +427,7 @@ fn make_walker(loc: &LocationInfo) -> ignore::Walk {
}
};

ignore::WalkBuilder::new(&loc.workspace_root)
WalkBuilder::new(&loc.workspace_root)
.filter_entry(move |entry| {
// we only filter down for directories
if !entry.file_type().map_or(false, |x| x.is_dir()) {
@@ -493,6 +553,7 @@ fn test_run(mut cmd: TestCommand, color: &str) -> Result<(), Box<dyn Error>> {
process_snapshots(
ProcessCommand {
target_args: cmd.target_args.clone(),
snapshot_filter: None,
quiet: true,
},
Some(Operation::Reject),
@@ -527,7 +588,7 @@ fn test_run(mut cmd: TestCommand, color: &str) -> Result<(), Box<dyn Error>> {

if let Ok(loc) = handle_target_args(&cmd.target_args) {
let mut deleted_any = false;
for entry in make_walker(&loc) {
for entry in make_deletion_walker(&loc) {
let rel_path = match entry {
Ok(ref entry) => entry.path(),
_ => continue,
@@ -555,12 +616,15 @@ fn test_run(mut cmd: TestCommand, color: &str) -> Result<(), Box<dyn Error>> {
eprintln!("{}: no unreferenced snapshots found", style("info").bold());
}
}

fs::remove_file(&path).ok();
}

if cmd.review || cmd.accept {
process_snapshots(
ProcessCommand {
target_args: cmd.target_args.clone(),
snapshot_filter: None,
quiet: false,
},
if cmd.accept {
@@ -589,6 +653,41 @@ fn test_run(mut cmd: TestCommand, color: &str) -> Result<(), Box<dyn Error>> {
Ok(())
}

fn pending_snapshots_cmd(cmd: PendingSnapshotsCommand) -> Result<(), Box<dyn Error>> {
let loc = handle_target_args(&cmd.target_args)?;
let mut snapshot_containers = load_snapshot_containers(&loc)?;

for (snapshot_container, _package) in snapshot_containers.iter_mut() {
let target_file = snapshot_container.target_file().to_path_buf();
let is_inline = snapshot_container.snapshot_file().is_none();
for snapshot_ref in snapshot_container.iter_snapshots() {
if cmd.as_json {
let info = if is_inline {
SnapshotKey::InlineSnapshot {
path: &target_file,
line: snapshot_ref.line.unwrap(),
name: snapshot_ref.new.snapshot_name(),
old_snapshot: snapshot_ref.old.as_ref().map(|x| x.contents_str()),
new_snapshot: snapshot_ref.new.contents_str(),
expression: snapshot_ref.new.metadata().expression(),
}
} else {
SnapshotKey::NamedSnapshot { path: &target_file }
};
println!("{}", serde_json::to_string(&info).unwrap());
} else {
if is_inline {
println!("{}:{}", target_file.display(), snapshot_ref.line.unwrap());
} else {
println!("{}", target_file.display());
}
}
}
}

Ok(())
}

pub fn run() -> Result<(), Box<dyn Error>> {
// chop off cargo
let mut args: Vec<_> = env::args_os().collect();
@@ -605,5 +704,6 @@ pub fn run() -> Result<(), Box<dyn Error>> {
Command::Accept(cmd) => process_snapshots(cmd, Some(Operation::Accept)),
Command::Reject(cmd) => process_snapshots(cmd, Some(Operation::Reject)),
Command::Test(cmd) => test_run(cmd, color),
Command::PendingSnapshots(cmd) => pending_snapshots_cmd(cmd),
}
}
3 changes: 1 addition & 2 deletions tests/test_basic.rs
Original file line number Diff line number Diff line change
@@ -42,8 +42,7 @@ fn test_unnamed_json_vector() {
mod nested {
#[test]
fn test_nested_module() {
use insta::assert_snapshot;
assert_snapshot!("aoeu");
insta::assert_snapshot!("aoeu");
}
}

25 changes: 11 additions & 14 deletions tests/test_redaction.rs
Original file line number Diff line number Diff line change
@@ -8,19 +8,19 @@ use serde::Serialize;

#[test]
fn test_selector_parser() {
macro_rules! assert_selector {
macro_rules! assert_selector_snapshot {
($short:expr, $sel:expr) => {
assert_debug_snapshot!($short, Selector::parse($sel).unwrap());
};
}

assert_selector!("foo_bar", ".foo.bar");
assert_selector!("foo_bar_alt", ".foo[\"bar\"]");
assert_selector!("foo_bar_full_range", ".foo.bar[]");
assert_selector!("foo_bar_range_to", ".foo.bar[:10]");
assert_selector!("foo_bar_range_from", ".foo.bar[10:]");
assert_selector!("foo_bar_range", ".foo.bar[10:20]");
assert_selector!("foo_bar_deep", ".foo.bar.**");
assert_selector_snapshot!("foo_bar", ".foo.bar");
assert_selector_snapshot!("foo_bar_alt", ".foo[\"bar\"]");
assert_selector_snapshot!("foo_bar_full_range", ".foo.bar[]");
assert_selector_snapshot!("foo_bar_range_to", ".foo.bar[:10]");
assert_selector_snapshot!("foo_bar_range_from", ".foo.bar[10:]");
assert_selector_snapshot!("foo_bar_range", ".foo.bar[10:20]");
assert_selector_snapshot!("foo_bar_deep", ".foo.bar.**");
}

#[derive(Serialize)]
@@ -77,8 +77,7 @@ fn test_with_random_value_and_trailing_comma() {
#[cfg(feature = "csv")]
#[test]
fn test_with_random_value_csv() {
use insta::assert_csv_snapshot;
assert_csv_snapshot!("user_csv", &User {
insta::assert_csv_snapshot!("user_csv", &User {
id: 44,
username: "julius_csv".to_string(),
email: Email("julius@example.com".to_string()),
@@ -91,8 +90,7 @@ fn test_with_random_value_csv() {
#[cfg(feature = "ron")]
#[test]
fn test_with_random_value_ron() {
use insta::assert_ron_snapshot;
assert_ron_snapshot!("user_ron", &User {
insta::assert_ron_snapshot!("user_ron", &User {
id: 53,
username: "john_ron".to_string(),
email: Email("john@example.com".to_string()),
@@ -105,8 +103,7 @@ fn test_with_random_value_ron() {
#[cfg(feature = "toml")]
#[test]
fn test_with_random_value_toml() {
use insta::assert_toml_snapshot;
assert_toml_snapshot!("user_toml", &User {
insta::assert_toml_snapshot!("user_toml", &User {
id: 53,
username: "john_ron".to_string(),
email: Email("john@example.com".to_string()),
2 changes: 2 additions & 0 deletions vscode-insta/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
out/
17 changes: 17 additions & 0 deletions vscode-insta/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// A launch configuration that launches the extension inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
}
]
}
9 changes: 9 additions & 0 deletions vscode-insta/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"git.ignoreLimitWarning": true,
"[json]": {
"editor.tabSize": 2
},
"[typescript]": {
"editor.tabSize": 2
}
}
4 changes: 4 additions & 0 deletions vscode-insta/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.vscode/**
.vscode-test/**
.gitignore
vsc-extension-quickstart.md
33 changes: 33 additions & 0 deletions vscode-insta/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Change Log

Contains all notable changes to the vscode plugin for insta.

## 1.0.6

* Enable syntax highlighting for `.snap.new` files
* Added support for accepting/rejecting/diffing snapshots.
* Added sidebar view for pending snapshots.

## 1.0.5

* Require commas after named snapshot assertions

## 1.0.4

* Added fuzzy jump to definition

## 1.0.3

* Bugfix release

## 1.0.2

* Added jump to definition

## 1.0.1

* Improvements to the syntax grammer

## 1.0.0

* Initial release
42 changes: 42 additions & 0 deletions vscode-insta/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# insta snapshots

This extension lets you better work with Rust's [insta snapshot](https://crates.io/crates/insta)
files. It adds syntax highlighting and other improvements.

## Features

The following features are currently available.

### Jump to Definition

After loading the extension you can "jump to definition" by hitting "F12" on
a snapshot assertion macro:

![jump to definition](https://raw.githubusercontent.com/mitsuhiko/insta/master/vscode-insta/images/jump-to-definition.gif)

### Pending Snapshots View

All pending snapshots are show in the sidebar if insta is used in your project:

![sidebar](https://raw.githubusercontent.com/mitsuhiko/insta/master/vscode-insta/images/view.png)

Clicking on a snapshot opens a diff view where you can accept and reject the
snapshot. This also works for inline snapshots. Additionally you can instruct
cargo insta to accept or reject all snapshots in one go.

### Accepting / Rejecting

Snapshots can be diffed, accepted and rejected right from within vscode. This is available
through the following commands:

* "Compare Snapshots": opens a comparison view, also from the tree view.
* "Switch Between Snapshots": switches between current and new snapshot.
* "Accept New Snapshot": moves the new snapshot over the old snapshot.
* "Reject New Snapshot": rejects (deletes) the new snapshot.

### Syntax Highlighting

For all insta `.snap` snapshots from insta syntax highlighting is provided as if they are YAML files. For RON snapshots some small
tweaks are applied to make them more pleasing to the eyes:

![example screenshot](https://raw.githubusercontent.com/mitsuhiko/insta/master/vscode-insta/images/screenshot.png)
Binary file added vscode-insta/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added vscode-insta/images/jump-to-definition.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added vscode-insta/images/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added vscode-insta/images/view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions vscode-insta/language-configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"comments": {
"lineComment": "#"
},
// symbols used as brackets
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
// symbols that are auto closed when typing
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
// symbols that can be used to surround a selection
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
]
}
204 changes: 204 additions & 0 deletions vscode-insta/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
{
"name": "insta",
"displayName": "insta snapshots",
"description": "Syntax support for insta snapshots",
"version": "1.0.5",
"publisher": "mitsuhiko",
"license": "Apache-2.0",
"author": {
"name": "mitsuhiko"
},
"engines": {
"vscode": "^1.52.0"
},
"categories": [
"Programming Languages"
],
"repository": {
"type": "git",
"url": "https://github.com/mitsuhiko/insta.git"
},
"homepage": "https://github.com/mitsuhiko/insta",
"icon": "images/icon.png",
"activationEvents": [
"onLanguage:rust",
"onLanguage:insta-snapshots",
"onView:pendingInstaSnapshots",
"onCommand:mitsuhiko.insta.open-snapshot-diff",
"onCommand:mitsuhiko.insta.accept-snapshot",
"onCommand:mitsuhiko.insta.reject-snapshot",
"onCommand:mitsuhiko.insta.switch-snapshot-view",
"onCommand:mitsuhiko.insta.accept-all-snapshots",
"onCommand:mitsuhiko.insta.reject-all-snapshots"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "mitsuhiko.insta.open-snapshot-diff",
"title": "Compare Snapshots",
"category": "Insta",
"icon": "$(diff)"
},
{
"command": "mitsuhiko.insta.accept-snapshot",
"title": "Accept New Snapshot",
"category": "Insta",
"icon": "$(check)"
},
{
"command": "mitsuhiko.insta.reject-snapshot",
"title": "Reject New Snapshot",
"category": "Insta",
"icon": "$(discard)"
},
{
"command": "mitsuhiko.insta.switch-snapshot-view",
"title": "Switch Between Snapshots",
"category": "Insta",
"icon": "$(symbol-boolean)"
},
{
"command": "mitsuhiko.insta.refresh-pending-snapshots",
"title": "Refresh Pending Snapshots",
"category": "Insta",
"icon": "$(refresh)"
},
{
"command": "mitsuhiko.insta.accept-all-snapshots",
"title": "Accept All Snapshots",
"category": "Insta",
"icon": "$(check-all)"
},
{
"command": "mitsuhiko.insta.reject-all-snapshots",
"title": "Reject All Snapshots",
"category": "Insta",
"icon": "$(discard)"
}
],
"keybindings": [
{
"command": "mitsuhiko.insta.accept-snapshot",
"when": "isInDiffEditor || editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/",
"key": "ctrl+i ctrl+a",
"mac": "cmd+i cmd+a"
},
{
"command": "mitsuhiko.insta.reject-snapshot",
"when": "isInDiffEditor || editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/",
"key": "ctrl+i ctrl+r",
"mac": "cmd+i cmd+r"
},
{
"command": "mitsuhiko.insta.switch-snapshot-view",
"when": "!isInDiffEditor && editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/",
"key": "ctrl+i ctrl+o",
"mac": "cmd+i cmd+o"
},
{
"command": "mitsuhiko.insta.open-snapshot-diff",
"when": "!isInDiffEditor && editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/",
"key": "ctrl+i ctrl+d",
"mac": "cmd+i cmd+d"
}
],
"menus": {
"editor/title": [
{
"command": "mitsuhiko.insta.accept-snapshot",
"group": "navigation@-1.4",
"when": "isInDiffEditor || editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/"
},
{
"command": "mitsuhiko.insta.reject-snapshot",
"group": "navigation@-1.3",
"when": "isInDiffEditor || editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/"
},
{
"command": "mitsuhiko.insta.switch-snapshot-view",
"group": "navigation@-1.2",
"when": "!isInDiffEditor && editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/"
},
{
"command": "mitsuhiko.insta.open-snapshot-diff",
"group": "navigation@-1.1",
"when": "!isInDiffEditor && editorIsOpen && resourceFilename =~ /\\.snap(\\.new)?$/"
}
],
"explorer/context": [
{
"command": "mitsuhiko.insta.open-snapshot-diff",
"group": "3_compare",
"when": "resourceFilename =~ /\\.snap\\.new$/"
}
],
"view/title": [
{
"command": "mitsuhiko.insta.accept-all-snapshots",
"when": "view == pendingInstaSnapshots",
"group": "navigation"
},
{
"command": "mitsuhiko.insta.reject-all-snapshots",
"when": "view == pendingInstaSnapshots",
"group": "navigation"
},
{
"command": "mitsuhiko.insta.refresh-pending-snapshots",
"when": "view == pendingInstaSnapshots",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "mitsuhiko.insta.open-snapshot-diff",
"when": "view == pendingInstaSnapshots",
"group": "inline"
}
]
},
"views": {
"explorer": [
{
"id": "pendingInstaSnapshots",
"name": "Pending Insta Snapshots",
"when": "inInstaSnapshotsProject",
"contextualTitle": "Pending Insta Snapshots"
}
]
},
"languages": [
{
"id": "insta-snapshots",
"aliases": [
"Insta Snapshots",
"insta-snapshots"
],
"extensions": [
".snap.new",
".snap"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "insta-snapshots",
"scopeName": "source.insta-snapshots",
"path": "./syntaxes/insta-snapshots.tmLanguage.json"
}
]
},
"devDependencies": {
"@types/node": "^14.14.19",
"typescript": "^4.1.3",
"vscode": "^1.1.37"
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install"
}
}
29 changes: 29 additions & 0 deletions vscode-insta/src/InlineSnapshotProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
CancellationToken,
ProviderResult,
workspace,
Uri,
TextDocumentContentProvider,
} from "vscode";
import { PendingSnapshotsProvider } from "./PendingSnapshotsProvider";

export class InlineSnapshotProvider implements TextDocumentContentProvider {
constructor(private pendingSnapshotsProvider: PendingSnapshotsProvider) {}
provideTextDocumentContent(
uri: Uri,
token: CancellationToken
): ProviderResult<string> {
const snapshot = this.pendingSnapshotsProvider.getInlineSnapshot(uri);
if (!snapshot) {
throw new Error("Snapshot not found");
}
const inlineInfo = snapshot.inlineInfo!;
const contents =
inlineInfo[uri.path == "inline.snap" ? "oldSnapshot" : "newSnapshot"];
return `---\nsource: ${workspace.asRelativePath(snapshot.resourceUri!)}:${
inlineInfo.line
}\nexpression: ${JSON.stringify(inlineInfo.expression)}\nname: ${
inlineInfo.name || "unknown"
}\n---\n${contents}`;
}
}
66 changes: 66 additions & 0 deletions vscode-insta/src/PendingSnapshotsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
ProviderResult,
TreeDataProvider,
TreeItem,
Event,
WorkspaceFolder,
EventEmitter,
Uri,
} from "vscode";
import { getPendingSnapshots } from "./insta";
import { Snapshot } from "./Snapshot";

export class PendingSnapshotsProvider implements TreeDataProvider<Snapshot> {
private _onDidChangeTreeData: EventEmitter<
Snapshot | undefined | void
> = new EventEmitter<Snapshot | undefined | void>();
onDidChangeTreeData?:
| Event<void | Snapshot | null | undefined>
| undefined = this._onDidChangeTreeData.event;
public cachedInlineSnapshots: { [key: string]: Snapshot } = {};
private pendingRefresh?: NodeJS.Timeout;

constructor(private workspaceRoot?: WorkspaceFolder) {}

refresh(): void {
this._onDidChangeTreeData.fire();
}

refreshDebounced(): void {
if (this.pendingRefresh !== undefined) {
clearTimeout(this.pendingRefresh);
}
this.pendingRefresh = setTimeout(() => {
this.pendingRefresh = undefined;
this.refresh();
}, 200);
}

getInlineSnapshot(uri: Uri): Snapshot | undefined {
return (
(uri.scheme === "instaInlineSnapshot" &&
this.cachedInlineSnapshots[uri.fragment]) ||
undefined
);
}

getTreeItem(element: Snapshot): TreeItem | Thenable<TreeItem> {
return element;
}

getChildren(element?: Snapshot): ProviderResult<Snapshot[]> {
const { workspaceRoot } = this;
if (element || !workspaceRoot) {
return Promise.resolve([]);
}

return getPendingSnapshots(workspaceRoot.uri).then((snapshots) => {
return snapshots.map((snapshot) => {
if (snapshot.inlineInfo) {
this.cachedInlineSnapshots[snapshot.key] = snapshot;
}
return snapshot;
});
});
}
}
48 changes: 48 additions & 0 deletions vscode-insta/src/Snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { workspace, Uri, TreeItem } from "vscode";

export type InlineSnapshotInfo = {
oldSnapshot?: string;
newSnapshot: string;
line: number;
expression?: string;
name?: string;
};

export class Snapshot extends TreeItem {
public key: string;
public inlineInfo?: InlineSnapshotInfo;

constructor(public rootUri: Uri, snapshotInfo: any) {
super(Uri.file(snapshotInfo.path));
const relPath = workspace.asRelativePath(snapshotInfo.path);
const line = snapshotInfo.line;
this.label = line !== undefined ? `${relPath}:${line}` : relPath;
this.key =
line !== undefined ? `${snapshotInfo.path}:${line}` : snapshotInfo.path;

if (snapshotInfo.type === "inline_snapshot") {
this.description = snapshotInfo.name || "(inline)";
this.inlineInfo = {
oldSnapshot:
snapshotInfo.old_snapshot === null
? undefined
: snapshotInfo.old_snapshot,
newSnapshot: snapshotInfo.new_snapshot,
line: snapshotInfo.line,
expression:
snapshotInfo.expression === null
? undefined
: snapshotInfo.expression,
name: snapshotInfo.name === null ? undefined : snapshotInfo.name,
};
}

this.command = {
command: "mitsuhiko.insta.open-snapshot-diff",
title: "",
arguments: [this],
};
}

contextValue = "pendingInstaSnapshot";
}
157 changes: 157 additions & 0 deletions vscode-insta/src/SnapshotPathProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
DefinitionProvider,
TextDocument,
CancellationToken,
Definition,
Location,
Position,
ProviderResult,
workspace,
Uri,
} from "vscode";

const NAMED_SNAPSHOT_ASSERTION: RegExp = /(?:\binsta::)?(?:assert(?:_\w+)?_snapshot!)\(\s*['"]([^'"]+)['"]\s*,/;
const UNNAMED_SNAPSHOT_ASSERTION: RegExp = /(?:\binsta::)?(?:assert(?:_\w+)?_snapshot!)\(/;
const FUNCTION: RegExp = /\bfn\s+([\w]+)\s*\(/;
const TEST_DECL: RegExp = /#\[test\]/;
const FILENAME_PARTITION: RegExp = /^(.*)[/\\](.*?)\.rs$/;
const SNAPSHOT_FUNCTION_STRIP: RegExp = /^test_(.*?)$/;

type SnapshotMatch = {
snapshotName: string;
path: string;
localModuleName: string;
};

export class SnapshotPathProvider implements DefinitionProvider {
/**
* This looks up an explicitly named snapshot (simple case)
*/
private resolveNamedSnapshot(
document: TextDocument,
position: Position
): SnapshotMatch | null {
const line =
(position.line >= 1 ? document.lineAt(position.line - 1).text : "") +
document.lineAt(position.line).text;

const snapshotMatch = line.match(NAMED_SNAPSHOT_ASSERTION);
if (!snapshotMatch) {
return null;
}
const snapshotName = snapshotMatch[1];
const fileNameMatch = document.fileName.match(FILENAME_PARTITION);
if (!fileNameMatch) {
return null;
}
const path = fileNameMatch[1];
const localModuleName = fileNameMatch[2];
return { snapshotName, path, localModuleName };
}

/**
* This locates an implicitly (unnamed) snapshot.
*/
private resolveUnnamedSnapshot(
document: TextDocument,
position: Position
): SnapshotMatch | null {
function unnamedSnapshotAt(lineno: number): boolean {
const line = document.lineAt(lineno).text;
return !!(
line.match(UNNAMED_SNAPSHOT_ASSERTION) &&
!line.match(NAMED_SNAPSHOT_ASSERTION)
);
}

// if we can't find an unnnamed snapshot at the given position we bail.
if (!unnamedSnapshotAt(position.line)) {
return null;
}

// otherwise scan backwards for unnamed snapshot matches until we find
// a test function declaration.
let snapshotNumber = 1;
let scanLine = position.line - 1;
let functionName = null;

while (scanLine >= 0) {
// stop if we find a test function declaration
let functionMatch;
if (
scanLine > 1 &&
(functionMatch = document.lineAt(scanLine).text.match(FUNCTION)) &&
document.lineAt(scanLine - 1).text.match(TEST_DECL)
) {
functionName = functionMatch[1];
break;
}
if (unnamedSnapshotAt(scanLine)) {
snapshotNumber++;
}
scanLine--;
}

// if we couldn't find a function we have to bail.
if (!functionName) {
return null;
}

const snapshotName = `${functionName.match(SNAPSHOT_FUNCTION_STRIP)![1]}${
snapshotNumber > 1 ? `-${snapshotNumber}` : ""
}`;
const fileNameMatch = document.fileName.match(FILENAME_PARTITION);
if (!fileNameMatch) {
return null;
}

const path = fileNameMatch[1];
const localModuleName = fileNameMatch[2];
return { snapshotName, path, localModuleName };
}

public provideDefinition(
document: TextDocument,
position: Position,
token: CancellationToken
): ProviderResult<Definition> {
const snapshotMatch =
this.resolveNamedSnapshot(document, position) ||
this.resolveUnnamedSnapshot(document, position);
if (!snapshotMatch) {
return null;
}

const getSearchPath = function (
mode: "exact" | "wildcard-prefix" | "wildcard-all"
): string {
return workspace.asRelativePath(
`${snapshotMatch.path}/snapshots/${mode !== "exact" ? "*__" : ""}${
snapshotMatch.localModuleName
}${mode === "wildcard-all" ? "__*" : ""}__${
snapshotMatch.snapshotName
}.snap`
);
};

function findFiles(path: string): Thenable<Uri | null> {
return workspace
.findFiles(path, "", 1, token)
.then((results) => results[0] || null);
}

// we try to find the file in three passes:
// - exact matchin the snapshot folder.
// - with a wildcard module prefix (crate__foo__NAME__SNAP)
// - with a wildcard module prefix and suffix (crate__foo__NAME__tests__SNAP)
// This is needed since snapshots can be contained in submodules. Since
// getting the actual module name is tedious we just hope the match is
// unique.
return findFiles(getSearchPath("exact"))
.then((rv) => rv || findFiles(getSearchPath("wildcard-prefix")))
.then((rv) => rv || findFiles(getSearchPath("wildcard-all")))
.then((snapshot) =>
snapshot ? new Location(snapshot, new Position(0, 0)) : null
);
}
}
46 changes: 46 additions & 0 deletions vscode-insta/src/cargo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as cp from "child_process";
import { Uri, workspace } from "vscode";

function metadataReferencesInsta(metadata: any): boolean {
for (const pkg of metadata.packages) {
if (pkg.name === "insta") {
return true;
}
for (const dependency of pkg.dependencies) {
if (dependency.name === "insta") {
return true;
}
}
}
return false;
}

export async function projectUsesInsta(root: Uri): Promise<boolean> {
const rootCargoToml = Uri.joinPath(root, "Cargo.toml");
try {
await workspace.fs.stat(rootCargoToml);
} catch (e) {
return false;
}

return new Promise((resolve, reject) => {
let buffer = "";
const child = cp.spawn("cargo", [
"metadata",
"--no-deps",
"--format-version=1",
]);
child.stdout?.setEncoding("utf8");
child.stdout.on("data", (data) => (buffer += data));
child.on("close", (exitCode) => {
if (exitCode != 0) {
return resolve(false);
}
try {
resolve(metadataReferencesInsta(JSON.parse(buffer)));
} catch (e) {
reject(e);
}
});
});
}
275 changes: 275 additions & 0 deletions vscode-insta/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import { platform } from "os";
import {
ExtensionContext,
languages,
workspace,
Uri,
commands,
window,
FileSystemError,
} from "vscode";
import { projectUsesInsta } from "./cargo";
import { InlineSnapshotProvider } from "./InlineSnapshotProvider";
import { processAllSnapshots, processInlineSnapshot } from "./insta";
import { PendingSnapshotsProvider } from "./PendingSnapshotsProvider";
import { Snapshot } from "./Snapshot";
import { SnapshotPathProvider } from "./SnapshotPathProvider";

const INSTA_CONTEXT_NAME = "inInstaSnapshotsProject";

function getSnapshotPairs(uri: Uri): [Uri, Uri] | undefined {
if (uri.path.match(/\.snap$/)) {
return [uri, Uri.parse(`${uri}.new`)];
} else if (uri.path.match(/\.snap\.new$/)) {
return [uri.with({ path: uri.path.substr(0, uri.path.length - 4) }), uri];
}
}

async function openNamedSnapshotDiff(selectedSnapshot?: Uri) {
if (!selectedSnapshot) {
selectedSnapshot = window.activeTextEditor?.document.uri;
}
if (!selectedSnapshot) {
window.showErrorMessage("No snapshot selected");
return;
}
const pair = getSnapshotPairs(selectedSnapshot);
if (!pair) {
window.showErrorMessage("Not an insta snapshot file");
return;
}

let [oldSnapshot, newSnapshot]: [Uri, Uri] = pair;
try {
await workspace.fs.stat(oldSnapshot);
} catch (e) {
// todo: windows
oldSnapshot = Uri.file(platform() == "win32" ? "NUL" : "/dev/null");
}

await commands.executeCommand(
"vscode.diff",
oldSnapshot,
newSnapshot,
"Snapshot Diff",
{
preview: true,
}
);
}

async function openInlineSnapshotDiff(snapshot: Snapshot) {
const key = encodeURIComponent(snapshot.key);
await commands.executeCommand(
"vscode.diff",
Uri.parse(`instaInlineSnapshot:inline.snap#${key}`),
Uri.parse(`instaInlineSnapshot:inline.snap.new#${key}`),
"Inline Snapshot Diff",
{
preview: true,
}
);
}

async function performSnapshotAction(
action: "accept" | "reject",
pendingSnapshotsProvider: PendingSnapshotsProvider,
selectedSnapshot?: Uri
) {
// in most cases when we're invoked we don't have a selected snapshot yet.
// in that cas we always go by the active text editor's document. However in
// case that document is not a snapshot file (because for instance it's the
// empty file we open for completely new snapshots), then we look at all other
// visible text editors for the first snapshot.
if (!selectedSnapshot) {
selectedSnapshot = window.activeTextEditor?.document.uri;
if (selectedSnapshot && !selectedSnapshot.path.match(/\.snap(\.new)?$/)) {
window.visibleTextEditors.forEach((editor) => {
if (editor.document.uri.path.match(/\.snap(\.new)?$/)) {
selectedSnapshot = editor.document.uri;
}
});
}
}

if (!selectedSnapshot) {
window.showErrorMessage(`Cannot ${action} snapshot: no snapshot selected`);
return;
}

// inline snapshots need to be handled through cargo-insta due to the
// patching. special case it here.
if (selectedSnapshot.scheme === "instaInlineSnapshot") {
const snapshot = pendingSnapshotsProvider.getInlineSnapshot(
selectedSnapshot
);
if (!snapshot || !(await processInlineSnapshot(snapshot, action))) {
window.showErrorMessage(`Cannot ${action} snapshot: cargo-insta failed`);
} else {
const currentActiveUri = window.activeTextEditor?.document.uri;
if (currentActiveUri && selectedSnapshot.path.match(/\.snap(\.new)?$/)) {
commands.executeCommand("workbench.action.closeActiveEditor");
}
}
return;
}

const pair = getSnapshotPairs(selectedSnapshot);
if (!pair) {
window.showErrorMessage(`Cannot ${action} snapshot: not an insta snapshot`);
return;
}

if (action === "accept") {
try {
await workspace.fs.stat(pair[1]);
} catch (error) {
window.showErrorMessage("Could not accept snapshot: no new snapshot");
return;
}
await workspace.fs.rename(pair[1], pair[0], { overwrite: true });
window.showInformationMessage("New snapshot accepted");
} else if (action === "reject") {
try {
await workspace.fs.delete(pair[1]);
} catch (error) {
if (error instanceof FileSystemError && error.code === "FileNotFound") {
window.showInformationMessage("No new snapshot to reject");
} else {
throw error;
}
return;
}
window.showInformationMessage("New snapshot rejected");
}
}

async function switchSnapshotView(selectedSnapshot?: Uri): Promise<void> {
if (!selectedSnapshot) {
selectedSnapshot = window.activeTextEditor?.document.uri;
}
if (!selectedSnapshot) {
return;
}

const pair = getSnapshotPairs(selectedSnapshot);
if (!pair) {
window.showErrorMessage("Not an insta snapshot file");
return;
}

const otherFile = pair[0].path == selectedSnapshot.path ? pair[1] : pair[0];
try {
await workspace.fs.stat(otherFile);
} catch (e) {
window.showInformationMessage("Alternative snapshot does not exist.");
return;
}
await commands.executeCommand("vscode.open", otherFile);
}

async function setInstaContext(value: boolean): Promise<void> {
await commands.executeCommand("setContext", INSTA_CONTEXT_NAME, value);
}

function checkInstaContext() {
const rootUri = workspace.workspaceFolders?.[0].uri;
if (rootUri) {
projectUsesInsta(rootUri).then((usesInsta) => setInstaContext(usesInsta));
} else {
setInstaContext(false);
}
}

function performOnAllSnapshots(op: "accept" | "reject") {
const root = workspace.workspaceFolders?.[0];
if (!root) {
return;
}
processAllSnapshots(root.uri, op).then((okay) => {
if (okay) {
window.showInformationMessage(`Successfully ${op}ed all snapshots.`);
} else {
window.showErrorMessage(`Could not ${op} snapshots.`);
}
});
}

export function activate(context: ExtensionContext): void {
const root = workspace.workspaceFolders?.[0];
const pendingSnapshots = new PendingSnapshotsProvider(root);

const snapWatcher = workspace.createFileSystemWatcher(
"**/*.{snap,snap.new,pending-snap}"
);
snapWatcher.onDidChange(() => pendingSnapshots.refreshDebounced());
snapWatcher.onDidCreate(() => pendingSnapshots.refreshDebounced());
snapWatcher.onDidDelete(() => pendingSnapshots.refreshDebounced());

const cargoTomlWatcher = workspace.createFileSystemWatcher("**/Cargo.toml");
cargoTomlWatcher.onDidChange(() => checkInstaContext());
cargoTomlWatcher.onDidCreate(() => checkInstaContext());
cargoTomlWatcher.onDidDelete(() => checkInstaContext());

if (root) {
projectUsesInsta(root.uri).then((usesInsta) => setInstaContext(usesInsta));
}

context.subscriptions.push(
snapWatcher,
cargoTomlWatcher,
window.registerTreeDataProvider("pendingInstaSnapshots", pendingSnapshots),
workspace.registerTextDocumentContentProvider(
"instaInlineSnapshot",
new InlineSnapshotProvider(pendingSnapshots)
),
languages.registerDefinitionProvider(
[
{
scheme: "file",
language: "rust",
},
],
new SnapshotPathProvider()
),
commands.registerCommand(
"mitsuhiko.insta.open-snapshot-diff",
async (selectedFile?: Uri | Snapshot) => {
// when we're invoked from the pending snapshots view the first
// argument is the node (Snapshot) instead of the URI.
if (selectedFile instanceof Snapshot) {
if (selectedFile.inlineInfo) {
await openInlineSnapshotDiff(selectedFile);
return;
} else {
selectedFile = selectedFile.resourceUri;
}
}
await openNamedSnapshotDiff(selectedFile);
}
),
commands.registerCommand(
"mitsuhiko.insta.accept-snapshot",
(selectedFile?: Uri) =>
performSnapshotAction("accept", pendingSnapshots, selectedFile)
),
commands.registerCommand(
"mitsuhiko.insta.reject-snapshot",
(selectedFile?: Uri) =>
performSnapshotAction("reject", pendingSnapshots, selectedFile)
),
commands.registerCommand(
"mitsuhiko.insta.switch-snapshot-view",
(selectedFile?: Uri) => switchSnapshotView(selectedFile)
),
commands.registerCommand("mitsuhiko.insta.refresh-pending-snapshots", () =>
pendingSnapshots.refresh()
),
commands.registerCommand("mitsuhiko.insta.accept-all-snapshots", () =>
performOnAllSnapshots("accept")
),
commands.registerCommand("mitsuhiko.insta.reject-all-snapshots", () =>
performOnAllSnapshots("reject")
)
);
}
74 changes: 74 additions & 0 deletions vscode-insta/src/insta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as cp from "child_process";
import { Uri } from "vscode";
import { Snapshot } from "./Snapshot";

export function getPendingSnapshots(root: Uri): Promise<Snapshot[]> {
return new Promise((resolve, reject) => {
let buffer = "";
const child = cp.spawn(
"cargo",
["insta", "pending-snapshots", "--as-json"],
{
cwd: root.fsPath,
}
);
if (!child) {
reject(new Error("could not spawn cargo-insta"));
return;
}
child.stdout?.setEncoding("utf8");
child.stdout.on("data", (data) => (buffer += data));
child.on("close", (_exitCode) => {
const snapshots = buffer
.split(/\n/g)
.map((line) => {
try {
return new Snapshot(root, JSON.parse(line));
} catch (e) {
return null;
}
})
.filter((x) => x !== null);
resolve(snapshots as any);
});
});
}

export function processInlineSnapshot(
snapshot: Snapshot,
op: "accept" | "reject"
): Promise<boolean> {
if (!snapshot.inlineInfo) {
return Promise.resolve(false);
}
return new Promise((resolve, reject) => {
const child = cp.spawn("cargo", ["insta", op, "--snapshot", snapshot.key], {
cwd: snapshot.rootUri.fsPath,
});
if (!child) {
reject(new Error("could not spawn cargo-insta"));
return;
}
child.on("close", (exitCode) => {
resolve(exitCode === 0);
});
});
}

export function processAllSnapshots(
rootUri: Uri,
op: "accept" | "reject"
): Promise<boolean> {
return new Promise((resolve, reject) => {
const child = cp.spawn("cargo", ["insta", op], {
cwd: rootUri.fsPath,
});
if (!child) {
reject(new Error("could not spawn cargo-insta"));
return;
}
child.on("close", (exitCode) => {
resolve(exitCode === 0);
});
});
}
682 changes: 682 additions & 0 deletions vscode-insta/syntaxes/insta-snapshots.tmLanguage.json

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions vscode-insta/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": "src",
/* Strict Type-Checking Option */
"strict": true, /* enable all strict type-checking options */
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"exclude": [
"node_modules",
".vscode-test"
]
}
312 changes: 312 additions & 0 deletions vscode-insta/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==

"@types/node@^14.14.19":
version "14.14.19"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.19.tgz#5135176a8330b88ece4e9ab1fdcfc0a545b4bab4"
integrity sha512-4nhBPStMK04rruRVtVc6cDqhu7S9GZai0fpXgPXrFpcPX6Xul8xnrjSdGB4KPBVYG/R5+fXWdCM8qBoiULWGPQ==

agent-base@4, agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
dependencies:
es6-promisify "^5.0.0"

agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"

balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=

brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"

browser-stdout@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==

buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==

commander@2.15.1:
version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==

concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=

debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"

debug@4:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"

debug@^3.1.0:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"

diff@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==

es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==

es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
dependencies:
es6-promise "^4.0.3"

escape-string-regexp@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=

fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=

glob@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
dependencies:
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"

glob@^7.1.2:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
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"

growl@1.10.5:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==

has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=

he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=

http-proxy-agent@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
dependencies:
agent-base "4"
debug "3.1.0"

http-proxy-agent@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
dependencies:
"@tootallnate/once" "1"
agent-base "6"
debug "4"

https-proxy-agent@^2.2.1:
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
dependencies:
agent-base "^4.3.0"
debug "^3.1.0"

https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"

inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"

inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==

minimatch@3.0.4, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"

minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=

mkdirp@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"

mocha@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6"
integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==
dependencies:
browser-stdout "1.3.1"
commander "2.15.1"
debug "3.1.0"
diff "3.5.0"
escape-string-regexp "1.0.5"
glob "7.1.2"
growl "1.10.5"
he "1.1.1"
minimatch "3.0.4"
mkdirp "0.5.1"
supports-color "5.4.0"

ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=

ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==

ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==

once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"

path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=

semver@^5.4.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==

source-map-support@^0.5.0:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"

source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==

supports-color@5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==
dependencies:
has-flag "^3.0.0"

typescript@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==

vscode-test@^0.4.1:
version "0.4.3"
resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8"
integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==
dependencies:
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.1"

vscode@^1.1.37:
version "1.1.37"
resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.37.tgz#c2a770bee4bb3fff765e2b72c7bcc813b8a6bb0a"
integrity sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==
dependencies:
glob "^7.1.2"
http-proxy-agent "^4.0.1"
https-proxy-agent "^5.0.0"
mocha "^5.2.0"
semver "^5.4.1"
source-map-support "^0.5.0"
vscode-test "^0.4.1"

wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=