Skip to content

Commit e7813d0

Browse files
author
Omar Tawfik
committed
Added debug data tips and break resolution tests
1 parent 1d02edc commit e7813d0

File tree

4 files changed

+165
-9
lines changed

4 files changed

+165
-9
lines changed

vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,28 @@ open Microsoft.FSharp.Compiler.Range
3131
[<ExportLanguageService(typeof<ILanguageDebugInfoService>, FSharpCommonConstants.FSharpLanguageName)>]
3232
type internal FSharpLanguageDebugInfoService() =
3333

34-
static member GetDataTipInformation(sourceText: SourceText, position: int, tokens: List<ClassifiedSpan>): DebugDataTipInfo =
34+
static member GetDataTipInformation(sourceText: SourceText, position: int, tokens: List<ClassifiedSpan>): TextSpan option =
3535
let tokenIndex = tokens |> Seq.tryFindIndex(fun t -> t.TextSpan.Contains(position))
3636

3737
if tokenIndex.IsNone then
38-
Unchecked.defaultof<DebugDataTipInfo>
38+
None
3939
else
4040
let token = tokens.[tokenIndex.Value]
4141

4242
match token.ClassificationType with
4343

4444
| ClassificationTypeNames.StringLiteral ->
45-
new DebugDataTipInfo(token.TextSpan, sourceText.GetSubText(token.TextSpan).ToString())
45+
Some(token.TextSpan)
4646

4747
| ClassificationTypeNames.Identifier ->
4848
let textLine = sourceText.Lines.GetLineFromPosition(position)
4949
match QuickParse.GetCompleteIdentifierIsland false (textLine.ToString()) (position - textLine.Start) with
50-
| None -> Unchecked.defaultof<DebugDataTipInfo>
51-
| Some(island, islandPosition, _) ->
52-
let islandDocumentStart = textLine.Start + islandPosition - 1 // TextSpan expects a zero-based absolute value
53-
new DebugDataTipInfo(TextSpan.FromBounds(islandDocumentStart, islandDocumentStart + island.Length), island)
50+
| None -> None
51+
| Some(island, islandEnd, _) ->
52+
let islandDocumentStart = textLine.Start + islandEnd - island.Length
53+
Some(TextSpan.FromBounds(islandDocumentStart, islandDocumentStart + island.Length))
5454

55-
| _ -> Unchecked.defaultof<DebugDataTipInfo>
55+
| _ -> None
5656

5757

5858
interface ILanguageDebugInfoService with
@@ -70,7 +70,9 @@ type internal FSharpLanguageDebugInfoService() =
7070
let textSpan = TextSpan.FromBounds(0, sourceText.Length)
7171
let tokens = FSharpColorizationService.GetColorizationData(sourceText, textSpan, Some(document.Name), defines, cancellationToken)
7272

73-
return FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, position, tokens)
73+
return match FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, position, tokens) with
74+
| None -> Unchecked.defaultof<DebugDataTipInfo>
75+
| Some(textSpan) -> new DebugDataTipInfo(textSpan, sourceText.GetSubText(textSpan).ToString())
7476
}
7577

