@barefootjs/mojolicious 0.5.2 → 0.6.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.
@@ -770,7 +770,7 @@ function renderArrayMethod(method, object, args, emit) {
770
770
  switch (method) {
771
771
  case "join": {
772
772
  const obj = emit(object);
773
- const sep = emit(args[0]);
773
+ const sep = args.length >= 1 ? emit(args[0]) : `','`;
774
774
  return `join(${sep}, @{${obj}})`;
775
775
  }
776
776
  case "includes": {
@@ -787,18 +787,21 @@ function renderArrayMethod(method, object, args, emit) {
787
787
  }
788
788
  case "at": {
789
789
  const obj = emit(object);
790
- const idx = emit(args[0]);
790
+ const idx = args.length >= 1 ? emit(args[0]) : "0";
791
791
  return `bf->at(${obj}, ${idx})`;
792
792
  }
793
793
  case "concat": {
794
+ if (args.length === 0) {
795
+ return emit(object);
796
+ }
794
797
  const a = emit(object);
795
798
  const b = emit(args[0]);
796
799
  return `bf->concat(${a}, ${b})`;
797
800
  }
798
801
  case "slice": {
799
802
  const recv = emit(object);
800
- const start = emit(args[0]);
801
- const end = args.length === 2 ? emit(args[1]) : "undef";
803
+ const start = args.length >= 1 ? emit(args[0]) : "0";
804
+ const end = args.length >= 2 ? emit(args[1]) : "undef";
802
805
  return `bf->slice(${recv}, ${start}, ${end})`;
803
806
  }
804
807
  case "reverse":
@@ -818,6 +821,53 @@ function renderArrayMethod(method, object, args, emit) {
818
821
  const recv = emit(object);
819
822
  return `bf->trim(${recv})`;
820
823
  }
824
+ case "split": {
825
+ const recv = emit(object);
826
+ if (args.length === 0) {
827
+ return `bf->split(${recv})`;
828
+ }
829
+ const sep = emit(args[0]);
830
+ if (args.length === 1) {
831
+ return `bf->split(${recv}, ${sep})`;
832
+ }
833
+ const limit = emit(args[1]);
834
+ return `bf->split(${recv}, ${sep}, ${limit})`;
835
+ }
836
+ case "startsWith":
837
+ case "endsWith": {
838
+ const fn = method === "startsWith" ? "starts_with" : "ends_with";
839
+ const recv = emit(object);
840
+ const arg = emit(args[0]);
841
+ if (args.length >= 2) {
842
+ return `bf->${fn}(${recv}, ${arg}, ${emit(args[1])})`;
843
+ }
844
+ return `bf->${fn}(${recv}, ${arg})`;
845
+ }
846
+ case "replace": {
847
+ const recv = emit(object);
848
+ const oldS = emit(args[0]);
849
+ const newS = emit(args[1]);
850
+ return `bf->replace(${recv}, ${oldS}, ${newS})`;
851
+ }
852
+ case "repeat": {
853
+ const recv = emit(object);
854
+ const count = args.length === 0 ? "0" : emit(args[0]);
855
+ return `bf->repeat(${recv}, ${count})`;
856
+ }
857
+ case "padStart":
858
+ case "padEnd": {
859
+ const fn = method === "padStart" ? "pad_start" : "pad_end";
860
+ const recv = emit(object);
861
+ if (args.length === 0) {
862
+ return `bf->${fn}(${recv}, 0)`;
863
+ }
864
+ const target = emit(args[0]);
865
+ if (args.length === 1) {
866
+ return `bf->${fn}(${recv}, ${target})`;
867
+ }
868
+ const pad = emit(args[1]);
869
+ return `bf->${fn}(${recv}, ${target}, ${pad})`;
870
+ }
821
871
  default: {
822
872
  const _exhaustive = method;
823
873
  throw new Error(`renderArrayMethod: unhandled ArrayMethod '${_exhaustive}'`);
@@ -1 +1 @@
1
- {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
package/dist/build.js CHANGED
@@ -770,7 +770,7 @@ function renderArrayMethod(method, object, args, emit) {
770
770
  switch (method) {
771
771
  case "join": {
772
772
  const obj = emit(object);
773
- const sep = emit(args[0]);
773
+ const sep = args.length >= 1 ? emit(args[0]) : `','`;
774
774
  return `join(${sep}, @{${obj}})`;
775
775
  }
776
776
  case "includes": {
@@ -787,18 +787,21 @@ function renderArrayMethod(method, object, args, emit) {
787
787
  }
788
788
  case "at": {
789
789
  const obj = emit(object);
790
- const idx = emit(args[0]);
790
+ const idx = args.length >= 1 ? emit(args[0]) : "0";
791
791
  return `bf->at(${obj}, ${idx})`;
792
792
  }
793
793
  case "concat": {
794
+ if (args.length === 0) {
795
+ return emit(object);
796
+ }
794
797
  const a = emit(object);
795
798
  const b = emit(args[0]);
796
799
  return `bf->concat(${a}, ${b})`;
797
800
  }
798
801
  case "slice": {
799
802
  const recv = emit(object);
800
- const start = emit(args[0]);
801
- const end = args.length === 2 ? emit(args[1]) : "undef";
803
+ const start = args.length >= 1 ? emit(args[0]) : "0";
804
+ const end = args.length >= 2 ? emit(args[1]) : "undef";
802
805
  return `bf->slice(${recv}, ${start}, ${end})`;
803
806
  }
804
807
  case "reverse":
@@ -818,6 +821,53 @@ function renderArrayMethod(method, object, args, emit) {
818
821
  const recv = emit(object);
819
822
  return `bf->trim(${recv})`;
820
823
  }
824
+ case "split": {
825
+ const recv = emit(object);
826
+ if (args.length === 0) {
827
+ return `bf->split(${recv})`;
828
+ }
829
+ const sep = emit(args[0]);
830
+ if (args.length === 1) {
831
+ return `bf->split(${recv}, ${sep})`;
832
+ }
833
+ const limit = emit(args[1]);
834
+ return `bf->split(${recv}, ${sep}, ${limit})`;
835
+ }
836
+ case "startsWith":
837
+ case "endsWith": {
838
+ const fn = method === "startsWith" ? "starts_with" : "ends_with";
839
+ const recv = emit(object);
840
+ const arg = emit(args[0]);
841
+ if (args.length >= 2) {
842
+ return `bf->${fn}(${recv}, ${arg}, ${emit(args[1])})`;
843
+ }
844
+ return `bf->${fn}(${recv}, ${arg})`;
845
+ }
846
+ case "replace": {
847
+ const recv = emit(object);
848
+ const oldS = emit(args[0]);
849
+ const newS = emit(args[1]);
850
+ return `bf->replace(${recv}, ${oldS}, ${newS})`;
851
+ }
852
+ case "repeat": {
853
+ const recv = emit(object);
854
+ const count = args.length === 0 ? "0" : emit(args[0]);
855
+ return `bf->repeat(${recv}, ${count})`;
856
+ }
857
+ case "padStart":
858
+ case "padEnd": {
859
+ const fn = method === "padStart" ? "pad_start" : "pad_end";
860
+ const recv = emit(object);
861
+ if (args.length === 0) {
862
+ return `bf->${fn}(${recv}, 0)`;
863
+ }
864
+ const target = emit(args[0]);
865
+ if (args.length === 1) {
866
+ return `bf->${fn}(${recv}, ${target})`;
867
+ }
868
+ const pad = emit(args[1]);
869
+ return `bf->${fn}(${recv}, ${target}, ${pad})`;
870
+ }
821
871
  default: {
822
872
  const _exhaustive = method;
823
873
  throw new Error(`renderArrayMethod: unhandled ArrayMethod '${_exhaustive}'`);
package/dist/index.js CHANGED
@@ -770,7 +770,7 @@ function renderArrayMethod(method, object, args, emit) {
770
770
  switch (method) {
771
771
  case "join": {
772
772
  const obj = emit(object);
773
- const sep = emit(args[0]);
773
+ const sep = args.length >= 1 ? emit(args[0]) : `','`;
774
774
  return `join(${sep}, @{${obj}})`;
775
775
  }
776
776
  case "includes": {
@@ -787,18 +787,21 @@ function renderArrayMethod(method, object, args, emit) {
787
787
  }
788
788
  case "at": {
789
789
  const obj = emit(object);
790
- const idx = emit(args[0]);
790
+ const idx = args.length >= 1 ? emit(args[0]) : "0";
791
791
  return `bf->at(${obj}, ${idx})`;
792
792
  }
793
793
  case "concat": {
794
+ if (args.length === 0) {
795
+ return emit(object);
796
+ }
794
797
  const a = emit(object);
795
798
  const b = emit(args[0]);
796
799
  return `bf->concat(${a}, ${b})`;
797
800
  }
798
801
  case "slice": {
799
802
  const recv = emit(object);
800
- const start = emit(args[0]);
801
- const end = args.length === 2 ? emit(args[1]) : "undef";
803
+ const start = args.length >= 1 ? emit(args[0]) : "0";
804
+ const end = args.length >= 2 ? emit(args[1]) : "undef";
802
805
  return `bf->slice(${recv}, ${start}, ${end})`;
803
806
  }
804
807
  case "reverse":
@@ -818,6 +821,53 @@ function renderArrayMethod(method, object, args, emit) {
818
821
  const recv = emit(object);
819
822
  return `bf->trim(${recv})`;
820
823
  }
824
+ case "split": {
825
+ const recv = emit(object);
826
+ if (args.length === 0) {
827
+ return `bf->split(${recv})`;
828
+ }
829
+ const sep = emit(args[0]);
830
+ if (args.length === 1) {
831
+ return `bf->split(${recv}, ${sep})`;
832
+ }
833
+ const limit = emit(args[1]);
834
+ return `bf->split(${recv}, ${sep}, ${limit})`;
835
+ }
836
+ case "startsWith":
837
+ case "endsWith": {
838
+ const fn = method === "startsWith" ? "starts_with" : "ends_with";
839
+ const recv = emit(object);
840
+ const arg = emit(args[0]);
841
+ if (args.length >= 2) {
842
+ return `bf->${fn}(${recv}, ${arg}, ${emit(args[1])})`;
843
+ }
844
+ return `bf->${fn}(${recv}, ${arg})`;
845
+ }
846
+ case "replace": {
847
+ const recv = emit(object);
848
+ const oldS = emit(args[0]);
849
+ const newS = emit(args[1]);
850
+ return `bf->replace(${recv}, ${oldS}, ${newS})`;
851
+ }
852
+ case "repeat": {
853
+ const recv = emit(object);
854
+ const count = args.length === 0 ? "0" : emit(args[0]);
855
+ return `bf->repeat(${recv}, ${count})`;
856
+ }
857
+ case "padStart":
858
+ case "padEnd": {
859
+ const fn = method === "padStart" ? "pad_start" : "pad_end";
860
+ const recv = emit(object);
861
+ if (args.length === 0) {
862
+ return `bf->${fn}(${recv}, 0)`;
863
+ }
864
+ const target = emit(args[0]);
865
+ if (args.length === 1) {
866
+ return `bf->${fn}(${recv}, ${target})`;
867
+ }
868
+ const pad = emit(args[1]);
869
+ return `bf->${fn}(${recv}, ${target}, ${pad})`;
870
+ }
821
871
  default: {
822
872
  const _exhaustive = method;
823
873
  throw new Error(`renderArrayMethod: unhandled ArrayMethod '${_exhaustive}'`);
package/lib/BarefootJS.pm CHANGED
@@ -526,6 +526,172 @@ sub trim ($self, $recv) {
526
526
  return $s;
527
527
  }
528
528
 
529
+ # `String.prototype.split(sep)` (#1448 Tier B) — string → ARRAY ref.
530
+ #
531
+ # Two JS-parity wrinkles drive the helper (a bare `split` emit would
532
+ # diverge from both JS and Go):
533
+ #
534
+ # * Perl's `split` treats its first argument as a *regex*, so a
535
+ # separator like '.' or '|' would match far too much. We
536
+ # `quotemeta` it to force literal-string matching, mirroring JS's
537
+ # string-separator semantics (the regex-separator form stays
538
+ # refused upstream — see the parser arm).
539
+ # * Perl's `split` drops trailing empty fields by default; JS keeps
540
+ # them (`"a,".split(",")` is `["a", ""]`). Passing the `-1` limit
541
+ # preserves them, matching JS and Go's `strings.Split`.
542
+ #
543
+ # An empty separator splits into individual characters (JS + Go agree).
544
+ # Undef receiver renders as the single-element `['']` — the same
545
+ # "missing prop → empty string" convention `bf->trim` uses.
546
+
547
+ sub split ($self, $recv, $sep = undef, $limit = undef) {
548
+ my $s = defined $recv && !ref($recv) ? "$recv" : '';
549
+
550
+ my @parts;
551
+ if (!defined $sep) {
552
+ # No separator → the whole string in a single-element array
553
+ # (matches JS `"x".split()` / `.split(undefined)`).
554
+ @parts = ($s);
555
+ }
556
+ elsif ("$sep" eq '') {
557
+ # Empty separator → individual characters. No `-1` limit here:
558
+ # on an empty pattern Perl's `split` with `-1` appends a spurious
559
+ # trailing empty field ("abc" → 'a','b','c',''), which JS/Go don't.
560
+ @parts = split //, $s;
561
+ }
562
+ elsif ($s eq '') {
563
+ # Empty input with a non-empty separator: JS `"".split(",")` is
564
+ # `[""]` and Go's `strings.Split("", ",")` is `[""]`, but Perl's
565
+ # `split /,/, ''` returns the empty list — special-case for parity.
566
+ @parts = ('');
567
+ }
568
+ else {
569
+ # `quotemeta` forces literal-string matching (JS string-separator
570
+ # semantics); the `-1` keeps trailing empty fields (JS keeps them,
571
+ # Perl's bare `split` drops them).
572
+ my $q = quotemeta("$sep");
573
+ @parts = split /$q/, $s, -1;
574
+ }
575
+
576
+ # Optional `limit` caps the number of pieces (JS `split(sep, limit)`).
577
+ # 0 → empty; a negative limit keeps all (JS ToUint32 wrap makes it
578
+ # effectively unbounded) — both match Go's `bf_split`.
579
+ if (defined $limit) {
580
+ my $n = int($limit);
581
+ if ($n == 0) { @parts = () }
582
+ elsif ($n > 0 && $n < scalar @parts) { @parts = @parts[0 .. $n - 1] }
583
+ }
584
+
585
+ return [@parts];
586
+ }
587
+
588
+ # `String.prototype.startsWith(prefix, position?)` (#1448 Tier B) —
589
+ # string → boolean (1 / 0). `substr`-anchored literal comparison mirrors
590
+ # Go's `strings.HasPrefix`. An empty prefix is always true (JS parity);
591
+ # undef / non-string receivers coerce to the empty string first. The
592
+ # optional `position` re-anchors the test (clamped to `[0, length]`),
593
+ # matching JS `"abc".startsWith("b", 1)`.
594
+
595
+ sub starts_with ($self, $recv, $prefix, $position = undef) {
596
+ my $s = defined $recv && !ref($recv) ? "$recv" : '';
597
+ my $p = defined $prefix ? "$prefix" : '';
598
+ if (defined $position) {
599
+ my $n = int($position);
600
+ $n = 0 if $n < 0;
601
+ $n = length($s) if $n > length($s);
602
+ $s = substr($s, $n);
603
+ }
604
+ return substr($s, 0, length $p) eq $p ? 1 : 0;
605
+ }
606
+
607
+ # `String.prototype.endsWith(suffix, endPosition?)` (#1448 Tier B) —
608
+ # string → boolean (1 / 0). Mirrors Go's `strings.HasSuffix`. An empty
609
+ # suffix is always true (JS parity); a suffix longer than the string is
610
+ # false. `substr($s, -length $x)` would mis-read the whole string when
611
+ # `length $x == 0`, so that case short-circuits. The optional
612
+ # `endPosition` treats the string as if it were only that many chars
613
+ # long (clamped to `[0, length]`), matching JS `"abc".endsWith("b", 2)`.
614
+
615
+ sub ends_with ($self, $recv, $suffix, $end_position = undef) {
616
+ my $s = defined $recv && !ref($recv) ? "$recv" : '';
617
+ my $x = defined $suffix ? "$suffix" : '';
618
+ if (defined $end_position) {
619
+ my $e = int($end_position);
620
+ $e = 0 if $e < 0;
621
+ $e = length($s) if $e > length($s);
622
+ $s = substr($s, 0, $e);
623
+ }
624
+ return 1 if $x eq '';
625
+ return 0 if length($s) < length($x);
626
+ return substr($s, -length $x) eq $x ? 1 : 0;
627
+ }
628
+
629
+ # `String.prototype.replace(pattern, replacement)` — string-pattern
630
+ # form only (#1448 Tier B), replacing the FIRST occurrence (JS string-
631
+ # pattern semantics). Spliced via index/substr rather than `s///` so
632
+ # BOTH the pattern and the replacement are literal: no Perl regex
633
+ # metacharacters in the pattern and no `$1` / `$&` interpolation in the
634
+ # replacement. Go's `bf_replace` (strings.Replace, n=1) treats the
635
+ # replacement literally too, so the two adapters stay byte-equal — this
636
+ # diverges from JS only for replacement strings containing `$`-patterns
637
+ # (rare in template position). An empty pattern inserts the replacement
638
+ # at the front (`"abc".replace("", "X")` → "Xabc"), matching JS + Go.
639
+
640
+ sub replace ($self, $recv, $pattern, $replacement) {
641
+ my $s = defined $recv && !ref($recv) ? "$recv" : '';
642
+ my $o = defined $pattern ? "$pattern" : '';
643
+ my $n = defined $replacement ? "$replacement" : '';
644
+ return $n . $s if $o eq '';
645
+ my $i = index($s, $o);
646
+ return $s if $i < 0;
647
+ return substr($s, 0, $i) . $n . substr($s, $i + length($o));
648
+ }
649
+
650
+ # `String.prototype.repeat(n)` — the receiver concatenated n times
651
+ # (#1448 Tier B), via Perl's `x` operator. JS throws RangeError for a
652
+ # negative count, but SSR templates degrade to the empty string rather
653
+ # than dying mid-render, so a count <= 0 returns "" (Go's `bf_repeat`
654
+ # applies the same clamp). The count is truncated toward zero
655
+ # (`int`), matching JS's ToIntegerOrInfinity on `"a".repeat(3.7)`.
656
+
657
+ sub repeat ($self, $recv, $count) {
658
+ my $s = defined $recv && !ref($recv) ? "$recv" : '';
659
+ my $n = defined $count ? int($count) : 0;
660
+ return $n <= 0 ? '' : $s x $n;
661
+ }
662
+
663
+ # `String.prototype.padStart` / `padEnd` (#1448 Tier B) — pad the
664
+ # receiver to `$target` characters with `$pad` (default a single space)
665
+ # repeated and truncated to fill, prepended or appended. Length is
666
+ # measured in characters (Perl `length`), matching Go's rune-based
667
+ # `bf_pad_*` — diverges from JS's UTF-16-unit length only for
668
+ # astral-plane input. An empty pad, or a receiver already >= `$target`,
669
+ # returns the receiver unchanged (JS parity). The `$target` is
670
+ # truncated toward zero (JS ToLength on the first arg).
671
+
672
+ sub _pad ($s, $target, $pad, $at_start) {
673
+ $pad = ' ' unless defined $pad;
674
+ $pad = "$pad";
675
+ return $s if $pad eq '';
676
+ my $len = length $s;
677
+ my $t = int($target // 0);
678
+ return $s if $len >= $t;
679
+ my $need = $t - $len;
680
+ # Repeat enough copies to cover $need, then trim to exactly $need.
681
+ my $fill = substr($pad x (int($need / length($pad)) + 1), 0, $need);
682
+ return $at_start ? $fill . $s : $s . $fill;
683
+ }
684
+
685
+ sub pad_start ($self, $recv, $target, $pad = undef) {
686
+ my $s = defined $recv && !ref($recv) ? "$recv" : '';
687
+ return _pad($s, $target, $pad, 1);
688
+ }
689
+
690
+ sub pad_end ($self, $recv, $target, $pad = undef) {
691
+ my $s = defined $recv && !ref($recv) ? "$recv" : '';
692
+ return _pad($s, $target, $pad, 0);
693
+ }
694
+
529
695
  # `Array.prototype.sort(cmp)` / `Array.prototype.toSorted(cmp)`
530
696
  # lowering (#1448 Tier B). Non-mutating — JS's mutate-vs-new
531
697
  # distinction is moot in SSR template context.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/mojolicious",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Mojolicious EP template adapter for BarefootJS - generates .html.ep files from IR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,14 +52,14 @@
52
52
  "directory": "packages/adapter-mojolicious"
53
53
  },
54
54
  "dependencies": {
55
- "@barefootjs/shared": "0.5.2"
55
+ "@barefootjs/shared": "0.6.0"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@barefootjs/jsx": ">=0.2.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@barefootjs/adapter-tests": "0.1.0",
62
- "@barefootjs/jsx": "0.5.2",
62
+ "@barefootjs/jsx": "0.6.0",
63
63
  "typescript": "^5.0.0"
64
64
  }
65
65
  }
@@ -928,11 +928,26 @@ import { fixture as arrayLastIndexOfFixture } from '../../../adapter-tests/fixtu
928
928
  import { fixture as arrayAtFixture } from '../../../adapter-tests/fixtures/methods/array-at'
929
929
  import { fixture as arrayConcatFixture } from '../../../adapter-tests/fixtures/methods/array-concat'
930
930
  import { fixture as arraySliceFixture } from '../../../adapter-tests/fixtures/methods/array-slice'
931
+ import { fixture as arraySliceCopyFixture } from '../../../adapter-tests/fixtures/methods/array-slice-copy'
932
+ import { fixture as arrayJoinDefaultFixture } from '../../../adapter-tests/fixtures/methods/array-join-default'
933
+ import { fixture as arrayAtDefaultFixture } from '../../../adapter-tests/fixtures/methods/array-at-default'
934
+ import { fixture as arrayConcatCopyFixture } from '../../../adapter-tests/fixtures/methods/array-concat-copy'
931
935
  import { fixture as arrayReverseFixture } from '../../../adapter-tests/fixtures/methods/array-reverse'
932
936
  import { fixture as arrayToReversedFixture } from '../../../adapter-tests/fixtures/methods/array-toReversed'
933
937
  import { fixture as stringToLowerCaseFixture } from '../../../adapter-tests/fixtures/methods/string-toLowerCase'
934
938
  import { fixture as stringToUpperCaseFixture } from '../../../adapter-tests/fixtures/methods/string-toUpperCase'
935
939
  import { fixture as stringTrimFixture } from '../../../adapter-tests/fixtures/methods/string-trim'
940
+ // #1448 Tier B — string methods.
941
+ import { fixture as stringSplitFixture } from '../../../adapter-tests/fixtures/methods/string-split'
942
+ import { fixture as stringSplitLimitFixture } from '../../../adapter-tests/fixtures/methods/string-split-limit'
943
+ import { fixture as stringStartsWithFixture } from '../../../adapter-tests/fixtures/methods/string-startsWith'
944
+ import { fixture as stringStartsWithPositionFixture } from '../../../adapter-tests/fixtures/methods/string-startsWith-position'
945
+ import { fixture as stringEndsWithFixture } from '../../../adapter-tests/fixtures/methods/string-endsWith'
946
+ import { fixture as stringEndsWithPositionFixture } from '../../../adapter-tests/fixtures/methods/string-endsWith-position'
947
+ import { fixture as stringReplaceFixture } from '../../../adapter-tests/fixtures/methods/string-replace'
948
+ import { fixture as stringRepeatFixture } from '../../../adapter-tests/fixtures/methods/string-repeat'
949
+ import { fixture as stringPadStartFixture } from '../../../adapter-tests/fixtures/methods/string-padStart'
950
+ import { fixture as stringPadEndFixture } from '../../../adapter-tests/fixtures/methods/string-padEnd'
936
951
  // #1448 Tier B — .sort / .toSorted fixtures (loop-chained + standalone).
937
952
  import { fixture as arraySortFieldAscFixture } from '../../../adapter-tests/fixtures/methods/array-sort-field-asc'
938
953
  import { fixture as arraySortFieldDescFixture } from '../../../adapter-tests/fixtures/methods/array-sort-field-desc'
@@ -955,6 +970,12 @@ describe('MojoAdapter - #1448 Tier A/B fixture-driven lowering pins', () => {
955
970
  { fixture: arrayAtFixture, expect: 'bf->at($items, -1)' },
956
971
  { fixture: arrayConcatFixture, expect: 'bf->concat($left, $right)' },
957
972
  { fixture: arraySliceFixture, expect: 'bf->slice($items, 1, 3)' },
973
+ // #1448 full-arity — zero-arg defaults.
974
+ { fixture: arraySliceCopyFixture, expect: 'bf->slice($items, 0, undef)' },
975
+ { fixture: arrayJoinDefaultFixture, expect: `join(',', @{$items})` },
976
+ // `.at()` → index 0; `.concat()` → the receiver (shallow copy).
977
+ { fixture: arrayAtDefaultFixture, expect: 'bf->at($items, 0)' },
978
+ { fixture: arrayConcatCopyFixture, expect: `join('|', @{$items})` },
958
979
  { fixture: arrayReverseFixture, expect: 'bf->reverse($items)' },
959
980
  // .toReversed shares the helper with .reverse — pinning both
960
981
  // routings catches a future divergence between them.
@@ -962,6 +983,23 @@ describe('MojoAdapter - #1448 Tier A/B fixture-driven lowering pins', () => {
962
983
  { fixture: stringToLowerCaseFixture,expect: 'lc($value)' },
963
984
  { fixture: stringToUpperCaseFixture,expect: 'uc($value)' },
964
985
  { fixture: stringTrimFixture, expect: 'bf->trim($value)' },
986
+ // #1448 Tier B — string → array. `.split(',')` lowers to
987
+ // `bf->split`, here chained into `.join('|')` so the array ref is
988
+ // observable (`join('|', @{bf->split($value, ',')})`).
989
+ { fixture: stringSplitFixture, expect: `bf->split($value, ',')` },
990
+ { fixture: stringSplitLimitFixture, expect: `bf->split($value, ',', 2)` },
991
+ // #1448 Tier B — string → boolean at condition position (`% if`).
992
+ { fixture: stringStartsWithFixture, expect: 'bf->starts_with($value, $prefix)' },
993
+ { fixture: stringStartsWithPositionFixture, expect: `bf->starts_with($value, 'world', 6)` },
994
+ { fixture: stringEndsWithFixture, expect: 'bf->ends_with($value, $suffix)' },
995
+ { fixture: stringEndsWithPositionFixture, expect: `bf->ends_with($value, 'hello', 5)` },
996
+ // #1448 Tier B — string → string, first-occurrence replace.
997
+ { fixture: stringReplaceFixture, expect: `bf->replace($value, 'o', '0')` },
998
+ // #1448 Tier B — string → string, repeat n times.
999
+ { fixture: stringRepeatFixture, expect: 'bf->repeat($value, 3)' },
1000
+ // #1448 Tier B — string → string, padded to a target width.
1001
+ { fixture: stringPadStartFixture, expect: `bf->pad_start($value, 5, '0')` },
1002
+ { fixture: stringPadEndFixture, expect: `bf->pad_end($value, 5, '.')` },
965
1003
  // #1448 Tier B — sort / toSorted. The loop-chained field cases
966
1004
  // hoist into a `my $bf_iter_lN = bf->sort(...)` local; the
967
1005
  // standalone primitive cases inline the call. Each comparison key
@@ -1065,15 +1103,21 @@ export function C() {
1065
1103
  { name: 'reduce', expr: `items().reduce((a, b) => a + b.n, 0)`, badEmit: '->{reduce}' },
1066
1104
  { name: 'flatMap', expr: `items().flatMap(i => i.tags)`, badEmit: '->{flatMap}' },
1067
1105
  { name: 'flat', expr: `items().flat()`, badEmit: '->{flat}' },
1106
+ // Lowered methods whose MEANINGFUL extra argument isn't lowered yet
1107
+ // (#1448): the `fromIndex` of `.includes`/`.indexOf`/`.lastIndexOf`
1108
+ // and the variadic `.concat`. The parser refuses these (silently
1109
+ // dropping the arg would change the result). (The zero-arg defaults
1110
+ // `.join()`/`.slice()` and JS-ignored trailing args like `.trim(1)`
1111
+ // are accepted — pinned in the positive blocks.)
1112
+ { name: 'includes (2-arg fromIndex)', expr: `items().includes("a", 1)`, badEmit: '->{includes}' },
1113
+ { name: 'concat (variadic)', expr: `items().concat(items(), items())`, badEmit: '->{concat}' },
1068
1114
  // Tier B/C string methods — previously slipped through with no
1069
- // diagnostic; now routed through the AST / `isSupported` gate.
1070
- { name: 'split', expr: `name().split(",")`, badEmit: '->{split}' },
1071
- { name: 'startsWith', expr: `name().startsWith("a")`, badEmit: '->{startsWith}' },
1072
- { name: 'endsWith', expr: `name().endsWith("z")`, badEmit: '->{endsWith}' },
1073
- { name: 'replace', expr: `name().replace("a", "b")`, badEmit: '->{replace}' },
1074
- { name: 'repeat', expr: `name().repeat(3)`, badEmit: '->{repeat}' },
1075
- { name: 'padStart', expr: `name().padStart(5, "0")`, badEmit: '->{padStart}' },
1076
- { name: 'padEnd', expr: `name().padEnd(5, "0")`, badEmit: '->{padEnd}' },
1115
+ // diagnostic; now routed through the AST / `isSupported` gate. The
1116
+ // full Tier B string set (`split`, `startsWith`, `endsWith`,
1117
+ // `replace`, `repeat`, `padStart`, `padEnd`) has since landed its
1118
+ // full-arity lowering and moved to the positive fixture-pin block
1119
+ // above (the regex-pattern `replace` form stays refused — pinned
1120
+ // separately below). `charAt` is Tier C and stays refused entirely.
1077
1121
  { name: 'charAt', expr: `name().charAt(0)`, badEmit: '->{charAt}' },
1078
1122
  ]
1079
1123
  for (const { name, expr, badEmit } of unsupported) {
@@ -1118,24 +1162,43 @@ export function C(props: { config: string }) {
1118
1162
  })
1119
1163
 
1120
1164
  // Predicate-level use of an unsupported string method also fails the
1121
- // build loudly (intended): a `.filter(t => t.name.startsWith("a"))`
1165
+ // build loudly (intended): a `.filter(t => t.name.charAt(0) === "a")`
1122
1166
  // whose predicate calls one of the gated methods now refuses the whole
1123
1167
  // loop with BF101 (via the shared `isSupported` predicate gate in
1124
- // jsx-to-ir) rather than lowering to a broken `->{startsWith}` inside
1168
+ // jsx-to-ir) rather than lowering to a broken `->{charAt}` inside
1125
1169
  // the grep. Pinning this so the loud-failure contract can't silently
1126
- // regress back to the old emit-broken-template behaviour.
1170
+ // regress back to the old emit-broken-template behaviour. (`charAt`
1171
+ // is a Tier C method that stays refused — earlier this test used
1172
+ // `startsWith`, which has since landed its Tier B lowering.)
1127
1173
  test('unsupported string method inside a .filter() predicate raises BF101', () => {
1128
1174
  const result = compileJSX(`
1129
1175
  "use client"
1130
1176
  import { createSignal } from "@barefootjs/client"
1131
1177
  export function C() {
1132
1178
  const [items, setItems] = createSignal<{ name: string }[]>([])
1133
- return <ul>{items().filter(t => t.name.startsWith("a")).map(t => <li key={t.name}>{t.name}</li>)}</ul>
1179
+ return <ul>{items().filter(t => t.name.charAt(0) === "a").map(t => <li key={t.name}>{t.name}</li>)}</ul>
1134
1180
  }
1135
1181
  `.trimStart(), 'test.tsx', { adapter: new MojoAdapter() })
1136
1182
  expect(result.errors?.some(e => e.code === 'BF101')).toBe(true)
1137
1183
  })
1138
1184
 
1185
+ // The string-pattern form of `.replace` lowers (#1448 Tier B), but
1186
+ // the regex-pattern form stays refused with BF101 — the Perl `s///`
1187
+ // vs Go `regexp.ReplaceAllString` flavour gap is the open design
1188
+ // question. Pinning the refusal so it can't regress into a broken
1189
+ // `->{replace}` emit for the regex form.
1190
+ test('regex-pattern .replace raises BF101 (string-pattern form is lowered)', () => {
1191
+ const result = compileJSX(`
1192
+ function C({ value }: { value: string }) {
1193
+ return <div>{value.replace(/o/g, "0")}</div>
1194
+ }
1195
+ export { C }
1196
+ `.trimStart(), 'test.tsx', { adapter: new MojoAdapter() })
1197
+ expect(result.errors?.some(e => e.code === 'BF101')).toBe(true)
1198
+ const template = result.files?.find(f => f.path.endsWith('.html.ep'))?.content ?? ''
1199
+ expect(template).not.toContain('->{replace}')
1200
+ })
1201
+
1139
1202
  // Tier B `.sort` / `.toSorted` follow-ups still refused with BF021.
1140
1203
  // The Mojo client-only loop placeholder is an empty element (the
1141
1204
  // client runtime repopulates it via the `bf-s` scope marker), so the
@@ -1172,7 +1235,9 @@ export function C() {
1172
1235
  // more `HASH ref` crash), so we assert the build error rather than a
1173
1236
  // render crash. Skipped on hosts without Mojolicious installed.
1174
1237
  test('e2e: @client renders placeholder; bare is caught at build with BF101', async () => {
1175
- const bare = emit(`name().repeat(3)`, false)
1238
+ // Uses the Tier C `charAt` (still refused) — earlier this test used
1239
+ // `repeat`, which has since landed its #1448 Tier B lowering.
1240
+ const bare = emit(`name().charAt(0)`, false)
1176
1241
  expect(bare.errors.some(e => e.code === 'BF101')).toBe(true)
1177
1242
 
1178
1243
  try {
@@ -1182,7 +1247,7 @@ export function C() {
1182
1247
  import { createSignal } from "@barefootjs/client"
1183
1248
  export function C() {
1184
1249
  const [name, setName] = createSignal("hello")
1185
- return <div>{/* @client */ name().repeat(3)}</div>
1250
+ return <div>{/* @client */ name().charAt(0)}</div>
1186
1251
  }
1187
1252
  `.trimStart(),
1188
1253
  adapter: new MojoAdapter(),
@@ -1295,9 +1295,10 @@ function renderArrayMethod(
1295
1295
  // arr.join(sep) → join(sep, @{arr}). The default `${obj}->{join}`
1296
1296
  // hash-lookup fallback would emit invalid Perl, which is why the
1297
1297
  // IR carves out a dedicated method node instead of routing
1298
- // through the generic call dispatcher.
1298
+ // through the generic call dispatcher. `.join()` defaults the
1299
+ // separator to `,` (JS) and ignores any extra argument.
1299
1300
  const obj = emit(object)
1300
- const sep = emit(args[0])
1301
+ const sep = args.length >= 1 ? emit(args[0]) : `','`
1301
1302
  return `join(${sep}, @{${obj}})`
1302
1303
  }
1303
1304
  case 'includes': {
@@ -1333,9 +1334,10 @@ function renderArrayMethod(
1333
1334
  // `.at(i)` with negative-index support — `.at(-1)` is the
1334
1335
  // last element. The Mojo helper wraps the same `length + i`
1335
1336
  // arithmetic the Go `bf_at` does so the lowering stays
1336
- // symmetric across adapters.
1337
+ // symmetric across adapters. `.at()` with no argument is `.at(0)`
1338
+ // (the first element); extra arguments are ignored.
1337
1339
  const obj = emit(object)
1338
- const idx = emit(args[0])
1340
+ const idx = args.length >= 1 ? emit(args[0]) : '0'
1339
1341
  return `bf->at(${obj}, ${idx})`
1340
1342
  }
1341
1343
  case 'concat': {
@@ -1343,20 +1345,26 @@ function renderArrayMethod(
1343
1345
  // ref so the result composes with `.join(...)` / other
1344
1346
  // array-shape methods downstream (the canonical Tier A
1345
1347
  // conformance fixture chains `.concat(...).join(' ')`).
1348
+ // `.concat()` with no argument is a shallow copy — indistinguishable
1349
+ // from the receiver in an SSR snapshot, so it lowers to the receiver.
1350
+ if (args.length === 0) {
1351
+ return emit(object)
1352
+ }
1346
1353
  const a = emit(object)
1347
1354
  const b = emit(args[0])
1348
1355
  return `bf->concat(${a}, ${b})`
1349
1356
  }
1350
1357
  case 'slice': {
1351
- // `.slice(start)` / `.slice(start, end)`. The Mojo helper
1352
- // mirrors the Go arithmetic (negative-index normalisation,
1353
- // out-of-bounds clamping, empty result on start >= end).
1354
- // Absent `end` lowers as `undef`, which the helper treats as
1355
- // "to length". Returns a new ARRAY ref so the result composes
1356
- // with `.join(...)` downstream.
1358
+ // `.slice()` / `.slice(start)` / `.slice(start, end)`. The Mojo
1359
+ // helper mirrors the Go arithmetic (negative-index normalisation,
1360
+ // out-of-bounds clamping, empty result on start >= end). A
1361
+ // missing `start` defaults to 0 (full copy); an absent `end`
1362
+ // lowers as `undef`, which the helper treats as "to length". JS
1363
+ // ignores a third+ argument. Returns a new ARRAY ref so the
1364
+ // result composes with `.join(...)` downstream.
1357
1365
  const recv = emit(object)
1358
- const start = emit(args[0])
1359
- const end = args.length === 2 ? emit(args[1]) : 'undef'
1366
+ const start = args.length >= 1 ? emit(args[0]) : '0'
1367
+ const end = args.length >= 2 ? emit(args[1]) : 'undef'
1360
1368
  return `bf->slice(${recv}, ${start}, ${end})`
1361
1369
  }
1362
1370
  case 'reverse':
@@ -1388,6 +1396,86 @@ function renderArrayMethod(
1388
1396
  const recv = emit(object)
1389
1397
  return `bf->trim(${recv})`
1390
1398
  }
1399
+ case 'split': {
1400
+ // `.split()` / `.split(sep)` / `.split(sep, limit)` — string →
1401
+ // ARRAY ref via `bf->split`. With no separator the helper returns
1402
+ // the whole string as a single element; otherwise it quotemetas
1403
+ // the separator (literal match, not regex) and keeps trailing
1404
+ // empties (`-1`), staying byte-equal with Go's `bf_split`. The
1405
+ // optional `limit` caps the pieces; JS ignores a third+ argument.
1406
+ // See #1448 Tier B.
1407
+ const recv = emit(object)
1408
+ if (args.length === 0) {
1409
+ return `bf->split(${recv})`
1410
+ }
1411
+ const sep = emit(args[0])
1412
+ if (args.length === 1) {
1413
+ return `bf->split(${recv}, ${sep})`
1414
+ }
1415
+ const limit = emit(args[1])
1416
+ return `bf->split(${recv}, ${sep}, ${limit})`
1417
+ }
1418
+ case 'startsWith':
1419
+ case 'endsWith': {
1420
+ // `.startsWith(prefix, position?)` / `.endsWith(suffix,
1421
+ // endPosition?)` — string → boolean. The Perl helpers
1422
+ // (`bf->starts_with` / `bf->ends_with`) do a `substr`-anchored
1423
+ // comparison so the search string is matched literally (no regex
1424
+ // metachar surprises) and undef receivers stay quiet. The optional
1425
+ // second argument re-anchors the test; JS ignores a third+
1426
+ // argument. See #1448 Tier B.
1427
+ const fn = method === 'startsWith' ? 'starts_with' : 'ends_with'
1428
+ const recv = emit(object)
1429
+ const arg = emit(args[0])
1430
+ if (args.length >= 2) {
1431
+ return `bf->${fn}(${recv}, ${arg}, ${emit(args[1])})`
1432
+ }
1433
+ return `bf->${fn}(${recv}, ${arg})`
1434
+ }
1435
+ case 'replace': {
1436
+ // `.replace(old, new)` — string-pattern form, first occurrence.
1437
+ // The `bf->replace` helper splices via index/substr (not `s///`)
1438
+ // so both the pattern and the replacement are literal — no Perl
1439
+ // regex metacharacters and no `$1` / `$&` interpolation in the
1440
+ // replacement, keeping it byte-equal with Go's `bf_replace`. The
1441
+ // regex-pattern form is refused upstream at the parser. See
1442
+ // #1448 Tier B.
1443
+ const recv = emit(object)
1444
+ const oldS = emit(args[0])
1445
+ const newS = emit(args[1])
1446
+ return `bf->replace(${recv}, ${oldS}, ${newS})`
1447
+ }
1448
+ case 'repeat': {
1449
+ // `.repeat(n)` — string repeated `n` times. The `bf->repeat`
1450
+ // helper wraps Perl's `x` operator with the same negative-count
1451
+ // → "" clamp and integer truncation Go's `bf_repeat` applies, so
1452
+ // the two adapters stay byte-equal. Full JS arity: the no-argument
1453
+ // form is `repeat(0)` → ""; a second+ argument is ignored.
1454
+ // See #1448 Tier B.
1455
+ const recv = emit(object)
1456
+ const count = args.length === 0 ? '0' : emit(args[0])
1457
+ return `bf->repeat(${recv}, ${count})`
1458
+ }
1459
+ case 'padStart':
1460
+ case 'padEnd': {
1461
+ // `.padStart(target, pad?)` / `.padEnd(target, pad?)`. The
1462
+ // `bf->pad_*` helpers default the pad to a single space when the
1463
+ // arg is omitted and measure length in characters, matching Go's
1464
+ // rune-based `bf_pad_*`. Full JS arity: the no-argument form is
1465
+ // `padStart(0)` → the receiver unchanged; a third+ argument is
1466
+ // ignored. See #1448 Tier B.
1467
+ const fn = method === 'padStart' ? 'pad_start' : 'pad_end'
1468
+ const recv = emit(object)
1469
+ if (args.length === 0) {
1470
+ return `bf->${fn}(${recv}, 0)`
1471
+ }
1472
+ const target = emit(args[0])
1473
+ if (args.length === 1) {
1474
+ return `bf->${fn}(${recv}, ${target})`
1475
+ }
1476
+ const pad = emit(args[1])
1477
+ return `bf->${fn}(${recv}, ${target}, ${pad})`
1478
+ }
1391
1479
  default: {
1392
1480
  // TS-level exhaustiveness guard. If this throws at runtime, the
1393
1481
  // IR was constructed against a newer `ArrayMethod` variant that