CompletionProviderTests.fs 14.2 KB
Newer Older
D
Don Syme 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// To run the tests in this file:
//
// Technique 1: Compile VisualFSharp.Unittests.dll and run it as a set of unit tests
//
// Technique 2:
//
//   Enable some tests in the #if EXE section at the end of the file, 
//   then compile this file as an EXE that has InternalsVisibleTo access into the
//   appropriate DLLs.  This can be the quickest way to get turnaround on updating the tests
//   and capturing large amounts of structured output.
(*
    cd Debug\net40\bin
    .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll  -r .\FSharp.Editor.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\vsintegration\tests\unittests\CompletionProviderTests.fs 
    .\VisualFSharp.Unittests.exe 
*)
// Technique 3: 
// 
//    Use F# Interactive.  This only works for FSharp.Compiler.Service.dll which has a public API

21
// 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.
D
Don Syme 已提交
22
module Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.CompletionProviderTests
23 24 25 26 27 28

open System
open System.Linq

open NUnit.Framework

29
open Microsoft.CodeAnalysis
30 31 32 33 34 35
open Microsoft.CodeAnalysis.Completion
open Microsoft.CodeAnalysis.Text
open Microsoft.VisualStudio.FSharp.Editor

open Microsoft.FSharp.Compiler.SourceCodeServices

D
Don Syme 已提交
36 37 38 39 40 41 42 43 44
let filePath = "C:\\test.fs"
let internal options = { 
    ProjectFileName = "C:\\test.fsproj"
    ProjectFileNames =  [| filePath |]
    ReferencedProjects = [| |]
    OtherOptions = [| |]
    IsIncompleteTypeCheckEnvironment = true
    UseScriptResolutionRules = false
    LoadTime = DateTime.MaxValue
45
    OriginalLoadReferences = []
D
Don Syme 已提交
46
    UnresolvedReferences = None
D
Don Syme 已提交
47
    ExtraProjectInfo = None
D
Don Syme 已提交
48 49 50 51 52
}

let VerifyCompletionList(fileContents: string, marker: string, expected: string list, unexpected: string list) =
    let caretPosition = fileContents.IndexOf(marker) + marker.Length
    let results = 
53
        FSharpCompletionProvider.ProvideCompletionsAsyncAux(FSharpChecker.Instance, SourceText.From(fileContents), caretPosition, options, filePath, 0, fun _ -> []) 
D
Don Syme 已提交
54
        |> Async.RunSynchronously 
55
        |> Option.defaultValue (ResizeArray())
D
Don Syme 已提交
56 57 58
        |> Seq.map(fun result -> result.DisplayText)

    for item in expected do
59
        Assert.IsTrue(results.Contains(item), sprintf "Completions should contain '%s'. Got '%s'." item (String.Join(", ", results)))
D
Don Syme 已提交
60 61

    for item in unexpected do
62
        Assert.IsFalse(results.Contains(item), sprintf "Completions should not contain '%s'. Got '{%s}'" item (String.Join(", ", results)))
63 64 65 66 67

let VerifyCompletionListExactly(fileContents: string, marker: string, expected: string list) =
    let caretPosition = fileContents.IndexOf(marker) + marker.Length
    
    let actual = 
68
        FSharpCompletionProvider.ProvideCompletionsAsyncAux(FSharpChecker.Instance, SourceText.From(fileContents), caretPosition, options, filePath, 0, fun _ -> []) 
69 70 71 72 73 74 75 76 77 78 79 80 81
        |> 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))))
82 83 84

let VerifyNoCompletionList(fileContents: string, marker: string) =
    VerifyCompletionListExactly(fileContents, marker, [])
85 86 87 88

[<OneTimeSetUp>]
let usingDefaultSettings() = 
    SettingsPersistence.setSettings { ShowAfterCharIsTyped = true; ShowAfterCharIsDeleted = false }
89
    
D
Don Syme 已提交
90 91 92
[<Test>]
let ShouldTriggerCompletionAtCorrectMarkers() =
    let testCases = 
93 94
       [("x", true)
        ("y", true)
D
Don Syme 已提交
95 96 97 98 99 100 101 102 103
        ("1", false)
        ("2", false)
        ("x +", false)
        ("Console.Write", false)
        ("System.", true)
        ("Console.", true) ]

    for (marker: string, shouldBeTriggered: bool) in testCases do
    let fileContents = """
104 105 106 107 108
let x = 1
let y = 2
System.Console.WriteLine(x + y)
"""

D
Don Syme 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    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)
    Assert.AreEqual(shouldBeTriggered, triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should compute the correct result")

[<Test>]
let ShouldNotTriggerCompletionAfterAnyTriggerOtherThanInsertion() = 
    for triggerKind in [CompletionTriggerKind.Deletion; CompletionTriggerKind.Other; CompletionTriggerKind.Snippets ] do
    let fileContents = "System.Console.WriteLine(123)"
    let caretPosition = fileContents.IndexOf("System.")
    let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId())
    let getInfo() = documentId, filePath, []
    let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux(SourceText.From(fileContents), caretPosition, triggerKind, getInfo)
    Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")
