diff --git a/.fantomasignore b/.fantomasignore
index fabfd3547e1..119fe1d5ca3 100644
--- a/.fantomasignore
+++ b/.fantomasignore
@@ -7,7 +7,8 @@ fcs-samples/
scripts/
setup/
tests/
-vsintegration/
+vsintegration/*
+!vsintegration/tests/FSharp.Editor.Tests
artifacts/
# Explicitly unformatted implementation files
diff --git a/FSharp.Editor.sln b/FSharp.Editor.sln
new file mode 100644
index 00000000000..70ffe6bb0ea
--- /dev/null
+++ b/FSharp.Editor.sln
@@ -0,0 +1,76 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32113.165
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{321F47BC-8148-4C8D-B340-08B7BF07D31D}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Compiler.Service", "src\Compiler\FSharp.Compiler.Service.fsproj", "{AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Core", "src\FSharp.Core\FSharp.Core.fsproj", "{67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.DependencyManager.Nuget", "src\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj", "{24399E68-9000-4556-BDDD-8D74A9660D28}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor", "vsintegration\src\FSharp.Editor\FSharp.Editor.fsproj", "{86E148BE-92C8-47CC-A070-11D769C6D898}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.PatternMatcher", "vsintegration\src\FSharp.PatternMatcher\FSharp.PatternMatcher.csproj", "{4FFA5E03-4128-48C9-8FCD-D7C60729ED74}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.UIResources", "vsintegration\src\FSharp.UIResources\FSharp.UIResources.csproj", "{DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.VS.FSI", "vsintegration\src\FSharp.VS.FSI\FSharp.VS.FSI.fsproj", "{EAC029EB-4A8F-4966-9B38-60D73D8E20D1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ Proto|Any CPU = Proto|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Proto|Any CPU.ActiveCfg = Proto|Any CPU
+ {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Proto|Any CPU.Build.0 = Proto|Any CPU
+ {24399E68-9000-4556-BDDD-8D74A9660D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {24399E68-9000-4556-BDDD-8D74A9660D28}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {24399E68-9000-4556-BDDD-8D74A9660D28}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {24399E68-9000-4556-BDDD-8D74A9660D28}.Release|Any CPU.Build.0 = Release|Any CPU
+ {24399E68-9000-4556-BDDD-8D74A9660D28}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {86E148BE-92C8-47CC-A070-11D769C6D898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {86E148BE-92C8-47CC-A070-11D769C6D898}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {86E148BE-92C8-47CC-A070-11D769C6D898}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {86E148BE-92C8-47CC-A070-11D769C6D898}.Release|Any CPU.Build.0 = Release|Any CPU
+ {86E148BE-92C8-47CC-A070-11D769C6D898}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {68ED1BEB-EB1D-4334-A708-988D54C83B66}
+ EndGlobalSection
+EndGlobal
diff --git a/TESTGUIDE.md b/TESTGUIDE.md
index 91a28af528a..9eeaa90ead4 100644
--- a/TESTGUIDE.md
+++ b/TESTGUIDE.md
@@ -106,8 +106,9 @@ The F# tests are split as follows:
* [FSharp.Compiler.ComponentTests](tests/FSharp.Compiler.ComponentTests) - Validation of compiler APIs.
-* [VisualFSharp.UnitTests](vsintegration/tests/unittests) - Visual F# Tools IDE Unit Test Suite
- This suite exercises a wide range of behaviors in the F# Visual Studio project system and language service.
+* [VisualFSharp.UnitTests](vsintegration/tests/unittests) - Validation of a wide range of behaviors in the F# Visual Studio project system and language service (including the legacy one).
+
+* [FSharp.Editor.Tests](vsintegration/tests/FSharp.Editor.Tests) - Visual F# Tools IDE Test Suite.
### FSharp Suite
@@ -148,9 +149,9 @@ Tags are in the left column, paths to to corresponding test folders are in the r
If you want to re-run a particular test area, the easiest way to do so is to set a temporary tag for that area in test.lst (e.g. "RERUN") and adjust `ttags` [run.fsharpqa.test.fsx script](tests/fsharpqa/run.fsharpqa.test.fsx) and run it.
-### FSharp.Compiler.UnitTests, FSharp.Core.UnitTests, VisualFSharp.UnitTests
+### FSharp.Compiler.UnitTests, FSharp.Core.UnitTests, VisualFSharp.UnitTests, FSharp.Editor.Tests
-These are all NUnit tests. You can execute these tests individually via the Visual Studio NUnit3 runner
+These are all currently NUnit tests (we hope to migrate them to xUnit). You can execute these tests individually via the Visual Studio NUnit3 runner
extension or the command line via `nunit3-console.exe`.
Note that for compatibility reasons, the IDE unit tests should be run in a 32-bit process,
diff --git a/VisualFSharp.sln b/VisualFSharp.sln
index 60b7ece4d7f..c9bb5ddc220 100644
--- a/VisualFSharp.sln
+++ b/VisualFSharp.sln
@@ -193,6 +193,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FCSBenchmarks", "FCSBenchma
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fsharp.ProfilingStartpointProject", "tests\benchmarks\Fsharp.ProfilingStartpointProject\Fsharp.ProfilingStartpointProject.fsproj", "{FE23BB65-276A-4E41-8CC7-F7752241DEBA}"
EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{CBC96CC7-65AB-46EA-A82E-F6A788DABF80}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1019,6 +1021,18 @@ Global
{FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Release|Any CPU.Build.0 = Release|Any CPU
{FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Release|x86.ActiveCfg = Release|Any CPU
{FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Release|x86.Build.0 = Release|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Debug|x86.Build.0 = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Proto|x86.ActiveCfg = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Proto|x86.Build.0 = Debug|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Release|x86.ActiveCfg = Release|Any CPU
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1099,6 +1113,7 @@ Global
{583182E1-3484-4A8F-AC06-7C0D232C0CA4} = {39CDF34B-FB23-49AE-AB27-0975DA379BB5}
{39CDF34B-FB23-49AE-AB27-0975DA379BB5} = {DFB6ADD7-3149-43D9-AFA0-FC4A818B472B}
{FE23BB65-276A-4E41-8CC7-F7752241DEBA} = {39CDF34B-FB23-49AE-AB27-0975DA379BB5}
+ {CBC96CC7-65AB-46EA-A82E-F6A788DABF80} = {F7876C9B-FB6A-4EFB-B058-D6967DB75FB2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {48EDBBBE-C8EE-4E3C-8B19-97184A487B37}
diff --git a/eng/Build.ps1 b/eng/Build.ps1
index c2d58455868..98a71be0b07 100644
--- a/eng/Build.ps1
+++ b/eng/Build.ps1
@@ -592,6 +592,7 @@ try {
if ($testVs -and -not $noVisualStudio) {
TestUsingNUnit -testProject "$RepoRoot\vsintegration\tests\UnitTests\VisualFSharp.UnitTests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\VisualFSharp.UnitTests\"
+ TestUsingNUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj"
}
# verify nupkgs have access to the source code
diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
index d27151bf5e5..7d75ba27ad8 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
@@ -14,6 +14,7 @@
+
diff --git a/vsintegration/tests/UnitTests/BraceMatchingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs
similarity index 65%
rename from vsintegration/tests/UnitTests/BraceMatchingServiceTests.fs
rename to vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs
index f0a158b694b..49b852c5d4a 100644
--- a/vsintegration/tests/UnitTests/BraceMatchingServiceTests.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs
@@ -1,35 +1,33 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
-namespace VisualFSharp.UnitTests.Editor
-open System
-open System.Threading
+namespace FSharp.Editor.Tests
+open System
open NUnit.Framework
-
-open Microsoft.CodeAnalysis.Classification
-open Microsoft.CodeAnalysis.Editor
open Microsoft.CodeAnalysis.Text
open FSharp.Compiler.CodeAnalysis
open Microsoft.VisualStudio.FSharp.Editor
-open Microsoft.VisualStudio.FSharp.LanguageService
-open UnitTests.TestLib.LanguageService
-[][]
-type BraceMatchingServiceTests() =
+[]
+type BraceMatchingServiceTests() =
+ let checker = FSharpChecker.Create()
+
let fileName = "C:\\test.fs"
- let projectOptions: FSharpProjectOptions = {
- ProjectFileName = "C:\\test.fsproj"
- ProjectId = None
- SourceFiles = [| fileName |]
- ReferencedProjects = [| |]
- OtherOptions = [| |]
- IsIncompleteTypeCheckEnvironment = true
- UseScriptResolutionRules = false
- LoadTime = DateTime.MaxValue
- OriginalLoadReferences = []
- UnresolvedReferences = None
- Stamp = None
- }
+
+ let projectOptions: FSharpProjectOptions =
+ {
+ ProjectFileName = "C:\\test.fsproj"
+ ProjectId = None
+ SourceFiles = [| fileName |]
+ ReferencedProjects = [||]
+ OtherOptions = [||]
+ IsIncompleteTypeCheckEnvironment = true
+ UseScriptResolutionRules = false
+ LoadTime = DateTime.MaxValue
+ OriginalLoadReferences = []
+ UnresolvedReferences = None
+ Stamp = None
+ }
member private this.VerifyNoBraceMatch(fileContents: string, marker: string) =
let sourceText = SourceText.From(fileContents)
@@ -37,10 +35,14 @@ type BraceMatchingServiceTests() =
Assert.IsTrue(position >= 0, "Cannot find marker '{0}' in file contents", marker)
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
- match FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, fileName, parsingOptions, position, "UnitTest") |> Async.RunImmediate with
+
+ match
+ FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, fileName, parsingOptions, position, "UnitTest")
+ |> Async.RunImmediateExceptOnUI
+ with
| None -> ()
- | Some(left, right) -> Assert.Fail("Found match for brace '{0}'", marker)
-
+ | Some (left, right) -> Assert.Fail("Found match for brace '{0}'", marker)
+
member private this.VerifyBraceMatch(fileContents: string, startMarker: string, endMarker: string) =
let sourceText = SourceText.From(fileContents)
let startMarkerPosition = fileContents.IndexOf(startMarker)
@@ -48,16 +50,27 @@ type BraceMatchingServiceTests() =
Assert.IsTrue(startMarkerPosition >= 0, "Cannot find start marker '{0}' in file contents", startMarkerPosition)
Assert.IsTrue(endMarkerPosition >= 0, "Cannot find end marker '{0}' in file contents", endMarkerPosition)
-
+
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
- match FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, fileName, parsingOptions, startMarkerPosition, "UnitTest") |> Async.RunImmediate with
+
+ match
+ FSharpBraceMatchingService.GetBraceMatchingResult(
+ checker,
+ sourceText,
+ fileName,
+ parsingOptions,
+ startMarkerPosition,
+ "UnitTest"
+ )
+ |> Async.RunImmediateExceptOnUI
+ with
| None -> Assert.Fail("Didn't find a match for start brace at position '{0}", startMarkerPosition)
- | Some(left, right) ->
- let endPositionInRange(range) =
+ | Some (left, right) ->
+ let endPositionInRange (range) =
let span = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)
span.Start <= endMarkerPosition && endMarkerPosition <= span.End
- Assert.IsTrue(endPositionInRange(left) || endPositionInRange(right), "Found end match at incorrect position")
-
+
+ Assert.IsTrue(endPositionInRange (left) || endPositionInRange (right), "Found end match at incorrect position")
// Starting Brace
[]
@@ -69,8 +82,9 @@ type BraceMatchingServiceTests() =
[]
[]
[]
- member this.NestedBrackets(startMarker: string, endMarker: string) =
- let code = "
+ member this.NestedBrackets(startMarker: string, endMarker: string) =
+ let code =
+ "
(marker1
{marker2
(marker3
@@ -79,91 +93,105 @@ type BraceMatchingServiceTests() =
)marker3
}marker2
)marker1"
+
this.VerifyBraceMatch(code, startMarker, endMarker)
[]
- member this.BracketInExpression() =
+ member this.BracketInExpression() =
this.VerifyBraceMatch("let x = (3*5)-1", "(3*", ")-1")
[]
- member this.BraceInInterpolatedStringSimple() =
+ member this.BraceInInterpolatedStringSimple() =
this.VerifyBraceMatch("let x = $\"abc{1}def\"", "{1", "}def")
[]
- member this.BraceInInterpolatedStringTwoHoles() =
+ member this.BraceInInterpolatedStringTwoHoles() =
this.VerifyBraceMatch("let x = $\"abc{1}def{2+3}hij\"", "{2", "}hij")
[]
- member this.BraceInInterpolatedStringNestedRecord() =
+ member this.BraceInInterpolatedStringNestedRecord() =
this.VerifyBraceMatch("let x = $\"abc{ id{contents=3}.contents }\"", "{contents", "}.contents")
this.VerifyBraceMatch("let x = $\"abc{ id{contents=3}.contents }\"", "{ id", "}\"")
[]
[]
- member this.BraceInMultiLineCommentShouldNotBeMatched(startMarker: string) =
- let code = "
+ member this.BraceInMultiLineCommentShouldNotBeMatched(startMarker: string) =
+ let code =
+ "
let x = 3
(* This [start
is a multiline
comment ]end
*)
printf \"%d\" x"
+
this.VerifyNoBraceMatch(code, startMarker)
-
+
[]
- member this.BraceInAttributesMatch() =
- let code = "
+ member this.BraceInAttributesMatch() =
+ let code =
+ "
[]
module internal name"
+
this.VerifyBraceMatch(code, "[<", ">]")
-
+
[]
- member this.BraceEncapsulatingACommentShouldBeMatched() =
- let code = "
+ member this.BraceEncapsulatingACommentShouldBeMatched() =
+ let code =
+ "
let x = 3 + (start
(* this is a comment *)
)end"
+
this.VerifyBraceMatch(code, "(start", ")end")
-
+
[]
[]
[]
[startsInComment")>]
- member this.BraceStartingOrEndingInCommentShouldNotBeMatched(startMarker: string) =
- let code = "
+ member this.BraceStartingOrEndingInCommentShouldNotBeMatched(startMarker: string) =
+ let code =
+ "
let x = 123 + (endsInComment
(* )endsInComment startsInComment"
+
this.VerifyNoBraceMatch(code, startMarker)
-
+
[]
[]
[]
[startsInDisabledCode")>]
- member this.BraceStartingOrEndingInDisabledCodeShouldNotBeMatched(startMarker: string) =
- let code = "
+ member this.BraceStartingOrEndingInDisabledCodeShouldNotBeMatched(startMarker: string) =
+ let code =
+ "
let x = 123 + (endsInDisabledCode
#if UNDEFINED
)endsInDisabledCode startsInDisabledCode"
+
this.VerifyNoBraceMatch(code, startMarker)
-
+
[]
[]
[]
[startsInString")>]
- member this.BraceStartingOrEndingInStringShouldNotBeMatched(startMarker: string) =
- let code = "
+ member this.BraceStartingOrEndingInStringShouldNotBeMatched(startMarker: string) =
+ let code =
+ "
let x = \"stringValue\" + (endsInString +
\" )endsInString startsInString"
+
this.VerifyNoBraceMatch(code, startMarker)
-
+
[]
- member this.BraceMatchingAtEndOfLine_Bug1597() =
+ member this.BraceMatchingAtEndOfLine_Bug1597() =
// https://github.com/dotnet/fsharp/issues/1597
- let code = """
+ let code =
+ """
[]
let main argv =
let arg1 = ""
@@ -171,21 +199,25 @@ let main argv =
let arg3 = ""
(printfn "%A '%A' '%A'" (arg1) (arg2) (arg3))endBrace
0 // return an integer exit code"""
+
this.VerifyBraceMatch(code, "(printfn", ")endBrace")
-
- []
- []
- [", [|9;10;15|])>]
- [", [|9;10;11;15;16|])>]
- []
- []\nlet a7 = 70", [|0;1;22|])>]
- []
+
+ []
+ []
+ [", [| 9; 10; 15 |])>]
+ [", [| 9; 10; 11; 15; 16 |])>]
+ []
+ []\nlet a7 = 70", [| 0; 1; 22 |])>]
+ []
member this.DoNotMatchOnInnerSide(fileContents: string, matchingPositions: int[]) =
let sourceText = SourceText.From(fileContents)
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
-
+
for position in matchingPositions do
- match FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, fileName, parsingOptions, position, "UnitTest") |> Async.RunSynchronously with
+ match
+ FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, fileName, parsingOptions, position, "UnitTest")
+ |> Async.RunSynchronously
+ with
| Some _ -> ()
| None ->
match position with
diff --git a/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs
new file mode 100644
index 00000000000..bc5ca99f999
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests
+
+open System
+open NUnit.Framework
+open Microsoft.CodeAnalysis.Text
+open Microsoft.VisualStudio.FSharp.Editor
+open FSharp.Editor.Tests.Helpers
+
+[]
+type BreakpointResolutionServiceTests() =
+
+ let fileName = "C:\\test.fs"
+
+ let code =
+ "
+// This is a comment
+
+type exampleType(parameter: int) =
+ member this.exampleMember = parameter
+
+[]
+let main argv =
+ let integerValue = 123456
+ let stringValue = \"This is a string\"
+ let objectValue = exampleType(789)
+
+ printfn \"%d %s %A\" integerValue stringValue objectValue
+
+ let booleanValue = true
+ match booleanValue with
+ | true -> printfn \"correct\"
+ | false -> printfn \"wrong\"
+
+ 0 // return an integer exit code
+ "
+
+ static member private testCases: Object[][] =
+ [|
+ [| "This is a comment"; None |]
+ [| "123456"; Some("let integerValue = 123456") |]
+ [| "stringValue"; Some("let stringValue = \"This is a string\"") |]
+ [| "789"; Some("let objectValue = exampleType(789)") |]
+ [| "correct"; Some("printfn \"correct\"") |]
+ [| "wrong"; Some("printfn \"wrong\"") |]
+ [| "0"; Some("0") |]
+ |]
+
+ []
+ member this.TestBreakpointResolution(searchToken: string, expectedResolution: string option) =
+ let searchPosition = code.IndexOf(searchToken)
+ Assert.IsTrue(searchPosition >= 0, "SearchToken '{0}' is not found in code", searchToken)
+
+ let document, sourceText =
+ RoslynTestHelpers.CreateSingleDocumentSolution(fileName, code)
+
+ let searchSpan =
+ TextSpan.FromBounds(searchPosition, searchPosition + searchToken.Length)
+
+ let actualResolutionOption =
+ FSharpBreakpointResolutionService.GetBreakpointLocation(document, searchSpan)
+ |> Async.RunSynchronously
+
+ match actualResolutionOption with
+ | None -> Assert.IsTrue(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position")
+ | Some (actualResolutionRange) ->
+ let actualResolution =
+ sourceText
+ .GetSubText(RoslynHelpers.FSharpRangeToTextSpan(sourceText, actualResolutionRange))
+ .ToString()
+
+ Assert.IsTrue(
+ expectedResolution.IsSome,
+ "BreakpointResolutionService resolved a breakpoint while it shouldn't at: {0}",
+ actualResolution
+ )
+
+ Assert.AreEqual(expectedResolution.Value, actualResolution, "Expected and actual resolutions should match")
diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs
new file mode 100644
index 00000000000..3f51bcc694e
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs
@@ -0,0 +1,1291 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests
+
+module CompletionProviderTests =
+
+ open System
+ open System.Linq
+ open NUnit.Framework
+ open Microsoft.CodeAnalysis
+ open Microsoft.CodeAnalysis.Completion
+ open Microsoft.CodeAnalysis.Text
+ open Microsoft.VisualStudio.FSharp.Editor
+ open FSharp.Compiler.CodeAnalysis
+ open FSharp.Editor.Tests.Helpers
+
+ let filePath = "C:\\test.fs"
+
+ let internal projectOptions opts =
+ {
+ ProjectFileName = "C:\\test.fsproj"
+ ProjectId = None
+ SourceFiles = [| filePath |]
+ ReferencedProjects = [||]
+ OtherOptions = opts
+ IsIncompleteTypeCheckEnvironment = true
+ UseScriptResolutionRules = false
+ LoadTime = DateTime.MaxValue
+ OriginalLoadReferences = []
+ UnresolvedReferences = None
+ Stamp = None
+ }
+
+ let formatCompletions (completions: string seq) =
+ "\n\t" + String.Join("\n\t", completions)
+
+ let VerifyCompletionListWithOptions (fileContents: string, marker: string, expected: string list, unexpected: string list, opts) =
+ let options = projectOptions opts
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+
+ let document, _ =
+ RoslynTestHelpers.CreateSingleDocumentSolution(filePath, fileContents, options = options)
+
+ let results =
+ FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []))
+ |> Async.RunSynchronously
+ |> Option.defaultValue (ResizeArray())
+ |> Seq.map (fun result -> result.DisplayText)
+
+ let expectedFound = expected |> List.filter results.Contains
+
+ let expectedNotFound = expected |> List.filter (expectedFound.Contains >> not)
+
+ let unexpectedNotFound = unexpected |> List.filter (results.Contains >> not)
+
+ let unexpectedFound = unexpected |> List.filter (unexpectedNotFound.Contains >> not)
+
+ // If either of these are true, then the test fails.
+ let hasExpectedNotFound = not (List.isEmpty expectedNotFound)
+ let hasUnexpectedFound = not (List.isEmpty unexpectedFound)
+
+ if hasExpectedNotFound || hasUnexpectedFound then
+ let expectedNotFoundMsg =
+ if hasExpectedNotFound then
+ sprintf "\nExpected completions not found:%s\n" (formatCompletions expectedNotFound)
+ else
+ String.Empty
+
+ let unexpectedFoundMsg =
+ if hasUnexpectedFound then
+ sprintf "\nUnexpected completions found:%s\n" (formatCompletions unexpectedFound)
+ else
+ String.Empty
+
+ let completionsMsg = sprintf "\nin Completions:%s" (formatCompletions results)
+
+ let msg = sprintf "%s%s%s" expectedNotFoundMsg unexpectedFoundMsg completionsMsg
+
+ Assert.Fail(msg)
+
+ let VerifyCompletionList (fileContents, marker, expected, unexpected) =
+ VerifyCompletionListWithOptions(fileContents, marker, expected, unexpected, [||])
+
+ let VerifyCompletionListExactlyWithOptions (fileContents: string, marker: string, expected: string list, opts) =
+ let options = projectOptions opts
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+
+ let document, _ =
+ RoslynTestHelpers.CreateSingleDocumentSolution(filePath, fileContents, options = options)
+
+ let actual =
+ FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []))
+ |> Async.RunSynchronously
+ |> Option.defaultValue (ResizeArray())
+ |> Seq.toList
+ // sort items as Roslyn do - by `SortText`
+ |> List.sortBy (fun x -> x.SortText)
+
+ let actualNames = actual |> List.map (fun x -> x.DisplayText)
+
+ if actualNames <> expected then
+ Assert.Fail(
+ sprintf
+ "Expected:\n%s,\nbut was:\n%s\nactual with sort text:\n%s"
+ (String.Join("; ", expected |> List.map (sprintf "\"%s\"")))
+ (String.Join("; ", actualNames |> List.map (sprintf "\"%s\"")))
+ (String.Join("\n", actual |> List.map (fun x -> sprintf "%s => %s" x.DisplayText x.SortText)))
+ )
+
+ let VerifyCompletionListExactly (fileContents: string, marker: string, expected: string list) =
+ VerifyCompletionListExactlyWithOptions(fileContents, marker, expected, [||])
+
+ let VerifyNoCompletionList (fileContents: string, marker: string) =
+ VerifyCompletionListExactly(fileContents, marker, [])
+
+ let VerifyCompletionListSpan (fileContents: string, marker: string, expected: string) =
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let sourceText = SourceText.From(fileContents)
+
+ let resultSpan =
+ CompletionUtils.getDefaultCompletionListSpan (sourceText, caretPosition, documentId, filePath, [])
+
+ Assert.AreEqual(expected, sourceText.ToString(resultSpan))
+
+ []
+ let ShouldTriggerCompletionAtCorrectMarkers () =
+ let testCases =
+ [
+ ("x", true)
+ ("y", true)
+ ("1", false)
+ ("2", false)
+ ("x +", false)
+ ("Console.Write", false)
+ ("System.", true)
+ ("Console.", true)
+ ]
+
+ for (marker, shouldBeTriggered) in testCases do
+ let fileContents =
+ """
+let x = 1
+let y = 2
+System.Console.WriteLine(x + y)
+"""
+
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.AreEqual(
+ shouldBeTriggered,
+ triggered,
+ "FSharpCompletionProvider.ShouldTriggerCompletionAux() should compute the correct result"
+ )
+
+ []
+ let ShouldNotTriggerCompletionAfterAnyTriggerOtherThanInsertionOrDeletion () =
+ for triggerKind in [ CompletionTriggerKind.Invoke; CompletionTriggerKind.Snippets ] do
+ let fileContents = "System.Console.WriteLine(123)"
+ let caretPosition = fileContents.IndexOf("rite")
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ triggerKind,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")
+
+ []
+ let ShouldNotTriggerCompletionInStringLiterals () =
+ let fileContents = "let literal = \"System.Console.WriteLine()\""
+ let caretPosition = fileContents.IndexOf("System.")
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")
+
+ []
+ let ShouldNotTriggerCompletionInComments () =
+ let fileContents =
+ """
+(*
+This is a comment
+System.Console.WriteLine()
+*)
+"""
+
+ let caretPosition = fileContents.IndexOf("System.")
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")
+
+ []
+ let ShouldTriggerCompletionInInterpolatedString () =
+ let fileContents =
+ """
+
+let x = 1
+let y = 2
+let z = $"abc {System.Console.WriteLine(x + y)} def"
+"""
+
+ let testCases =
+ [
+ ("x", true)
+ ("y", true)
+ ("1", false)
+ ("2", false)
+ ("x +", false)
+ ("Console.Write", false)
+ ("System.", true)
+ ("Console.", true)
+ ]
+
+ for (marker, shouldBeTriggered) in testCases do
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.AreEqual(
+ shouldBeTriggered,
+ triggered,
+ sprintf "FSharpCompletionProvider.ShouldTriggerCompletionAux() should compute the correct result for marker '%s'" marker
+ )
+
+ []
+ let ShouldNotTriggerCompletionInExcludedCode () =
+ let fileContents =
+ """
+#if UNDEFINED
+System.Console.WriteLine()
+#endif
+"""
+
+ let caretPosition = fileContents.IndexOf("System.")
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")
+
+ []
+ let ShouldNotTriggerCompletionInOperatorWithDot () =
+ // Simulate mistyping '|>' as '|.'
+ let fileContents =
+ """
+let f() =
+ 12.0 |. sqrt
+"""
+
+ let caretPosition = fileContents.IndexOf("|.")
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger on operators")
+
+ []
+ let ShouldTriggerCompletionInAttribute () =
+ let fileContents =
+ """
+[]
+ let ShouldTriggerCompletionAfterDerefOperator () =
+ let fileContents =
+ """
+let foo = ref 12
+printfn "%d" !f
+"""
+
+ let marker = "!f"
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsTrue(triggered, "Completion should trigger after typing an identifier that follows a dereference operator (!).")
+
+ []
+ let ShouldTriggerCompletionAfterAddressOfOperator () =
+ let fileContents =
+ """
+type Point = { mutable X: int; mutable Y: int }
+let pnt = { X = 1; Y = 2 }
+use ptr = fixed &p
+"""
+
+ let marker = "&p"
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsTrue(triggered, "Completion should trigger after typing an identifier that follows an addressOf operator (&).")
+
+ []
+ let ShouldTriggerCompletionAfterArithmeticOperation () =
+ let fileContents =
+ """
+let xVal = 1.0
+let yVal = 2.0
+let zVal
+
+xVal+y
+xVal-y
+xVal*y
+xVal/y
+xVal%y
+xVal**y
+"""
+
+ let markers = [ "+y"; "-y"; "*y"; "/y"; "%y"; "**y" ]
+
+ for marker in markers do
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsTrue(triggered, "Completion should trigger after typing an identifier that follows a mathematical operation")
+
+ []
+ let ShouldTriggerCompletionAtStartOfFileWithInsertion =
+ let fileContents =
+ """
+l"""
+
+ let marker = "l"
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let getInfo () = documentId, filePath, []
+
+ let triggered =
+ FSharpCompletionProvider.ShouldTriggerCompletionAux(
+ SourceText.From(fileContents),
+ caretPosition,
+ CompletionTriggerKind.Insertion,
+ getInfo,
+ IntelliSenseOptions.Default
+ )
+
+ Assert.IsTrue(
+ triggered,
+ "Completion should trigger after typing an Insertion character at the top of the file, e.g. a function definition in a new script file."
+ )
+
+ []
+ let ShouldDisplayTypeMembers () =
+ let fileContents =
+ """
+type T1() =
+ member this.M1 = 5
+ member this.M2 = "literal"
+
+[]
+let main argv =
+ let obj = T1()
+ obj.
+"""
+
+ VerifyCompletionList(fileContents, "obj.", [ "M1"; "M2" ], [ "System" ])
+
+ []
+ let ShouldDisplaySystemNamespace () =
+ let fileContents =
+ """
+type T1 =
+ member this.M1 = 5
+ member this.M2 = "literal"
+System.Console.WriteLine()
+"""
+
+ VerifyCompletionList(fileContents, "System.", [ "Console"; "Array"; "String" ], [ "T1"; "M1"; "M2" ])
+
+ []
+ let ShouldDisplaySystemNamespaceInInterpolatedString () =
+ let fileContents =
+ """
+type T1 =
+ member this.M1 = 5
+ member this.M2 = "literal"
+let x = $"1 not the same as {System.Int32.MaxValue} is it"
+"""
+
+ VerifyCompletionList(fileContents, "System.", [ "Console"; "Array"; "String" ], [ "T1"; "M1"; "M2" ])
+
+ []
+ let ``Class instance members are ordered according to their kind and where they are defined (simple case, by a variable)`` () =
+ let fileContents =
+ """
+type Base() =
+ member _.BaseMethod() = 1
+ member _.BaseProp = 1
+
+type Class() =
+ inherit Base()
+ member this.MineMethod() = 1
+ member this.MineProp = 1
+
+let x = Class()
+x.
+"""
+
+ let expected =
+ [
+ "MineProp"
+ "BaseProp"
+ "MineMethod"
+ "BaseMethod"
+ "Equals"
+ "GetHashCode"
+ "GetType"
+ "ToString"
+ ]
+
+ VerifyCompletionListExactly(fileContents, "x.", expected)
+
+ []
+ let ``Class instance members are ordered according to their kind and where they are defined (simple case, by a constructor)`` () =
+ let fileContents =
+ """
+type Base() =
+ member _.BaseMethod() = 1
+ member _.BaseProp = 1
+
+type Class() =
+ inherit Base()
+ member this.MineMethod() = 1
+ member this.MineProp = 1
+
+let x = Class().
+"""
+
+ let expected =
+ [
+ "MineProp"
+ "BaseProp"
+ "MineMethod"
+ "BaseMethod"
+ "Equals"
+ "GetHashCode"
+ "GetType"
+ "ToString"
+ ]
+
+ VerifyCompletionListExactly(fileContents, "let x = Class().", expected)
+
+ []
+ let ``Class static members are ordered according to their kind and where they are defined`` () =
+ let fileContents =
+ """
+type Base() =
+ static member BaseStaticMethod() = 1
+ static member BaseStaticProp = 1
+
+type Class() =
+ inherit Base()
+ static member MineStaticMethod() = 1
+ static member MineStaticProp = 2
+
+Class.
+"""
+
+ let expected =
+ [ "MineStaticProp"; "BaseStaticProp"; "MineStaticMethod"; "BaseStaticMethod" ]
+
+ VerifyCompletionListExactly(fileContents, "Class.", expected)
+
+ []
+ let ``Class instance members are ordered according to their kind and where they are defined (complex case)`` () =
+ let fileContents =
+ """
+type Base() =
+ inherit System.Collections.Generic.List
+ member _.BaseMethod() = 1
+ member _.BaseProp = 1
+
+type Class() =
+ inherit Base()
+ member this.MineMethod() = 1
+ member this.MineProp = 1
+
+let x = Class()
+x.
+"""
+
+ let expected =
+ [
+ "MineProp"
+ "BaseProp"
+ "Capacity"
+ "Count"
+ "Item"
+ "MineMethod"
+ "Add"
+ "AddRange"
+ "AsReadOnly"
+ "BaseMethod"
+ "BinarySearch"
+ "Clear"
+ "Contains"
+ "ConvertAll"
+ "CopyTo"
+ "Equals"
+ "Exists"
+ "Find"
+ "FindAll"
+ "FindIndex"
+ "FindLast"
+ "FindLastIndex"
+ "ForEach"
+ "GetEnumerator"
+ "GetHashCode"
+ "GetRange"
+ "GetType"
+ "IndexOf"
+ "Insert"
+ "InsertRange"
+ "LastIndexOf"
+ "Remove"
+ "RemoveAll"
+ "RemoveAt"
+ "RemoveRange"
+ "Reverse"
+ "Sort"
+ "ToArray"
+ "ToString"
+ "TrimExcess"
+ "TrueForAll"
+ ]
+
+ VerifyCompletionListExactly(fileContents, "x.", expected)
+
+ []
+ let ``Constructing a new class with object initializer syntax`` () =
+ let fileContents =
+ """
+type A() =
+ member val SettableProperty = 1 with get, set
+ member val AnotherSettableProperty = 1 with get, set
+ member val NonSettableProperty = 1
+
+let _ = new A(Setta)
+"""
+
+ let expected = [ "SettableProperty"; "AnotherSettableProperty" ]
+ let notExpected = [ "NonSettableProperty" ]
+ VerifyCompletionList(fileContents, "(Setta", expected, notExpected)
+
+ []
+ let ``Constructing a new class with object initializer syntax and verifying 'at' character doesn't exist.`` () =
+ let fileContents =
+ """
+type A() =
+ member val SettableProperty = 1 with get, set
+ member val AnotherSettableProperty = 1 with get, set
+ member val NonSettableProperty = 1
+
+let _ = new A(Setta)
+"""
+
+ let expected = []
+
+ let notExpected =
+ [ "SettableProperty@"; "AnotherSettableProperty@"; "NonSettableProperty@" ]
+
+ VerifyCompletionList(fileContents, "(Setta", expected, notExpected)
+
+ []
+ let ``Constructing a new fully qualified class with object initializer syntax without ending paren`` () =
+ let fileContents =
+ """
+module M =
+ type A() =
+ member val SettableProperty = 1 with get, set
+ member val AnotherSettableProperty = 1 with get, set
+ member val NonSettableProperty = 1
+
+let _ = new M.A(Setta
+"""
+
+ let expected = [ "SettableProperty"; "AnotherSettableProperty" ]
+ let notExpected = [ "NonSettableProperty" ]
+ VerifyCompletionList(fileContents, "(Setta", expected, notExpected)
+
+ []
+ let ``Extension methods go after everything else, extension properties are treated as normal ones`` () =
+ let fileContents =
+ """
+open System.Collections.Generic
+
+type List<'a> with
+ member _.ExtensionProp = 1
+ member _.ExtensionMeth() = 1
+
+List().
+"""
+
+ let expected =
+ [
+ "Capacity"
+ "Count"
+ "Item"
+ "ExtensionProp"
+ "Add"
+ "AddRange"
+ "AsReadOnly"
+ "BinarySearch"
+ "Clear"
+ "Contains"
+ "ConvertAll"
+ "CopyTo"
+ "Exists"
+ "Find"
+ "FindAll"
+ "FindIndex"
+ "FindLast"
+ "FindLastIndex"
+ "ForEach"
+ "GetEnumerator"
+ "GetRange"
+ "IndexOf"
+ "Insert"
+ "InsertRange"
+ "LastIndexOf"
+ "Remove"
+ "RemoveAll"
+ "RemoveAt"
+ "RemoveRange"
+ "Reverse"
+ "Sort"
+ "ToArray"
+ "TrimExcess"
+ "TrueForAll"
+ "Equals"
+ "GetHashCode"
+ "GetType"
+ "ToString"
+ "ExtensionMeth"
+ ]
+
+ VerifyCompletionListExactly(fileContents, "List().", expected)
+
+ []
+ let ``Completion for open contains namespaces and static types`` () =
+ let fileContents =
+ """
+open type System.Ma
+"""
+
+ let expected = [ "Management"; "Math" ] // both namespace and static type
+ VerifyCompletionList(fileContents, "System.Ma", expected, [])
+
+ []
+ let ``No completion on type name at declaration site`` () =
+ let fileContents =
+ """
+type T
+
+"""
+
+ VerifyNoCompletionList(fileContents, "type T")
+
+ []
+ let ``No completion on name of unfinished function declaration`` () =
+ let fileContents =
+ """
+let f
+
+"""
+
+ VerifyNoCompletionList(fileContents, "let f")
+
+ []
+ let ``No completion on name of value declaration`` () =
+ let fileContents =
+ """
+let xyz = 1
+
+"""
+
+ VerifyNoCompletionList(fileContents, "let xy")
+
+ []
+ let ``No completion on name of function declaration`` () =
+ let fileContents =
+ """
+let foo x = 1
+
+"""
+
+ VerifyNoCompletionList(fileContents, "let fo")
+
+ []
+ let ``No completion on name of tupled function declaration`` () =
+ let fileContents =
+ """
+let foo (x, y) = 1
+
+"""
+
+ VerifyNoCompletionList(fileContents, "let fo")
+
+ []
+ let ``No completion on member name at declaration site`` () =
+ let fileContents =
+ """
+type T() =
+ member this.M
+"""
+
+ VerifyNoCompletionList(fileContents, "member this.M")
+
+ []
+ let ``No completion on function first argument name`` () =
+ let fileContents =
+ """
+let func (p
+"""
+
+ VerifyNoCompletionList(fileContents, "let func (p")
+
+ []
+ let ``No completion on function subsequent argument name`` () =
+ let fileContents =
+ """
+let func (p, h
+"""
+
+ VerifyNoCompletionList(fileContents, "let func (p, h")
+
+ []
+ let ``No completion on curried function subsequent argument name`` () =
+ let fileContents =
+ """
+let func (p) (h
+"""
+
+ VerifyNoCompletionList(fileContents, "let func (p) (h")
+
+ []
+ let ``No completion on method first argument name`` () =
+ let fileContents =
+ """
+type T() =
+ member this.M(p) = ()
+"""
+
+ VerifyNoCompletionList(fileContents, "member this.M(p")
+
+ []
+ let ``No completion on method subsequent argument name`` () =
+ let fileContents =
+ """
+type T() =
+ member this.M(p:int, h ) = ()
+"""
+
+ VerifyNoCompletionList(fileContents, "member this.M(p:int, h")
+
+ []
+ let ``Completion list on abstract member type signature contains modules and types but not keywords or functions`` =
+ let fileContents =
+ """
+type Interface =
+ abstract member Eat: l
+"""
+
+ VerifyCompletionList(fileContents, "Eat: l", [ "LanguagePrimitives"; "List" ], [ "let"; "log" ])
+
+ []
+ let ``Provide completion on first function argument type hint`` () =
+ let fileContents =
+ """
+let func (p:i
+"""
+
+ VerifyCompletionList(fileContents, "let func (p:i", [ "int" ], [])
+
+ []
+ let ``Provide completion on subsequent function argument type hint`` () =
+ let fileContents =
+ """
+let func (p:int, h:f
+"""
+
+ VerifyCompletionList(fileContents, "let func (p:int, h:f", [ "float" ], [])
+
+ []
+ let ``Provide completion on local function argument type hint`` () =
+ let fileContents =
+ """
+let top () =
+ let func (p:i
+"""
+
+ VerifyCompletionList(fileContents, "let func (p:i", [ "int" ], [])
+
+ []
+ let ``No completion on implicit constructor first argument name`` () =
+ let fileContents =
+ """
+type T(p) =
+"""
+
+ VerifyNoCompletionList(fileContents, "type T(p")
+
+ []
+ let ``No completion on implicit constructor subsequent argument name`` () =
+ let fileContents =
+ """
+type T(p:int, h) =
+"""
+
+ VerifyNoCompletionList(fileContents, "type T(p:int, h")
+
+ []
+ let ``Provide completion on implicit constructor argument type hint`` () =
+ let fileContents =
+ """
+type T(p:i) =
+"""
+
+ VerifyCompletionList(fileContents, "type T(p:i", [ "int" ], [])
+
+ []
+ let ``No completion on lambda argument name`` () =
+ let fileContents =
+ """
+let _ = fun (p) -> ()
+"""
+
+ VerifyNoCompletionList(fileContents, "let _ = fun (p")
+
+ []
+ let ``No completion on lambda argument name2`` () =
+ let fileContents =
+ """
+let _ = fun (p: int) -> ()
+"""
+
+ VerifyNoCompletionList(fileContents, "let _ = fun (p")
+
+ []
+ let ``Completions on lambda argument type hint contain modules and types but not keywords or functions`` () =
+ let fileContents =
+ """
+let _ = fun (p:l) -> ()
+"""
+
+ VerifyCompletionList(fileContents, "let _ = fun (p:l", [ "LanguagePrimitives"; "List" ], [ "let"; "log" ])
+
+ []
+ let ``Completions in match clause type test contain modules and types but not keywords or functions`` () =
+ let fileContents =
+ """
+match box 5 with
+| :? l as x -> ()
+| _ -> ()
+"""
+
+ VerifyCompletionList(fileContents, ":? l", [ "LanguagePrimitives"; "List" ], [ "let"; "log" ])
+
+ []
+ let ``Completions in catch clause type test contain modules and types but not keywords or functions`` () =
+ let fileContents =
+ """
+try
+ ()
+with :? l as x ->
+ ()
+"""
+
+ VerifyCompletionList(fileContents, ":? l", [ "LanguagePrimitives"; "List" ], [ "let"; "log" ])
+
+ []
+ let ``Extensions.Bug5162`` () =
+ let fileContents =
+ """
+module Extensions =
+ type System.Object with
+ member x.P = 1
+module M2 =
+ let x = 1
+ Ext
+"""
+
+ VerifyCompletionList(fileContents, " Ext", [ "Extensions"; "ExtraTopLevelOperators" ], [])
+
+ []
+ let ``Custom operations should be at the top of completion list inside computation expression`` () =
+ let fileContents =
+ """
+let joinLocal = 1
+
+let _ =
+ query {
+ for i in 1..10 do
+ select i
+ join
+ }
+"""
+
+ VerifyCompletionList(fileContents, " join", [ "groupJoin"; "join"; "leftOuterJoin"; "joinLocal" ], [])
+
+ []
+ let ``Byref Extension Methods`` () =
+ let fileContents =
+ """
+module Extensions =
+ open System
+ open System.Runtime.CompilerServices
+
+ []
+ type Message = Message of String
+
+ []
+ type MessageExtensions private () =
+ let (|Message|) (Message message) = message
+
+ []
+ static member Print (Message message : Message) =
+ printfn "%s" message
+
+ []
+ static member PrintRef (Message message : inref) =
+ printfn "%s" message
+
+ let wrappedMessage = Message "Hello World"
+
+ wrappedMessage.
+"""
+
+ VerifyCompletionList(fileContents, "wrappedMessage.", [ "PrintRef" ], [])
+
+ []
+ let ``Completion list span works with underscore in identifier`` () =
+ let fileContents =
+ """
+let x = A.B_C
+"""
+
+ VerifyCompletionListSpan(fileContents, "A.B_C", "B_C")
+
+ []
+ let ``Completion list span works with digit in identifier`` () =
+ let fileContents =
+ """
+let x = A.B1C
+"""
+
+ VerifyCompletionListSpan(fileContents, "A.B1C", "B1C")
+
+ []
+ let ``Completion list span works with enclosed backtick identifier`` () =
+ let fileContents =
+ """
+let x = A.``B C``
+"""
+
+ VerifyCompletionListSpan(fileContents, "A.``B C``", "``B C``")
+
+ []
+ let ``Completion list span works with partial backtick identifier`` () =
+ let fileContents =
+ """
+let x = A.``B C
+"""
+
+ VerifyCompletionListSpan(fileContents, "A.``B C", "``B C")
+
+ []
+ let ``Completion list span works with first of multiple enclosed backtick identifiers`` () =
+ let fileContents =
+ """
+let x = A.``B C`` + D.``E F``
+"""
+
+ VerifyCompletionListSpan(fileContents, "A.``B C``", "``B C``")
+
+ []
+ let ``Completion list span works with last of multiple enclosed backtick identifiers`` () =
+ let fileContents =
+ """
+let x = A.``B C`` + D.``E F``
+"""
+
+ VerifyCompletionListSpan(fileContents, "D.``E F``", "``E F``")
+
+ []
+ let ``No completion on record field identifier at declaration site`` () =
+ let fileContents =
+ """
+type A = { le: string }
+"""
+
+ VerifyNoCompletionList(fileContents, "le")
+
+ []
+ let ``Completion list on record field type at declaration site contains modules, types and type parameters but not keywords or functions``
+ ()
+ =
+ let fileContents =
+ """
+type A<'lType> = { Field: l }
+"""
+
+ VerifyCompletionList(fileContents, "Field: l", [ "LanguagePrimitives"; "List" ], [ "let"; "log" ])
+
+ []
+ let ``No completion on record stub with no fields at declaration site`` () =
+ let fileContents =
+ """
+type A = { }
+"""
+
+ VerifyNoCompletionList(fileContents, "{ ")
+
+ []
+ let ``No completion on record outside of all fields at declaration site`` () =
+ let fileContents =
+ """
+type A = { Field: string; }
+"""
+
+ VerifyNoCompletionList(fileContents, "; ")
+
+ []
+ let ``No completion on union case identifier at declaration site`` () =
+ let fileContents =
+ """
+type A =
+ | C of string
+"""
+
+ VerifyNoCompletionList(fileContents, "| C")
+
+ []
+ let ``No completion on union case field identifier at declaration site`` () =
+ let fileContents =
+ """
+type A =
+ | Case of blah: int * str: int
+"""
+
+ VerifyNoCompletionList(fileContents, "str")
+
+ []
+ let ``Completion list on union case type at declaration site contains modules, types and type parameters but not keywords or functions``
+ ()
+ =
+ let fileContents =
+ """
+type A<'lType> =
+ | Case of blah: int * str: l
+"""
+
+ VerifyCompletionList(fileContents, "str: l", [ "LanguagePrimitives"; "List"; "lType" ], [ "let"; "log" ])
+
+ []
+ let ``Completion list on union case type at declaration site contains modules, types and type parameters but not keywords or functions2``
+ ()
+ =
+ let fileContents =
+ """
+type A<'lType> =
+ | Case of l
+"""
+
+ VerifyCompletionList(fileContents, "of l", [ "LanguagePrimitives"; "List"; "lType" ], [ "let"; "log" ])
+
+ []
+ let ``Completion list on union case type at declaration site contains type parameter`` () =
+ let fileContents =
+ """
+type A<'keyType> =
+ | Case of key
+"""
+
+ VerifyCompletionList(fileContents, "of key", [ "keyType" ], [])
+
+ []
+ let ``Completion list on type alias contains modules and types but not keywords or functions`` () =
+ let fileContents =
+ """
+type A = l
+"""
+
+ VerifyCompletionList(fileContents, "= l", [ "LanguagePrimitives"; "List" ], [ "let"; "log" ])
+
+ []
+ let ``No completion on enum case identifier at declaration site`` () =
+ let fileContents =
+ """
+type A =
+ | C = 0
+"""
+
+ VerifyNoCompletionList(fileContents, "| C")
+
+ []
+ let ``Completion list in generic function body contains type parameter`` () =
+ let fileContents =
+ """
+let Null<'wrappedType> () =
+ Unchecked.defaultof
+"""
+
+ VerifyCompletionList(fileContents, "defaultof]
+ let ``Completion list in generic method body contains type parameter`` () =
+ let fileContents =
+ """
+type A () =
+ member _.Null<'wrappedType> () = Unchecked.defaultof
+"""
+
+ VerifyCompletionList(fileContents, "defaultof]
+ let ``Completion list in generic class method body contains type parameter`` () =
+ let fileContents =
+ """
+type A<'wrappedType> () =
+ member _.Null () = Unchecked.defaultof
+"""
+
+ VerifyCompletionList(fileContents, "defaultof]
+ let ``Completion list in type application contains modules, types and type parameters but not keywords or functions`` () =
+ let fileContents =
+ """
+let emptyMap<'keyType, 'lValueType> () =
+ Map.empty<'keyType, l>
+"""
+
+ VerifyCompletionList(fileContents, ", l", [ "LanguagePrimitives"; "List"; "lValueType" ], [ "let"; "log" ])
+
+ []
+ let ``Completion list for interface with static abstract method type invocation contains static property with residue`` () =
+ let fileContents =
+ """
+type IStaticProperty<'T when 'T :> IStaticProperty<'T>> =
+ static abstract StaticProperty: 'T
+
+let f_IWSAM_flex_StaticProperty(x: #IStaticProperty<'T>) =
+ 'T.StaticProperty
+"""
+
+ VerifyCompletionListWithOptions(fileContents, "'T.Stati", [ "StaticProperty" ], [], [| "/langversion:preview" |])
+
+ []
+ let ``Completion list for interface with static abstract method type invocation contains static property after dot`` () =
+ let fileContents =
+ """
+type IStaticProperty<'T when 'T :> IStaticProperty<'T>> =
+ static abstract StaticProperty: 'T
+
+let f_IWSAM_flex_StaticProperty(x: #IStaticProperty<'T>) =
+ 'T.StaticProperty
+"""
+
+ VerifyCompletionListWithOptions(fileContents, "'T.", [ "StaticProperty" ], [], [| "/langversion:preview" |])
+
+ []
+ let ``Completion list for SRTP invocation contains static property with residue`` () =
+ let fileContents =
+ """
+let inline f_StaticProperty_SRTP<'T when 'T : (static member StaticProperty: 'T) >() =
+ 'T.StaticProperty
+
+"""
+
+ VerifyCompletionListWithOptions(fileContents, "'T.Stati", [ "StaticProperty" ], [], [| "/langversion:preview" |])
+
+ []
+ let ``Completion list for SRTP invocation contains static property after dot`` () =
+ let fileContents =
+ """
+let inline f_StaticProperty_SRTP<'T when 'T : (static member StaticProperty: 'T) >() =
+ 'T.StaticProperty
+
+"""
+
+ VerifyCompletionListWithOptions(fileContents, "'T.", [ "StaticProperty" ], [], [| "/langversion:preview" |])
diff --git a/vsintegration/tests/UnitTests/DocumentDiagnosticAnalyzerTests.fs b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs
similarity index 64%
rename from vsintegration/tests/UnitTests/DocumentDiagnosticAnalyzerTests.fs
rename to vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs
index b438c0b96ff..423fbc6e0b3 100644
--- a/vsintegration/tests/UnitTests/DocumentDiagnosticAnalyzerTests.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs
@@ -1,75 +1,64 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
-namespace VisualFSharp.UnitTests.Editor
-open System
-open System.Threading
+namespace FSharp.Editor.Tests
open NUnit.Framework
-
open Microsoft.CodeAnalysis
-open Microsoft.CodeAnalysis.Classification
-open Microsoft.CodeAnalysis.Editor
-open Microsoft.CodeAnalysis.Text
-
open Microsoft.VisualStudio.FSharp.Editor
-open Microsoft.VisualStudio.FSharp.LanguageService
-
-open FSharp.Compiler.CodeAnalysis
-open FSharp.Compiler.Text
+open FSharp.Editor.Tests.Helpers
-open UnitTests.TestLib.LanguageService
-
-[][]
-type DocumentDiagnosticAnalyzerTests() =
+[]
+type DocumentDiagnosticAnalyzerTests() =
let filePath = "C:\\test.fs"
let startMarker = "(*start*)"
let endMarker = "(*end*)"
- let projectOptions: FSharpProjectOptions = {
- ProjectFileName = "C:\\test.fsproj"
- ProjectId = None
- SourceFiles = [| filePath |]
- ReferencedProjects = [| |]
- OtherOptions = [| |]
- IsIncompleteTypeCheckEnvironment = true
- UseScriptResolutionRules = false
- LoadTime = DateTime.MaxValue
- OriginalLoadReferences = []
- UnresolvedReferences = None
- Stamp = None
- }
-
- let getDiagnostics (fileContents: string) =
+
+ let getDiagnostics (fileContents: string) =
async {
- let document, _ = RoslynTestHelpers.CreateSingleDocumentSolution(filePath, fileContents)
- let! syntacticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax)
- let! semanticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic)
+ let document, _ =
+ RoslynTestHelpers.CreateSingleDocumentSolution(filePath, fileContents)
+
+ let! syntacticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax)
+ let! semanticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic)
return syntacticDiagnostics.AddRange(semanticDiagnostics)
- } |> Async.RunSynchronously
+ }
+ |> Async.RunSynchronously
member private this.VerifyNoErrors(fileContents: string, ?additionalFlags: string[]) =
- let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
- let additionalOptions = match additionalFlags with
- | None -> projectOptions
- | Some(flags) -> {projectOptions with OtherOptions = Array.append projectOptions.OtherOptions flags}
-
let errors = getDiagnostics fileContents
+
if not errors.IsEmpty then
Assert.Fail("There should be no errors generated", errors)
member private this.VerifyErrorAtMarker(fileContents: string, expectedMarker: string, ?expectedMessage: string) =
- let errors = getDiagnostics fileContents |> Seq.filter(fun e -> e.Severity = DiagnosticSeverity.Error) |> Seq.toArray
+ let errors =
+ getDiagnostics fileContents
+ |> Seq.filter (fun e -> e.Severity = DiagnosticSeverity.Error)
+ |> Seq.toArray
+
Assert.AreEqual(1, errors.Length, "There should be exactly one error generated")
let actualError = errors.[0]
+
if expectedMessage.IsSome then
Assert.AreEqual(expectedMessage.Value, actualError.GetMessage(), "Error messages should match")
+
Assert.AreEqual(DiagnosticSeverity.Error, actualError.Severity)
let expectedStart = fileContents.IndexOf(expectedMarker)
Assert.AreEqual(expectedStart, actualError.Location.SourceSpan.Start, "Error start positions should match")
let expectedEnd = expectedStart + expectedMarker.Length
Assert.AreEqual(expectedEnd, actualError.Location.SourceSpan.End, "Error end positions should match")
- member private this.VerifyDiagnosticBetweenMarkers(fileContents: string, expectedMessage: string, expectedSeverity: DiagnosticSeverity) =
- let errors = getDiagnostics fileContents |> Seq.filter(fun e -> e.Severity = expectedSeverity) |> Seq.toArray
+ member private this.VerifyDiagnosticBetweenMarkers
+ (
+ fileContents: string,
+ expectedMessage: string,
+ expectedSeverity: DiagnosticSeverity
+ ) =
+ let errors =
+ getDiagnostics fileContents
+ |> Seq.filter (fun e -> e.Severity = expectedSeverity)
+ |> Seq.toArray
+
Assert.AreEqual(1, errors.Length, "There should be exactly one error generated")
let actualError = errors.[0]
Assert.AreEqual(expectedSeverity, actualError.Severity)
@@ -78,127 +67,151 @@ type DocumentDiagnosticAnalyzerTests() =
Assert.AreEqual(expectedStart, actualError.Location.SourceSpan.Start, "Error start positions should match")
let expectedEnd = fileContents.IndexOf(endMarker)
Assert.AreEqual(expectedEnd, actualError.Location.SourceSpan.End, "Error end positions should match")
-
+
member private this.VerifyErrorBetweenMarkers(fileContents: string, expectedMessage: string) =
this.VerifyDiagnosticBetweenMarkers(fileContents, expectedMessage, DiagnosticSeverity.Error)
-
+
member private this.VerifyWarningBetweenMarkers(fileContents: string, expectedMessage: string) =
this.VerifyDiagnosticBetweenMarkers(fileContents, expectedMessage, DiagnosticSeverity.Warning)
-
[]
- member public this.Error_Expression_IllegalIntegerLiteral() =
+ member public this.Error_Expression_IllegalIntegerLiteral() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let _ = 1
let a = 0.1(*start*).(*end*)0
""",
- expectedMessage = "Missing qualification after '.'")
-
+ expectedMessage = "Missing qualification after '.'"
+ )
+
[]
- member public this.Error_Expression_IncompleteDefine() =
+ member public this.Error_Expression_IncompleteDefine() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let a = (*start*);(*end*)
""",
- expectedMessage = "Unexpected symbol ';' in binding")
-
+ expectedMessage = "Unexpected symbol ';' in binding"
+ )
+
[]
- member public this.Error_Expression_KeywordAsValue() =
+ member public this.Error_Expression_KeywordAsValue() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let b =
(*start*)type(*end*)
""",
- expectedMessage = "Incomplete structured construct at or before this point in binding")
-
+ expectedMessage = "Incomplete structured construct at or before this point in binding"
+ )
+
[]
- member public this.Error_Type_WithoutName() =
+ member public this.Error_Type_WithoutName() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
type (*start*)=(*end*)
""",
- expectedMessage = "Unexpected symbol '=' in type name")
-
+ expectedMessage = "Unexpected symbol '=' in type name"
+ )
+
[]
- member public this.AbstractClasses_Constructors_PositiveTests_1() =
- this.VerifyNoErrors("""
+ member public this.AbstractClasses_Constructors_PositiveTests_1() =
+ this.VerifyNoErrors(
+ """
[]
type C(a : int) =
new(a : string) = C(int a)
new(b) = match b with Some _ -> C(1) | _ -> C("")
- """)
+ """
+ )
[]
- member public this.AbstractClasses_Constructors_PositiveTests_2() =
- this.VerifyNoErrors("""
+ member public this.AbstractClasses_Constructors_PositiveTests_2() =
+ this.VerifyNoErrors(
+ """
[]
type C(a : int) =
new(a : string) = new C(int a)
new(b) = match b with Some _ -> new C(1) | _ -> new C("")
- """)
+ """
+ )
[]
- member public this.AbstractClasses_Constructors_PositiveTests_3() =
- this.VerifyNoErrors("""
+ member public this.AbstractClasses_Constructors_PositiveTests_3() =
+ this.VerifyNoErrors(
+ """
[]
type O(o : int) =
new() = O(1)
- """)
+ """
+ )
[]
- member public this.AbstractClasses_Constructors_PositiveTests_4() =
- this.VerifyNoErrors("""
+ member public this.AbstractClasses_Constructors_PositiveTests_4() =
+ this.VerifyNoErrors(
+ """
[]
type O(o : int) =
new() = O() then printfn "A"
- """)
+ """
+ )
[]
- member public this.AbstractClasses_Constructors_PositiveTests_5() =
- this.VerifyNoErrors("""
+ member public this.AbstractClasses_Constructors_PositiveTests_5() =
+ this.VerifyNoErrors(
+ """
[]
type O(o : int) =
new() = new O(1) then printfn "A"
- """)
+ """
+ )
[]
- member public this.AbstractClasses_Constructors_PositiveTests_6() =
- this.VerifyNoErrors("""
+ member public this.AbstractClasses_Constructors_PositiveTests_6() =
+ this.VerifyNoErrors(
+ """
[]
type D() = class end
[]
type E =
inherit D
new() = { inherit D(); }
- """)
-
+ """
+ )
+
[]
- member public this.AbstractClasses_Constructors_NegativeTests_1() =
+ member public this.AbstractClasses_Constructors_NegativeTests_1() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
[]
type D =
val d : D
new() = {d = D()}
""",
- expectedMarker = "D()")
-
+ expectedMarker = "D()"
+ )
+
[]
- member public this.AbstractClasses_Constructors_NegativeTests_2() =
+ member public this.AbstractClasses_Constructors_NegativeTests_2() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
[]
type Z () =
new(a : int) =
Z(10) then ignore(Z())
""",
- expectedMarker = "Z()")
-
+ expectedMarker = "Z()"
+ )
+
[]
- member public this.AbstractClasses_Constructors_NegativeTests_3() =
+ member public this.AbstractClasses_Constructors_NegativeTests_3() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
[]
type X() =
member val V : bool = true
@@ -207,116 +220,144 @@ type X() =
type Y() =
member val M : bool = (new X()).V
""",
- expectedMarker = "new X()")
-
+ expectedMarker = "new X()"
+ )
+
[]
- member public this.Waring_Construct_TypeMatchWithoutAnnotation() =
+ member public this.Waring_Construct_TypeMatchWithoutAnnotation() =
this.VerifyWarningBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let f () =
let g1 (x:'a) = x
let g2 (y:'a) = ((*start*)y(*end*):string)
g1 3, g1 "3", g2 "4"
""",
- expectedMessage = "This construct causes code to be less generic " +
- "than indicated by the type annotations. The " +
- "type variable 'a has been constrained to be " +
- "type 'string'.")
+ expectedMessage =
+ "This construct causes code to be less generic "
+ + "than indicated by the type annotations. The "
+ + "type variable 'a has been constrained to be "
+ + "type 'string'."
+ )
[]
- member public this.Error_Identifer_IllegalFloatPointLiteral() =
+ member public this.Error_Identifer_IllegalFloatPointLiteral() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let x: float = 1.2(*start*).(*end*)3
""",
- expectedMessage = "Missing qualification after '.'")
-
+ expectedMessage = "Missing qualification after '.'"
+ )
+
[]
- member public this.Error_TypeCheck_ParseError_Bug67133() =
+ member public this.Error_TypeCheck_ParseError_Bug67133() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let gDateTime (arr: (*start*)DateTime(*end*)[]) =
arr.[0]
""",
- expectedMessage = "The type 'DateTime' is not defined.")
-
+ expectedMessage = "The type 'DateTime' is not defined."
+ )
+
[]
- member public this.Error_CyclicalDeclarationDoesNotCrash() =
+ member public this.Error_CyclicalDeclarationDoesNotCrash() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
type (*start*)A(*end*) = int * A
""",
- expectedMessage = "This type definition involves an immediate cyclic reference through an abbreviation")
-
+ expectedMessage = "This type definition involves an immediate cyclic reference through an abbreviation"
+ )
+
[]
- member public this.Warning_FlagsAndSettings_TargetOptionsRespected() =
+ member public this.Warning_FlagsAndSettings_TargetOptionsRespected() =
this.VerifyWarningBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
[]
let fn x = 0
let y = (*start*)fn(*end*) 1
""",
- expectedMessage = "This construct is deprecated. x")
+ expectedMessage = "This construct is deprecated. x"
+ )
[]
member public this.Basic_Case() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let x = 3
let y = (*start*)x(*end*) 4
let arr = [| 1; 2; 3 |]
""",
- expectedMessage = "This value is not a function and cannot be applied.")
+ expectedMessage = "This value is not a function and cannot be applied."
+ )
[]
member public this.Multiline_Bug5449() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let f x = x + 1
let r = (*start*)f 3(*end*) 4
""",
- expectedMessage = "This value is not a function and cannot be applied.")
+ expectedMessage = "This value is not a function and cannot be applied."
+ )
[]
member public this.InComputationExpression_Bug6095_A() =
this.VerifyWarningBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let a = async {
let! (*start*)[| r1; r2 |](*end*) = Async.Parallel [| async.Return(1); async.Return(2) |]
let yyyy = 4
return r1,r2
}
""",
- expectedMessage = "Incomplete pattern matches on this expression. For example, the value '[|_; _; _|]' may indicate a case not covered by the pattern(s).")
+ expectedMessage =
+ "Incomplete pattern matches on this expression. For example, the value '[|_; _; _|]' may indicate a case not covered by the pattern(s)."
+ )
[]
member public this.InComputationExpression_Bug6095_B() =
this.VerifyWarningBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
let f = (*start*)function(*end*) | [| a;b |] -> ()
""",
- expectedMessage = "Incomplete pattern matches on this expression. For example, the value '[|_; _; _|]' may indicate a case not covered by the pattern(s).")
+ expectedMessage =
+ "Incomplete pattern matches on this expression. For example, the value '[|_; _; _|]' may indicate a case not covered by the pattern(s)."
+ )
[]
member public this.InComputationExpression_Bug6095_C() =
this.VerifyWarningBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
for (*start*)[|a;b|](*end*) in [| [|42|] |] do ()
""",
- expectedMessage = "Incomplete pattern matches on this expression. For example, the value '[|_; _; _|]' may indicate a case not covered by the pattern(s). Unmatched elements will be ignored.")
-
+ expectedMessage =
+ "Incomplete pattern matches on this expression. For example, the value '[|_; _; _|]' may indicate a case not covered by the pattern(s). Unmatched elements will be ignored."
+ )
+
[]
member public this.InComputationExpression_Bug914685() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
async { if true then return 1 } |> ignore
""",
- expectedMarker = "if true then")
+ expectedMarker = "if true then"
+ )
[]
member public this.ExtraEndif() =
this.VerifyErrorBetweenMarkers(
- fileContents = """
+ fileContents =
+ """
#if UNDEFINED
1
#else
@@ -324,12 +365,14 @@ async { if true then return 1 } |> ignore
#endif
(*start*)#endif(*end*)
""",
- expectedMessage = "#endif has no matching #if in implementation file")
-
+ expectedMessage = "#endif has no matching #if in implementation file"
+ )
+
[]
member public this.Squiggles_HashNotFirstSymbol_If() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
(*comment*) #if UNDEFINED
1
#else
@@ -337,12 +380,14 @@ async { if true then return 1 } |> ignore
#endif
""",
expectedMarker = " #if UNDEFINED",
- expectedMessage = "#if directive must appear as the first non-whitespace character on a line")
-
+ expectedMessage = "#if directive must appear as the first non-whitespace character on a line"
+ )
+
[]
member public this.Squiggles_HashNotFirstSymbol_Endif() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
#if DEBUG
1
#else
@@ -350,32 +395,38 @@ async { if true then return 1 } |> ignore
(*comment*) #endif
""",
expectedMarker = " #endif",
- expectedMessage = "#endif directive must appear as the first non-whitespace character on a line")
-
+ expectedMessage = "#endif directive must appear as the first non-whitespace character on a line"
+ )
+
[]
member public this.Squiggles_HashIfWithMultilineComment() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
#if DEBUG (*comment*)
#endif
""",
expectedMarker = "(*comment*)",
- expectedMessage = "Expected single line comment or end of line")
-
+ expectedMessage = "Expected single line comment or end of line"
+ )
+
[]
member public this.Squiggles_HashIfWithUnexpected() =
this.VerifyErrorAtMarker(
- fileContents = """
+ fileContents =
+ """
#if DEBUG TEST
#endif
""",
expectedMarker = "TEST",
- expectedMessage = "Incomplete preprocessor expression")
-
+ expectedMessage = "Incomplete preprocessor expression"
+ )
+
[]
member public this.OverloadsAndExtensionMethodsForGenericTypes() =
this.VerifyNoErrors(
- fileContents = """
+ fileContents =
+ """
open System.Linq
type T =
@@ -388,14 +439,17 @@ type T =
member this.GetEnumerator() : System.Collections.IEnumerator = failwith "not implemented"
let g (t : T) = t.Count()
- """)
-
+ """
+ )
+
[]
member public this.DocumentDiagnosticsDontReportProjectErrors_Bug1596() =
// https://github.com/dotnet/fsharp/issues/1596
this.VerifyNoErrors(
- fileContents = """
+ fileContents =
+ """
let x = 3
printf "%d" x
""",
- additionalFlags = [| "--times" |])
+ additionalFlags = [| "--times" |]
+ )
diff --git a/vsintegration/tests/FSharp.Editor.Tests/DocumentHighlightsServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/DocumentHighlightsServiceTests.fs
new file mode 100644
index 00000000000..c6749195615
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/DocumentHighlightsServiceTests.fs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests
+
+open FSharp.Editor.Tests.Helpers
+
+module DocumentHighlightsServiceTests =
+
+ open System
+ open NUnit.Framework
+ open Microsoft.CodeAnalysis
+ open Microsoft.CodeAnalysis.Text
+ open Microsoft.VisualStudio.FSharp.Editor
+ open FSharp.Compiler.CodeAnalysis
+ open FSharp.Compiler.Text
+
+ let filePath = "C:\\test.fs"
+
+ let internal projectOptions =
+ {
+ ProjectFileName = "C:\\test.fsproj"
+ ProjectId = None
+ SourceFiles = [| filePath |]
+ ReferencedProjects = [||]
+ OtherOptions = [||]
+ IsIncompleteTypeCheckEnvironment = true
+ UseScriptResolutionRules = false
+ LoadTime = DateTime.MaxValue
+ UnresolvedReferences = None
+ OriginalLoadReferences = []
+ Stamp = None
+ }
+
+ let private getSpans (sourceText: SourceText) (caretPosition: int) =
+ let document = RoslynTestHelpers.CreateSingleDocumentSolution(filePath, sourceText)
+
+ FSharpDocumentHighlightsService.GetDocumentHighlights(document, caretPosition)
+ |> Async.RunSynchronously
+ |> Option.defaultValue [||]
+
+ let private span sourceText isDefinition (startLine, startCol) (endLine, endCol) =
+ let range =
+ Range.mkRange filePath (Position.mkPos startLine startCol) (Position.mkPos endLine endCol)
+
+ {
+ IsDefinition = isDefinition
+ TextSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)
+ }
+
+ []
+ let ShouldHighlightAllSimpleLocalSymbolReferences () =
+ let fileContents =
+ """
+ let foo x =
+ x + x
+ let y = foo 2
+ """
+
+ let sourceText = SourceText.From(fileContents)
+ let caretPosition = fileContents.IndexOf("foo") + 1
+ let spans = getSpans sourceText caretPosition
+
+ let expected =
+ [| span sourceText true (2, 8) (2, 11); span sourceText false (4, 12) (4, 15) |]
+
+ Assert.AreEqual(expected, spans)
+
+ []
+ let ShouldHighlightAllQualifiedSymbolReferences () =
+ let fileContents =
+ """
+ let x = System.DateTime.Now
+ let y = System.DateTime.MaxValue
+ """
+
+ let sourceText = SourceText.From(fileContents)
+ let caretPosition = fileContents.IndexOf("DateTime") + 1
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+
+ let spans = getSpans sourceText caretPosition
+
+ let expected =
+ [|
+ span sourceText false (2, 19) (2, 27)
+ span sourceText false (3, 19) (3, 27)
+ |]
+
+ Assert.AreEqual(expected, spans)
+
+ let caretPosition = fileContents.IndexOf("Now") + 1
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+ let spans = getSpans sourceText caretPosition
+ let expected = [| span sourceText false (2, 28) (2, 31) |]
+
+ Assert.AreEqual(expected, spans)
diff --git a/vsintegration/tests/UnitTests/EditorFormattingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/EditorFormattingServiceTests.fs
similarity index 66%
rename from vsintegration/tests/UnitTests/EditorFormattingServiceTests.fs
rename to vsintegration/tests/FSharp.Editor.Tests/EditorFormattingServiceTests.fs
index 739b2600f6e..abf741b972d 100644
--- a/vsintegration/tests/UnitTests/EditorFormattingServiceTests.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/EditorFormattingServiceTests.fs
@@ -1,10 +1,9 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
-namespace VisualFSharp.UnitTests.Editor
-open System
+namespace FSharp.Editor.Tests
+open System
open NUnit.Framework
-
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text
open Microsoft.VisualStudio.FSharp.Editor
@@ -12,27 +11,29 @@ open FSharp.Compiler.CodeAnalysis
open Microsoft.CodeAnalysis.Formatting
[]
-[]
-type EditorFormattingServiceTests() =
+type EditorFormattingServiceTests() =
let filePath = "C:\\test.fs"
- let projectOptions : FSharpProjectOptions = {
- ProjectFileName = "C:\\test.fsproj"
- ProjectId = None
- SourceFiles = [| filePath |]
- ReferencedProjects = [| |]
- OtherOptions = [| |]
- IsIncompleteTypeCheckEnvironment = true
- UseScriptResolutionRules = false
- LoadTime = DateTime.MaxValue
- OriginalLoadReferences = []
- UnresolvedReferences = None
- Stamp = None
- }
+
+ let projectOptions: FSharpProjectOptions =
+ {
+ ProjectFileName = "C:\\test.fsproj"
+ ProjectId = None
+ SourceFiles = [| filePath |]
+ ReferencedProjects = [||]
+ OtherOptions = [||]
+ IsIncompleteTypeCheckEnvironment = true
+ UseScriptResolutionRules = false
+ LoadTime = DateTime.MaxValue
+ OriginalLoadReferences = []
+ UnresolvedReferences = None
+ Stamp = None
+ }
let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
let indentStyle = FormattingOptions.IndentStyle.Smart
-
- let indentTemplate = """
+
+ let indentTemplate =
+ """
let foo = [
15
]marker1
@@ -52,7 +53,8 @@ let def =
)marker4
"""
- let pasteTemplate = """
+ let pasteTemplate =
+ """
let foo =
printfn "Something here"
@@ -63,7 +65,7 @@ marker2
marker3
marker4"""
-
+
[]
[]
[]
@@ -74,10 +76,24 @@ marker4"""
Assert.IsTrue(position >= 0, "Precondition failed: unable to find marker in template")
let sourceText = SourceText.From(indentTemplate)
- let lineNumber = sourceText.Lines |> Seq.findIndex (fun line -> line.Span.Contains position)
+
+ let lineNumber =
+ sourceText.Lines |> Seq.findIndex (fun line -> line.Span.Contains position)
+
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
-
- let changesOpt = FSharpEditorFormattingService.GetFormattingChanges(documentId, sourceText, filePath, checker, indentStyle, parsingOptions, position) |> Async.RunSynchronously
+
+ let changesOpt =
+ FSharpEditorFormattingService.GetFormattingChanges(
+ documentId,
+ sourceText,
+ filePath,
+ checker,
+ indentStyle,
+ parsingOptions,
+ position
+ )
+ |> Async.RunSynchronously
+
match changesOpt with
| None -> Assert.Fail("Expected a text change, but got None")
| Some changes ->
@@ -92,11 +108,14 @@ marker4"""
let checker = FSharpChecker.Create()
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
- let clipboard = prefix + """[]
+ let clipboard =
+ prefix
+ + """[]
type SomeNameHere () =
member _.Test ()"""
- let start = """
+ let start =
+ """
let foo =
printfn "Something here"
@@ -105,7 +124,8 @@ let foo =
somethingElseHere
"""
- let expected = """
+ let expected =
+ """
let foo =
printfn "Something here"
@@ -115,23 +135,32 @@ let foo =
somethingElseHere
"""
-
+
let sourceText = SourceText.From(start.Replace("$", clipboard))
let span = TextSpan(start.IndexOf '$', clipboard.Length)
let formattingOptions = { FormatOnPaste = enabled }
let changesOpt =
- FSharpEditorFormattingService.GetPasteChanges(documentId, sourceText, filePath, formattingOptions, 4, parsingOptions, clipboard, span)
+ FSharpEditorFormattingService.GetPasteChanges(
+ documentId,
+ sourceText,
+ filePath,
+ formattingOptions,
+ 4,
+ parsingOptions,
+ clipboard,
+ span
+ )
|> Async.RunSynchronously
|> Option.map List.ofSeq
-
+
if enabled then
match changesOpt with
| Some changes ->
let changedText = sourceText.WithChanges(changes).ToString()
Assert.AreEqual(expected, changedText)
- | _ -> Assert.Fail (sprintf "Expected text changes, but got %+A" changesOpt)
+ | _ -> Assert.Fail(sprintf "Expected text changes, but got %+A" changesOpt)
else
Assert.AreEqual(None, changesOpt, "Expected no changes as FormatOnPaste is disabled")
@@ -141,11 +170,14 @@ somethingElseHere
let checker = FSharpChecker.Create()
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
- let clipboard = prefix + """[]
+ let clipboard =
+ prefix
+ + """[]
type SomeNameHere () =
member _.Test ()"""
- let start = """
+ let start =
+ """
$
let foo =
@@ -154,7 +186,8 @@ let foo =
somethingElseHere
"""
- let expected = """
+ let expected =
+ """
[]
type SomeNameHere () =
member _.Test ()
@@ -164,14 +197,23 @@ let foo =
somethingElseHere
"""
-
+
let sourceText = SourceText.From(start.Replace("$", clipboard))
let span = TextSpan(start.IndexOf '$', clipboard.Length)
let formattingOptions = { FormatOnPaste = true }
let changesOpt =
- FSharpEditorFormattingService.GetPasteChanges(documentId, sourceText, filePath, formattingOptions, 4, parsingOptions, clipboard, span)
+ FSharpEditorFormattingService.GetPasteChanges(
+ documentId,
+ sourceText,
+ filePath,
+ formattingOptions,
+ 4,
+ parsingOptions,
+ clipboard,
+ span
+ )
|> Async.RunSynchronously
|> Option.map List.ofSeq
@@ -179,18 +221,20 @@ somethingElseHere
| Some changes ->
let changedText = sourceText.WithChanges(changes).ToString()
Assert.AreEqual(expected, changedText)
- | _ -> Assert.Fail (sprintf "Expected a changes, but got %+A" changesOpt)
+ | _ -> Assert.Fail(sprintf "Expected a changes, but got %+A" changesOpt)
[]
member this.TestPasteChanges_PastingWithAutoIndentationInPasteSpan() =
let checker = FSharpChecker.Create()
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
- let clipboard = """[]
+ let clipboard =
+ """[]
type SomeNameHere () =
member _.Test ()"""
- let start = """
+ let start =
+ """
let foo =
printfn "Something here"
@@ -199,7 +243,8 @@ let foo =
somethingElseHere
"""
- let expected = """
+ let expected =
+ """
let foo =
printfn "Something here"
@@ -209,7 +254,7 @@ let foo =
somethingElseHere
"""
-
+
let sourceText = SourceText.From(start.Replace("$", clipboard))
// If we're pasting on an empty line which has been automatically indented,
@@ -220,7 +265,16 @@ somethingElseHere
let formattingOptions = { FormatOnPaste = true }
let changesOpt =
- FSharpEditorFormattingService.GetPasteChanges(documentId, sourceText, filePath, formattingOptions, 4, parsingOptions, clipboard, span)
+ FSharpEditorFormattingService.GetPasteChanges(
+ documentId,
+ sourceText,
+ filePath,
+ formattingOptions,
+ 4,
+ parsingOptions,
+ clipboard,
+ span
+ )
|> Async.RunSynchronously
|> Option.map List.ofSeq
@@ -228,4 +282,4 @@ somethingElseHere
| Some changes ->
let changedText = sourceText.WithChanges(changes).ToString()
Assert.AreEqual(expected, changedText)
- | _ -> Assert.Fail (sprintf "Expected a changes, but got %+A" changesOpt)
+ | _ -> Assert.Fail(sprintf "Expected a changes, but got %+A" changesOpt)
diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
new file mode 100644
index 00000000000..53a86636a80
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
@@ -0,0 +1,50 @@
+
+
+
+ net472
+ nunit
+ false
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs
new file mode 100644
index 00000000000..b06a009c055
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests
+
+open System
+open System.Collections.Generic
+open NUnit.Framework
+open Microsoft.CodeAnalysis.Text
+open Microsoft.VisualStudio.FSharp.Editor
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Editor.Tests.Helpers
+
+// AppDomain helper
+type Worker() =
+
+ let filePath = "C:\\test.fsx"
+
+ let projectOptions =
+ {
+ ProjectFileName = "C:\\test.fsproj"
+ ProjectId = None
+ SourceFiles = [| filePath |]
+ ReferencedProjects = [||]
+ OtherOptions = [||]
+ IsIncompleteTypeCheckEnvironment = true
+ UseScriptResolutionRules = true
+ LoadTime = DateTime.MaxValue
+ OriginalLoadReferences = []
+ UnresolvedReferences = None
+ Stamp = None
+ }
+
+ member _.VerifyCompletionListExactly(fileContents: string, marker: string, expected: List) =
+ let caretPosition = fileContents.IndexOf(marker) + marker.Length
+
+ let document =
+ RoslynTestHelpers.CreateSingleDocumentSolution(filePath, SourceText.From(fileContents), options = projectOptions)
+
+ let expected = expected |> Seq.toList
+
+ let actual =
+ let x =
+ FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []))
+ |> Async.RunSynchronously
+
+ x
+ |> Option.defaultValue (ResizeArray())
+ |> Seq.toList
+ // sort items as Roslyn do - by `SortText`
+ |> List.sortBy (fun x -> x.SortText)
+
+ let actualNames = actual |> List.map (fun x -> x.DisplayText)
+
+ if actualNames <> expected then
+ Assert.Fail(
+ sprintf
+ "Expected:\n%s,\nbut was:\n%s\nactual with sort text:\n%s"
+ (String.Join("; ", expected |> List.map (sprintf "\"%s\"")))
+ (String.Join("; ", actualNames |> List.map (sprintf "\"%s\"")))
+ (String.Join("\n", actual |> List.map (fun x -> sprintf "%s => %s" x.DisplayText x.SortText)))
+ )
+
+module FsxCompletionProviderTests =
+
+ let getWorker () = Worker()
+
+ []
+#if RELEASE
+ []
+#endif
+ let fsiShouldTriggerCompletionInFsxFile () =
+ let fileContents =
+ """
+ fsi.
+ """
+
+ let expected =
+ List(
+ [
+ "CommandLineArgs"
+ "EventLoop"
+ "FloatingPointFormat"
+ "FormatProvider"
+ "PrintDepth"
+ "PrintLength"
+ "PrintSize"
+ "PrintWidth"
+ "ShowDeclarationValues"
+ "ShowIEnumerable"
+ "ShowProperties"
+ "AddPrinter"
+ "AddPrintTransformer"
+ "Equals"
+ "GetHashCode"
+ "GetType"
+ "ToString"
+ ]
+ )
+
+ // We execute in a seperate appdomain so that we can set BaseDirectory to a non-existent location
+ getWorker().VerifyCompletionListExactly(fileContents, "fsi.", expected)
diff --git a/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs
new file mode 100644
index 00000000000..2386e432c4d
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs
@@ -0,0 +1,167 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests
+
+open System
+open System.IO
+open NUnit.Framework
+open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.Text
+open Microsoft.VisualStudio.FSharp.Editor
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.EditorServices
+open FSharp.Compiler.Text
+open FSharp.Editor.Tests.Helpers
+
+[]
+module GoToDefinitionServiceTests =
+
+ let userOpName = "GoToDefinitionServiceTests"
+
+ let private findDefinition (document: Document, sourceText: SourceText, position: int, defines: string list) : range option =
+ maybe {
+ let textLine = sourceText.Lines.GetLineFromPosition position
+ let textLinePos = sourceText.Lines.GetLinePosition position
+ let fcsTextLineNumber = Line.fromZ textLinePos.Line
+
+ let! lexerSymbol =
+ Tokenizer.getSymbolAtPosition (
+ document.Id,
+ sourceText,
+ position,
+ document.FilePath,
+ defines,
+ SymbolLookupKind.Greedy,
+ false,
+ false
+ )
+
+ let _, checkFileResults =
+ document.GetFSharpParseAndCheckResultsAsync(nameof (userOpName))
+ |> Async.RunSynchronously
+
+ let declarations =
+ checkFileResults.GetDeclarationLocation(
+ fcsTextLineNumber,
+ lexerSymbol.Ident.idRange.EndColumn,
+ textLine.ToString(),
+ lexerSymbol.FullIsland,
+ false
+ )
+
+ match declarations with
+ | FindDeclResult.DeclFound range -> return range
+ | _ -> return! None
+ }
+
+ let makeOptions filePath args =
+ {
+ ProjectFileName = "C:\\test.fsproj"
+ ProjectId = None
+ SourceFiles = [| filePath |]
+ ReferencedProjects = [||]
+ OtherOptions = args
+ IsIncompleteTypeCheckEnvironment = true
+ UseScriptResolutionRules = false
+ LoadTime = DateTime.MaxValue
+ OriginalLoadReferences = []
+ UnresolvedReferences = None
+ Stamp = None
+ }
+
+ let GoToDefinitionTest (fileContents: string, caretMarker: string, expected, opts) =
+
+ let filePath = Path.GetTempFileName() + ".fs"
+ File.WriteAllText(filePath, fileContents)
+ let options = makeOptions filePath opts
+
+ let caretPosition = fileContents.IndexOf(caretMarker) + caretMarker.Length - 1 // inside the marker
+
+ let document, sourceText =
+ RoslynTestHelpers.CreateSingleDocumentSolution(filePath, fileContents, options = options)
+
+ let actual =
+ findDefinition (document, sourceText, caretPosition, [])
+ |> Option.map (fun range -> (range.StartLine, range.EndLine, range.StartColumn, range.EndColumn))
+
+ if actual <> expected then
+ Assert.Fail(
+ sprintf
+ "Incorrect information returned for fileContents=<<<%s>>>, caretMarker=<<<%s>>>, expected =<<<%A>>>, actual = <<<%A>>>"
+ fileContents
+ caretMarker
+ expected
+ actual
+ )
+
+ []
+ let ``goto definition smoke test`` () =
+
+ let manyTestCases =
+ [
+ // Test1
+ ("""
+type TestType() =
+ member this.Member1(par1: int) =
+ printf "%d" par1
+ member this.Member2(par2: string) =
+ printf "%s" par2
+
+[]
+let main argv =
+ let obj = TestType()
+ obj.Member1(5)
+ obj.Member2("test")""",
+ [
+ ("printf \"%d\" par1", Some(3, 3, 24, 28))
+ ("printf \"%s\" par2", Some(5, 5, 24, 28))
+ ("let obj = TestType", Some(2, 2, 5, 13))
+ ("let obj", Some(10, 10, 8, 11))
+ ("obj.Member1", Some(3, 3, 16, 23))
+ ("obj.Member2", Some(5, 5, 16, 23))
+ ])
+ // Test2
+ ("""
+module Module1 =
+ let foo x = x
+
+let _ = Module1.foo 1
+""",
+ [ ("let _ = Module", Some(2, 2, 7, 14)) ])
+ ]
+
+ for fileContents, testCases in manyTestCases do
+ for caretMarker, expected in testCases do
+
+ printfn "Test case: caretMarker=<<<%s>>>" caretMarker
+ GoToDefinitionTest(fileContents, caretMarker, expected, [||])
+
+ []
+ let ``goto definition for string interpolation`` () =
+
+ let fileContents =
+ """
+let xxxxx = 1
+let yyyy = $"{abc{xxxxx}def}" """
+
+ let caretMarker = "xxxxx"
+ let expected = Some(2, 2, 4, 9)
+
+ GoToDefinitionTest(fileContents, caretMarker, expected, [||])
+
+ []
+ let ``goto definition for static abstract method invocation`` () =
+
+ let fileContents =
+ """
+type IStaticProperty<'T when 'T :> IStaticProperty<'T>> =
+ static abstract StaticProperty: 'T
+
+let f_IWSAM_flex_StaticProperty(x: #IStaticProperty<'T>) =
+ 'T.StaticProperty
+"""
+
+ let caretMarker = "'T.StaticProperty"
+ let expected = Some(3, 3, 20, 34)
+
+ GoToDefinitionTest(fileContents, caretMarker, expected, [| "/langversion:preview" |])
diff --git a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs
new file mode 100644
index 00000000000..1c02be655bf
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs
@@ -0,0 +1,436 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests
+
+open System
+open System.Threading
+open NUnit.Framework
+open Microsoft.CodeAnalysis
+open FSharp.Compiler.CodeAnalysis
+open Microsoft.VisualStudio.FSharp.Editor
+open Microsoft.IO
+open FSharp.Editor.Tests.Helpers
+
+[]
+type HelpContextServiceTests() =
+ let PathRelativeToTestAssembly p =
+ Path.Combine(Path.GetDirectoryName(Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath), p)
+
+ let filePath = "C:\\test.fs"
+
+ let makeOptions args =
+ {
+ ProjectFileName = "C:\\test.fsproj"
+ ProjectId = None
+ SourceFiles = [| filePath |]
+ ReferencedProjects = [||]
+ OtherOptions = args
+ IsIncompleteTypeCheckEnvironment = true
+ UseScriptResolutionRules = false
+ LoadTime = DateTime.MaxValue
+ OriginalLoadReferences = []
+ UnresolvedReferences = None
+ Stamp = None
+ }
+
+ let getMarkers (source: string) =
+ let mutable cnt = 0
+
+ [
+ for i in 0 .. (source.Length - 1) do
+ if source.[i] = '$' then
+ yield (i - cnt)
+ cnt <- cnt + 1
+ ]
+
+ let TestF1KeywordsWithOptions (expectedKeywords: string option list, lines: string list, opts: string[]) =
+ let options = makeOptions opts
+
+ let fileContentsWithMarkers = String.Join("\r\n", lines)
+ let fileContents = fileContentsWithMarkers.Replace("$", "")
+
+ let document, sourceText =
+ RoslynTestHelpers.CreateSingleDocumentSolution(filePath, fileContents, options = options)
+
+ let markers = getMarkers fileContentsWithMarkers
+
+ let res =
+ [
+ for marker in markers do
+ let span = Microsoft.CodeAnalysis.Text.TextSpan(marker, 0)
+ let textLine = sourceText.Lines.GetLineFromPosition(marker)
+ let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
+
+ let classifiedSpans =
+ Tokenizer.getClassifiedSpans (documentId, sourceText, textLine.Span, Some "test.fs", [], CancellationToken.None)
+
+ FSharpHelpContextService.GetHelpTerm(document, span, classifiedSpans)
+ |> Async.RunSynchronously
+ ]
+
+ let equalLength = (expectedKeywords.Length = res.Length)
+ Assert.True(equalLength)
+
+ for (exp, res) in List.zip expectedKeywords res do
+ Assert.AreEqual(exp, res)
+
+ let TestF1Keywords (expectedKeywords, lines) =
+ TestF1KeywordsWithOptions(expectedKeywords, lines, [||])
+
+ []
+#if RELEASE
+ []
+#endif
+ member _.``F1 help keyword NoKeyword.Negative``() =
+ let file =
+ [
+ "let s = \"System.Con$sole\""
+ "let n = 999$99"
+ "#if UNDEFINED"
+ " let w = List.re$v []"
+ "#endif"
+ ]
+
+ let keywords = [ None; None; None ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Preprocessor``() =
+ let file = [ "#i$f foobaz"; "#e$ndif" ]
+ let keywords = [ Some "#if_FS"; Some "#endif_FS" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Regression.DotNetMethod.854364``() =
+ let file = [ "let i : int = 42"; "i.ToStri$ng()"; "i.ToStri$ng(\"format\")" ]
+ let keywords = [ Some "System.Int32.ToString"; Some "System.Int32.ToString" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Namespaces``() =
+ let file =
+ [
+ "open Syst$em.N$et"
+ "open System.I$O"
+ "open Microsoft.FSharp.Core"
+ ""
+ "System.Cons$ole.WriteLine()"
+ ]
+
+ let keywords =
+ [ Some "System"; Some "System.Net"; Some "System.IO"; Some "System.Console" ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Namespaces.BeforeDot``() =
+ let file =
+ [
+ "open System$.Net$"
+ "open System$.IO"
+ "open System$.Collections$.Generic$"
+ "open Microsoft.FSharp.Core"
+ ""
+ "System$.Console$.WriteLine()"
+ ]
+
+ let keywords =
+ [
+ Some "System"
+ Some "System.Net"
+ Some "System"
+ Some "System"
+ Some "System.Collections"
+ Some "System.Collections.Generic"
+ Some "System"
+ Some "System.Console"
+ ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Namespaces.AfterDot``() =
+ let file =
+ [
+ "open $System.$Net"
+ "open $System.IO"
+ "open $System.$Collections.$Generic"
+ "open Microsoft.FSharp.Core"
+ ""
+ "$System.$Console.$WriteLine()"
+ ]
+
+ let keywords =
+ [
+ Some "System"
+ Some "System.Net"
+ Some "System"
+ Some "System"
+ Some "System.Collections"
+ Some "System.Collections.Generic"
+ Some "System"
+ Some "System.Console"
+ Some "System.Console.WriteLine"
+ ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword QuotedIdentifiers``() =
+ let file =
+ [
+ "let `$`escaped func`` x y = x + y"
+ "let ``escaped value`$` = 1"
+ "let x = 1"
+ "``escaped func`` x$ ``escaped value``"
+ "``escaped func``$ x ``escaped value``"
+ "``escaped func`` x $``escaped value``"
+ "let ``z$`` = 1"
+ "``$z`` |> printfn \"%d\""
+ ]
+
+ let keywords =
+ [
+ Some "Test.escaped func"
+ Some "Test.escaped value"
+ Some "Test.x"
+ Some "Test.escaped func"
+ Some "Test.escaped value"
+ Some "Test.z"
+ Some "Test.z"
+ ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Attributes``() =
+ let file =
+ [
+ "open System.Runtime.InteropServices"
+ "open System.Runtime.CompilerServices"
+ "[]"
+ "type X = "
+ " []"
+ " val mutable f : int"
+ " []"
+ " member _.Run() = ()"
+ "[]"
+ "type Y = class end"
+ ]
+
+ let keywords =
+ [
+ Some "Microsoft.FSharp.Core.StructAttribute.#ctor"
+ Some "Microsoft.FSharp.Core.DefaultValueAttribute.#ctor"
+ Some "System.Runtime.CompilerServices.MethodImplAttribute.#ctor"
+ Some "System.Runtime.InteropServices.StructLayoutAttribute.Size"
+ ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+#if RELEASE
+ []
+#endif
+ //This test case Verify that when F1 is Hit on TypeProvider namespaces it contain the right keyword
+ member _.``F1 help keyword TypeProvider.Namespaces``() =
+ let file = [ "open N$1" ]
+ let keywords = [ Some "N1" ]
+
+ TestF1KeywordsWithOptions(
+ keywords,
+ file,
+ [|
+ "-r:"
+ + PathRelativeToTestAssembly(@"DummyProviderForLanguageServiceTesting.dll")
+ |]
+ )
+
+ []
+#if RELEASE
+ []
+#endif
+ //This test case Verify that when F1 is Hit on TypeProvider Type it contain the right keyword
+ member _.``F1 help keyword TypeProvider.type``() =
+
+ let file =
+ [
+ //Dummy Type Provider exposes a parametric type (N1.T) that takes 2 static params (string * int)
+ """let foo = typeof>"""
+ ]
+
+ let keywords = [ Some "N1.T" ]
+
+ TestF1KeywordsWithOptions(
+ keywords,
+ file,
+ [|
+ "-r:"
+ + PathRelativeToTestAssembly(@"DummyProviderForLanguageServiceTesting.dll")
+ |]
+ )
+
+ []
+ member _.``F1 help keyword EndOfLine``() =
+ let file = [ "open System.Net$"; "open System.IO$" ]
+ let keywords = [ Some "System.Net"; Some "System.IO" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword EndOfLine2``() =
+ let file = [ "module M"; "open System.Net$"; "open System.IO$" ]
+ let keywords = [ Some "System.Net"; Some "System.IO" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Comments``() =
+ let file = [ "($* co$mment *$)"; "/$/ com$ment" ]
+
+ let keywords =
+ [
+ Some "comment_FS"
+ Some "comment_FS"
+ Some "comment_FS"
+ Some "comment_FS"
+ Some "comment_FS"
+ ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword FSharpEntities``() =
+ let file =
+ [
+ "let (KeyValu$e(k,v)) = null"
+ "let w : int lis$t = []"
+ "let b = w.IsEm$pty"
+ "let m : map = Map.empty"
+ "m.A$dd(1,1)"
+ "let z = Li$st.r$ev w"
+ "let o = No$ne"
+ "let o1 = So$me 1"
+ "let c : System.IO.Str$eam = null"
+ "c.Async$Read(10)"
+ "let r = r$ef 0"
+ "r.conten$ts"
+ ]
+
+ let keywords =
+ [
+ Some "Microsoft.FSharp.Core.Operators.KeyValuePattern``2"
+ Some "Microsoft.FSharp.Collections.FSharpList`1"
+ Some "Microsoft.FSharp.Collections.FSharpList`1.IsEmpty"
+ Some "Microsoft.FSharp.Collections.FSharpMap`2.Add"
+ Some "Microsoft.FSharp.Collections.ListModule"
+ Some "Microsoft.FSharp.Collections.ListModule.Reverse``1" // per F1 keyword spec - one tick for classes, two ticks for members
+ Some "Microsoft.FSharp.Core.FSharpOption`1.None"
+ Some "Microsoft.FSharp.Core.FSharpOption`1.Some"
+ Some "System.IO.Stream"
+ Some "Microsoft.FSharp.Control.CommonExtensions.AsyncReadBytes"
+ Some "Microsoft.FSharp.Core.Operators.Ref``1"
+ Some "Microsoft.FSharp.Core.FSharpRef`1.contents"
+ ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Keywords``() =
+ let file =
+ [
+ "l$et r = ref 0"
+ "r :$= 1"
+ "let mut$able foo = 1"
+ "foo <$- 2"
+ "let$ z = 1"
+ ]
+
+ let keywords =
+ [ Some "let_FS"; Some ":=_FS"; Some "mutable_FS"; Some "<-_FS"; Some "let_FS" ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Regression.NewInstance.854367``() =
+ let file = [ "let q : System.Runtime.Remoting.TypeE$ntry = null" ]
+ let keywords = [ Some "System.Runtime.Remoting.TypeEntry" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Regression.NewInstance.854367.2``() =
+ let file =
+ [
+ "let q1 = new System.Runtime.Remoting.Type$Entry()" // this consutrctor exists but is not accessible (it is protected), but the help entry still goes to the type
+ ]
+
+ let keywords = [ Some "System.Runtime.Remoting.TypeEntry" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Classes.WebClient``() =
+ let file = [ "let w : System.Net.Web$Client = new System.Net.Web$Client()" ]
+ let keywords = [ Some "System.Net.WebClient"; Some "System.Net.WebClient.#ctor" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Classes.Object``() =
+ let file = [ "let w : System.Ob$ject = new System.Obj$ect()" ]
+ let keywords = [ Some "System.Object"; Some "System.Object.#ctor" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Classes.Generic``() =
+ let file = [ "let x : System.Collections.Generic.L$ist = null" ]
+ let keywords = [ Some "System.Collections.Generic.List`1" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Classes.Abbrev``() =
+ let file = [ "let z : Resi$zeArray = null" ]
+ let keywords = [ Some "System.Collections.Generic.List`1" ]
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword Members``() =
+ let file =
+ [
+ "open System.Linq"
+ "open System"
+ "let l = new ResizeArray()"
+ "let i = l.Cou$nt"
+ "l.Ad$d(1)"
+ "let m = new System.IO.MemoryStream()"
+ "m.BeginRe$ad()"
+ "l.Se$lect(fun i -> i + 1)"
+ "let d = new System.DateTi$me()"
+ "let s = String.Empty"
+ "s.Equ$als(null)"
+ "let i = 12"
+ "i.ToStr$ing()"
+ ]
+
+ let keywords =
+ [
+ Some "System.Collections.Generic.List`1.Count"
+ Some "System.Collections.Generic.List`1.Add"
+ Some "System.IO.Stream.BeginRead"
+ Some "System.Linq.Enumerable.Select``2" // per F1 keyword spec - one tick for classes, two ticks for members
+ Some "System.DateTime.#ctor"
+ Some "System.String.Equals"
+ Some "System.Int32.ToString"
+ ]
+
+ TestF1Keywords(keywords, file)
+
+ []
+ member _.``F1 help keyword static abstract interface method``() =
+ let file =
+ [
+ "type IStaticProperty<'T when 'T :> IStaticProperty<'T>> ="
+ " static abstract StaticProperty: 'T"
+ ""
+ "let f_IWSAM_flex_StaticProperty(x: #IStaticProperty<'T>) ="
+ " 'T.StaticProp$erty"
+ ]
+
+ let keywords = [ Some "Test.IStaticProperty`1.StaticProperty" ]
+ TestF1Keywords(keywords, file)
diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs
new file mode 100644
index 00000000000..f41f10fa138
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests.Helpers
+
+open System
+open System.IO
+open System.Reflection
+
+module AssemblyResolver =
+ open System.Globalization
+
+ let vsInstallDir =
+ // use the environment variable to find the VS installdir
+ let vsvar =
+ let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS")
+
+ if String.IsNullOrEmpty var then
+ Environment.GetEnvironmentVariable("VSAPPIDDIR")
+ else
+ var
+
+ if String.IsNullOrEmpty vsvar then
+ failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found."
+
+ Path.Combine(vsvar, "..")
+
+ let probingPaths =
+ [|
+ Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor")
+ Path.Combine(vsInstallDir, @"IDE\PublicAssemblies")
+ Path.Combine(vsInstallDir, @"IDE\PrivateAssemblies")
+ Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices")
+ Path.Combine(vsInstallDir, @"IDE\Extensions\Microsoft\CodeSense\Framework")
+ Path.Combine(vsInstallDir, @"IDE")
+ |]
+
+ let addResolver () =
+ AppDomain.CurrentDomain.add_AssemblyResolve (fun h args ->
+ let found () =
+ (probingPaths)
+ |> Seq.tryPick (fun p ->
+ try
+ let name = AssemblyName(args.Name)
+ let codebase = Path.GetFullPath(Path.Combine(p, name.Name) + ".dll")
+
+ if File.Exists(codebase) then
+ name.CodeBase <- codebase
+ name.CultureInfo <- Unchecked.defaultof
+ name.Version <- Unchecked.defaultof
+ Some(name)
+ else
+ None
+ with _ ->
+ None)
+
+ match found () with
+ | None -> Unchecked.defaultof
+ | Some name -> Assembly.Load(name))
diff --git a/vsintegration/tests/UnitTests/ProjectOptionsBuilder.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/ProjectOptionsBuilder.fs
similarity index 74%
rename from vsintegration/tests/UnitTests/ProjectOptionsBuilder.fs
rename to vsintegration/tests/FSharp.Editor.Tests/Helpers/ProjectOptionsBuilder.fs
index f9a20e674d6..395493ae148 100644
--- a/vsintegration/tests/UnitTests/ProjectOptionsBuilder.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/ProjectOptionsBuilder.fs
@@ -1,4 +1,6 @@
-namespace VisualFSharp.UnitTests.Editor
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests.Helpers
open System
open System.IO
@@ -9,14 +11,14 @@ module FileSystemHelpers =
let safeDeleteFile (path: string) =
try
File.Delete(path)
- with
- | _ -> ()
+ with _ ->
+ ()
let safeDeleteDirectory (path: string) =
try
Directory.Delete(path)
- with
- | _ -> ()
+ with _ ->
+ ()
type FSharpProject =
{
@@ -26,24 +28,29 @@ type FSharpProject =
}
/// Strips cursor information from each file and returns the name and cursor position of the last file to specify it.
- member this.GetCaretPosition () =
+ member this.GetCaretPosition() =
let caretSentinel = "$$"
let mutable cursorInfo: (string * int) = (null, 0)
+
this.Files
|> List.iter (fun (name, contents) ->
// find the '$$' sentinel that represents the cursor location
let caretPosition = contents.IndexOf(caretSentinel)
+
if caretPosition >= 0 then
- let newContents = contents.Substring(0, caretPosition) + contents.Substring(caretPosition + caretSentinel.Length)
+ let newContents =
+ contents.Substring(0, caretPosition)
+ + contents.Substring(caretPosition + caretSentinel.Length)
+
File.WriteAllText(Path.Combine(this.Directory, name), newContents)
cursorInfo <- (name, caretPosition))
+
cursorInfo
+
interface IDisposable with
member this.Dispose() =
// delete each source file
- this.Files
- |> List.map fst
- |> List.iter FileSystemHelpers.safeDeleteFile
+ this.Files |> List.map fst |> List.iter FileSystemHelpers.safeDeleteFile
// delete the directory
FileSystemHelpers.safeDeleteDirectory (this.Directory)
// project file doesn't really exist, nothing to delete
@@ -56,11 +63,17 @@ module internal ProjectOptionsBuilder =
let private ProjectName = XName.op_Implicit "Project"
let private ReferenceName = XName.op_Implicit "Reference"
- let private CreateSingleProjectFromMarkup(markup:XElement) =
- if markup.Name.LocalName <> "Project" then failwith "Expected root node to be "
+ let private CreateSingleProjectFromMarkup (markup: XElement) =
+ if markup.Name.LocalName <> "Project" then
+ failwith "Expected root node to be "
+
let projectRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())
- if Directory.Exists(projectRoot) then Directory.Delete(projectRoot, true)
+
+ if Directory.Exists(projectRoot) then
+ Directory.Delete(projectRoot, true)
+
Directory.CreateDirectory(projectRoot) |> ignore
+
let files = // filename -> fileContents
markup.Elements(FileName)
|> Seq.map (fun file ->
@@ -69,6 +82,7 @@ module internal ProjectOptionsBuilder =
File.WriteAllText(fileName, fileContents)
(fileName, fileContents))
|> List.ofSeq
+
let options =
{
ProjectFileName = Path.Combine(projectRoot, markup.Attribute(NameName).Value)
@@ -83,14 +97,17 @@ module internal ProjectOptionsBuilder =
UnresolvedReferences = None
Stamp = None
}
+
{
Directory = projectRoot
Options = options
Files = files
}
- let private CreateMultipleProjectsFromMarkup(markup:XElement) =
- if markup.Name.LocalName <> "Projects" then failwith "Expected root node to be "
+ let private CreateMultipleProjectsFromMarkup (markup: XElement) =
+ if markup.Name.LocalName <> "Projects" then
+ failwith "Expected root node to be "
+
let projectsAndXml =
markup.Elements(ProjectName)
|> Seq.map (fun xml -> (CreateSingleProjectFromMarkup xml, xml))
@@ -102,9 +119,10 @@ module internal ProjectOptionsBuilder =
let normalizedProjectName = Path.GetFileName(projectOptions.Options.ProjectFileName)
(normalizedProjectName, projectOptions))
|> Map.ofList
+
let projects =
projectsAndXml
- |> List.map(fun (projectOptions, xml) ->
+ |> List.map (fun (projectOptions, xml) ->
// bind references to their `FSharpProjectOptions` counterpart
let referenceList =
xml.Elements(ReferenceName)
@@ -117,33 +135,34 @@ module internal ProjectOptionsBuilder =
let binaryPath = Path.Combine(project.Directory, "bin", asmName + ".dll")
(binaryPath, project.Options))
|> Array.ofList
- let binaryRefs =
- referenceList
- |> Array.map fst
- |> Array.map (fun r -> "-r:" + r)
+
+ let binaryRefs = referenceList |> Array.map fst |> Array.map (fun r -> "-r:" + r)
let otherOptions = Array.append projectOptions.Options.OtherOptions binaryRefs
+
{ projectOptions with
- Options = { projectOptions.Options with
- ReferencedProjects = referenceList |> Array.map FSharpReferencedProject.CreateFSharp
- OtherOptions = otherOptions
- }
+ Options =
+ { projectOptions.Options with
+ ReferencedProjects = referenceList |> Array.map FSharpReferencedProject.CreateFSharp
+ OtherOptions = otherOptions
+ }
})
+
let rootProject = List.head projects
rootProject
-
- let CreateProjectFromMarkup(markup:XElement) =
+
+ let CreateProjectFromMarkup (markup: XElement) =
match markup.Name.LocalName with
| "Project" -> CreateSingleProjectFromMarkup markup
| "Projects" -> CreateMultipleProjectsFromMarkup markup
| name -> failwith <| sprintf "Unsupported root node name: %s" name
- let CreateProject(markup:string) =
- XDocument.Parse(markup).Root
- |> CreateProjectFromMarkup
+ let CreateProject (markup: string) =
+ XDocument.Parse(markup).Root |> CreateProjectFromMarkup
- let SingleFileProject(code:string) =
+ let SingleFileProject (code: string) =
code
- |> sprintf @"
+ |> sprintf
+ @"
diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs
new file mode 100644
index 00000000000..7e2af345e20
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs
@@ -0,0 +1,327 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace FSharp.Editor.Tests.Helpers
+
+open System
+open System.IO
+open System.Reflection
+open System.Linq
+open System.Collections.Generic
+open System.Collections.Immutable
+open Microsoft.CodeAnalysis
+open Microsoft.VisualStudio.Composition
+open Microsoft.CodeAnalysis.Host
+open Microsoft.CodeAnalysis.Text
+open Microsoft.VisualStudio.FSharp.Editor
+open Microsoft.CodeAnalysis.Host.Mef
+open FSharp.Compiler.CodeAnalysis
+
+[]
+module MefHelpers =
+
+ let getAssemblies () =
+ let self = Assembly.GetExecutingAssembly()
+ let here = AppContext.BaseDirectory
+
+ let imports =
+ [|
+ "Microsoft.CodeAnalysis.Workspaces.dll"
+ "Microsoft.VisualStudio.Shell.15.0.dll"
+ "FSharp.Editor.dll"
+ |]
+
+ let resolvedImports = imports.Select(fun name -> Path.Combine(here, name)).ToList()
+
+ let missingDlls =
+ resolvedImports.Where(fun path -> not (File.Exists(path))).ToList()
+
+ if (missingDlls.Any()) then
+ failwith "Missing imports"
+
+ let loadedImports = resolvedImports.Select(fun p -> Assembly.LoadFrom(p)).ToList()
+
+ let result =
+ loadedImports.ToDictionary(fun k -> Path.GetFileNameWithoutExtension(k.Location))
+
+ result.Values
+ |> Seq.append [| self |]
+ |> Seq.append MefHostServices.DefaultAssemblies
+ |> Array.ofSeq
+
+ let createExportProvider () =
+ let resolver = Resolver.DefaultInstance
+
+ let catalog =
+ let asms = getAssemblies ()
+
+ let partDiscovery =
+ PartDiscovery.Combine(
+ new AttributedPartDiscoveryV1(resolver),
+ new AttributedPartDiscovery(resolver, isNonPublicSupported = true)
+ )
+
+ let parts = partDiscovery.CreatePartsAsync(asms).Result
+ let catalog = ComposableCatalog.Create(resolver)
+ catalog.AddParts(parts)
+
+ let configuration =
+ CompositionConfiguration.Create(catalog.WithCompositionService())
+
+ let runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration)
+ let exportProviderFactory = runtimeComposition.CreateExportProviderFactory()
+ exportProviderFactory.CreateExportProvider()
+
+type TestWorkspaceServiceMetadata(serviceType: string, layer: string) =
+
+ member _.ServiceType = serviceType
+ member _.Layer = layer
+
+ new(data: IDictionary) =
+ let serviceType =
+ match data.TryGetValue("ServiceType") with
+ | true, result -> result :?> string
+ | _ -> Unchecked.defaultof<_>
+
+ let layer =
+ match data.TryGetValue("Layer") with
+ | true, result -> result :?> string
+ | _ -> Unchecked.defaultof<_>
+
+ TestWorkspaceServiceMetadata(serviceType, layer)
+
+ new(serviceType: Type, layer: string) = TestWorkspaceServiceMetadata(serviceType.AssemblyQualifiedName, layer)
+
+type TestLanguageServiceMetadata(language: string, serviceType: string, layer: string, data: IDictionary) =
+
+ member _.Language = language
+ member _.ServiceType = serviceType
+ member _.Layer = layer
+ member _.Data = data
+
+ new(data: IDictionary) =
+ let language =
+ match data.TryGetValue("Language") with
+ | true, result -> result :?> string
+ | _ -> Unchecked.defaultof<_>
+
+ let serviceType =
+ match data.TryGetValue("ServiceType") with
+ | true, result -> result :?> string
+ | _ -> Unchecked.defaultof<_>
+
+ let layer =
+ match data.TryGetValue("Layer") with
+ | true, result -> result :?> string
+ | _ -> Unchecked.defaultof<_>
+
+ TestLanguageServiceMetadata(language, serviceType, layer, data)
+
+type TestHostLanguageServices(workspaceServices: HostWorkspaceServices, language: string, exportProvider: ExportProvider) as this =
+ inherit HostLanguageServices()
+
+ let services1 =
+ exportProvider.GetExports()
+ |> Seq.filter (fun x -> x.Metadata.Language = language)
+
+ let factories1 =
+ exportProvider.GetExports