diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md index b647952d09..a5b9fe2ed7 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md @@ -1,6 +1,7 @@ ### Fixed * Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120)) +* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19151](https://github.com/dotnet/fsharp/pull/19151)) ### Added diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 6d3350f0bf..e6b2775961 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1068,8 +1068,41 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s (fun args -> let scriptFile = args[0] let scriptArgs = List.tail args + + // Filter out and process preferreduilang from script args + let isPreferredUiLangArg (arg: string) = + arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) + + let rec filterScriptArgs (args: string list) = + match args with + | [] -> [] + | (arg: string) :: rest when isPreferredUiLangArg arg -> + // Extract culture and set it + let colonIndex = arg.IndexOf(':') + + if colonIndex >= 0 && colonIndex < arg.Length - 1 then + let culture = arg.Substring(colonIndex + 1) + + try + // Validate culture first by creating CultureInfo + let cultureInfo = CultureInfo(culture) + // Only set if valid + tcConfigB.preferredUiLang <- Some culture + Thread.CurrentThread.CurrentUICulture <- cultureInfo + with + | :? CultureNotFoundException + | :? ArgumentException -> + // Ignore invalid culture, just don't set it + () + + filterScriptArgs rest + | arg :: rest -> arg :: filterScriptArgs rest + + let filteredScriptArgs = filterScriptArgs scriptArgs + inputFilesAcc <- inputFilesAcc @ [ (scriptFile, true) ] (* record script.fsx for evaluation *) - List.iter recordExplicitArg scriptArgs (* record rest of line as explicit arguments *) + List.iter recordExplicitArg filteredScriptArgs (* record rest of line as explicit arguments *) tcConfigB.noFeedback <- true (* "quiet", no banners responses etc *) interact <- false (* --exec, exit after eval *) [] (* no arguments passed on, all consumed here *) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 0bc7df3c9f..c7c7127c07 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -278,6 +278,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs new file mode 100644 index 0000000000..63fbfb3df7 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Scripting + +open Xunit +open System +open System.IO +open FSharp.Test +open FSharp.Test.CompilerAssertHelpers +open FSharp.Compiler.Interactive.Shell +open FSharp.Compiler.Diagnostics + +module ``PreferredUiLang tests`` = + + [] + let ``preferreduilang switch before script is consumed from CommandLineArgs``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| "--preferreduilang:es-ES"; tmpFile; "arg1" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang switch after script is consumed from CommandLineArgs``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "--preferreduilang:es-ES"; "arg1" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang with slash form is consumed from CommandLineArgs``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "/preferreduilang:de-DE"; "arg1" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang sets CurrentUICulture correctly``() = + let scriptContent = """ +let culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name +printfn "Culture: %s" culture +if not (culture.StartsWith("fr-FR")) then + printfn "Expected culture starting with fr-FR, got: %s" culture + exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| "--preferreduilang:fr-FR"; tmpFile |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang after script also sets culture``() = + let scriptContent = """ +let culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name +printfn "Culture: %s" culture +if not (culture.StartsWith("ja-JP")) then + printfn "Expected culture starting with ja-JP, got: %s" culture + exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "--preferreduilang:ja-JP" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``invalid culture in preferreduilang is ignored gracefully``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "--preferreduilang:invalid-culture-xyz"; "arg1" |] + "" + + // Should succeed - invalid culture is ignored, but switch is still consumed + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile)