@tishlang/tish 2.8.0 → 2.10.1
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/bin/tish +0 -0
- package/crates/tish/Cargo.toml +3 -1
- package/crates/tish/src/cli_help.rs +34 -0
- package/crates/tish/src/main.rs +138 -3
- package/crates/tish/tests/integration_test.rs +173 -0
- package/crates/tish_compile/src/codegen.rs +1569 -107
- package/crates/tish_compile/src/infer.rs +1946 -52
- package/crates/tish_compile/tests/perf_codegen_169_173.rs +139 -0
- package/crates/tish_compile_js/Cargo.toml +3 -0
- package/crates/tish_compile_js/src/codegen.rs +414 -5
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +354 -1
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -4,7 +4,10 @@ mod tests {
|
|
|
4
4
|
|
|
5
5
|
use tishlang_parser::parse;
|
|
6
6
|
|
|
7
|
-
use crate::{
|
|
7
|
+
use crate::{
|
|
8
|
+
compile_module_esm, compile_project_esm, compile_project_with_jsx, compile_with_jsx,
|
|
9
|
+
EmittedJsModule, ImportRewrite,
|
|
10
|
+
};
|
|
8
11
|
|
|
9
12
|
#[test]
|
|
10
13
|
fn lattish_jsx_emits_h_with_children_array() {
|
|
@@ -455,4 +458,354 @@ fn factory() {
|
|
|
455
458
|
let neg = crate::compile(&parse("console.log(-1 / 0)\n").unwrap(), true).unwrap();
|
|
456
459
|
assert!(neg.contains("-Infinity"), "-1/0 must emit -Infinity:\n{neg}");
|
|
457
460
|
}
|
|
461
|
+
|
|
462
|
+
// ── #282: ESM module output (one file per module, real import/export) ──────────────────────
|
|
463
|
+
|
|
464
|
+
/// Write a set of `(relative_path, source)` modules into a fresh temp dir and compile the entry
|
|
465
|
+
/// in ESM mode. Returns the emitted modules keyed for easy lookup by their output relative path.
|
|
466
|
+
fn build_esm(entry: &str, modules: &[(&str, &str)]) -> Vec<EmittedJsModule> {
|
|
467
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
468
|
+
let dir = tmp.path();
|
|
469
|
+
for (rel, src) in modules {
|
|
470
|
+
let p = dir.join(rel);
|
|
471
|
+
if let Some(parent) = p.parent() {
|
|
472
|
+
std::fs::create_dir_all(parent).unwrap();
|
|
473
|
+
}
|
|
474
|
+
let mut f = std::fs::File::create(&p).unwrap();
|
|
475
|
+
f.write_all(src.as_bytes()).unwrap();
|
|
476
|
+
f.sync_all().unwrap();
|
|
477
|
+
}
|
|
478
|
+
compile_project_esm(&dir.join(entry), Some(dir), false).expect("compile_project_esm failed")
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
fn module_js<'a>(mods: &'a [EmittedJsModule], rel: &str) -> &'a str {
|
|
482
|
+
mods.iter()
|
|
483
|
+
.find(|m| m.relative_path.to_string_lossy() == rel)
|
|
484
|
+
.map(|m| m.js.as_str())
|
|
485
|
+
.unwrap_or_else(|| panic!("module {rel} not emitted; got {:?}", mods.iter().map(|m| m.relative_path.display().to_string()).collect::<Vec<_>>()))
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#[test]
|
|
489
|
+
fn esm_emits_named_and_default_exports() {
|
|
490
|
+
let mods = build_esm(
|
|
491
|
+
"main.tish",
|
|
492
|
+
&[(
|
|
493
|
+
"main.tish",
|
|
494
|
+
"export const VERSION = \"1.0\"\nexport fn greet(n) { return \"hi \" + n }\nexport default greet\n",
|
|
495
|
+
)],
|
|
496
|
+
);
|
|
497
|
+
let js = module_js(&mods, "main.js");
|
|
498
|
+
assert!(js.contains("export const VERSION = \"1.0\";"), "named const export:\n{js}");
|
|
499
|
+
assert!(js.contains("export function greet "), "named fn export:\n{js}");
|
|
500
|
+
assert!(js.contains("export default greet;"), "default export:\n{js}");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#[test]
|
|
504
|
+
fn esm_rewrites_import_specifier_to_js_with_alias() {
|
|
505
|
+
let mods = build_esm(
|
|
506
|
+
"main.tish",
|
|
507
|
+
&[
|
|
508
|
+
("dep.tish", "export const ssrH = 42\nexport fn greet(n) { return n }\n"),
|
|
509
|
+
(
|
|
510
|
+
"main.tish",
|
|
511
|
+
"import { ssrH as h, greet } from \"./dep.tish\"\nimport * as M from \"./dep.tish\"\nconsole.log(h)\nconsole.log(greet(M.ssrH))\n",
|
|
512
|
+
),
|
|
513
|
+
],
|
|
514
|
+
);
|
|
515
|
+
let js = module_js(&mods, "main.js");
|
|
516
|
+
assert!(
|
|
517
|
+
js.contains("import { ssrH as h, greet } from \"./dep.js\";"),
|
|
518
|
+
"named import with alias, .tish->.js:\n{js}"
|
|
519
|
+
);
|
|
520
|
+
assert!(js.contains("import * as M from \"./dep.js\";"), "namespace import:\n{js}");
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#[test]
|
|
524
|
+
fn esm_one_file_per_module_preserves_tree() {
|
|
525
|
+
let mods = build_esm(
|
|
526
|
+
"main.tish",
|
|
527
|
+
&[
|
|
528
|
+
("lib/util.tish", "export fn id(x) { return x }\n"),
|
|
529
|
+
("main.tish", "import { id } from \"./lib/util.tish\"\nconsole.log(id(1))\n"),
|
|
530
|
+
],
|
|
531
|
+
);
|
|
532
|
+
// Nested module keeps its relative path; importer points at the nested `.js`.
|
|
533
|
+
let _ = module_js(&mods, "lib/util.js");
|
|
534
|
+
let main = module_js(&mods, "main.js");
|
|
535
|
+
assert!(
|
|
536
|
+
main.contains("from \"./lib/util.js\";"),
|
|
537
|
+
"nested relative import preserved:\n{main}"
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
#[test]
|
|
542
|
+
fn esm_module_outside_project_root_is_emitted() {
|
|
543
|
+
// #282 follow-up: a dependency in a *sibling* package (outside the entry's project root)
|
|
544
|
+
// must still be emitted — the output tree is rooted at the directory common to all modules.
|
|
545
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
546
|
+
let base = tmp.path();
|
|
547
|
+
std::fs::create_dir_all(base.join("app/src")).unwrap();
|
|
548
|
+
std::fs::create_dir_all(base.join("lib")).unwrap();
|
|
549
|
+
std::fs::write(base.join("lib/util.tish"), "export fn id(x) { return x }\n").unwrap();
|
|
550
|
+
std::fs::write(
|
|
551
|
+
base.join("app/src/main.tish"),
|
|
552
|
+
"import { id } from \"../../lib/util.tish\"\nconsole.log(id(1))\n",
|
|
553
|
+
)
|
|
554
|
+
.unwrap();
|
|
555
|
+
// Project root is the entry's package (`app`); `lib/util.tish` lives outside it.
|
|
556
|
+
let mods = compile_project_esm(
|
|
557
|
+
&base.join("app/src/main.tish"),
|
|
558
|
+
Some(&base.join("app")),
|
|
559
|
+
false,
|
|
560
|
+
)
|
|
561
|
+
.expect("compile_project_esm failed for sibling dep");
|
|
562
|
+
let rels: Vec<String> = mods
|
|
563
|
+
.iter()
|
|
564
|
+
.map(|m| m.relative_path.to_string_lossy().replace('\\', "/"))
|
|
565
|
+
.collect();
|
|
566
|
+
let main_js = mods
|
|
567
|
+
.iter()
|
|
568
|
+
.find(|m| m.relative_path.to_string_lossy().replace('\\', "/").ends_with("app/src/main.js"))
|
|
569
|
+
.map(|m| m.js.clone());
|
|
570
|
+
assert!(
|
|
571
|
+
rels.iter().any(|r| r.ends_with("lib/util.js")),
|
|
572
|
+
"sibling dep emitted under common base: {:?}",
|
|
573
|
+
rels
|
|
574
|
+
);
|
|
575
|
+
assert!(
|
|
576
|
+
rels.iter().any(|r| r.ends_with("app/src/main.js")),
|
|
577
|
+
"entry emitted under its own subtree: {:?}",
|
|
578
|
+
rels
|
|
579
|
+
);
|
|
580
|
+
let main_js = main_js.expect("entry module present");
|
|
581
|
+
assert!(
|
|
582
|
+
main_js.contains("from \"../../lib/util.js\";"),
|
|
583
|
+
"relative import to sibling rewritten to .js:\n{main_js}"
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
#[test]
|
|
588
|
+
fn esm_bare_node_modules_dep_is_emitted() {
|
|
589
|
+
// #282 follow-up: a bare specifier resolved from `node_modules` (like `lattish`) is emitted
|
|
590
|
+
// into the output tree and the importer points at it with a relative `.js` specifier.
|
|
591
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
592
|
+
let base = tmp.path();
|
|
593
|
+
std::fs::create_dir_all(base.join("src")).unwrap();
|
|
594
|
+
std::fs::create_dir_all(base.join("node_modules/pkg")).unwrap();
|
|
595
|
+
std::fs::write(
|
|
596
|
+
base.join("node_modules/pkg/package.json"),
|
|
597
|
+
"{\"name\":\"pkg\",\"main\":\"index.tish\"}\n",
|
|
598
|
+
)
|
|
599
|
+
.unwrap();
|
|
600
|
+
std::fs::write(
|
|
601
|
+
base.join("node_modules/pkg/index.tish"),
|
|
602
|
+
"export fn ping() { return \"pong\" }\n",
|
|
603
|
+
)
|
|
604
|
+
.unwrap();
|
|
605
|
+
std::fs::write(
|
|
606
|
+
base.join("src/main.tish"),
|
|
607
|
+
"import { ping } from \"pkg\"\nconsole.log(ping())\n",
|
|
608
|
+
)
|
|
609
|
+
.unwrap();
|
|
610
|
+
let mods = compile_project_esm(&base.join("src/main.tish"), Some(base), false)
|
|
611
|
+
.expect("compile_project_esm failed for node_modules dep");
|
|
612
|
+
let rels: Vec<String> = mods
|
|
613
|
+
.iter()
|
|
614
|
+
.map(|m| m.relative_path.to_string_lossy().replace('\\', "/"))
|
|
615
|
+
.collect();
|
|
616
|
+
let main_js = mods
|
|
617
|
+
.iter()
|
|
618
|
+
.find(|m| m.relative_path.to_string_lossy().replace('\\', "/").ends_with("src/main.js"))
|
|
619
|
+
.map(|m| m.js.clone());
|
|
620
|
+
assert!(
|
|
621
|
+
rels.iter().any(|r| r.ends_with("node_modules/pkg/index.js")),
|
|
622
|
+
"node_modules dep emitted: {:?}",
|
|
623
|
+
rels
|
|
624
|
+
);
|
|
625
|
+
let main_js = main_js.expect("entry module present");
|
|
626
|
+
assert!(
|
|
627
|
+
main_js.contains("from \"../node_modules/pkg/index.js\";"),
|
|
628
|
+
"bare specifier rewritten to relative .js path:\n{main_js}"
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#[test]
|
|
633
|
+
fn esm_rejects_native_imports() {
|
|
634
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
635
|
+
let dir = tmp.path();
|
|
636
|
+
let p = dir.join("main.tish");
|
|
637
|
+
std::fs::write(&p, "import { readFile } from \"fs\"\nconsole.log(1)\n").unwrap();
|
|
638
|
+
let err = compile_project_esm(&p, Some(dir), false).unwrap_err();
|
|
639
|
+
assert!(
|
|
640
|
+
err.message.contains("Native module import") && err.message.contains("esm"),
|
|
641
|
+
"expected a native-import rejection for ESM, got: {}",
|
|
642
|
+
err.message
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ── #284: single-module compile for Vite dev (in-graph HMR) ───────────────────────────────
|
|
647
|
+
|
|
648
|
+
/// Write `modules` into a fresh temp dir and compile just `entry` (no graph resolution),
|
|
649
|
+
/// in Vite-dev import-rewrite mode without a source map.
|
|
650
|
+
fn build_module_vite(entry: &str, modules: &[(&str, &str)]) -> String {
|
|
651
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
652
|
+
let dir = tmp.path();
|
|
653
|
+
for (rel, src) in modules {
|
|
654
|
+
let p = dir.join(rel);
|
|
655
|
+
if let Some(parent) = p.parent() {
|
|
656
|
+
std::fs::create_dir_all(parent).unwrap();
|
|
657
|
+
}
|
|
658
|
+
let mut f = std::fs::File::create(&p).unwrap();
|
|
659
|
+
f.write_all(src.as_bytes()).unwrap();
|
|
660
|
+
f.sync_all().unwrap();
|
|
661
|
+
}
|
|
662
|
+
compile_module_esm(
|
|
663
|
+
&dir.join(entry),
|
|
664
|
+
Some(dir),
|
|
665
|
+
false,
|
|
666
|
+
ImportRewrite::ViteDev,
|
|
667
|
+
false,
|
|
668
|
+
)
|
|
669
|
+
.expect("compile_module_esm failed")
|
|
670
|
+
.js
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
#[test]
|
|
674
|
+
fn compile_module_esm_vite_preserves_relative_tish_imports() {
|
|
675
|
+
let js = build_module_vite(
|
|
676
|
+
"main.tish",
|
|
677
|
+
&[(
|
|
678
|
+
"main.tish",
|
|
679
|
+
"import { id } from \"./dep.tish\"\nconsole.log(id(1))\n",
|
|
680
|
+
)],
|
|
681
|
+
);
|
|
682
|
+
assert!(
|
|
683
|
+
js.contains("import { id } from \"./dep.tish\";"),
|
|
684
|
+
"Vite dev keeps the .tish specifier so resolveId re-enters the plugin:\n{js}"
|
|
685
|
+
);
|
|
686
|
+
assert!(
|
|
687
|
+
!js.contains("./dep.js"),
|
|
688
|
+
"Vite dev must NOT rewrite .tish to .js:\n{js}"
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
#[test]
|
|
693
|
+
fn compile_module_esm_vite_leaves_bare_specifiers_unchanged() {
|
|
694
|
+
let js = build_module_vite(
|
|
695
|
+
"main.tish",
|
|
696
|
+
&[(
|
|
697
|
+
"main.tish",
|
|
698
|
+
"import { h } from \"lattish\"\nconsole.log(h)\n",
|
|
699
|
+
)],
|
|
700
|
+
);
|
|
701
|
+
assert!(
|
|
702
|
+
js.contains("import { h } from \"lattish\";"),
|
|
703
|
+
"bare specifier passes through for normal Node/Vite resolution:\n{js}"
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
#[test]
|
|
708
|
+
fn compile_module_esm_vite_emits_exports() {
|
|
709
|
+
let js = build_module_vite(
|
|
710
|
+
"main.tish",
|
|
711
|
+
&[(
|
|
712
|
+
"main.tish",
|
|
713
|
+
"export const VERSION = \"1.0\"\nexport fn greet(n) { return \"hi \" + n }\nexport default greet\n",
|
|
714
|
+
)],
|
|
715
|
+
);
|
|
716
|
+
assert!(js.contains("export const VERSION = \"1.0\";"), "named const export:\n{js}");
|
|
717
|
+
assert!(js.contains("export function greet "), "named fn export:\n{js}");
|
|
718
|
+
assert!(js.contains("export default greet;"), "default export:\n{js}");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
#[test]
|
|
722
|
+
fn compile_module_esm_does_not_resolve_graph() {
|
|
723
|
+
// Compiling a single module must not read or require its dependencies to exist on disk:
|
|
724
|
+
// only the entry file is created, yet the import to a missing `./dep.tish` compiles fine.
|
|
725
|
+
let js = build_module_vite(
|
|
726
|
+
"main.tish",
|
|
727
|
+
&[(
|
|
728
|
+
"main.tish",
|
|
729
|
+
"import { id } from \"./dep.tish\"\nconsole.log(id(1))\n",
|
|
730
|
+
)],
|
|
731
|
+
);
|
|
732
|
+
assert!(
|
|
733
|
+
js.contains("import { id } from \"./dep.tish\";"),
|
|
734
|
+
"single-module compile emits the import without loading the dependency:\n{js}"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
#[test]
|
|
739
|
+
fn compile_module_esm_rejects_native_imports() {
|
|
740
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
741
|
+
let dir = tmp.path();
|
|
742
|
+
let p = dir.join("main.tish");
|
|
743
|
+
std::fs::write(&p, "import { readFile } from \"fs\"\nconsole.log(1)\n").unwrap();
|
|
744
|
+
let err = compile_module_esm(&p, Some(dir), false, ImportRewrite::ViteDev, false)
|
|
745
|
+
.unwrap_err();
|
|
746
|
+
assert!(
|
|
747
|
+
err.message.contains("Native module import"),
|
|
748
|
+
"expected a native-import rejection, got: {}",
|
|
749
|
+
err.message
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
#[test]
|
|
754
|
+
fn compile_module_esm_source_map_maps_statement_to_tish_file() {
|
|
755
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
756
|
+
let dir = tmp.path();
|
|
757
|
+
let p = dir.join("main.tish");
|
|
758
|
+
std::fs::write(&p, "export fn greet(n) { return \"hi \" + n }\nconsole.log(greet(\"x\"))\n")
|
|
759
|
+
.unwrap();
|
|
760
|
+
let bundle =
|
|
761
|
+
compile_module_esm(&p, Some(dir), false, ImportRewrite::ViteDev, true).unwrap();
|
|
762
|
+
let map = bundle
|
|
763
|
+
.source_map_json
|
|
764
|
+
.expect("source map requested → must be present");
|
|
765
|
+
assert!(
|
|
766
|
+
map.contains("\"version\":3") || map.contains("\"version\": 3"),
|
|
767
|
+
"v3 source map:\n{map}"
|
|
768
|
+
);
|
|
769
|
+
assert!(
|
|
770
|
+
map.contains("main.tish"),
|
|
771
|
+
"map sources reference the .tish file:\n{map}"
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
#[test]
|
|
776
|
+
fn compile_module_esm_source_map_embeds_sources_content() {
|
|
777
|
+
// The map must carry sourcesContent so Vite/devtools never resolve `sources` from disk
|
|
778
|
+
// (otherwise Vite warns "Sourcemap ... points to missing source files").
|
|
779
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
780
|
+
let dir = tmp.path();
|
|
781
|
+
let p = dir.join("main.tish");
|
|
782
|
+
let src = "export fn greet(n) { return \"hi \" + n }\n";
|
|
783
|
+
std::fs::write(&p, src).unwrap();
|
|
784
|
+
let bundle =
|
|
785
|
+
compile_module_esm(&p, Some(dir), false, ImportRewrite::ViteDev, true).unwrap();
|
|
786
|
+
let map = bundle.source_map_json.expect("source map requested");
|
|
787
|
+
assert!(
|
|
788
|
+
map.contains("\"sourcesContent\""),
|
|
789
|
+
"map must include a sourcesContent field:\n{map}"
|
|
790
|
+
);
|
|
791
|
+
assert!(
|
|
792
|
+
map.contains("export fn greet"),
|
|
793
|
+
"sourcesContent must embed the original .tish source:\n{map}"
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
#[test]
|
|
798
|
+
fn compile_module_esm_source_map_requires_no_optimize() {
|
|
799
|
+
let tmp = tempfile::tempdir().expect("tempdir");
|
|
800
|
+
let dir = tmp.path();
|
|
801
|
+
let p = dir.join("main.tish");
|
|
802
|
+
std::fs::write(&p, "console.log(1)\n").unwrap();
|
|
803
|
+
let err = compile_module_esm(&p, Some(dir), true, ImportRewrite::ViteDev, true)
|
|
804
|
+
.unwrap_err();
|
|
805
|
+
assert!(
|
|
806
|
+
err.message.to_lowercase().contains("optimiz"),
|
|
807
|
+
"source map + optimize must be rejected, got: {}",
|
|
808
|
+
err.message
|
|
809
|
+
);
|
|
810
|
+
}
|
|
458
811
|
}
|
package/package.json
CHANGED
|
Binary file
|
package/platform/darwin-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|
package/platform/linux-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|