Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1557,14 +1557,19 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt
| [ ExprType String ] -> Helper.InstanceCall(c, "rfind", t, args, i.SignatureArgTypes, ?loc = r) |> Some
| [ ExprType Char as str; ExprType(Number(Int32, NumberInfo.Empty)) as start ]
| [ ExprType String as str; ExprType(Number(Int32, NumberInfo.Empty)) as start ] ->
// .NET's LastIndexOf(value, startIndex) searches the inclusive window [0, startIndex],
// so the exclusive end passed to Python's rfind must be startIndex + 1.
let endIdx =
makeBinOp r (Number(Int32, NumberInfo.Empty)) start (makeIntConst 1) BinaryPlus

Helper.InstanceCall(
c,
"rfind",
t,
[
str
Value(NumberConstant(NumberValue.Int32 0, NumberInfo.Empty), None)
start
endIdx
],
i.SignatureArgTypes,
?loc = r
Expand Down
10 changes: 10 additions & 0 deletions src/fable-library-beam/fable_string.erl
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,13 @@ remove(Str, StartIdx, Count) ->
binary:part(Str, StartIdx + Count, byte_size(Str) - StartIdx - Count)
]).

starts_with(_Str, <<>>) ->
true;
starts_with(Str, Prefix) ->
binary:match(Str, Prefix) =:= {0, byte_size(Prefix)}.

ends_with(_Str, <<>>) ->
true;
ends_with(Str, Suffix) ->
SuffixLen = byte_size(Suffix),
StrLen = byte_size(Str),
Expand Down Expand Up @@ -262,6 +266,8 @@ filter(Fn, Str) ->
index_of(Str, Sub) ->
index_of(Str, Sub, 0).

index_of(_Str, <<>>, StartIdx) ->
StartIdx;
index_of(Str, Sub, StartIdx) ->
SearchStr = binary:part(Str, StartIdx, byte_size(Str) - StartIdx),
case binary:match(SearchStr, Sub) of
Expand Down Expand Up @@ -293,6 +299,8 @@ index_of_any_loop([C | Rest], CharSet, Idx, StartIdx) ->
last_index_of(Str, Sub) ->
last_index_of(Str, Sub, byte_size(Str) - 1).

last_index_of(Str, <<>>, MaxIdx) ->
erlang:min(MaxIdx + 1, byte_size(Str));
last_index_of(Str, Sub, MaxIdx) ->
SubLen = byte_size(Sub),
SearchLen = erlang:min(MaxIdx + SubLen, byte_size(Str)),
Expand All @@ -302,6 +310,8 @@ last_index_of(Str, Sub, MaxIdx) ->
Matches -> element(1, lists:last(Matches))
end.

contains(_Str, <<>>) ->
true;
contains(Str, Sub) ->
binary:match(Str, Sub) =/= nomatch.

Expand Down
2 changes: 1 addition & 1 deletion src/fable-library-ts/String.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function endsWith(str: string, pattern: string, ic: boolean | StringCompa
return str.endsWith(pattern);
}
if (str.length >= pattern.length) {
return cmp(str.slice(-pattern.length), pattern, ic) === 0;
return cmp(str.slice(str.length - pattern.length), pattern, ic) === 0;
}
return false;
}
Expand Down
28 changes: 28 additions & 0 deletions tests/Beam/StringTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ let ``test String.EndsWith with OrdinalIgnoreCase works`` () =
"ABCD".EndsWith("xabcd", StringComparison.OrdinalIgnoreCase) |> equal false
"ABCD".EndsWith("abcd", StringComparison.OrdinalIgnoreCase) |> equal true

[<Fact>]
let ``test String.StartsWith and String.EndsWith with empty pattern works`` () =
// .NET: every string starts/ends with ""
"abcd".StartsWith("") |> equal true
"abcd".EndsWith("") |> equal true
"".StartsWith("") |> equal true
"".EndsWith("") |> equal true
"abcd".StartsWith("", StringComparison.OrdinalIgnoreCase) |> equal true
"abcd".EndsWith("", StringComparison.OrdinalIgnoreCase) |> equal true

// --- Substring ---

[<Fact>]
Expand All @@ -197,6 +207,12 @@ let ``test String.Contains works`` () =
"ABC".Contains("B") |> equal true
"ABC".Contains("Z") |> equal false

[<Fact>]
let ``test String.Contains with empty pattern works`` () =
// .NET: every string contains ""
"ABC".Contains("") |> equal true
"".Contains("") |> equal true

// --- IndexOf ---

[<Fact>]
Expand All @@ -207,6 +223,12 @@ let ``test String.IndexOf works`` () =
let ``test String.IndexOf works with offset`` () =
"abcdbc".IndexOf("bc", 3) |> equal 4

