未验证 提交 11605099 编写于 作者: P Petr 提交者: GitHub

Introducing FSharp.Editor.Tests (#14335)

上级 875dbcc6
......@@ -7,7 +7,8 @@ fcs-samples/
scripts/
setup/
tests/
vsintegration/
vsintegration/*
!vsintegration/tests/FSharp.Editor.Tests
artifacts/
# Explicitly unformatted implementation files
......
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
......@@ -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,
......
......@@ -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}
......
......@@ -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
......
......@@ -14,6 +14,7 @@
<ItemGroup>
<InternalsVisibleTo Include="FSharp.ProjectSystem.FSharp" />
<InternalsVisibleTo Include="VisualFSharp.UnitTests" />
<InternalsVisibleTo Include="FSharp.Editor.Tests" />
<InternalsVisibleTo Include="VisualFSharp.Salsa" />
</ItemGroup>
......
// 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
[<TestFixture>][<Category "Roslyn Services">]
type BraceMatchingServiceTests() =
[<TestFixture>]
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
[<TestCase("(marker1", ")marker1")>]
......@@ -69,8 +82,9 @@ type BraceMatchingServiceTests() =
[<TestCase("}marker2", "{marker2")>]
[<TestCase(")marker3", "(marker3")>]
[<TestCase("]marker4", "[marker4")>]
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)
[<Test>]
member this.BracketInExpression() =
member this.BracketInExpression() =
this.VerifyBraceMatch("let x = (3*5)-1", "(3*", ")-1")
[<Test>]
member this.BraceInInterpolatedStringSimple() =
member this.BraceInInterpolatedStringSimple() =
this.VerifyBraceMatch("let x = $\"abc{1}def\"", "{1", "}def")
[<Test>]
member this.BraceInInterpolatedStringTwoHoles() =
member this.BraceInInterpolatedStringTwoHoles() =
this.VerifyBraceMatch("let x = $\"abc{1}def{2+3}hij\"", "{2", "}hij")
[<Test>]
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", "}\"")
[<TestCase("[start")>]
[<TestCase("]end")>]
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)
[<Test>]
member this.BraceInAttributesMatch() =
let code = "
member this.BraceInAttributesMatch() =
let code =
"
[<Attribute>]
module internal name"
this.VerifyBraceMatch(code, "[<", ">]")
[<Test>]
member this.BraceEncapsulatingACommentShouldBeMatched() =
let code = "
member this.BraceEncapsulatingACommentShouldBeMatched() =
let code =
"
let x = 3 + (start
(* this is a comment *)
)end"
this.VerifyBraceMatch(code, "(start", ")end")
[<TestCase("(endsInComment")>]
[<TestCase(")endsInComment")>]
[<TestCase("<startsInComment")>]
[<TestCase(">startsInComment")>]
member this.BraceStartingOrEndingInCommentShouldNotBeMatched(startMarker: string) =
let code = "
member this.BraceStartingOrEndingInCommentShouldNotBeMatched(startMarker: string) =
let code =
"
let x = 123 + (endsInComment
(* )endsInComment <startsInComment *)
>startsInComment"
this.VerifyNoBraceMatch(code, startMarker)
[<TestCase("(endsInDisabledCode")>]
[<TestCase(")endsInDisabledCode")>]
[<TestCase("<startsInDisabledCode")>]
[<TestCase(">startsInDisabledCode")>]
member this.BraceStartingOrEndingInDisabledCodeShouldNotBeMatched(startMarker: string) =
let code = "
member this.BraceStartingOrEndingInDisabledCodeShouldNotBeMatched(startMarker: string) =
let code =
"
let x = 123 + (endsInDisabledCode
#if UNDEFINED
)endsInDisabledCode <startsInDisabledCode
#endif
>startsInDisabledCode"
this.VerifyNoBraceMatch(code, startMarker)
[<TestCase("(endsInString")>]
[<TestCase(")endsInString")>]
[<TestCase("<startsInString")>]
[<TestCase(">startsInString")>]
member this.BraceStartingOrEndingInStringShouldNotBeMatched(startMarker: string) =
let code = "
member this.BraceStartingOrEndingInStringShouldNotBeMatched(startMarker: string) =
let code =
"
let x = \"stringValue\" + (endsInString +
\" )endsInString <startsInString \" +
+ >startsInString"
this.VerifyNoBraceMatch(code, startMarker)
[<Test>]
member this.BraceMatchingAtEndOfLine_Bug1597() =
member this.BraceMatchingAtEndOfLine_Bug1597() =
// https://github.com/dotnet/fsharp/issues/1597
let code = """
let code =
"""
[<EntryPoint>]
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")
[<TestCase ("let a1 = [ 0 .. 100 ]", [|9;20|])>]
[<TestCase ("let a2 = [| 0 .. 100 |]", [|9;10;22|])>]
[<TestCase ("let a3 = <@ 0 @>", [|9;10;15|])>]
[<TestCase ("let a4 = <@@ 0 @@>", [|9;10;11;15;16|])>]
[<TestCase ("let a6 = ( () )", [|9;16|])>]
[<TestCase ("[<ReflectedDefinition>]\nlet a7 = 70", [|0;1;22|])>]
[<TestCase ("let a8 = seq { yield() }", [|13;23|])>]
[<TestCase("let a1 = [ 0 .. 100 ]", [| 9; 20 |])>]
[<TestCase("let a2 = [| 0 .. 100 |]", [| 9; 10; 22 |])>]
[<TestCase("let a3 = <@ 0 @>", [| 9; 10; 15 |])>]
[<TestCase("let a4 = <@@ 0 @@>", [| 9; 10; 11; 15; 16 |])>]
[<TestCase("let a6 = ( () )", [| 9; 16 |])>]
[<TestCase("[<ReflectedDefinition>]\nlet a7 = 70", [| 0; 1; 22 |])>]
[<TestCase("let a8 = seq { yield() }", [| 13; 23 |])>]
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
......
// 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 Microsoft.VisualStudio.FSharp.Editor
open Microsoft.VisualStudio.FSharp.LanguageService
open FSharp.Editor.Tests.Helpers
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Text
open UnitTests.TestLib.LanguageService
[<TestFixture>][<Category "Roslyn Services">]
type BreakpointResolutionServiceTests() =
[<TestFixture>]
type BreakpointResolutionServiceTests() =
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 code = "
let code =
"
// This is a comment
type exampleType(parameter: int) =
......@@ -56,31 +35,45 @@ let main argv =
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") |]
|]
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") |]
|]
[<TestCaseSource("testCases")>]
member this.TestBreakpointResolution(searchToken: string, expectedResolution: string option) =
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
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)
| 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")
\ No newline at end of file
// 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)
}
[<Test>]
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)
[<Test>]
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)
// 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
[<TestFixture>]
[<Category "Roslyn Services">]
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"""
[<TestCase("marker1", "]")>]
[<TestCase("marker2", "}")>]
[<TestCase("marker3", " |]")>]
......@@ -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 + """[<Class>]
let clipboard =
prefix
+ """[<Class>]
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 + """[<Class>]
let clipboard =
prefix
+ """[<Class>]
type SomeNameHere () =
member _.Test ()"""
let start = """
let start =
"""
$
let foo =
......@@ -154,7 +186,8 @@ let foo =
somethingElseHere
"""
let expected = """
let expected =
"""
[<Class>]
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)
[<Test>]
member this.TestPasteChanges_PastingWithAutoIndentationInPasteSpan() =
let checker = FSharpChecker.Create()
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions
let clipboard = """[<Class>]
let clipboard =
"""[<Class>]
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)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<UnitTestType>nunit</UnitTestType>
<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
</PropertyGroup>
<ItemGroup>
<Compile Include="Helpers\RoslynHelpers.fs" />
<Compile Include="Helpers\ProjectOptionsBuilder.fs" />
<Compile Include="Helpers\AssemblyResolver.fs" />
<Compile Include="BraceMatchingServiceTests.fs" />
<Compile Include="BreakpointResolutionServiceTests.fs" />
<Compile Include="QuickInfoProviderTests.fs" />
<Compile Include="FsxCompletionProviderTests.fs" />
<Compile Include="SignatureHelpProviderTests.fs" />
<Compile Include="DocumentDiagnosticAnalyzerTests.fs" />
<Compile Include="LanguageDebugInfoServiceTests.fs" />
<Compile Include="IndentationServiceTests.fs" />
<Compile Include="CompletionProviderTests.fs" />
<Compile Include="GoToDefinitionServiceTests.fs" />
<Compile Include="HelpContextServiceTests.fs" />
<Compile Include="QuickInfoTests.fs" />
<Compile Include="Hints\HintTestFramework.fs" />
<Compile Include="Hints\OptionParserTests.fs" />
<Compile Include="Hints\InlineParameterNameHintTests.fs" />
<Compile Include="Hints\InlineTypeHintTests.fs" />
<Compile Include="Hints\OverallHintExperienceTests.fs" />
<Compile Include="EditorFormattingServiceTests.fs" />
<Compile Include="RoslynSourceTextTests.fs" />
<Compile Include="SemanticColorizationServiceTests.fs" />
<Compile Include="SyntacticColorizationServiceTests.fs" />
<Compile Include="DocumentHighlightsServiceTests.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.EditorFeatures.Text" Version="$(MicrosoftCodeAnalysisEditorFeaturesTextVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.ExternalAccess.FSharp" Version="$(MicrosoftCodeAnalysisExternalAccessFSharpVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="$(MicrosoftVisualStudioEditorVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="$(MicrosoftVisualStudioThreadingVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\FSharp.Editor\FSharp.Editor.fsproj" />
</ItemGroup>
</Project>
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
//
// To run the tests in this file: Compile VisualFSharp.UnitTests.dll and run it as a set of unit tests
namespace VisualFSharp.UnitTests.Editor
namespace FSharp.Editor.Tests
open System
open System.Collections.Generic
open System.IO
open System.Linq
open System.Reflection
open NUnit.Framework
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Completion
open Microsoft.CodeAnalysis.Text
open Microsoft.VisualStudio.FSharp.Editor
open FSharp.Compiler.CodeAnalysis
open UnitTests.TestLib.LanguageService
open FSharp.Editor.Tests.Helpers
// AppDomain helper
type Worker () =
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
}
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<string>) =
let caretPosition = fileContents.IndexOf(marker) + marker.Length
let document = RoslynTestHelpers.CreateSingleDocumentSolution(filePath, SourceText.From(fileContents), options = projectOptions)
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())
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)
......@@ -53,26 +52,50 @@ type Worker () =
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))))
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()
[<Test>]
let fsiShouldTriggerCompletionInFsxFile() =
let fileContents = """
#if RELEASE
[<Ignore "Fails in some CI, reproduces locally in Release mode, needs investigation">]
#endif
let fsiShouldTriggerCompletionInFsxFile () =
let fileContents =
"""
fsi.
"""
let expected = List<string>([
"CommandLineArgs"; "EventLoop"; "FloatingPointFormat"; "FormatProvider"; "PrintDepth";
"PrintLength"; "PrintSize"; "PrintWidth"; "ShowDeclarationValues"; "ShowIEnumerable";
"ShowProperties"; "AddPrinter"; "AddPrintTransformer"; "Equals"; "GetHashCode";
"GetType"; "ToString"; ])
let expected =
List<string>(
[
"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)
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
//
// To run the tests in this file: Compile VisualFSharp.UnitTests.dll and run it as a set of unit tests
namespace VisualFSharp.UnitTests.Editor
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
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.EditorServices
open FSharp.Compiler.Text
open UnitTests.TestLib.LanguageService
open FSharp.Editor.Tests.Helpers
[<TestFixture; Category "Roslyn Services">]
[<TestFixture>]
module GoToDefinitionServiceTests =
let userOpName = "GoToDefinitionServiceTests"
let private findDefinition
(
document: Document,
sourceText: SourceText,
position: int,
defines: string list
) : range option =
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)
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 =
{
let makeOptions filePath args =
{
ProjectFileName = "C:\\test.fsproj"
ProjectId = None
SourceFiles = [| filePath |]
ReferencedProjects = [| |]
SourceFiles = [| filePath |]
ReferencedProjects = [||]
OtherOptions = args
IsIncompleteTypeCheckEnvironment = true
UseScriptResolutionRules = false
......@@ -65,21 +76,31 @@ module GoToDefinitionServiceTests =
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 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
)
[<Test>]
let ``goto definition smoke test``() =
let ``goto definition smoke test`` () =
let manyTestCases =
[
// Test1
("""
let manyTestCases =
[
// Test1
("""
type TestType() =
member this.Member1(par1: int) =
printf "%d" par1
......@@ -91,50 +112,56 @@ 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
("""
[
("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)) ])
]
[ ("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, [| |])
for fileContents, testCases in manyTestCases do
for caretMarker, expected in testCases do
printfn "Test case: caretMarker=<<<%s>>>" caretMarker
GoToDefinitionTest(fileContents, caretMarker, expected, [||])
[<Test>]
let ``goto definition for string interpolation``() =
let ``goto definition for string interpolation`` () =
let fileContents = """
let fileContents =
"""
let xxxxx = 1
let yyyy = $"{abc{xxxxx}def}" """
let caretMarker = "xxxxx"
let expected = Some(2, 2, 4, 9)
GoToDefinitionTest (fileContents, caretMarker, expected, [| |])
GoToDefinitionTest(fileContents, caretMarker, expected, [||])
[<Test>]
let ``goto definition for static abstract method invocation``() =
let ``goto definition for static abstract method invocation`` () =
let fileContents = """
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" |])
GoToDefinitionTest(fileContents, caretMarker, expected, [| "/langversion:preview" |])
// 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<CultureInfo>
name.Version <- Unchecked.defaultof<Version>
Some(name)
else
None
with _ ->
None)
match found () with
| None -> Unchecked.defaultof<Assembly>
| Some name -> Assembly.Load(name))
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 <Project>"
let private CreateSingleProjectFromMarkup (markup: XElement) =
if markup.Name.LocalName <> "Project" then
failwith "Expected root node to be <Project>"
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 <Projects>"
let private CreateMultipleProjectsFromMarkup (markup: XElement) =
if markup.Name.LocalName <> "Projects" then
failwith "Expected root node to be <Projects>"
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
@"
<Project Name=""testProject.fsproj"">
<File Name=""testFile.fs"">
<![CDATA[%s]]>
......
// 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
[<AutoOpen>]
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<string, obj>) =
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<string, obj>) =
member _.Language = language
member _.ServiceType = serviceType
member _.Layer = layer
member _.Data = data
new(data: IDictionary<string, obj>) =
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<ILanguageService, TestLanguageServiceMetadata>()
|> Seq.filter (fun x -> x.Metadata.Language = language)
let factories1 =
exportProvider.GetExports<ILanguageServiceFactory, TestLanguageServiceMetadata>()
|> Seq.filter (fun x -> x.Metadata.Language = language)
|> Seq.map (fun x -> Lazy<_, _>((fun () -> x.Value.CreateLanguageService(this)), x.Metadata))
let otherServices1 = Seq.append factories1 services1
let otherServicesMap1 =
otherServices1
|> Seq.map (fun x -> KeyValuePair(x.Metadata.ServiceType, x))
|> Seq.distinctBy (fun x -> x.Key)
|> System.Collections.Concurrent.ConcurrentDictionary
override this.WorkspaceServices = workspaceServices
override this.Language = language
override this.GetService<'T when 'T :> ILanguageService>() : 'T =
match otherServicesMap1.TryGetValue(typeof<'T>.AssemblyQualifiedName) with
| true, otherService -> otherService.Value :?> 'T
| _ ->
try
exportProvider.GetExport<'T>().Value
with _ ->
Unchecked.defaultof<'T>
type TestHostWorkspaceServices(hostServices: HostServices, workspace: Workspace) as this =
inherit HostWorkspaceServices()
let exportProvider = createExportProvider ()
let services1 =
exportProvider.GetExports<IWorkspaceService, TestWorkspaceServiceMetadata>()
let factories1 =
exportProvider.GetExports<IWorkspaceServiceFactory, TestWorkspaceServiceMetadata>()
|> Seq.map (fun x -> Lazy<_, _>((fun () -> x.Value.CreateService(this)), x.Metadata))
let otherServices1 = Seq.append factories1 services1
let otherServicesMap1 =
otherServices1
|> Seq.map (fun x -> KeyValuePair(x.Metadata.ServiceType, x))
|> Seq.distinctBy (fun x -> x.Key)
|> System.Collections.Concurrent.ConcurrentDictionary
let langServices =
TestHostLanguageServices(this, LanguageNames.FSharp, exportProvider)
override _.Workspace = workspace
override this.GetService<'T when 'T :> IWorkspaceService>() : 'T =
let ty = typeof<'T>
match otherServicesMap1.TryGetValue(ty.AssemblyQualifiedName) with
| true, otherService -> otherService.Value :?> 'T
| _ ->
try
exportProvider.GetExport<'T>().Value
with _ ->
Unchecked.defaultof<'T>
override _.FindLanguageServices(filter) = Seq.empty
override _.GetLanguageServices(languageName) =
match languageName with
| LanguageNames.FSharp -> langServices :> HostLanguageServices
| _ -> raise (NotSupportedException(sprintf "Language '%s' not supported in FSharp VS tests." languageName))
override _.HostServices = hostServices
type TestHostServices() =
inherit HostServices()
override this.CreateWorkspaceServices(workspace) =
TestHostWorkspaceServices(this, workspace) :> HostWorkspaceServices
[<AbstractClass; Sealed>]
type RoslynTestHelpers private () =
static member CreateSingleDocumentSolution(filePath, text: SourceText, ?options: FSharpProjectOptions) =
let isScript =
String.Equals(Path.GetExtension(filePath), ".fsx", StringComparison.OrdinalIgnoreCase)
let workspace = new AdhocWorkspace(TestHostServices())
let projId = ProjectId.CreateNewId()
let docId = DocumentId.CreateNewId(projId)
let docInfo =
DocumentInfo.Create(
docId,
filePath,
loader = TextLoader.From(text.Container, VersionStamp.Create(DateTime.UtcNow)),
filePath = filePath,
sourceCodeKind =
if isScript then
SourceCodeKind.Script
else
SourceCodeKind.Regular
)
let projFilePath = "C:\\test.fsproj"
let projInfo =
ProjectInfo.Create(
projId,
VersionStamp.Create(DateTime.UtcNow),
projFilePath,
"test.dll",
LanguageNames.FSharp,
documents = [ docInfo ],
filePath = projFilePath
)
let solutionInfo =
SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(DateTime.UtcNow), "test.sln", [ projInfo ])
let solution = workspace.AddSolution(solutionInfo)
let workspaceService = workspace.Services.GetService<IFSharpWorkspaceService>()
let document = solution.GetProject(projId).GetDocument(docId)
match options with
| Some options ->
let options =
{ options with
ProjectId = Some(Guid.NewGuid().ToString())
}
workspaceService.FSharpProjectOptionsManager.SetCommandLineOptions(
projId,
options.SourceFiles,
options.OtherOptions |> ImmutableArray.CreateRange
)
document.SetFSharpProjectOptionsForTesting(options)
| _ -> workspaceService.FSharpProjectOptionsManager.SetCommandLineOptions(projId, [| filePath |], ImmutableArray.Empty)
document
static member CreateTwoDocumentSolution(filePath1, text1: SourceText, filePath2, text2: SourceText) =
let workspace = new AdhocWorkspace(TestHostServices())
let projId = ProjectId.CreateNewId()
let docId1 = DocumentId.CreateNewId(projId)
let docId2 = DocumentId.CreateNewId(projId)
let docInfo1 =
DocumentInfo.Create(
docId1,
filePath1,
loader = TextLoader.From(text1.Container, VersionStamp.Create(DateTime.UtcNow)),
filePath = filePath1,
sourceCodeKind = SourceCodeKind.Regular
)
let docInfo2 =
DocumentInfo.Create(
docId2,
filePath2,
loader = TextLoader.From(text2.Container, VersionStamp.Create(DateTime.UtcNow)),
filePath = filePath2,
sourceCodeKind = SourceCodeKind.Regular
)
let projFilePath = "C:\\test.fsproj"
let projInfo =
ProjectInfo.Create(
projId,
VersionStamp.Create(DateTime.UtcNow),
projFilePath,
"test.dll",
LanguageNames.FSharp,
documents = [ docInfo1; docInfo2 ],
filePath = projFilePath
)
let solutionInfo =
SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(DateTime.UtcNow), "test.sln", [ projInfo ])
let solution = workspace.AddSolution(solutionInfo)
workspace
.Services
.GetService<IFSharpWorkspaceService>()
.FSharpProjectOptionsManager.SetCommandLineOptions(projId, [| filePath1; filePath2 |], ImmutableArray.Empty)
let document1 = solution.GetProject(projId).GetDocument(docId1)
let document2 = solution.GetProject(projId).GetDocument(docId2)
document1, document2
static member CreateSingleDocumentSolution(filePath, code: string, ?options) =
let text = SourceText.From(code)
RoslynTestHelpers.CreateSingleDocumentSolution(filePath, text, ?options = options), text
static member CreateTwoDocumentSolution(filePath1, code1: string, filePath2, code2: string) =
let text1 = SourceText.From code1
let text2 = SourceText.From code2
RoslynTestHelpers.CreateTwoDocumentSolution(filePath1, text1, filePath2, text2)
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Editor.Tests.Hints
open System.Threading
open Microsoft.VisualStudio.FSharp.Editor
open Microsoft.VisualStudio.FSharp.Editor.Hints
open Microsoft.CodeAnalysis.Text
open Hints
open FSharp.Editor.Tests.Helpers
module HintTestFramework =
// another representation for extra convenience
type TestHint =
{ Content: string; Location: int * int }
let private convert hint =
let content =
hint.Parts |> Seq.map (fun hintPart -> hintPart.Text) |> String.concat ""
// that's about different coordinate systems
// in tests, the most convenient is the one used in editor,
// hence this conversion
let location = (hint.Range.StartLine - 1, hint.Range.EndColumn + 1)
{
Content = content
Location = location
}
let getFsDocument code =
use project = SingleFileProject code
let fileName = fst project.Files.Head
let document, _ = RoslynTestHelpers.CreateSingleDocumentSolution(fileName, code)
document
let getFsiAndFsDocuments (fsiCode: string) (fsCode: string) =
RoslynTestHelpers.CreateTwoDocumentSolution("test.fsi", SourceText.From fsiCode, "test.fs", SourceText.From fsCode)
let getHints document hintKinds =
async {
let! hints = HintService.getHintsForDocument document hintKinds "test" CancellationToken.None
return hints |> Seq.map convert
}
|> Async.RunSynchronously
let getTypeHints document =
getHints document (Set.empty.Add(HintKind.TypeHint))
let getParameterNameHints document =
getHints document (Set.empty.Add(HintKind.ParameterNameHint))
let getAllHints document =
let hintKinds = Set.empty.Add(HintKind.TypeHint).Add(HintKind.ParameterNameHint)
getHints document hintKinds
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Editor.Tests.Hints
open NUnit.Framework
open HintTestFramework
module InlineParameterNameHintTests =
[<Test>]
let ``Hint is shown for a let binding`` () =
let code =
"""
let greet friend = $"hello {friend}"
let greeting = greet "darkness"
"""
let document = getFsDocument code
let expected =
[
{
Content = "friend = "
Location = (2, 22)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are shown for multiple function calls`` () =
let code =
"""
let greet friend = $"hello {friend}"
let greeting1 = greet "Noel"
let greeting2 = greet "Liam"
"""
let document = getFsDocument code
let expected =
[
{
Content = "friend = "
Location = (2, 23)
}
{
Content = "friend = "
Location = (3, 23)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are shown for multiple parameters`` () =
let code =
"""
let greet friend1 friend2 = $"hello {friend1} and {friend2}"
let greeting = greet "Liam" "Noel"
"""
let document = getFsDocument code
let expected =
[
{
Content = "friend1 = "
Location = (2, 22)
}
{
Content = "friend2 = "
Location = (2, 29)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are shown for tuple items`` () =
let code =
"""
let greet (friend1, friend2) = $"hello {friend1} and {friend2}"
let greeting = greet ("Liam", "Noel")
"""
let document = getFsDocument code
let expected =
[
{
Content = "friend1 = "
Location = (2, 23)
}
{
Content = "friend2 = "
Location = (2, 31)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are shown for active patterns`` () =
let code =
"""
let (|Even|Odd|) n =
if n % 2 = 0 then Even
else Odd
let evenOrOdd number =
match number with
| Even -> "even"
| Odd -> "odd"
let even = evenOrOdd 42
let odd = evenOrOdd 41
"""
let document = getFsDocument code
let expected =
[
{
Content = "number = "
Location = (10, 22)
}
{
Content = "number = "
Location = (11, 21)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>] // here we don't want an empty hint before "x"
let ``Hints are not shown for nameless parameters`` () =
let code =
"""
let exists predicate option =
match option with
| None -> false
| Some x -> predicate x
"""
let document = getFsDocument code
let result = getParameterNameHints document
Assert.IsEmpty(result)
[<Test>] // here we don't want a useless (?) hint "value = "
let ``Hints are not shown for parameters of built-in operators`` () =
let code =
"""
let postTrue = not true
"""
let document = getFsDocument code
let result = getParameterNameHints document
Assert.IsEmpty(result)
[<Test>]
let ``Hints are not shown for parameters of custom operators`` () =
let code =
"""
let (===) value1 value2 = value1 = value2
let c = "javascript" === "javascript"
"""
let document = getFsDocument code
let result = getParameterNameHints document
Assert.IsEmpty(result)
[<Test>]
let ``Hints are shown for method parameters`` () =
let code =
"""
let theAnswer = System.Console.WriteLine 42
"""
let document = getFsDocument code
let expected =
[
{
Content = "value = "
Location = (1, 42)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are shown for parameters of overloaded and curried methods`` () =
let code =
"""
type C () =
member _.Normal (alone: string) = 1
member _.Normal (what: string, what2: int) = 1
member _.Curried (curr1: string, curr2: int) (x: int) = 1
let c = C ()
let a = c.Curried ("hmm", 2) 1
let a = c.Normal ("hmm", 2)
let a = c.Normal "hmm"
"""
let document = getFsDocument code
let expected =
[
{
Content = "curr1 = "
Location = (8, 20)
}
{
Content = "curr2 = "
Location = (8, 27)
}
{ Content = "x = "; Location = (8, 30) }
{
Content = "what = "
Location = (9, 19)
}
{
Content = "what2 = "
Location = (9, 26)
}
{
Content = "alone = "
Location = (10, 18)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are shown for constructor parameters`` () =
let code =
"""
type C (blahFirst: int) =
new (blah: int, blah2: string) = C blah
let a = C (1, "")
"""
let document = getFsDocument code
let expected =
[
{
Content = "blahFirst = "
Location = (2, 40)
}
{
Content = "blah = "
Location = (4, 12)
}
{
Content = "blah2 = "
Location = (4, 15)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are shown for discriminated union case fields with explicit names`` () =
let code =
"""
type Shape =
| Square of side: int
| Rectangle of width: int * height: int
let a = Square 1
let b = Rectangle (1, 2)
"""
let document = getFsDocument code
let expected =
[
{
Content = "side = "
Location = (5, 16)
}
{
Content = "width = "
Location = (6, 20)
}
{
Content = "height = "
Location = (6, 23)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints for discriminated union case fields are not shown when names are generated`` () =
let code =
"""
type Shape =
| Triangle of side1: int * int * side3: int
| Circle of int
let c = Triangle (1, 2, 3)
let d = Circle 1
"""
let document = getFsDocument code
let expected =
[
{
Content = "side1 = "
Location = (5, 19)
}
{
Content = "side3 = "
Location = (5, 25)
}
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints for discriminated union case fields are not shown when provided arguments don't match the expected count`` () =
let code =
"""
type Shape =
| Triangle of side1: int * side2: int * side3: int
| Circle of int
let c = Triangle (1, 2)
"""
let document = getFsDocument code
let actual = getParameterNameHints document
Assert.IsEmpty(actual)
[<Test>]
let ``Hints for discriminated union case fields are not shown for Cons`` () =
let code =
"""
type X =
member _.Test() = 42 :: [42; 42]
"""
let document = getFsDocument code
let actual = getParameterNameHints document
Assert.IsEmpty(actual)
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Editor.Tests.Hints
open NUnit.Framework
open HintTestFramework
module InlineTypeHintTests =
[<Test>]
let ``Hint is shown for a let binding`` () =
let code =
"""
type Song = { Artist: string; Title: string }
let s = { Artist = "Moby"; Title = "Porcelain" }
"""
let document = getFsDocument code
let expected =
[
{
Content = ": Song"
Location = (3, 6)
}
]
let actual = getTypeHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hint is shown for a parameter`` () =
let code =
"""
type Song = { Artist: string; Title: string }
let whoSings s = s.Artist
"""
let document = getFsDocument code
let expected =
[
{
Content = ": Song"
Location = (3, 15)
}
]
let actual = getTypeHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints are not shown in signature files`` () =
let fsiCode =
"""
module Test
val numbers: int[]
"""
let fsCode =
"""
module Test
let numbers = [|42|]
"""
let fsiDocument, _ = getFsiAndFsDocuments fsiCode fsCode
let result = getTypeHints fsiDocument
Assert.IsEmpty(result)
[<Test>]
let ``Hints are not shown for let-bound functions yet`` () =
let code =
"""
let setConsoleOut = System.Console.SetOut
"""
let document = getFsDocument code
let result = getTypeHints document
Assert.IsEmpty(result)
[<Test>]
let ``Hint is not shown for a let binding when the type is manually specified`` () =
let code =
"""
type Song = { Artist: string; Title: string }
let s: Song = { Artist = "Moby"; Title = "Porcelain" }
"""
let document = getFsDocument code
let result = getTypeHints document
Assert.IsEmpty(result)
[<Test>]
let ``Hint is not shown for a parameter when the type is manually specified`` () =
let code =
"""
type Song = { Artist: string; Title: string }
let whoSings (s: Song) = s.Artist
"""
let document = getFsDocument code
let result = getTypeHints document
Assert.IsEmpty(result)
[<Test>] // here we don't want a hint after "this"
let ``Hint is not shown for type self-identifiers`` () =
let code =
"""
type Song() =
member this.GetName() = "Porcelain"
"""
let document = getFsDocument code
let result = getTypeHints document
Assert.IsEmpty(result)
[<Test>] // here we don't want a hint after "x"
let ``Hint is not shown for type aliases`` () =
let code =
"""
type Song() as x =
member this.Name = "Porcelain"
"""
let document = getFsDocument code
let result = getTypeHints document
Assert.IsEmpty(result)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册