@tishlang/tish 2.9.0 → 2.12.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.
@@ -541,6 +541,256 @@ pub fn program_contains_jsx(program: &tishlang_ast::Program) -> bool {
541
541
  program.statements.iter().any(stmt_contains_jsx)
542
542
  }
543
543
 
544
+ /// Whether the program contains any JSX **fragment** (`<>…</>`). Fragments lower to
545
+ /// `h(Fragment, null, …)`, so they require the `Fragment` runtime binding in addition to `h`.
546
+ pub fn program_contains_jsx_fragment(program: &tishlang_ast::Program) -> bool {
547
+ program.statements.iter().any(stmt_contains_jsx_fragment)
548
+ }
549
+
550
+ /// Which JSX runtime bindings (`h`, `Fragment`) a module needs auto-imported, given which names are
551
+ /// already in module scope. Any JSX needs `h`; fragments additionally need `Fragment`. A binding is
552
+ /// omitted if it's already provided by a top-level import/declaration (see
553
+ /// [`collect_jsx_runtime_bindings_in_scope`]).
554
+ ///
555
+ /// Returns a subset of `["h", "Fragment"]` (in that order), or `[]` when the module has no JSX.
556
+ pub fn jsx_runtime_imports_needed(program: &tishlang_ast::Program) -> Vec<&'static str> {
557
+ if !program_contains_jsx(program) {
558
+ return Vec::new();
559
+ }
560
+ let in_scope = collect_jsx_runtime_bindings_in_scope(program);
561
+ let mut needed = Vec::new();
562
+ if !in_scope.contains("h") {
563
+ needed.push("h");
564
+ }
565
+ if program_contains_jsx_fragment(program) && !in_scope.contains("Fragment") {
566
+ needed.push("Fragment");
567
+ }
568
+ needed
569
+ }
570
+
571
+ /// Top-level names that already bind the JSX runtime symbols `h` / `Fragment`, so the auto-import
572
+ /// must not shadow or duplicate them. Covers named imports (local alias), top-level `const`/`let`,
573
+ /// and `fn` declarations, including those wrapped in `export`.
574
+ pub fn collect_jsx_runtime_bindings_in_scope(program: &tishlang_ast::Program) -> HashSet<String> {
575
+ use tishlang_ast::{ExportDeclaration, ImportSpecifier, Statement};
576
+ let mut names = HashSet::new();
577
+ let note = |n: &str, names: &mut HashSet<String>| {
578
+ if n == "h" || n == "Fragment" {
579
+ names.insert(n.to_string());
580
+ }
581
+ };
582
+ for stmt in &program.statements {
583
+ match stmt {
584
+ Statement::Import { specifiers, .. } => {
585
+ for s in specifiers {
586
+ if let ImportSpecifier::Named { name, alias, .. } = s {
587
+ let local = alias.as_deref().unwrap_or(name.as_ref());
588
+ note(local, &mut names);
589
+ }
590
+ }
591
+ }
592
+ Statement::VarDecl { name, .. } | Statement::FunDecl { name, .. } => {
593
+ note(name.as_ref(), &mut names)
594
+ }
595
+ Statement::Export { declaration, .. } => {
596
+ if let ExportDeclaration::Named(inner) = declaration.as_ref() {
597
+ match inner.as_ref() {
598
+ Statement::VarDecl { name, .. } | Statement::FunDecl { name, .. } => {
599
+ note(name.as_ref(), &mut names)
600
+ }
601
+ _ => {}
602
+ }
603
+ }
604
+ }
605
+ _ => {}
606
+ }
607
+ }
608
+ names
609
+ }
610
+
611
+ fn stmt_contains_jsx_fragment(stmt: &tishlang_ast::Statement) -> bool {
612
+ use tishlang_ast::{ExportDeclaration, Statement};
613
+ match stmt {
614
+ Statement::Block { statements, .. } | Statement::Multi { statements, .. } => {
615
+ statements.iter().any(stmt_contains_jsx_fragment)
616
+ }
617
+ Statement::VarDecl { init, .. } => init.as_ref().is_some_and(expr_contains_jsx_fragment),
618
+ Statement::VarDeclDestructure { init, .. } => expr_contains_jsx_fragment(init),
619
+ Statement::ExprStmt { expr, .. } => expr_contains_jsx_fragment(expr),
620
+ Statement::Return { value, .. } => value.as_ref().is_some_and(expr_contains_jsx_fragment),
621
+ Statement::If {
622
+ cond,
623
+ then_branch,
624
+ else_branch,
625
+ ..
626
+ } => {
627
+ expr_contains_jsx_fragment(cond)
628
+ || stmt_contains_jsx_fragment(then_branch)
629
+ || else_branch
630
+ .as_ref()
631
+ .is_some_and(|s| stmt_contains_jsx_fragment(s))
632
+ }
633
+ Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
634
+ expr_contains_jsx_fragment(cond) || stmt_contains_jsx_fragment(body)
635
+ }
636
+ Statement::For {
637
+ init,
638
+ cond,
639
+ update,
640
+ body,
641
+ ..
642
+ } => {
643
+ init.as_ref().is_some_and(|s| stmt_contains_jsx_fragment(s))
644
+ || cond.as_ref().is_some_and(expr_contains_jsx_fragment)
645
+ || update.as_ref().is_some_and(expr_contains_jsx_fragment)
646
+ || stmt_contains_jsx_fragment(body)
647
+ }
648
+ Statement::ForOf { iterable, body, .. } => {
649
+ expr_contains_jsx_fragment(iterable) || stmt_contains_jsx_fragment(body)
650
+ }
651
+ Statement::Switch {
652
+ expr,
653
+ cases,
654
+ default_body,
655
+ ..
656
+ } => {
657
+ expr_contains_jsx_fragment(expr)
658
+ || cases.iter().any(|(e, ss)| {
659
+ e.as_ref().is_some_and(expr_contains_jsx_fragment)
660
+ || ss.iter().any(stmt_contains_jsx_fragment)
661
+ })
662
+ || default_body
663
+ .as_ref()
664
+ .is_some_and(|ss| ss.iter().any(stmt_contains_jsx_fragment))
665
+ }
666
+ Statement::Try {
667
+ body,
668
+ catch_body,
669
+ finally_body,
670
+ ..
671
+ } => {
672
+ stmt_contains_jsx_fragment(body)
673
+ || catch_body
674
+ .as_ref()
675
+ .is_some_and(|s| stmt_contains_jsx_fragment(s))
676
+ || finally_body
677
+ .as_ref()
678
+ .is_some_and(|s| stmt_contains_jsx_fragment(s))
679
+ }
680
+ Statement::FunDecl { body, .. } => stmt_contains_jsx_fragment(body),
681
+ Statement::Throw { value, .. } => expr_contains_jsx_fragment(value),
682
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
683
+ ExportDeclaration::Named(inner) => stmt_contains_jsx_fragment(inner),
684
+ ExportDeclaration::Default(e) => expr_contains_jsx_fragment(e),
685
+ },
686
+ Statement::Import { .. }
687
+ | Statement::Break { .. }
688
+ | Statement::Continue { .. }
689
+ | Statement::TypeAlias { .. }
690
+ | Statement::DeclareVar { .. }
691
+ | Statement::DeclareFun { .. } => false,
692
+ }
693
+ }
694
+
695
+ fn expr_contains_jsx_fragment(expr: &Expr) -> bool {
696
+ match expr {
697
+ Expr::JsxFragment { .. } => true,
698
+ Expr::JsxElement {
699
+ props, children, ..
700
+ } => {
701
+ props.iter().any(|p| match p {
702
+ JsxProp::Attr {
703
+ value: JsxAttrValue::Expr(e),
704
+ ..
705
+ }
706
+ | JsxProp::Spread(e) => expr_contains_jsx_fragment(e),
707
+ _ => false,
708
+ }) || children.iter().any(|c| match c {
709
+ JsxChild::Expr(e) => expr_contains_jsx_fragment(e),
710
+ JsxChild::Text(_) => false,
711
+ })
712
+ }
713
+ Expr::Binary { left, right, .. } => {
714
+ expr_contains_jsx_fragment(left) || expr_contains_jsx_fragment(right)
715
+ }
716
+ Expr::Unary { operand, .. } => expr_contains_jsx_fragment(operand),
717
+ Expr::Assign { value, .. } => expr_contains_jsx_fragment(value),
718
+ Expr::Call { callee, args, .. } => {
719
+ expr_contains_jsx_fragment(callee)
720
+ || args.iter().any(|a| match a {
721
+ tishlang_ast::CallArg::Expr(e) | tishlang_ast::CallArg::Spread(e) => {
722
+ expr_contains_jsx_fragment(e)
723
+ }
724
+ })
725
+ }
726
+ Expr::Member { object, prop, .. } => {
727
+ expr_contains_jsx_fragment(object)
728
+ || matches!(prop, tishlang_ast::MemberProp::Expr(e) if expr_contains_jsx_fragment(e))
729
+ }
730
+ Expr::Index { object, index, .. } => {
731
+ expr_contains_jsx_fragment(object) || expr_contains_jsx_fragment(index)
732
+ }
733
+ Expr::Conditional {
734
+ cond,
735
+ then_branch,
736
+ else_branch,
737
+ ..
738
+ } => {
739
+ expr_contains_jsx_fragment(cond)
740
+ || expr_contains_jsx_fragment(then_branch)
741
+ || expr_contains_jsx_fragment(else_branch)
742
+ }
743
+ Expr::Array { elements, .. } => elements.iter().any(|el| match el {
744
+ ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_contains_jsx_fragment(e),
745
+ }),
746
+ Expr::Object { props, .. } => props.iter().any(|p| match p {
747
+ ObjectProp::KeyValue(_, e, _) | ObjectProp::Spread(e) => expr_contains_jsx_fragment(e),
748
+ }),
749
+ Expr::ArrowFunction { body, .. } => match body {
750
+ tishlang_ast::ArrowBody::Expr(e) => expr_contains_jsx_fragment(e),
751
+ tishlang_ast::ArrowBody::Block(s) => stmt_contains_jsx_fragment(s),
752
+ },
753
+ Expr::NullishCoalesce { left, right, .. } => {
754
+ expr_contains_jsx_fragment(left) || expr_contains_jsx_fragment(right)
755
+ }
756
+ Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_contains_jsx_fragment),
757
+ Expr::Await { operand, .. } => expr_contains_jsx_fragment(operand),
758
+ Expr::TypeOf { operand, .. } => expr_contains_jsx_fragment(operand),
759
+ Expr::Delete { target, .. } => expr_contains_jsx_fragment(target),
760
+ Expr::CompoundAssign { value, .. } | Expr::LogicalAssign { value, .. } => {
761
+ expr_contains_jsx_fragment(value)
762
+ }
763
+ Expr::MemberAssign { object, value, .. } => {
764
+ expr_contains_jsx_fragment(object) || expr_contains_jsx_fragment(value)
765
+ }
766
+ Expr::IndexAssign {
767
+ object,
768
+ index,
769
+ value,
770
+ ..
771
+ } => {
772
+ expr_contains_jsx_fragment(object)
773
+ || expr_contains_jsx_fragment(index)
774
+ || expr_contains_jsx_fragment(value)
775
+ }
776
+ Expr::New { callee, args, .. } => {
777
+ expr_contains_jsx_fragment(callee)
778
+ || args.iter().any(|a| match a {
779
+ tishlang_ast::CallArg::Expr(e) | tishlang_ast::CallArg::Spread(e) => {
780
+ expr_contains_jsx_fragment(e)
781
+ }
782
+ })
783
+ }
784
+ Expr::PostfixInc { .. }
785
+ | Expr::PrefixInc { .. }
786
+ | Expr::PostfixDec { .. }
787
+ | Expr::PrefixDec { .. }
788
+ | Expr::Literal { .. }
789
+ | Expr::Ident { .. }
790
+ | Expr::NativeModuleLoad { .. } => false,
791
+ }
792
+ }
793
+
544
794
  fn stmt_contains_jsx(stmt: &tishlang_ast::Statement) -> bool {
545
795
  use tishlang_ast::{ExportDeclaration, Statement};
546
796
  match stmt {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/tish",
3
- "version": "2.9.0",
3
+ "version": "2.12.0",
4
4
  "description": "Tish - minimal TS/JS-compatible language. Run, REPL, build to native or other targets.",
5
5
  "license": "PIF",
6
6
  "repository": {
Binary file
Binary file
Binary file
Binary file
Binary file