[<Fact>]
let ``test String.IndexOf with empty pattern works`` () =
"abcdbc".IndexOf("") |> equal 0
"abcdbc".IndexOf("", 3) |> equal 3
"abcdbc".IndexOf("", 6) |> equal 6

[<Fact>]
let ``test String.IndexOf char works`` () =
"abcd".IndexOf('b') * 100 + "abcd".IndexOf('e') |> equal 99
Expand Down Expand Up @@ -253,6 +275,12 @@ let ``test String.LastIndexOf with index and StringComparison`` () =
"abcdbcebc".LastIndexOf("b", 3, StringComparison.Ordinal)
|> equal 1

[<Fact>]
let ``test String.LastIndexOf with empty pattern works`` () =
"abcdbc".LastIndexOf("") |> equal 6
"abcdbc".LastIndexOf("", 3) |> equal 4
"abcdbc".LastIndexOf("", 0) |> equal 1

// --- Access char by index ---

[<Fact>]
Expand Down
21 changes: 21 additions & 0 deletions tests/Js/Main/StringTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,27 @@ let tests = testList "Strings" [
"ABCD".EndsWith(fst arg, true, CultureInfo.InvariantCulture)
|> equal (snd arg)

testCase "String.EndsWith with empty pattern and StringComparison works" <| fun () ->
// .NET: every string ends with "" for all StringComparison values
"hello".EndsWith("", StringComparison.CurrentCulture) |> equal true
"hello".EndsWith("", StringComparison.InvariantCulture) |> equal true
"hello".EndsWith("", StringComparison.Ordinal) |> equal true
"hello".EndsWith("", StringComparison.OrdinalIgnoreCase) |> equal true
"hello".EndsWith("", StringComparison.InvariantCultureIgnoreCase) |> equal true
"".EndsWith("", StringComparison.CurrentCulture) |> equal true
"hello".StartsWith("", StringComparison.CurrentCulture) |> equal true
"hello".StartsWith("", StringComparison.Ordinal) |> equal true
"hello".StartsWith("", StringComparison.OrdinalIgnoreCase) |> equal true

testCase "String.EndsWith with non-empty pattern and StringComparison works" <| fun () ->
"hello".EndsWith("LO", StringComparison.OrdinalIgnoreCase) |> equal true
"hello".EndsWith("LO", StringComparison.InvariantCultureIgnoreCase) |> equal true
"hello".EndsWith("lo", StringComparison.Ordinal) |> equal true
"hello".EndsWith("LO", StringComparison.Ordinal) |> equal false
"hello".EndsWith("he", StringComparison.Ordinal) |> equal false
"hello".EndsWith("hello", StringComparison.CurrentCulture) |> equal true
"hello".EndsWith("xhello", StringComparison.Ordinal) |> equal false

testCase "String.Trim works" <| fun () ->
" abc ".Trim()
|> equal "abc"
Expand Down
30 changes: 24 additions & 6 deletions tests/Python/TestString.fs
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,12 @@ let ``test String.Contains works`` () =
"ABC".Contains("B") |> equal true
"ABC".Contains("Z") |> equal false

[<Fact>]
let ``test String.Contains with empty pattern works`` () =
// .NET: every string contains ""
"ABC".Contains("") |> equal true
"".Contains("") |> equal true

[<Fact>]
let ``test String.Contains with StringComparison works`` () =
"ABC".Contains("b", StringComparison.Ordinal) |> equal false
Expand Down Expand Up @@ -594,6 +600,12 @@ let ``test String.IndexOf works with offset`` () =
"abcdbc".IndexOf("bc", 3)
|> equal 4

[<Fact>]
let ``test String.IndexOf with empty pattern works`` () =
"abcdbc".IndexOf("") |> equal 0
"abcdbc".IndexOf("", 3) |> equal 3
"abcdbc".IndexOf("", 6) |> equal 6

[<Fact>]
let ``test String.LastIndexOf works`` () =
"abcdbc".LastIndexOf("bc") * 100 + "abcd".LastIndexOf("bd")
Expand All @@ -604,6 +616,12 @@ let ``test String.LastIndexOf works with offset`` () =
"abcdbcebc".LastIndexOf("bc", 3)
|> equal 1

[<Fact>]
let ``test String.LastIndexOf with empty pattern works`` () =
"abcdbc".LastIndexOf("") |> equal 6
"abcdbc".LastIndexOf("", 3) |> equal 4
"abcdbc".LastIndexOf("", 0) |> equal 1

[<Fact>]
let ``test String.IndexOf with StringComparison works`` () =
"abcdbc".IndexOf("b", StringComparison.Ordinal)
Expand Down Expand Up @@ -652,42 +670,42 @@ let ``test String.EndsWith char works`` () =

[<Fact>]
let ``test String.StartsWith works`` () =
let args = [("ab", true); ("bc", false); ("cd", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", true); ("bc", false); ("cd", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"abcd".StartsWith(fst arg)
|> equal (snd arg)

[<Fact>]
let ``test String.StartsWith with OrdinalIgnoreCase works`` () =
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".StartsWith(fst arg, StringComparison.OrdinalIgnoreCase)
|> equal (snd arg)

[<Fact>]
let ``test String.StartsWith with ignoreCase boolean works`` () =
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".StartsWith(fst arg, true, CultureInfo.InvariantCulture)
|> equal (snd arg)

[<Fact>]
let ``test String.EndsWith works`` () =
let args = [("ab", false); ("cd", true); ("bc", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", false); ("cd", true); ("bc", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"abcd".EndsWith(fst arg)
|> equal (snd arg)

[<Fact>]
let ``test String.EndsWith with OrdinalIgnoreCase works`` () =
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true)]
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".EndsWith(fst arg, StringComparison.OrdinalIgnoreCase)
|> equal (snd arg)

[<Fact>]
let ``test String.EndsWith with ignoreCase boolean works`` () =
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true)]
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".EndsWith(fst arg, true, CultureInfo.InvariantCulture)
|> equal (snd arg)
Expand Down
30 changes: 24 additions & 6 deletions tests/Rust/tests/src/StringTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,12 @@ let ``String.Contains works`` () =
"ABC".Contains("B") |> equal true
"ABC".Contains("Z") |> equal false

[<Fact>]
let ``String.Contains with empty pattern works`` () =
// .NET: every string contains ""
"ABC".Contains("") |> equal true
"".Contains("") |> equal true

[<Fact>]
let ``String.Contains with char works`` () =
"ABC".Contains('B') |> equal true
Expand Down Expand Up @@ -1081,6 +1087,12 @@ let ``String.IndexOf works with offset`` () =
"abcdbc".IndexOf("bc", 3)
|> equal 4

[<Fact>]
let ``String.IndexOf with empty pattern works`` () =
"abcdbc".IndexOf("") |> equal 0
"abcdbc".IndexOf("", 3) |> equal 3
"abcdbc".IndexOf("", 6) |> equal 6

[<Fact>]
let ``String.LastIndexOf works`` () =
"abcdbc".LastIndexOf("bc") * 100 + "abcd".LastIndexOf("bd")
Expand All @@ -1091,6 +1103,12 @@ let ``String.LastIndexOf works with offset`` () =
"abcdbcebc".LastIndexOf("bc", 3)
|> equal 1

[<Fact>]
let ``String.LastIndexOf with empty pattern works`` () =
"abcdbc".LastIndexOf("") |> equal 6
"abcdbc".LastIndexOf("", 3) |> equal 4
"abcdbc".LastIndexOf("", 0) |> equal 1

[<Fact>]
let ``String.IndexOfAny works`` () =
"abcdbcebc".IndexOfAny([|'b'|]) |> equal 1
Expand Down Expand Up @@ -1122,42 +1140,42 @@ let ``String.EndsWith char works`` () =

[<Fact>]
let ``String.StartsWith works`` () =
let args = [("ab", true); ("bc", false); ("cd", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", true); ("bc", false); ("cd", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"abcd".StartsWith(fst arg)
|> equal (snd arg)

[<Fact>]
let ``String.StartsWith with OrdinalIgnoreCase works`` () =
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".StartsWith(fst arg, StringComparison.OrdinalIgnoreCase)
|> equal (snd arg)

[<Fact>]
let ``String.StartsWith with ignoreCase boolean works`` () =
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", true); ("AB", true); ("BC", false); ("cd", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".StartsWith(fst arg, true, CultureInfo.InvariantCulture)
|> equal (snd arg)

[<Fact>]
let ``String.EndsWith works`` () =
let args = [("ab", false); ("cd", true); ("bc", false); ("abcdx", false); ("abcd", true)]
let args = [("ab", false); ("cd", true); ("bc", false); ("abcdx", false); ("abcd", true); ("", true)]
for arg in args do
"abcd".EndsWith(fst arg)
|> equal (snd arg)

[<Fact>]
let ``String.EndsWith with OrdinalIgnoreCase works`` () =
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true)]
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".EndsWith(fst arg, StringComparison.OrdinalIgnoreCase)
|> equal (snd arg)

[<Fact>]
let ``String.EndsWith with ignoreCase boolean works`` () =
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true)]
let args = [("ab", false); ("CD", true); ("cd", true); ("bc", false); ("xabcd", false); ("abcd", true); ("", true)]
for arg in args do
"ABCD".EndsWith(fst arg, true, CultureInfo.InvariantCulture)
|> equal (snd arg)
Expand Down
Loading