Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed String.StartsWith/EndsWith (#3934) #3935

Merged
merged 1 commit into from
Oct 22, 2024
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
1 change: 1 addition & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

* [Rust] Fixed try finally handler order of execution (by @ncave)
* [JS/TS/Python/Rust] Fixed String.StartsWith/EndsWith (#3934) (by @ncave)

## 4.22.0 - 2024-10-02

Expand Down
24 changes: 20 additions & 4 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,6 @@ let implementedStringFunctions =
[|
"Compare"
"CompareTo"
"EndsWith"
"Format"
"IndexOfAny"
"Insert"
Expand Down Expand Up @@ -1369,12 +1368,29 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt

makeEqOp r left (makeIntConst 0) BinaryGreaterOrEqual |> Some
| "StartsWith", Some c, [ _str ] ->
let left = Helper.InstanceCall(c, "find", Int32.Number, args)

makeEqOp r left (makeIntConst 0) BinaryEqual |> Some
Helper.LibCall(com, "string", "startsWithExact", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "StartsWith", Some c, [ _str; _comp ] ->
Helper.LibCall(com, "string", "startsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "StartsWith", Some c, [ value; ignoreCase; _culture ] ->
addWarning com ctx.InlinePath r "CultureInfo argument is ignored"
let args = [ value; ignoreCase ]

Helper.LibCall(com, "string", "startsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "EndsWith", Some c, [ _str ] ->
Helper.LibCall(com, "string", "endsWithExact", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "EndsWith", Some c, [ _str; _comp ] ->
Helper.LibCall(com, "string", "endsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "EndsWith", Some c, [ value; ignoreCase; _culture ] ->
addWarning com ctx.InlinePath r "CultureInfo argument is ignored"
let args = [ value; ignoreCase ]

Helper.LibCall(com, "string", "endsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| ReplaceName [ "ToUpper", "upper"; "ToUpperInvariant", "upper"; "ToLower", "lower"; "ToLowerInvariant", "lower" ] methName,
Some c,
args -> Helper.InstanceCall(c, methName, t, args, i.SignatureArgTypes, ?loc = r) |> Some
Expand Down
12 changes: 12 additions & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1509,12 +1509,24 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt
makeEqOp r left (makeIntConst 0) BinaryGreaterOrEqual |> Some
| "StartsWith", Some c, [ _str ] -> Helper.InstanceCall(c, "startsWith", Boolean, args) |> Some
| "StartsWith", Some c, [ _str; _comp ] ->
Helper.LibCall(com, "String", "startsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "StartsWith", Some c, [ value; ignoreCase; _culture ] ->
addWarning com ctx.InlinePath r "CultureInfo argument is ignored"
let args = [ value; ignoreCase ]

Helper.LibCall(com, "String", "startsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "EndsWith", Some c, [ _str ] -> Helper.InstanceCall(c, "endsWith", Boolean, args) |> Some
| "EndsWith", Some c, [ _str; _comp ] ->
Helper.LibCall(com, "String", "endsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some
| "EndsWith", Some c, [ value; ignoreCase; _culture ] ->
addWarning com ctx.InlinePath r "CultureInfo argument is ignored"
let args = [ value; ignoreCase ]

Helper.LibCall(com, "String", "endsWith", t, args, i.SignatureArgTypes, thisArg = c, ?loc = r)
|> Some

| ReplaceName [ "ToUpper", "toLocaleUpperCase"
"ToUpperInvariant", "toUpperCase"
Expand Down
23 changes: 16 additions & 7 deletions src/fable-library-py/fable_library/string_.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,7 @@ def compare(string1: str, string2: str, /) -> int:


@overload
def compare(string1: str, string2: str, ignore_case: bool, culture: StringComparison, /) -> int:
...
def compare(string1: str, string2: str, ignore_case: bool, culture: StringComparison, /) -> int: ...


def compare(*args: Any) -> int:
Expand Down Expand Up @@ -527,15 +526,25 @@ def compare_to(this: str, other: str) -> int:
return cmp(this, other, StringComparison.CurrentCulture)


def ends_with(string: str, search: str):
idx = string.rfind(search)
return idx >= 0 and idx == len(string) - len(search)
def ends_with_exact(string: str, pattern: str):
idx = string.rfind(pattern)
return idx >= 0 and idx == len(string) - len(pattern)


def starts_with(string: str, pattern: str, ic: int):
def ends_with(string: str, pattern: str, ic: bool | StringComparison):
if len(string) >= len(pattern):
return cmp(string[0 : len(pattern)], pattern, True if ic else False) == 0
return cmp(string[len(string) - len(pattern) : len(string)], pattern, ic) == 0
return False


def starts_with_exact(string: str, pattern: str):
idx = string.find(pattern)
return idx == 0


def starts_with(string: str, pattern: str, ic: bool | StringComparison):
if len(string) >= len(pattern):
return cmp(string[0 : len(pattern)], pattern, ic) == 0
return False


Expand Down
4 changes: 2 additions & 2 deletions src/fable-library-ts/String.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function compareTo(x: string, y: string) {
return cmp(x, y, StringComparison.CurrentCulture);
}

export function startsWith(str: string, pattern: string, ic: number) {
export function startsWith(str: string, pattern: string, ic: boolean | StringComparison) {
if (ic === StringComparison.Ordinal) { // to avoid substring allocation
return str.startsWith(pattern);
}
Expand All @@ -73,7 +73,7 @@ export function startsWith(str: string, pattern: string, ic: number) {
return false;
}

export function endsWith(str: string, pattern: string, ic: number) {
export function endsWith(str: string, pattern: string, ic: boolean | StringComparison) {
if (ic === StringComparison.Ordinal) { // to avoid substring allocation
return str.endsWith(pattern);
}
Expand Down
39 changes: 32 additions & 7 deletions tests/Dart/src/StringTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -800,24 +800,49 @@ let tests() =
// failing
//"abcdbcebc".IndexOfAny([|'c';'b'|]) |> equal 1

// testCase "String.StartsWith char works" <| fun () ->
// "abcd".StartsWith('a') |> equal true
// "abcd".StartsWith('d') |> equal false

// testCase "String.EndsWith char works" <| fun () ->
// "abcd".EndsWith('a') |> equal false
// "abcd".EndsWith('d') |> equal true

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

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

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

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

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

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

testCase "String.Trim works" <| fun () ->
" abc ".Trim()
Expand Down
36 changes: 28 additions & 8 deletions tests/Js/Main/StringTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -814,29 +814,49 @@ let tests = testList "Strings" [
"abcdbcebc".IndexOfAny([|'f';'e'|], 2, 4) |> equal -1
"abcdbcebc".IndexOfAny([|'c';'b'|]) |> equal 1

// testCase "String.StartsWith char works" <| fun () ->
// "abcd".StartsWith('a') |> equal true
// "abcd".StartsWith('d') |> equal false

// testCase "String.EndsWith char works" <| fun () ->
// "abcd".EndsWith('a') |> equal false
// "abcd".EndsWith('d') |> equal true

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

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

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

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

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

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

testCase "String.Trim works" <| fun () ->
" abc ".Trim()
Expand Down
51 changes: 41 additions & 10 deletions tests/Python/TestString.fs
Original file line number Diff line number Diff line change
Expand Up @@ -553,26 +553,57 @@ let ``test String.IndexOfAny works`` () =
"abcdbcebc".IndexOfAny([|'f';'e'|], 2, 4) |> equal -1
"abcdbcebc".IndexOfAny([|'c';'b'|]) |> equal 1

// [<Fact>]
// let ``test String.StartsWith char works`` () =
// "abcd".StartsWith('a') |> equal true
// "abcd".StartsWith('d') |> equal false

// [<Fact>]
// let ``test String.EndsWith char works`` () =
// "abcd".EndsWith('a') |> equal false
// "abcd".EndsWith('d') |> equal true

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

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

[<Fact>]
let ``test String.Trim works`` () =
Expand Down
50 changes: 33 additions & 17 deletions tests/Rust/tests/src/StringTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1059,42 +1059,58 @@ let ``String.LastIndexOfAny works`` () =
"abcdbcebc".LastIndexOfAny([|'f';'e'|], 6) |> equal 6
"abcdbcebc".LastIndexOfAny([|'f';'e'|], 7, 1) |> equal -1

[<Fact>]
let ``String.StartsWith works`` () =
"abcd".StartsWith("ab") |> equal true
"abcd".StartsWith("cd") |> equal false
"abcd".StartsWith("abcdx") |> equal false

[<Fact>]
let ``String.StartsWith char works`` () =
"abcd".StartsWith('a') |> equal true
"abcd".StartsWith('d') |> equal false

[<Fact>]
let ``String.StartsWith with StringComparison works`` () =
let args = [("ab", true); ("cd", false); ("abcdx", false)]
let ``String.EndsWith char works`` () =
"abcd".EndsWith('a') |> equal false
"abcd".EndsWith('d') |> equal true

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

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

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

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

[<Fact>]
let ``String.Trim works`` () =
" abc ".Trim()
Expand Down
Loading