7678
Async.StartAsTask(computation, TaskCreationOptions.None, cancellationToken).ContinueWith(fun(task: Task<DebugDataTipInfo>) ->
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
namespace Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn
3+
4+
open System
5+
open System.Threading
6+
7+
open NUnit.Framework
8+
9+
open Microsoft.CodeAnalysis.Classification
10+
open Microsoft.CodeAnalysis.Editor
11+
open Microsoft.CodeAnalysis.Text
12+
13+
open Microsoft.VisualStudio.FSharp.Editor
14+
open Microsoft.VisualStudio.FSharp.LanguageService
15+
16+
open Microsoft.FSharp.Compiler.SourceCodeServices
17+
open Microsoft.FSharp.Compiler.Range
18+
19+
[<TestFixture>]
20+
type BreakpointResolutionServiceTests() =
21+
22+
let fileName = "C:\\test.fs"
23+
let options: FSharpProjectOptions = {
24+
ProjectFileName = "C:\\test.fsproj"
25+
ProjectFileNames = [| fileName |]
26+
ReferencedProjects = [| |]
27+
OtherOptions = [| |]
28+
IsIncompleteTypeCheckEnvironment = true
29+
UseScriptResolutionRules = false
30+
LoadTime = DateTime.MaxValue
31+
UnresolvedReferences = None
32+
}
33+
let code = "
34+
// This is a comment
35+
36+
type exampleType(parameter: int) =
37+
member this.exampleMember = parameter
38+
39+
[<EntryPoint>]
40+
let main argv =
41+
let integerValue = 123456
42+
let stringValue = \"This is a string\"
43+
let objectValue = exampleType(789)
44+
45+
printfn \"%d %s %A\" integerValue stringValue objectValue
46+
47+
let booleanValue = true
48+
match booleanValue with
49+
| true -> printfn \"correct\"
50+
| false -> printfn \"wrong\"
51+
52+
0 // return an integer exit code
53+
"
54+
55+
static member private testCases: Object[][] = [|
56+
[| "This is a comment"; None |]
57+
[| "123456"; Some("let integerValue = 123456") |]
58+
[| "stringValue"; Some("let stringValue = \"This is a string\"") |]
59+
[| "789"; Some("let objectValue = exampleType(789)") |]
60+
[| "correct"; Some("printfn \"correct\"") |]
61+
[| "wrong"; Some("printfn \"wrong\"") |]
62+
[| "0"; Some("0") |]
63+
|]
64+
65+
[<TestCaseSource("testCases")>]
66+
member this.TestBreakpointResolution(searchToken: string, expectedResolution: string option) =
67+
let searchPosition = code.IndexOf(searchToken)
68+
Assert.IsTrue(searchPosition >= 0, "SearchToken '{0}' is not found in code", searchToken)
69+
70+
let sourceText = SourceText.From(code)
71+
let searchSpan = TextSpan.FromBounds(searchPosition, searchPosition + searchToken.Length)
72+
let actualResolutionOption = FSharpBreakpointResolutionService.GetBreakpointLocation(sourceText, fileName, searchSpan, options) |> Async.RunSynchronously
73+
74+
match actualResolutionOption with
75+
| None -> Assert.IsTrue(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position")
76+
| Some(actualResolutionRange) ->
77+
let actualResolution = sourceText.GetSubText(CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, actualResolutionRange)).ToString()
78+
Assert.IsTrue(expectedResolution.IsSome, "BreakpointResolutionService resolved a breakpoint while it shouldn't at: {0}", actualResolution)
79+
Assert.AreEqual(expectedResolution.Value, actualResolution, "Expected and actual resolutions should match")
80+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
namespace Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn
3+
4+
open System
5+
open System.Threading
6+
7+
open NUnit.Framework
8+
9+
open Microsoft.CodeAnalysis.Classification
10+
open Microsoft.CodeAnalysis.Editor
11+
open Microsoft.CodeAnalysis.Text
12+
13+
open Microsoft.VisualStudio.FSharp.Editor
14+
open Microsoft.VisualStudio.FSharp.LanguageService
15+
16+
open Microsoft.FSharp.Compiler.SourceCodeServices
17+
open Microsoft.FSharp.Compiler.Range
18+
19+
[<TestFixture>]
20+
type LanguageDebugInfoServiceTests() =
21+
let fileName = "C:\\test.fs"
22+
let defines = []
23+
let code = "
24+
// This is a comment
25+
26+
[<EntryPoint>]
27+
let main argv =
28+
let integerValue = 123456
29+
let stringValue = \"This is a string\"
30+
let objectValue = exampleType(789)
31+
32+
printfn \"%d %s %A\" integerValue stringValue objectValue
33+
34+
let booleanValue = true
35+
match booleanValue with
36+
| true -> printfn \"correct\"
37+
| false -> printfn \"%d\" objectValue.exampleMember
38+
39+
0 // return an integer exit code
40+
"
41+
static member private testCases: Object[][] = [|
42+
[| "123456"; None |] // Numeric literals are not interesting
43+
[| "is a string"; Some("\"This is a string\"") |]
44+
[| "objectValue"; Some("objectValue") |]
45+
[| "exampleMember"; Some("objectValue.exampleMember") |]
46+
[| "%s"; Some("\"%d %s %A\"") |]
47+
|]
48+
49+
[<TestCaseSource("testCases")>]
50+
member this.TestDebugInfo(searchToken: string, expectedDataTip: string option) =
51+
let searchPosition = code.IndexOf(searchToken)
52+
Assert.IsTrue(searchPosition >= 0, "SearchToken '{0}' is not found in code", searchToken)
53+
54+
let sourceText = SourceText.From(code)
55+
let tokens = FSharpColorizationService.GetColorizationData(sourceText, TextSpan.FromBounds(0, sourceText.Length), Some(fileName), defines, CancellationToken.None)
56+
let actualDataTipSpanOption = FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, searchPosition, tokens)
57+
58+
match actualDataTipSpanOption with
59+
| None -> Assert.IsTrue(expectedDataTip.IsNone, "LanguageDebugInfoService failed to produce a data tip")
60+
| Some(actualDataTipSpan) ->
61+
let actualDataTipText = sourceText.GetSubText(actualDataTipSpan).ToString()
62+
Assert.IsTrue(expectedDataTip.IsSome, "LanguageDebugInfoService produced a data tip while it shouldn't at: {0}", actualDataTipText)
63+
Assert.AreEqual(expectedDataTip.Value, actualDataTipText, "Expected and actual data tips should match")
64+
65+
66+
67+
68+

vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
<Compile Include="IndentationServiceTests.fs">
4444
<Link>Roslyn\Utilities\IndentationServiceTests.fs</Link>
4545
</Compile>
46+
<Compile Include="BreakpointResolutionService.fs">
47+
<Link>Roslyn\Debugging\BreakpointResolutionService.fs</Link>
48+
</Compile>
49+
<Compile Include="LanguageDebugInfoServiceTests.fs">
50+
<Link>Roslyn\Debugging\LanguageDebugInfoServiceTests.fs</Link>
51+
</Compile>
4652
<Compile Include="Tests.InternalCollections.fs" />
4753
<Compile Include="Tests.Build.fs" />
4854
<Compile Include="Tests.TaskReporter.fs" />

0 commit comments

Comments
 (0)