@tishlang/tish 1.0.11 → 1.0.13

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.
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tish"
3
- version = "0.1.0"
3
+ version = "1.0.13"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -14,6 +14,7 @@ use rustyline::{Behavior, ColorMode, CompletionType, Config, Editor};
14
14
  #[derive(Parser)]
15
15
  #[command(name = "tish")]
16
16
  #[command(about = "Tish - minimal TS/JS-compatible language")]
17
+ #[command(version = env!("CARGO_PKG_VERSION"))]
17
18
  #[command(after_help = "To disable optimizations: TISH_NO_OPTIMIZE=1")]
18
19
  pub(crate) struct Cli {
19
20
  #[command(subcommand)]
@@ -163,6 +163,26 @@ fn tish_bin() -> PathBuf {
163
163
  default
164
164
  }
165
165
 
166
+ /// tish -V and --version print the version.
167
+ #[test]
168
+ fn test_tish_version_flag() {
169
+ let bin = tish_bin();
170
+ assert!(bin.exists(), "tish binary not found. Run `cargo build -p tish` first.");
171
+ let out = Command::new(&bin).arg("-V").output().expect("run tish -V");
172
+ assert!(out.status.success(), "tish -V failed: {}", String::from_utf8_lossy(&out.stderr));
173
+ let stdout = String::from_utf8_lossy(&out.stdout);
174
+ assert!(
175
+ stdout.contains(env!("CARGO_PKG_VERSION")),
176
+ "tish -V should print version {}; got: {}",
177
+ env!("CARGO_PKG_VERSION"),
178
+ stdout
179
+ );
180
+ let out2 = Command::new(&bin).arg("--version").output().expect("run tish --version");
181
+ assert!(out2.status.success());
182
+ let stdout2 = String::from_utf8_lossy(&out2.stdout);
183
+ assert!(stdout2.contains(env!("CARGO_PKG_VERSION")), "tish --version should print version");
184
+ }
185
+
166
186
  /// Parse async-await example (validates async fn parsing).
167
187
  #[test]
168
188
  fn test_async_await_parse() {
@@ -1,8 +1,10 @@
1
1
  #[cfg(test)]
2
2
  mod tests {
3
+ use std::io::Write;
4
+
3
5
  use tish_parser::parse;
4
6
 
5
- use crate::{compile_with_jsx, JsxMode};
7
+ use crate::{compile_project_with_jsx, compile_with_jsx, JsxMode};
6
8
 
7
9
  #[test]
8
10
  fn lattish_jsx_emits_h_with_children_array() {
@@ -21,6 +23,54 @@ mod tests {
21
23
  assert!(js.contains("h(Fragment, null, ["));
22
24
  }
23
25
 
26
+ #[test]
27
+ fn jsx_text_whitespace_coalesced() {
28
+ let src = r#"fn X() { return <p>First paragraph</p> }"#;
29
+ let program = parse(src).unwrap();
30
+ let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
31
+ assert!(
32
+ js.contains("\"First paragraph\""),
33
+ "expected \"First paragraph\" in output, got: {}",
34
+ &js[..400.min(js.len())]
35
+ );
36
+ assert!(
37
+ !js.contains("\"First\", \"paragraph\""),
38
+ "text should be coalesced, not split"
39
+ );
40
+ }
41
+
42
+ #[test]
43
+ fn jsx_text_whitespace_coalesced_multiline() {
44
+ let src = "fn App() {\n return <p>First paragraph</p>\n}";
45
+ let program = parse(src).unwrap();
46
+ let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
47
+ assert!(
48
+ js.contains("\"First paragraph\""),
49
+ "multiline: expected \"First paragraph\", got: {}",
50
+ &js[..400.min(js.len())]
51
+ );
52
+ }
53
+
54
+ #[test]
55
+ fn jsx_text_whitespace_via_compile_project() {
56
+ let dir = std::env::temp_dir().join("tish_compile_project_test");
57
+ let _ = std::fs::create_dir_all(&dir);
58
+ let path = dir.join("test.tish");
59
+ let src = "fn App() {\n return <p>First paragraph</p>\n}";
60
+ let mut f = std::fs::File::create(&path).unwrap();
61
+ f.write_all(src.as_bytes()).unwrap();
62
+ f.sync_all().unwrap();
63
+ drop(f);
64
+ let js = compile_project_with_jsx(&path, Some(&dir), false, JsxMode::LattishH)
65
+ .expect("compile_project_with_jsx failed");
66
+ assert!(
67
+ js.contains("\"First paragraph\""),
68
+ "compile_project: expected \"First paragraph\", got: {}",
69
+ &js[..500.min(js.len())]
70
+ );
71
+ let _ = std::fs::remove_file(&path);
72
+ }
73
+
24
74
  #[test]
25
75
  fn vdom_emits_vdom_h() {
26
76
  let src = r#"fn X() { return <p/> }"#;
@@ -27,6 +27,21 @@ export fn h(tag, props, children) {
27
27
  if (children === undefined || children === null) {
28
28
  children = []
29
29
  }
30
+ if (typeof tag === "function") {
31
+ let p = props && props !== null ? props : {}
32
+ if (children.length > 0) {
33
+ let newProps = {}
34
+ let keys = Object.keys(p)
35
+ let i = 0
36
+ while (i < keys.length) {
37
+ newProps[keys[i]] = p[keys[i]]
38
+ i = i + 1
39
+ }
40
+ newProps.children = children
41
+ p = newProps
42
+ }
43
+ return tag(p)
44
+ }
30
45
  if (tag === Fragment) {
31
46
  let f = document.createDocumentFragment()
32
47
  let i = 0
@@ -1561,6 +1561,17 @@ impl<'a> Parser<'a> {
1561
1561
  })
1562
1562
  }
1563
1563
 
1564
+ /// Push text child, merging with previous Text if any (preserves spaces between tokens).
1565
+ fn push_or_merge_text(&self, children: &mut Vec<JsxChild>, s: Arc<str>) {
1566
+ if let Some(JsxChild::Text(prev)) = children.last() {
1567
+ let merged = format!("{} {}", prev.as_ref(), s.as_ref());
1568
+ let last = children.len() - 1;
1569
+ children[last] = JsxChild::Text(Arc::from(merged.as_str()));
1570
+ } else {
1571
+ children.push(JsxChild::Text(s));
1572
+ }
1573
+ }
1574
+
1564
1575
  /// Parse JSX children until </Tag> or </>
1565
1576
  fn parse_jsx_children(&mut self, close_tag: &str) -> Result<Vec<JsxChild>, String> {
1566
1577
  let mut children = Vec::new();
@@ -1601,7 +1612,7 @@ impl<'a> Parser<'a> {
1601
1612
  let t = self.advance().unwrap();
1602
1613
  let s = t.literal.clone().unwrap_or_default();
1603
1614
  if !s.is_empty() {
1604
- children.push(JsxChild::Text(s));
1615
+ self.push_or_merge_text(&mut children, s);
1605
1616
  }
1606
1617
  }
1607
1618
  Some(TokenKind::Ident) => {
@@ -1609,7 +1620,7 @@ impl<'a> Parser<'a> {
1609
1620
  let t = self.advance().unwrap();
1610
1621
  let s = t.literal.clone().unwrap_or_default();
1611
1622
  if !s.is_empty() {
1612
- children.push(JsxChild::Text(s));
1623
+ self.push_or_merge_text(&mut children, s);
1613
1624
  }
1614
1625
  }
1615
1626
  _ => {
@@ -1659,7 +1670,14 @@ impl<'a> Parser<'a> {
1659
1670
  let t = self.advance().unwrap();
1660
1671
  let s = t.literal.clone().unwrap_or_default();
1661
1672
  if !s.is_empty() {
1662
- children.push(JsxChild::Text(s));
1673
+ self.push_or_merge_text(&mut children, s);
1674
+ }
1675
+ }
1676
+ Some(TokenKind::Ident) => {
1677
+ let t = self.advance().unwrap();
1678
+ let s = t.literal.clone().unwrap_or_default();
1679
+ if !s.is_empty() {
1680
+ self.push_or_merge_text(&mut children, s);
1663
1681
  }
1664
1682
  }
1665
1683
  _ => return Err(format!("Unexpected token in JSX fragment: {:?}", self.peek_kind())),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/tish",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Tish - minimal TS/JS-compatible language. Run, REPL, compile to native.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
Binary file
Binary file
Binary file
Binary file
Binary file