diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml
index 5091aec9f0d..edd2c68d426 100644
--- a/.github/workflows/copilot-setup-steps.yml
+++ b/.github/workflows/copilot-setup-steps.yml
@@ -22,10 +22,6 @@ jobs:
env:
CI: false
run: dotnet build ./FSharp.Compiler.Service.sln --verbosity quiet
- - name: Restore the language server solution
- env:
- CI: false
- run: dotnet build ./VSFSharpExtension.sln --verbosity quiet
- name: Restore dotnet tools
env:
CI: false
diff --git a/src/Compiler/Driver/CompilerOptions.fs b/src/Compiler/Driver/CompilerOptions.fs
index cdeda6b6566..4dd36dec6bd 100644
--- a/src/Compiler/Driver/CompilerOptions.fs
+++ b/src/Compiler/Driver/CompilerOptions.fs
@@ -1308,6 +1308,13 @@ let advancedFlagsFsi tcConfigB =
None,
Some(FSComp.SR.optsClearResultsCache ())
)
+ CompilerOption(
+ "typecheck-only",
+ tagNone,
+ OptionUnit(fun () -> tcConfigB.typeCheckOnly <- true),
+ None,
+ Some(FSComp.SR.optsTypecheckOnly ())
+ )
]
let advancedFlagsFsc tcConfigB =
diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt
index 78dde5bd148..b29c31d6671 100644
--- a/src/Compiler/FSComp.txt
+++ b/src/Compiler/FSComp.txt
@@ -903,6 +903,7 @@ optsVersion,"Display compiler version banner and exit"
optsResponseFile,"Read response file for more options"
optsCodepage,"Specify the codepage used to read source files"
optsClearResultsCache,"Clear the package manager results cache"
+optsTypecheckOnly,"Perform type checking only, do not execute code"
optsUtf8output,"Output messages in UTF-8 encoding"
optsFullpaths,"Output messages with fully qualified paths"
optsLib,"Specify a directory for the include path which is used to resolve source files and assemblies (Short form: -I)"
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index 662ba9752e7..cbe606b648c 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -2217,6 +2217,10 @@ type internal FsiDynamicCompiler
inputs
))
+ // Add this check after CheckClosedInputSet
+ if tcConfig.typeCheckOnly then
+ raise StopProcessing
+
let codegenResults, optEnv, fragName =
ProcessTypedImpl(
diagnosticsLogger,
diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
index 952624f9fcd..601fc70be62 100644
--- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
+++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
@@ -271,6 +271,7 @@
+
diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs
new file mode 100644
index 00000000000..e960237f6f7
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs
@@ -0,0 +1,48 @@
+module FSharp.Compiler.ComponentTests.Scripting.TypeCheckOnlyTests
+
+open Xunit
+open FSharp.Test
+open FSharp.Test.Compiler
+
+[]
+let ``typecheck-only flag works for valid script``() =
+ Fsx """
+let x = 42
+printfn "This should not execute"
+"""
+ |> withOptions ["--typecheck-only"]
+ |> compile
+ |> shouldSucceed
+
+[]
+let ``typecheck-only flag catches type errors``() =
+ Fsx """
+let x: int = "string" // Type error
+"""
+ |> withOptions ["--typecheck-only"]
+ |> compile
+ |> shouldFail
+ |> withDiagnostics [
+ (Error 1, Line 2, Col 14, Line 2, Col 22, "This expression was expected to have type\n 'int' \nbut here has type\n 'string'")
+ ]
+
+[]
+let ``typecheck-only flag prevents execution side effects``() =
+ Fsx """
+printfn "MyCrazyString"
+let x = 42
+"""
+ |> withOptions ["--typecheck-only"]
+ |> runFsi
+ |> shouldSucceed
+ |> VerifyNotInOutput "MyCrazyString"
+
+[]
+let ``script executes without typecheck-only flag``() =
+ Fsx """
+printfn "MyCrazyString"
+let x = 42
+"""
+ |> runFsi
+ |> shouldSucceed
+ |> verifyOutput "MyCrazyString"
\ No newline at end of file
diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs
index e3c9269a5bd..ecf0bd54642 100644
--- a/tests/FSharp.Test.Utilities/Compiler.fs
+++ b/tests/FSharp.Test.Utilities/Compiler.fs
@@ -1421,6 +1421,18 @@ Actual:
failwith $"""Output does not match expected:{Environment.NewLine}{item}{Environment.NewLine}Actual:{Environment.NewLine}{actual}{Environment.NewLine}"""
cResult
+ let verifyNotInOutput (expected: string) (cResult: CompilationResult) : CompilationResult =
+ match getOutput cResult with
+ | None -> cResult
+ | Some actual ->
+ if actual.Contains(expected) then
+ failwith $"""Output should not contain:{Environment.NewLine}{expected}{Environment.NewLine}Actual:{Environment.NewLine}{actual}{Environment.NewLine}"""
+ else
+ cResult
+
+ let VerifyNotInOutput (expected: string) (cResult: CompilationResult) : CompilationResult =
+ verifyNotInOutput expected cResult
+
type ImportScope = { Kind: ImportDefinitionKind; Name: string }
type PdbVerificationOption =
@@ -1906,6 +1918,13 @@ Actual:
let withStdOutContains (substring: string) (result: CompilationResult) : CompilationResult =
checkOutputInOrder "STDOUT" [substring] (fun o -> o.StdOut) result
+ let withStdOutNotContains (substring: string) (result: CompilationResult) : CompilationResult =
+ let checkOutput = fun o -> o.StdOut
+ let output = checkOutput result.Output
+ if output.Contains(substring) then
+ failwith $"""STDOUT should not contain:{Environment.NewLine}{substring}{Environment.NewLine}Actual:{Environment.NewLine}{output}{Environment.NewLine}"""
+ result
+
let withStdOutContainsAllInOrder (substrings: string list) (result: CompilationResult) : CompilationResult =
checkOutputInOrder "STDOUT" substrings (fun o -> o.StdOut) result