@tishlang/tish-lsp 3.3.3 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -0
- package/bin/tish-lsp +0 -0
- package/crates/tish/Cargo.toml +4 -1
- package/crates/tish/src/cli_help.rs +84 -0
- package/crates/tish/src/main.rs +93 -1
- package/crates/tish/tests/scope_merge.rs +90 -0
- package/crates/tish/tests/test_runner.rs +673 -0
- package/crates/tish_bytecode/src/compiler.rs +25 -4
- package/crates/tish_compile/src/check.rs +178 -0
- package/crates/tish_compile/src/codegen.rs +383 -63
- package/crates/tish_compile/src/lib.rs +33 -1
- package/crates/tish_compile/src/resolve.rs +121 -19
- package/crates/tish_compile/src/types.rs +35 -1
- package/crates/tish_compile/tests/call_arity_641.rs +104 -0
- package/crates/tish_core/src/lib.rs +103 -11
- package/crates/tish_eval/src/eval.rs +16 -0
- package/crates/tish_eval/src/value_convert.rs +10 -0
- package/crates/tish_lsp/Cargo.toml +1 -1
- package/crates/tish_test/Cargo.toml +47 -0
- package/crates/tish_test/src/assert.rs +435 -0
- package/crates/tish_test/src/config.rs +85 -0
- package/crates/tish_test/src/coverage.rs +237 -0
- package/crates/tish_test/src/deep_equal.rs +261 -0
- package/crates/tish_test/src/discovery.rs +70 -0
- package/crates/tish_test/src/expect.rs +977 -0
- package/crates/tish_test/src/instrument.rs +457 -0
- package/crates/tish_test/src/isolation.rs +14 -0
- package/crates/tish_test/src/lib.rs +22 -0
- package/crates/tish_test/src/load.rs +152 -0
- package/crates/tish_test/src/mocks.rs +270 -0
- package/crates/tish_test/src/module.rs +318 -0
- package/crates/tish_test/src/registry.rs +235 -0
- package/crates/tish_test/src/report.rs +312 -0
- package/crates/tish_test/src/runner.rs +797 -0
- package/crates/tish_test/src/snapshots.rs +197 -0
- package/crates/tish_vm/src/vm.rs +17 -14
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
//! Snapshot matchers — `__snapshots__/<testfile>.<name>.snap` relative to the test file.
|
|
2
|
+
|
|
3
|
+
use std::cell::RefCell;
|
|
4
|
+
use std::fs;
|
|
5
|
+
use std::path::{Path, PathBuf};
|
|
6
|
+
|
|
7
|
+
use tishlang_core::{set_pending_throw, Value};
|
|
8
|
+
|
|
9
|
+
use crate::assert::assertion_error;
|
|
10
|
+
|
|
11
|
+
thread_local! {
|
|
12
|
+
static CURRENT_FILE: RefCell<String> = const { RefCell::new(String::new()) };
|
|
13
|
+
/// Full name of the running test. Snapshots are keyed by it so two unnamed
|
|
14
|
+
/// `toMatchSnapshot()` calls in different tests cannot collide on one file.
|
|
15
|
+
static CURRENT_TEST: RefCell<String> = const { RefCell::new(String::new()) };
|
|
16
|
+
static UPDATE: RefCell<bool> = const { RefCell::new(false) };
|
|
17
|
+
static CI: RefCell<bool> = const { RefCell::new(false) };
|
|
18
|
+
static COUNTERS: RefCell<std::collections::HashMap<String, usize>> =
|
|
19
|
+
RefCell::new(std::collections::HashMap::new());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Set the active test file (used to resolve `__snapshots__/`).
|
|
23
|
+
pub fn set_current_file(path: &str) {
|
|
24
|
+
CURRENT_FILE.with(|f| *f.borrow_mut() = path.to_string());
|
|
25
|
+
COUNTERS.with(|c| c.borrow_mut().clear());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Set the running test's full name, and reset its per-test snapshot counter.
|
|
29
|
+
pub fn set_current_test(full_name: &str) {
|
|
30
|
+
CURRENT_TEST.with(|t| *t.borrow_mut() = full_name.to_string());
|
|
31
|
+
COUNTERS.with(|c| {
|
|
32
|
+
c.borrow_mut().remove(full_name);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn set_update_snapshots(update: bool) {
|
|
37
|
+
UPDATE.with(|u| *u.borrow_mut() = update);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// In CI a *missing* snapshot is a failure, not something to write and pass. Otherwise a
|
|
41
|
+
/// deleted `.snap` file makes the suite go green on the very run that should catch it.
|
|
42
|
+
pub fn set_ci_mode(ci: bool) {
|
|
43
|
+
CI.with(|c| *c.borrow_mut() = ci);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn ci_mode() -> bool {
|
|
47
|
+
CI.with(|c| *c.borrow())
|
|
48
|
+
|| std::env::var("CI")
|
|
49
|
+
.ok()
|
|
50
|
+
.is_some_and(|v| v != "0" && v != "false")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn update_snapshots() -> bool {
|
|
54
|
+
UPDATE.with(|u| *u.borrow())
|
|
55
|
+
|| std::env::var("TISH_UPDATE_SNAPSHOTS").ok().as_deref() == Some("1")
|
|
56
|
+
|| std::env::var("UPDATE_SNAPSHOTS").ok().as_deref() == Some("1")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn snapshot_dir_for(test_file: &str) -> PathBuf {
|
|
60
|
+
let p = Path::new(test_file);
|
|
61
|
+
let parent = p.parent().unwrap_or_else(|| Path::new("."));
|
|
62
|
+
parent.join("__snapshots__")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn sanitize(name: &str) -> String {
|
|
66
|
+
let s: String = name
|
|
67
|
+
.chars()
|
|
68
|
+
.map(|c| {
|
|
69
|
+
if c.is_alphanumeric() || c == '_' || c == '-' {
|
|
70
|
+
c
|
|
71
|
+
} else {
|
|
72
|
+
'_'
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.collect();
|
|
76
|
+
// Keep file names bounded; a long describe > it chain would otherwise blow past NAME_MAX.
|
|
77
|
+
if s.len() > 120 {
|
|
78
|
+
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
|
|
79
|
+
for b in name.as_bytes() {
|
|
80
|
+
hash ^= *b as u64;
|
|
81
|
+
hash = hash.wrapping_mul(0x100_0000_01b3);
|
|
82
|
+
}
|
|
83
|
+
format!("{}_{hash:x}", &s[..120])
|
|
84
|
+
} else {
|
|
85
|
+
s
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn snap_path(test_file: &str, name: &str) -> PathBuf {
|
|
90
|
+
let stem = Path::new(test_file)
|
|
91
|
+
.file_stem()
|
|
92
|
+
.and_then(|s| s.to_str())
|
|
93
|
+
.unwrap_or("test");
|
|
94
|
+
snapshot_dir_for(test_file).join(format!("{stem}.{}.snap", sanitize(name)))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Default snapshot key: `<test full name> <n>`, mirroring Jest. Keyed by test rather than by
|
|
98
|
+
/// file so unnamed snapshots in sibling tests get distinct files.
|
|
99
|
+
fn next_anon_name() -> String {
|
|
100
|
+
let test = CURRENT_TEST.with(|t| t.borrow().clone());
|
|
101
|
+
let key = if test.is_empty() {
|
|
102
|
+
CURRENT_FILE.with(|f| f.borrow().clone())
|
|
103
|
+
} else {
|
|
104
|
+
test
|
|
105
|
+
};
|
|
106
|
+
COUNTERS.with(|c| {
|
|
107
|
+
let mut map = c.borrow_mut();
|
|
108
|
+
let n = map.entry(key.clone()).or_insert(0);
|
|
109
|
+
*n += 1;
|
|
110
|
+
format!("{key} {}", *n)
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn serialize(v: &Value) -> String {
|
|
115
|
+
tishlang_core::json_stringify(v)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Compare `received` against the on-disk snapshot (or write/update it).
|
|
119
|
+
pub fn match_snapshot(received: &Value, hint: Option<&str>) -> Value {
|
|
120
|
+
let file = CURRENT_FILE.with(|f| f.borrow().clone());
|
|
121
|
+
if file.is_empty() {
|
|
122
|
+
set_pending_throw(assertion_error(
|
|
123
|
+
"toMatchSnapshot: no current test file (runner must call set_current_file)",
|
|
124
|
+
Some(received.clone()),
|
|
125
|
+
None,
|
|
126
|
+
"toMatchSnapshot",
|
|
127
|
+
true,
|
|
128
|
+
));
|
|
129
|
+
return Value::Null;
|
|
130
|
+
}
|
|
131
|
+
let name = hint.map(|s| s.to_string()).unwrap_or_else(next_anon_name);
|
|
132
|
+
let path = snap_path(&file, &name);
|
|
133
|
+
let actual = serialize(received);
|
|
134
|
+
let update = update_snapshots();
|
|
135
|
+
let exists = path.exists();
|
|
136
|
+
|
|
137
|
+
if !exists && ci_mode() && !update {
|
|
138
|
+
set_pending_throw(assertion_error(
|
|
139
|
+
format!(
|
|
140
|
+
"expect(received).toMatchSnapshot()\n\nSnapshot missing (CI mode): {}",
|
|
141
|
+
path.display()
|
|
142
|
+
),
|
|
143
|
+
Some(received.clone()),
|
|
144
|
+
None,
|
|
145
|
+
"toMatchSnapshot",
|
|
146
|
+
true,
|
|
147
|
+
));
|
|
148
|
+
return Value::Null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if update || !exists {
|
|
152
|
+
if let Some(parent) = path.parent() {
|
|
153
|
+
let _ = fs::create_dir_all(parent);
|
|
154
|
+
}
|
|
155
|
+
if let Err(e) = fs::write(&path, format!("{}\n", actual)) {
|
|
156
|
+
set_pending_throw(assertion_error(
|
|
157
|
+
format!("toMatchSnapshot: failed to write {}: {}", path.display(), e),
|
|
158
|
+
Some(received.clone()),
|
|
159
|
+
None,
|
|
160
|
+
"toMatchSnapshot",
|
|
161
|
+
true,
|
|
162
|
+
));
|
|
163
|
+
return Value::Null;
|
|
164
|
+
}
|
|
165
|
+
return Value::Null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let expected = match fs::read_to_string(&path) {
|
|
169
|
+
Ok(s) => s.trim_end_matches('\n').to_string(),
|
|
170
|
+
Err(e) => {
|
|
171
|
+
set_pending_throw(assertion_error(
|
|
172
|
+
format!("toMatchSnapshot: failed to read {}: {}", path.display(), e),
|
|
173
|
+
Some(received.clone()),
|
|
174
|
+
None,
|
|
175
|
+
"toMatchSnapshot",
|
|
176
|
+
true,
|
|
177
|
+
));
|
|
178
|
+
return Value::Null;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if actual != expected {
|
|
183
|
+
set_pending_throw(assertion_error(
|
|
184
|
+
format!(
|
|
185
|
+
"expect(received).toMatchSnapshot()\n\nSnapshot: {}\n\n- Expected\n+ Received\n\n- {}\n+ {}",
|
|
186
|
+
path.display(),
|
|
187
|
+
expected,
|
|
188
|
+
actual
|
|
189
|
+
),
|
|
190
|
+
Some(received.clone()),
|
|
191
|
+
Some(Value::String(expected.as_str().into())),
|
|
192
|
+
"toMatchSnapshot",
|
|
193
|
+
true,
|
|
194
|
+
));
|
|
195
|
+
}
|
|
196
|
+
Value::Null
|
|
197
|
+
}
|
package/crates/tish_vm/src/vm.rs
CHANGED
|
@@ -2178,6 +2178,11 @@ impl Vm {
|
|
|
2178
2178
|
}
|
|
2179
2179
|
}
|
|
2180
2180
|
Opcode::JumpBack => {
|
|
2181
|
+
// Cooperative timeout poll: a runaway loop is otherwise uninterruptible.
|
|
2182
|
+
// Disarmed (every path but `tish test --timeout`) this is one relaxed load.
|
|
2183
|
+
if tishlang_core::execution_deadline_exceeded() {
|
|
2184
|
+
return Some(Err(tishlang_core::DEADLINE_ERROR.to_string()));
|
|
2185
|
+
}
|
|
2181
2186
|
let dist = Self::read_u16(code, &mut ip) as usize;
|
|
2182
2187
|
// #190 OSR: `ip` now points just past the JumpBack (region end); the loop header is
|
|
2183
2188
|
// `region_end - dist`. Once a loop is hot, try to run its remaining iterations in
|
|
@@ -3092,6 +3097,10 @@ impl Vm {
|
|
|
3092
3097
|
}
|
|
3093
3098
|
}
|
|
3094
3099
|
Opcode::JumpBack => {
|
|
3100
|
+
// Cooperative timeout poll — see the frame VM's JumpBack.
|
|
3101
|
+
if tishlang_core::execution_deadline_exceeded() {
|
|
3102
|
+
return Err(tishlang_core::DEADLINE_ERROR.to_string());
|
|
3103
|
+
}
|
|
3095
3104
|
let dist = Self::read_u16(code, &mut ip) as usize;
|
|
3096
3105
|
// #190 OSR: on a hot back-edge, try to finish the loop natively. Slot-based frames
|
|
3097
3106
|
// only (`slot_locals` is the live frame); non-slot / non-numeric loops fail the
|
|
@@ -3686,20 +3695,14 @@ impl Vm {
|
|
|
3686
3695
|
return Err("LoadNativeExport: export_name constant out of bounds or not string".to_string());
|
|
3687
3696
|
}
|
|
3688
3697
|
};
|
|
3689
|
-
//
|
|
3690
|
-
//
|
|
3691
|
-
//
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
let regs = self.native_modules.borrow();
|
|
3698
|
-
regs.get(spec)
|
|
3699
|
-
.and_then(|m| m.borrow().get(&Arc::from(export_name)).cloned())
|
|
3700
|
-
} else {
|
|
3701
|
-
None
|
|
3702
|
-
};
|
|
3698
|
+
// Consult modules registered via `Vm::register_native_module` before
|
|
3699
|
+
// built-in lookup (used by `tish test` for `tish:test` / `tish:assert`,
|
|
3700
|
+
// and by embedders for `cargo:` / `ffi:`).
|
|
3701
|
+
let from_registry: Option<Value> = {
|
|
3702
|
+
let regs = self.native_modules.borrow();
|
|
3703
|
+
regs.get(spec)
|
|
3704
|
+
.and_then(|m| m.borrow().get(&Arc::from(export_name)).cloned())
|
|
3705
|
+
};
|
|
3703
3706
|
let v = from_registry
|
|
3704
3707
|
.or_else(|| get_builtin_export(self.capabilities.as_ref(), spec, export_name))
|
|
3705
3708
|
.ok_or_else(|| {
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|