124
    
D
Don Syme 已提交
125 126 127 128 129 130 131 132
[<Test>]
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)
    Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")
133
    
D
Don Syme 已提交
134 135 136
[<Test>]
let ShouldNotTriggerCompletionInComments() =
    let fileContents = """
137 138 139 140 141
(*
This is a comment
System.Console.WriteLine()
*)
"""
D
Don Syme 已提交
142 143 144 145 146
    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)
    Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")
147
    
D
Don Syme 已提交
148 149 150
[<Test>]
let ShouldNotTriggerCompletionInExcludedCode() =
    let fileContents = """
151 152 153 154
#if UNDEFINED
System.Console.WriteLine()
#endif
"""
D
Don Syme 已提交
155 156 157 158 159 160 161 162 163
    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)
    Assert.IsFalse(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger")

[<Test>]
let ShouldDisplayTypeMembers() =
    let fileContents = """
164 165 166 167 168 169 170 171 172
type T1() =
    member this.M1 = 5
    member this.M2 = "literal"

[<EntryPoint>]
let main argv =
    let obj = T1()
    obj.
"""
D
Don Syme 已提交
173
    VerifyCompletionList(fileContents, "obj.", ["M1"; "M2"], ["System"])
174

D
Don Syme 已提交
175 176 177
[<Test>]
let ShouldDisplaySystemNamespace() =
    let fileContents = """
178 179 180 181 182
type T1 =
    member this.M1 = 5
    member this.M2 = "literal"
System.Console.WriteLine()
"""
D
Don Syme 已提交
183 184
    VerifyCompletionList(fileContents, "System.", ["Console"; "Array"; "String"], ["T1"; "M1"; "M2"])

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
[<Test>]
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)

[<Test>]
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)


[<Test>]
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)

[<Test>]
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<int>
    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)

[<Test>]
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"; "ExtensionProp"; "Item"; "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)

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
[<Test>]
let ``No completion on type name at declaration site``() =
    let fileContents = """
type T

"""
    VerifyNoCompletionList(fileContents, "type T")

[<Test>]
let ``No completion on function name at declaration site``() =
    let fileContents = """
let f

"""
    VerifyNoCompletionList(fileContents, "let f")

[<Test>]
let ``No completion on member name at declaration site``() =
    let fileContents = """
type T() =
    member this.M
"""
    VerifyNoCompletionList(fileContents, "member this.M")

[<Test>]
let ``No completion on function first argument name``() =
    let fileContents = """
let func (p
"""
    VerifyNoCompletionList(fileContents, "let func (p")

[<Test>]
let ``No completion on function subsequent argument name``() =
    let fileContents = """
let func (p, h
"""
    VerifyNoCompletionList(fileContents, "let func (p, h")

[<Test>]
let ``No completion on curried function subsequent argument name``() =
    let fileContents = """
let func (p) (h
"""
    VerifyNoCompletionList(fileContents, "let func (p) (h")

[<Test>]
let ``No completion on method first argument name``() =
    let fileContents = """
type T() =
    member this.M(p) = ()
"""
    VerifyNoCompletionList(fileContents, "member this.M(p")

[<Test>]
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")

[<Test>]
let ``Provide completion on first function argument type hint``() =
    let fileContents = """
let func (p:i
"""
    VerifyCompletionList(fileContents, "let func (p:i", ["int"], [])

[<Test>]
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"], [])

[<Test>]
let ``Provide completion on local function argument type hint``() =
    let fileContents = """
let top () =
    let func (p:i
"""
    VerifyCompletionList(fileContents, "let func (p:i", ["int"], [])

[<Test>]
let ``No completion on implicit constructor first argument name``() =
    let fileContents = """
type T(p) =
"""
    VerifyNoCompletionList(fileContents, "type T(p")
D
Don Syme 已提交
366

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
[<Test>]
let ``No completion on implicit constructor subsequent argument name``() =
    let fileContents = """
type T(p:int, h) =
"""
    VerifyNoCompletionList(fileContents, "type T(p:int, h")

[<Test>]
let ``Provide completion on implicit constructor argument type hint``() =
    let fileContents = """
type T(p:i) =
"""
    VerifyCompletionList(fileContents, "type T(p:i", ["int"], [])

[<Test>]
let ``No completion on lambda argument name``() =
    let fileContents = """
let _ = fun (p) -> ()
"""
    VerifyNoCompletionList(fileContents, "let _ = fun (p")

[<Test>]
let ``Provide completion on lambda argument type hint``() =
    let fileContents = """
let _ = fun (p:i) -> ()
"""
    VerifyCompletionList(fileContents, "let _ = fun (p:i", ["int"], [])

#if EXE
D
Don Syme 已提交
396 397
ShouldDisplaySystemNamespace()
#endif