@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.
- package/dist/adapter/index.js +54 -4
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/build.js +54 -4
- package/dist/index.js +54 -4
- package/lib/BarefootJS.pm +166 -0
- package/package.json +3 -3
- package/src/__tests__/mojo-adapter.test.ts +79 -14
- package/src/adapter/mojo-adapter.ts +100 -12
package/dist/adapter/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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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.
|
|
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 `->{
|
|
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.
|
|
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
|
-
|
|
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().
|
|
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
|
|
1352
|
-
// mirrors the Go arithmetic (negative-index normalisation,
|
|
1353
|
-
// out-of-bounds clamping, empty result on start >= end).
|
|
1354
|
-
//
|
|
1355
|
-
//
|
|
1356
|
-
//
|
|
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
|
|
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
|