namespace UnitTests.Tests.LanguageService

open System
open System.IO
open NUnit.Framework
open Salsa.Salsa
open Salsa.VsOpsUtils
open UnitTests.TestLib.Salsa
open UnitTests.TestLib.Utils
open System.Collections.Generic
open System.Text.RegularExpressions
open UnitTests.TestLib.LanguageService

type GotoDefinitionTests()  = 
    inherit LanguageServiceBaseTests()

    //GoToDefinitionSuccess Helper Function
    member private this.VerifyGoToDefnSuccessAtStartOfMarker(fileContents : string, marker : string,  definitionCode : string,?addtlRefAssy : list<string>) =
        let (sln, proj, file) = this.CreateSingleFileProject(fileContents, ?references = addtlRefAssy)

        MoveCursorToStartOfMarker (file, marker)
        let identifier = (GetIdentifierAtCursor file).Value |> fst //use marker to get the identifier
        let result = GotoDefinitionAtCursor file
        CheckGotoDefnResult
            (GotoDefnSuccess identifier definitionCode) 
            file
            result 
                
    //GoToDefinitionFail Helper Function
    member private this.VerifyGoToDefnFailAtStartOfMarker(fileContents : string,  marker :string,?addtlRefAssy : list<string>) =
        
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = fileContents,
            marker = marker,
            f = (fun (file,result) -> CheckGotoDefnResult GotoDefnFailure file result),
            ?addtlRefAssy = addtlRefAssy
            )


    //GoToDefinitionFail Helper Function
    member private this.VerifyGoToDefnFailAtStartOfMarker(fileContents : string,  marker :string, f : OpenFile * GotoDefnResult -> unit, ?addtlRefAssy : list<string>) =
        let (sln, proj, file) = this.CreateSingleFileProject(fileContents, ?references = addtlRefAssy)

        MoveCursorToStartOfMarker (file, marker)
        let result = GotoDefinitionAtCursor file
        f (file, result)


    //GoToDefinition verify no Error dialog
    //The verification result should be:
    //  Fail at automation lab
    //  Succeed on dev machine with enlistment installed.
    member private this.VerifyGoToDefnNoErrorDialogAtStartOfMarker(fileContents : string,  marker :string, definitionCode : string, ?addtlRefAssy : list<string>) =
        let (sln, proj, file) = this.CreateSingleFileProject(fileContents, ?references = addtlRefAssy)

        MoveCursorToStartOfMarker (file, marker)
        let identifier = (GetIdentifierAtCursor file).Value |> fst //use marker to get the identifier
        let result = GotoDefinitionAtCursor file
        if not result.Success then
            CheckGotoDefnResult
                GotoDefnFailure 
                file
                result
        else
            CheckGotoDefnResult
                (GotoDefnSuccess identifier definitionCode) 
                file 
                result 
                                  
    [<Test>]
    member public this.``Value``() = 
        this.VerifyGoToDefnSuccessAtStartOfMarker(
            fileContents = """
                type DiscUnion =
                    | Alpha of string
                    | Beta of decimal * unit
                    | Gamma

                let valueX = Beta(1.0M, ())(*GotoTypeDef*)
                let valueY = valueX (*GotoValDef*)
                """,
            marker = "valueX (*GotoValDef*)",
            definitionCode = "let valueX = Beta(1.0M, ())(*GotoTypeDef*)")
    
    [<Test>]
    member public this.``DisUnionMember``() =
        this.VerifyGoToDefnSuccessAtStartOfMarker(
                            fileContents = """
                type DiscUnion =
                    | Alpha of string
                    | Beta of decimal * unit
                    | Gamma

                let valueX = Beta(1.0M, ())(*GotoTypeDef*)
                let valueY = valueX (*GotoValDef*)
                """,
            marker = "Beta(1.0M, ())(*GotoTypeDef*)",
            definitionCode = "| Beta of decimal * unit")        

    [<Test>]
    member public this.``PrimitiveType``() =
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = """
                // Can't goto def on an int literal
                let bi = 123456I""",
            marker = "123456I")
           
    [<Test>]
    member public this.``OnTypeDefintion``() =
        this.VerifyGoToDefnSuccessAtStartOfMarker(
            fileContents = """
                //regression test for bug 2516
                type One (*Marker1*) = One
                let f (x : One (*Marker2*)) = 2
                """,
            marker = "One (*Marker1*)",
            definitionCode = "type One (*Marker1*) = One")

    [<Test>]
    member public this.``Parameter``() = 
        this.VerifyGoToDefnSuccessAtStartOfMarker(
            fileContents = """
                //regression test for bug 2516
                type One (*Marker1*) = One
                let f (x : One (*Marker2*)) = 2
                """,
            marker = "One (*Marker2*)",
            definitionCode = "type One (*Marker1*) = One")    

    [<Test>]
    [<Category("TypeProvider")>]
    [<Category("TypeProvider.DefinitionLocationAttribute")>]
    // This test case check the GotoDefinition (i.e. the TypeProviderDefinitionLocation Attribute)
    // We expect the correct FilePath, Line and Column on provided: Type, Event, Method, and Property
    // TODO: add a case for a provided Field
    member public this.``GotoDefinition.TypeProvider.DefinitionLocationAttribute``() =
        use _guard = this.UsingNewVS()
        // Note that the verification helped method is custom because we *do* care about the column as well, 
        // which is something that the general purpose method in this file (surprisingly!) does not do.
        let VerifyGoToDefnSuccessAtStartOfMarkerColumn(fileContents : string, marker : string,  definitionCode : string, typeProviderAssembly : string, columnMarker : string) =
            let (sln, proj, file) = GlobalFunctions.CreateNamedSingleFileProject (this.VS, (fileContents, "File.fs"))  

            // Add reference to the type provider
            this.AddAssemblyReference(proj,typeProviderAssembly)

            // Identify (line,col) of the destination, i.e. where we expect to land after hitting F12
            // We do this to avoid hardcoding absolute numbers in the code.
            MoveCursorToStartOfMarker (file,columnMarker)
            let _,column = GetCursorLocation(file)

            // Put curson at start of marker and then hit F12
            MoveCursorToStartOfMarker (file, marker)
            let identifier = (GetIdentifierAtCursor file).Value |> fst
            let result = GotoDefinitionAtCursor file

            // Execute validation (on file name and line)                   
            CheckGotoDefnResult
                (GotoDefnSuccess identifier definitionCode) 
                file 
                result 
            
            // Reminder: coordinates in the F# compiler are 1-based for lines, and 0-based for columns
            //           coordinates from type providers are 1-based for both lines and columns
            //           GetCursorLocation() seems to return something even more off by 1...
            let column' = column - 2

            match result.ToOption() with 
            | Some(span,_) -> Assert.AreEqual(column',span.iStartIndex, "The cursor landed on the incorrect column!") 
            | None ->  Assert.Fail <| sprintf "Expected to find the definition at column '%d' but GotoDefn failed." column'

        // Basic scenario on a provided Type
        let ``Type.BasicScenario``() = 
            VerifyGoToDefnSuccessAtStartOfMarkerColumn("""
                let a = typeof<N.T(*GotoValDef*)>
                // A0(*ColumnMarker*)1234567890
                // B01234567890
                // C01234567890 """,
            "T(*GotoValDef*)",
             "// A0(*ColumnMarker*)1234567890",            
            System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttribute.dll"),
            "(*ColumnMarker*)")

        // This test case checks the type with space in between like N.``T T`` for GotoDefinition
        let ``Type.SpaceInTheType``() = 
             VerifyGoToDefnSuccessAtStartOfMarkerColumn("""
                let a = typeof<N.``T T``>
                // A0(*ColumnMarker*)1234567890
                // B01234567890
                // C01234567890 """,
            "T``",
            "// A0(*ColumnMarker*)1234567890",
            System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttributeWithSpaceInTheType.dll"),
            "(*ColumnMarker*)") 
        
        // Basic scenario on a provided Constructor
        let ``Constructor.BasicScenario``() = 
            
            VerifyGoToDefnSuccessAtStartOfMarkerColumn(""" 
                let foo = new N.T(*GotoValDef*)()
                // A0(*ColumnMarker*)1234567890
                // B01234567890
                // C01234567890 """,
            "T(*GotoValDef*)",
             "// A0(*ColumnMarker*)1234567890",            
            System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttribute.dll"),
            "(*ColumnMarker*)")
          
        // Basic scenario on a provided Method
        let ``Method.BasicScenario``() = 
            VerifyGoToDefnSuccessAtStartOfMarkerColumn("""
                let t = new N.T.M(*GotoValDef*)()
                // A0(*ColumnMarker*)1234567890
                // B01234567890
                // C01234567890 """,
            "M(*GotoValDef*)",
             "// A0(*ColumnMarker*)1234567890",            
            System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttribute.dll"),
            "(*ColumnMarker*)")
        
        // Basic scenario on a provided Property
        let ``Property.BasicScenario``() = 
            VerifyGoToDefnSuccessAtStartOfMarkerColumn(""" 
                let p = N.T.StaticProp(*GotoValDef*)
                // A0(*ColumnMarker*)1234567890
                // B01234567890
                // C01234567890 """,
            "StaticProp(*GotoValDef*)",
             "// A0(*ColumnMarker*)1234567890",            
            System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttribute.dll"),
            "(*ColumnMarker*)")
        
        // Basic scenario on a provided Event
        let ``Event.BasicScenario``() = 
            VerifyGoToDefnSuccessAtStartOfMarkerColumn(""" 
                let t = new N.T()
                t.Event1(*GotoValDef*)
                // A0(*ColumnMarker*)1234567890
                // B01234567890
                // C01234567890 """,
            "Event1(*GotoValDef*)",
             "// A0(*ColumnMarker*)1234567890",            
            System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttribute.dll"),
            "(*ColumnMarker*)")
        
        // Actually execute all the scenarios...      
        ``Type.BasicScenario``()
        ``Type.SpaceInTheType``()
        ``Constructor.BasicScenario``()
        ``Method.BasicScenario``()
        ``Property.BasicScenario``()
        ``Event.BasicScenario``()

    
    [<Test>]
    member public this.``GotoDefinition.NoSourceCodeAvailable``() = 
        this.VerifyGoToDefnFailAtStartOfMarker
            (
                fileContents = "System.String.Format(\"\")",
                marker = "ormat",
                f = (fun (_, result) ->
                    Assert.IsFalse(result.Success)
                    Assert.IsTrue(result.ErrorDescription.Contains("Source code is not available"))
                    )
            )
    
    [<Test>]
    member public this.``GotoDefinition.NoIdentifierAtLocation``() = 
        let useCases = 
            [
                "let x = 1", "1"
                "let x = 1.2", ".2"
                "let x = \"123\"", "2"
            ]
        for (source, marker) in useCases do
            this.VerifyGoToDefnFailAtStartOfMarker
                (
                    fileContents = source,
                    marker = marker,
                    f = (fun (_, result) ->
                        Assert.IsFalse(result.Success)
                        Assert.IsTrue(result.ErrorDescription.Contains("Cursor is not on identifier"))
                        )
                )

    [<Test>]
    member public this.``GotoDefinition.ProvidedTypeNoDefinitionLocationAttribute``() =  

        this.VerifyGoToDefnFailAtStartOfMarker
            (
                fileContents = """
                type T = N1.T<"", 1>
                """,
                marker = "T<",
                f = (fun (_, result) ->
                    Assert.IsFalse(result.Success)
                    Assert.IsTrue(result.ErrorDescription.Contains("provided type 'T'"))
                    ),
                addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DummyProviderForLanguageServiceTesting.dll")]
            )
        
    [<Test>]
    member public this.``GotoDefinition.ProvidedMemberNoDefinitionLocationAttribute``() = 
        let useCases = 
            [
                """
                type T = N1.T<"", 1>
                T.Param1
                """, "ram1", "Param1"

                """
                type T = N1.T1
                T.M1(1)
                """, "1(", "M1"
            ]

        for (source, marker, name) in useCases do
            this.VerifyGoToDefnFailAtStartOfMarker
                (
                    fileContents = source,
                    marker = marker,
                    f = (fun (_, result) ->
                        Assert.IsFalse(result.Success)
                        let expectedText = sprintf "provided member '%s'" name
                        Assert.IsTrue(result.ErrorDescription.Contains(expectedText))
                        ),
                    addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DummyProviderForLanguageServiceTesting.dll")]
                )
    [<Test>]
    [<Category("TypeProvider")>]
    [<Category("TypeProvider.DefinitionLocationAttribute.Negative")>]
    // This test case is when the TypeProviderDefinitionLocationAttribute filepath doesn't exist  for TypeProvider Type
    member public this.``GotoDefinition.TypeProvider.DefinitionLocationAttribute.Type.FileDoesnotExist``() =
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = """
                let a = typeof<N.T(*GotoValDef*)>
                // A0(*Marker*)1234567890
                // B01234567890
                // C01234567890 """,
            marker = "T(*GotoValDef*)",
            addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttributeFileDoesnotExist.dll")])

    [<Test>]
    [<Category("TypeProvider")>]
    [<Category("TypeProvider.DefinitionLocationAttribute.Negative")>]
    [<Ignore("Need some work to detect the line doesnot exist.")>]
    //This test case is when the TypeProviderDefinitionLocationAttribute Line doesn't exist  for TypeProvider Type
    member public this.``GotoDefinition.TypeProvider.DefinitionLocationAttribute.Type.LineDoesnotExist``() =
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = """
                let a = typeof<N.T(*GotoValDef*)>
                // A0(*Marker*)1234567890
                // B01234567890
                // C01234567890 """,
            marker = "T(*GotoValDef*)",
            addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttributeLineDoesnotExist.dll")])
     
    [<Test>]
    [<Category("TypeProvider")>]
    [<Category("TypeProvider.DefinitionLocationAttribute.Negative")>]
    // This test case is when the TypeProviderDefinitionLocationAttribute filepath doesn't exist  for TypeProvider Constructor
    member public this.``GotoDefinition.TypeProvider.DefinitionLocationAttribute.Constructor.FileDoesnotExist``() =
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = """
                let foo = new N.T(*GotoValDef*)()
                // A0(*Marker*)1234567890
                // B01234567890
                // C01234567890 """,
            marker = "T(*GotoValDef*)",
            addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttributeFileDoesnotExist.dll")])


         
    [<Test>]
    [<Category("TypeProvider")>]
    [<Category("TypeProvider.DefinitionLocationAttribute.Negative")>]
    //This test case is when the TypeProviderDefinitionLocationAttribute filepath doesn't exist  for TypeProvider Method
    member public this.``GotoDefinition.TypeProvider.DefinitionLocationAttribute.Method.FileDoesnotExist``() =
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = """ 
                let t = new N.T.M(*GotoValDef*)()
                // A0(*Marker*)1234567890
                // B01234567890
                // C01234567890  """,
            marker = "M(*GotoValDef*)",
            addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttributeFileDoesnotExist.dll")])

    [<Test>]
    [<Category("TypeProvider")>]
    [<Category("TypeProvider.DefinitionLocationAttribute.Negative")>]
    // This test case is when the TypeProviderDefinitionLocationAttribute filepath doesn't exist  for TypeProvider Property
    member public this.``GotoDefinition.TypeProvider.DefinitionLocationAttribute.Property.FileDoesnotExist``() =
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = """ 
                let p = N.T.StaticProp(*GotoValDef*)
                // A0(*Marker*)1234567890
                // B01234567890
                // C01234567890 """,
            marker = "StaticProp(*GotoValDef*)",
            addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttributeFileDoesnotExist.dll")])
    
    [<Test>]
    [<Category("TypeProvider")>]
    [<Category("TypeProvider.DefinitionLocationAttribute.Negative")>]
    //This test case is when the TypeProviderDefinitionLocationAttribute filepath doesn't exist  for TypeProvider Event
    member public this.``GotoDefinition.TypeProvider.DefinitionLocationAttribute.Event.FileDoesnotExist``() =
        this.VerifyGoToDefnFailAtStartOfMarker(
            fileContents = """ 
                let t = new N.T()
                t.Event1(*GotoValDef*)
                // A0(*Marker*)1234567890
                // B01234567890
                // C01234567890 """,
            marker = "Event1(*GotoValDef*)",
            addtlRefAssy = [System.IO.Path.Combine(System.Environment.CurrentDirectory, @"UnitTestsResources\MockTypeProviders\DefinitionLocationAttributeFileDoesnotExist.dll")])

    [<Test>]
    member public this.``ModuleDefintion``() =
        this.VerifyGoToDefnSuccessAtStartOfMarker(
            fileContents = """
                //regretion test for bug 2517
                module Foo (*MarkerModuleDefinition*) =
                  let x = ()
                """,
            marker = "Foo (*MarkerModuleDefinition*)",
            definitionCode = "module Foo (*MarkerModuleDefinition*) =")

    [<Test>]
    member public this.``Record.Field.Defintion``() = 
        this.VerifyGoToDefnSuccessAtStartOfMarker(
            fileContents = """
                //regretion test for bug 2518
                type MyRec =
                  { myX (*MarkerXFieldDefinition*) : int
                    myY (*MarkerYFieldDefinition*) : int
                  }
                let rDefault =
                  { myX (*MarkerXField*) = 2
                    myY (*MarkerYField*) = 3
                  }
                """,      
            marker = "myX (*MarkerXFieldDefinition*)",   
            definitionCode = "{ myX (*MarkerXFieldDefinition*) : int")

    [<Test>]
    member public this.``Record.Field.Usage``() = 
        this.VerifyGoToDefnSuccessAtStartOfMarker(
            fileContents = """
                //regretion test for bug 2518
                type MyRec =
                  { myX (*MarkerXFieldDefinition*) : int
                    myY (*MarkerYFieldDefinition*) : int
                  }
                let rDefault =
                  { myX (*MarkerXField*) = 2
                    myY (*MarkerYField*) = 3
                  }
                """,
            marker = "myY (*MarkerYField*)",
            definitionCode = " myY (*MarkerYFieldDefinition*) : int")

    /// run a GotoDefinition test where the expected result is a file that we
    /// have an `OpenFile` handle for (this won't work, e.g., if this file is a
    /// generated .fsi that (potentially) doesn't yet exist)

    /// exp  = (<identifier where cursor ought to be>, <whole line where cursor ought to be>, <name of file that's expected>) option
    /// file = <file to gotodefinition in>
    /// act  = <the result of the GotoDefinition call>
    member internal this.GotoDefinitionCheckResultAgainst (exp : (string * string * string) option)(file : OpenFile)(act : GotoDefnResult) : unit =
      match (exp, act.ToOption()) with
      | (Some (toFind, expLine, expFile), Some (span, actFile)) -> printfn "%s" "Result received, as expected; checking."
                                                                   Assert.AreEqual (expFile, actFile)
                                                                   printfn "%s" "Filename matches expected."
                                                                   MoveCursorTo(file, span.iStartLine + 1, span.iStartIndex + 1) // adjust & move to the identifier
                                                                   match GetIdentifierAtCursor file with // REVIEW: actually check that we're on the leftmost character of the identifier
                                                                   | None         -> Assert.Fail("No identifier at cursor!")
                                                                   | Some (id, _) -> Assert.AreEqual (toFind, id) // are we on the identifier we expect?
                                                                                     printfn "%s" "Identifier at cursor matches expected."
                                                                                     Assert.AreEqual (expLine.Trim (), (span.iStartLine |> (+) 1 |> GetLineNumber file).Trim ()) // ignore initial- / final-whitespace-introduced noise; adjust for difference in index numbers
                                                                                     printfn "%s" "Line at cursor matches expected."
      | (None,                            None)                 -> printfn "%s" "No result received, as expected." // sometimes we may expect GotoDefinition to fail, e.g., when the cursor isn't placed on a valid position (i.e., over an identifier, and, maybe, a constant if we decide to support that)
      | (Some _,                          None)                 -> Assert.Fail("No result received, but one was expected!") // distinguish this and the following case to give insight in case of failure
      | (None,                            Some _)               -> Assert.Fail("Result received, but none was expected!")

    /// this can be used when we don't have the expected file open; we still
    /// need its name

    /// exp = (<identifier where cursor ought to be>, <name of file that's expected>) option
    member internal this.GotoDefinitionCheckResultAgainstAnotherFile (proj : OpenProject)(exp : (string * string) option)(act : GotoDefnResult) : unit =
      match (exp, act.ToOption()) with
      | (Some (toFind, expFile), Some (span, actFile)) -> printfn "%s" "Result received, as expected; checking."
                                                          Assert.AreEqual (expFile, Path.GetFileName actFile)
                                                          printfn "%s" "Filename matches expected."
                                                          let file = OpenFile (proj, actFile)
                                                          let line = span.iStartLine |> ((+) 1) |> GetLineNumber file // need to adjust line number here
                                                          Assert.AreEqual (toFind, line.Substring (span.iStartIndex, toFind.Length))
                                                          printfn "%s" "Identifier at cursor matches expected."
      | (None,                   None)                 -> printfn "%s" "No result received, as expected." // sometimes we may expect GotoDefinition to fail, e.g., when the cursor isn't placed on a valid position (i.e., over an identifier, and, maybe, a constant if we decide to support that)
      | (Some _,                 None)                 -> Assert.Fail("No result received, but one was expected!") // distinguish this and the following case to give insight in case of failure
      | (None,                   Some _)               -> Assert.Fail("Result received, but none was expected!")

    /// exp = (<expected line>, <expected identifier>) option
    member this.GotoDefinitionTestWithSimpleFile (startLoc : string)(exp : (string * string) option) : unit =
        this.SolutionGotoDefinitionTestWithSimpleFile startLoc exp 

    [<Test>]
    member this.``GotoDefinition.OverloadResolution``() =
        let lines =
          [ "type D() ="
            "   override this.#3#ToString() = System.String.Empty"
            "   member this.#4#ToString(s : string) = ()"
            ""
            "   member this.#1#Foo() = ()"
            "   member this.#2#Foo(x) = ()"
            ""
            "let d = new D()"
            "d.Foo$1$()"
            "d.Foo$2$(1)"
            "d.ToString$3$()"
            "d.ToString$4$(\"aaa\") "
          ]
        this.GotoDefinitionTestWithMarkup lines
    [<Test>]
    member this.``GotoDefinition.OverloadResolutionForProperties``() =
        let lines = [ "type D() ="
                      "  member this.Foo"
                      "    with #1##2#get(i:int) = 1"
                      "    and set (i:int) v = ()"
                      ""
                      "  member this.Foo"
                      "    with #3##4#get (s:string) = 1"
                      "    and  set (s:string) v = ()"
                      ""
                      "D().$1$Foo 1"
                      "D().$2$Foo 1 <- 2"
                      "D().$3$Foo \"abc\""
                      "D().$4$Foo \"abc\" <- 2"
            ]
        this.GotoDefinitionTestWithMarkup lines

    [<Test>]
    member this.``GotoDefinition.OverloadResolutionWithOverrides``() =
        let lines =
          [ "[<AbstractClass>]"
            "type Base<'T>() ="
            "   member this.#2#Method() = ()"
            "   abstract Method : 'T -> unit"
            ""
            "type Derived() ="
            "   inherit Base<int>()"
            ""
            "   override this.#1#Method (i:int) = ()"
            ""
            "let d = new Derived()"
            "d.$1$Method 12"
            "d.$2$Method()"
          ]
        this.GotoDefinitionTestWithMarkup lines

    [<Test>]
    member this.``GotoDefinition.OverloadResolutionStatics``() =
        let lines =
          [   "type T ="
              "   static member #1#Foo(i : int) = ()"
              "   static member #2#Foo(s : string) = ()"
              ""
              "T.$1$Foo 1"
              "T.$2$Foo \"abc\""
          ]
        this.GotoDefinitionTestWithMarkup lines

    [<Test>]
    member this.``GotoDefinition.Constructors``() =
        let lines =
          [   "type #1a##1b##1c##1d#B() ="
              "  #2a##2b##2c##2d#new(i : int) = B()"
              "  #3a##3b##3c##3d#new(s : string) = B()"
              ""
              "B()"
              "B(1)"
              "B(\"abc\")"
              ""
              "new $1b$B()"
              "new $2b$B(1)"
              "new $3b$B(\"abc\")"
              ""
              "type D1() ="
              "    inherit $1c$B()"
              ""
              "type D2() ="
              "    inherit $2c$B(1)"

              "type D3() ="
              "    inherit $3c$B(\"abc\")"
              ""
              "let o1 = { new $1d$B() with"
              "             override this.ToString() = \"\""
              "         }"
              "let o2 = { new $2d$B(1) with"
              "             override this.ToString() = \"\""
              "        }"
              "let o2 = { new $3d$B(\"aaa\") with"
              "             override this.ToString() = \"\""
              "         }"

          ]
        this.GotoDefinitionTestWithMarkup lines

    member internal this.GotoDefinitionTestWithMarkup (lines : string list) =      
      let origins = Dictionary<string, int*int>()
      let targets = Dictionary<string, int*int>()
      let lines =
        [   let lineNo = ref 0
            for l in lines do
                let builder = new System.Text.StringBuilder(l)
                let cont = ref true
                while !cont do
                    let s = builder.ToString()
                    let index = s.IndexOfAny([|'$';'#'|])
                    if index < 0 then
                        cont := false
                    else
                        let c = s.[index]
                        let nextIndex = s.IndexOf(c, index+1) 
                        let marker = s.Substring(index+1, nextIndex - (index+1))
                        if c = '$' then 
                            origins.Add(marker, (!lineNo+1,index+1)) // caret positions are 1-based, but...
                        else 
                            targets.Add(marker, (!lineNo,index)) // ...spans are 0-based. Argh. Thank you, Salsa!
                        builder.Remove(index, nextIndex - index + 1) |> ignore
                yield builder.ToString()
                lineNo := !lineNo + 1
        ]

      let (_, _, file) = this.CreateSingleFileProject(lines)

      for KeyValue(marker,(line,col)) in origins do
          MoveCursorTo(file, line, col)
          let res = GotoDefinitionAtCursor file
          match res.ToOption() with
          |   None -> Assert.IsFalse(targets.ContainsKey(marker), sprintf "%s: definition not found " marker)
          |   Some (span,text) ->
                  match targets.TryGetValue(marker) with
                  |   false, _ ->  Assert.Fail(sprintf "%s: unexpected definition found" marker)
                  |   true, (line1, col1) -> 
                          Assert.IsTrue(span.iStartIndex = col1 && span.iStartLine = line1, 
                                sprintf "%s: wrong definition found expected %d %d but found %d %d %s" marker line1 col1 span.iStartLine span.iStartIndex text )

    
    /// exp = (<expected line>, <expected identifier>) option
    member internal this.SolutionGotoDefinitionTestWithSimpleFile (startLoc : string)(exp : (string * string) option) : unit =
      let lines = 
        [ "#light"
          "let _ = 3"
          "let _ = \"hi\""
          "let _ = 2 + 3"
          "let _ = []"
          "let _ = ()"
          "let _ = null"
          "let _ ="
          "  let x = () (*loc-2*)"
          "  x (*loc-1*)"
          "let _ ="
          "  let x = () (*loc-5*)"
          "  let x = () (*loc-3*)"
          "  x (*loc-4*)"
          "let _ ="
          "  let x = () (*loc-7*)"
          "  let x ="
          "    x (*loc-6*)"
          "  ()"
          "let _ ="
          "  let     x = ()"
          "  let rec x = (*loc-9*)"
          "    fun y -> (*loc-10*)"
          "      x y (*loc-8*)"
          "  ()"
          "let _ ="
          "  let (+) x _ = x (*loc-12*)"
          "  2 + 3 (*loc-11*)"
          "type Zero = (*loc-13*)"
          "let foo (_ : Zero) : 'a = failwith \"hi\" (*loc-14*)"
          "type One = (*loc-16*)"
          "  One (*loc-15*)"
          "let f (x : One) = (*loc-17*)"
          "  One (*loc-18*)"
          "type Nat = (*loc-19*)"
          "  | Suc of Nat (*loc-20*)"
          "  | Zro (*loc-21*)"
          "let rec plus m n = (*loc-23*)"
          "  match m with (*loc-22*)"
          "  | Zro   -> (*loc-24*)"
          "      n"
          "  | Suc m -> (*loc-25*)"
          "      Suc (plus m n) (*loc-26*)"
          "type MyRec = (*loc-27*)"
          "  { myX : int (*loc-28*)"
          "    myY : int (*loc-29*)"
          "  }"
          "let rDefault ="
          "  { myX = 2 (*loc-30*)"
          "    myY = 3 (*loc-31*)"
          "  }"
          "let _ = { rDefault with myX = 7 } (*loc-32*)"
          "let _ ="
          "  let a = 2"
          "  let id (x : 'a) (*loc-33*)"
          "    : 'a = x (*loc-34*)"
          "  ()"
          "let _ ="
          "  let foo          = ()"
          "  let f (_ as foo) = (*loc-35*)"
          "    foo (*loc-36*)"
          "  ()"
          "let _ ="
          "  let foo          = ()"
          "  let f (x as foo) = foo"
          "  ()"
          "let _ ="
          "  fun x (*loc-37*)"
          "      x -> (*loc-38*)"
          "    x (*loc-39*)"
          "let _ ="
          "  let f = () (*loc-40*)"
          "  let f = (*loc-41*)"
          "    function f -> (*loc-42*)"
          "      f (*loc-43*)"
          "  ()"
          "let _ ="
          "  let f x ="
          "    match x with"
          "    | Suc x (*loc-44*)"
          "    | x (*loc-45*) -> "
          "        x"
          "  ()"
          "let _ ="
          "  let f x ="
          "    match x with"
          "    | Suc y & z -> (*loc-47*)"
          "        y (*loc-46*)"
          "  ()"
          "let _ ="
          "  let f xs ="
          "    match xs with"
          "    | x :: xs -> (*loc-49*)"
          "        x (*loc-48*)"
          "    | _       -> []"
          "  ()"
          "let _ ="
          "  let f x ="
          "    match x with"
          "    | (y : int, z) -> (*loc-51*)"
          "         y (*loc-50*)"
          "  ()"
          "let _ ="
          "  let f xs ="
          "    match xs with"
          "    | x :: xs (*loc-54*)"
          "      when xs <> [] -> (*loc-52*)"
          "        x :: xs (*loc-53*)"
          "  ()"
          "module Too = (*loc-55*)"
          "  let foo = 0 (*loc-56*)"
          "module Bar ="
          "  open Too (*loc-57*)"
          "let _ = Too.foo (*loc-58*)"
          "module Overlap ="
          "  type Parity = Even | Odd"
          "  let (|Even|Odd|) x = (*loc-59*)"
          "    if x % 0 = 0"
          "       then Even (*loc-60*)"
          "       else Odd"
          "  let foo (x : int) ="
          "    match x with"
          "    | Even -> 1 (*loc-61*)"
          "    | Odd  -> 0"
          "  let patval = (|Even|Odd|) (*loc-61b*)"
          "let _ ="
          "  op_Addition 2 2"
          "type Class () = (*loc-62*)"
          "  member c.Method () = () (*loc-63*)"
          "  static member Foo () = () (*loc-64*)"
          "let _ ="
          "  let c = Class () (*loc-65*)"
          "  c.Method () (*loc-66*)"
          "  Class.Foo () (*loc-67*)"
          "type Class' () ="
          "  member c.Method  () = c.Method () (*loc-68*)"
          "  member c.Method1 () = c.Method2 () (*loc-69*)"
          "  member c.Method2 () = c.Method1 () (*loc-70*)"
          "  member c.Method3  () ="
          "    let c = Class ()"
          "    c.Method () (*loc-71*)"
          "type Colors = Red   = 1"
          "            | White = 2"
          "            | Blue  = 3"
          "let _ = Colors.Red"
          "let _ ="
          "  let x = 2"
          "  \"x(*loc-72*)\""
          "let _ ="
          "  let x = 2"
          "  \"this is a string"
          "    x(*loc-73*)"
          "  \""
          "let _ ="
          "  let rec ``let`` = (*loc-74*)"
          "    function 0 -> 1"
          "           | n -> n * ``let`` (n - 1) (*loc-75*)"
          "let id77 = 0"  
          "type C ="
          "  val id77 (*loc-77*) : int"
        ]
      this.SolutionGotoDefinitionTestWithLines lines startLoc exp




    member internal this.SolutionGotoDefinitionTestWithLines lines (startLoc : string)(exp : (string * string) option) : unit =        
      // The test itself
      let (_, _, file) = this.CreateSingleFileProject(lines)
      let fnm = 
        GetNameOfOpenFile file
        |> Path.GetFileName
      MoveCursorToStartOfMarker (file, startLoc)
      let res = GotoDefinitionAtCursor file |> this.GotoDefinitionFixupFilename
      match exp with
      | None              -> this.GotoDefinitionCheckResultAgainst None                     file res
      | Some (endLoc, id) -> this.GotoDefinitionCheckResultAgainst (Some (id, endLoc, fnm)) file res

    /// exp = (<identifier where cursor ought to be>, <name of file that's expected>) option
    member this.GotoDefinitionTestWithLib (startLoc : string)(exp : (string * string) option) : unit =
      let lines = [ "let _ = List.map (*loc-1*)" ]
      let (_, proj, file) = this.CreateSingleFileProject(lines)

      MoveCursorToStartOfMarker (file, startLoc)
      let res = GotoDefinitionAtCursor file
      this.GotoDefinitionCheckResultAgainstAnotherFile proj exp res

    member this.GotoDefinitionFixupFilename (x : GotoDefnResult) : GotoDefnResult =
      if x.Success then 
        GotoDefnResult.MakeSuccess(Path.GetFileName x.Url, x.Span) 
      else 
        GotoDefnResult.MakeError(x.ErrorDescription)

    // the format of the comments for each test displays the desired behaviour,
    // where `$` and `#` indicate the initial and final cursor positions,
    // respectively; if the two are the same then the `#` is omitted

    // the `loc-<number>` comments in the source used for the tests are to
    // ensure that we've found the correct position (i.e., these must be unique
    // in any given test source file)

    [<Test>]
    member this.``GotoDefinition.InheritedMembers``() =
        let lines =
            [ "[<AbstractClass>]"
              "type Foo() ="
              "   abstract Method : unit -> unit"
              "   abstract Property : int"
              "type Bar() ="
              "   inherit Foo()"
              "   override this.Method () = ()"
              "   override this.Property = 1"
              "let b = Bar()"
              "b.Method(*loc-1*)()"
              "b.Property(*loc-2*)"
            ]
        this.SolutionGotoDefinitionTestWithLines lines  "Method(*loc-1*)" (Some("override this.Method () = ()","this.Method"))
        this.SolutionGotoDefinitionTestWithLines lines  "Property(*loc-2*)" (Some("override this.Property = 1","this.Property"))


    /// let #x = () in $x
    [<Test>]
    [<Category("PerfCheck")>]
    member public this.``GotoDefinition.InsideClass.Bug3176`` () =
      this.GotoDefinitionTestWithSimpleFile "id77 (*loc-77*)" (Some("val id77 (*loc-77*) : int", "id77"))

    /// let #x = () in $x
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.TrivialLetRHS`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-1*)" (Some("let x = () (*loc-2*)", "x"))

    /// let #x = () in x$
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.TrivialLetRHSToRight`` () =
      this.GotoDefinitionTestWithSimpleFile " (*loc-1*)" (Some("let x = () (*loc-2*)", "x"))

    /// let $x = () in x
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.TrivialLetLHS`` () =
      this.GotoDefinitionTestWithSimpleFile "x = () (*loc-2*)" (Some("let x = () (*loc-2*)", "x"))

    /// let x = () in let #x = () in $x
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.NestedLetWithSameNameRHS`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-4*)" (Some("let x = () (*loc-3*)", "x"))

    /// let x = () in let $x = () in x
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.NestedLetWithSameNameLHSInner`` () =
      this.GotoDefinitionTestWithSimpleFile "x = () (*loc-3*)" (Some("let x = () (*loc-3*)", "x"))

    /// let $x = () in let x = () in x
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.NestedLetWithSameNameLHSOuter`` () =
      this.GotoDefinitionTestWithSimpleFile "x = () (*loc-5*)" (Some("let x = () (*loc-5*)", "x"))

    /// let #x = () in let x = $x in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.NestedLetWithXIsX`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-6*)" (Some("let x = () (*loc-7*)", "x"))

    /// let x = () in let rec #x = fun y -> $x y in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.NestedLetWithXRec`` () =
      this.GotoDefinitionTestWithSimpleFile "x y (*loc-8*)" (Some("let rec x = (*loc-9*)", "x"))

    /// let x = () in let rec x = fun #y -> x $y in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Binding.NestedLetWithXRecParam`` () =
      this.GotoDefinitionTestWithSimpleFile "y (*loc-8*)" (Some("fun y -> (*loc-10*)", "y"))

    /// let #(+) x _ = x in 2 $+ 3
    [<Test>]
    [<Ignore "Bug 2514 filed.">]
    member public this.``GotoDefinition.Simple.Binding.Operator`` () =
      this.GotoDefinitionTestWithSimpleFile "+ 3 (*loc-11*)" (Some("let (+) x _ = x (*loc-2*)", "+"))

    /// type #Zero =
    /// let f (_ : $Zero) = 0
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.NullType`` () =
      this.GotoDefinitionTestWithSimpleFile "Zero) : 'a = failwith \"hi\" (*loc-14*)" (Some("type Zero = (*loc-13*)", "Zero"))

    /// type One = $One
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.UnitTypeConsDef`` () =
      this.GotoDefinitionTestWithSimpleFile "One (*loc-15*)" (Some("One (*loc-15*)", "One"))

    /// type $One = One
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.UnitTypeTypenameDef`` () =
      this.GotoDefinitionTestWithSimpleFile "One = (*loc-16*)" (Some("type One = (*loc-16*)", "One"))

    /// type One = #One
    /// let f (_ : One) = $One
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.UnitTypeCons`` () =
      this.GotoDefinitionTestWithSimpleFile "One (*loc-18*)" (Some("One (*loc-15*)", "One"))

    /// type #One = One
    /// let f (_ : $One) = One
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.UnitTypeTypename`` () =
      this.GotoDefinitionTestWithSimpleFile "One) = (*loc-17*)" (Some("type One = (*loc-16*)", "One"))

    /// type $Nat = Suc of Nat | Zro
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.NatTypeTypenameDef`` () =
      this.GotoDefinitionTestWithSimpleFile "Nat = (*loc-19*)" (Some("type Nat = (*loc-19*)", "Nat"))

    /// type #Nat = Suc of $Nat | Zro
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.NatTypeConsArg`` () =
      this.GotoDefinitionTestWithSimpleFile "Nat (*loc-20*)" (Some("type Nat = (*loc-19*)", "Nat"))

    /// type Nat = Suc of Nat | #Zro
    /// fun m -> match m with | $Zro -> () | _ -> ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.NatPatZro`` () =
      this.GotoDefinitionTestWithSimpleFile "Zro   -> (*loc-24*)" (Some("| Zro (*loc-21*)", "Zro"))

    /// type Nat = $Suc of Nat | Zro
    /// fun m -> match m with | Zro -> () | $Suc _ -> ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.NatPatSuc`` () =
      this.GotoDefinitionTestWithSimpleFile "Suc m -> (*loc-25*)" (Some("| Suc of Nat (*loc-20*)", "Suc"))

    /// let rec plus m n = match m with | Zro -> n | Suc #m -> Suc (plus $m n)
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.NatPatSucVarUse`` () =
      this.GotoDefinitionTestWithSimpleFile "m n) (*loc-26*)" (Some("| Suc m -> (*loc-25*)", "m"))

    /// let rec plus m n = match m with | Zro -> n | Suc #m -> Suc (plus $m n)
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.NatPatSucOuterVarUse`` () =
      this.GotoDefinitionTestWithSimpleFile "n) (*loc-26*)" (Some("let rec plus m n = (*loc-23*)", "n"))

    /// type $MyRec = { myX : int ; myY : int }
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.RecordTypenameDef`` () =
      this.GotoDefinitionTestWithSimpleFile "MyRec = (*loc-27*)" (Some("type MyRec = (*loc-27*)", "MyRec"))

    /// type MyRec = { $myX : int ; myY : int }
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.RecordField1Def`` () =
      this.GotoDefinitionTestWithSimpleFile "myX : int (*loc-28*)" (Some("{ myX : int (*loc-28*)", "myX"))

    /// type MyRec = { myX : int ; $myY : int }
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.RecordField2Def`` () =
      this.GotoDefinitionTestWithSimpleFile "myY : int (*loc-29*)" (Some("myY : int (*loc-29*)", "myY"))

    /// type MyRec = { #myX : int ; myY : int }
    /// let rDefault = { $myX = 2 ; myY = 3 }
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.RecordField1Use`` () =
      this.GotoDefinitionTestWithSimpleFile "myX = 2 (*loc-30*)" (Some("{ myX : int (*loc-28*)", "myX"))

    /// type MyRec = { myX : int ; #myY : int }
    /// let rDefault = { myX = 2 ; $myY = 3 }
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.RecordField2Use`` () =
      this.GotoDefinitionTestWithSimpleFile "myY = 3 (*loc-31*)" (Some("myY : int (*loc-29*)", "myY"))

    /// type MyRec = { #myX : int ; myY : int }
    /// let rDefault = { myX = 2 ; myY = 3 }
    /// let _ = { rDefault with $myX = 7 }
    [<Test>]
    member public this.``GotoDefinition.Simple.Datatype.RecordField1UseInWith`` () =
      this.GotoDefinitionTestWithSimpleFile "myX = 7 } (*loc-32*)" (Some("{ myX : int (*loc-28*)", "myX"))


    /// let a = () in let id (x : '$a) : 'a = x
    [<Test>]
    member public this.``GotoDefinition.Simple.Polymorph.Leftmost`` () =
      this.GotoDefinitionTestWithSimpleFile "a) (*loc-33*)" None

    /// let a = () in let id (x : 'a) : '$a = x
    [<Test>]
    member public this.``GotoDefinition.Simple.Polymorph.NotLeftmost`` () =
      this.GotoDefinitionTestWithSimpleFile "a = x (*loc-34*)" None

    /// let foo = () in let f (_ as $foo) = foo in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.AsPatLHS`` () =
      this.GotoDefinitionTestWithSimpleFile "foo) = (*loc-35*)" (Some("let f (_ as foo) = (*loc-35*)", "foo"))

    /// let foo = () in let f (_ as #foo) = $foo in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.AsPatRHS`` () =
      this.GotoDefinitionTestWithSimpleFile "foo (*loc-36*)" (Some("let f (_ as foo) = (*loc-35*)", "foo"))

    /// fun $x x -> x
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.LambdaMultBind1`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-37*)" (Some("fun x (*loc-37*)", "x"))

    /// fun x $x -> x
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.LambdaMultBind2`` () =
      this.GotoDefinitionTestWithSimpleFile "x -> (*loc-38*)" (Some("x -> (*loc-38*)", "x"))

    /// fun x $x -> x
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.LambdaMultBindBody`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-39*)" (Some("x -> (*loc-38*)", "x"))

    /// let f = () in let $f = function f -> f in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.LotsOfFsFunc`` () =
      this.GotoDefinitionTestWithSimpleFile "f = (*loc-41*)" (Some("let f = (*loc-41*)", "f"))

    /// let f = () in let f = function $f -> f in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.LotsOfFsPat`` () =
      this.GotoDefinitionTestWithSimpleFile "f -> (*loc-42*)" (Some("function f -> (*loc-42*)", "f"))

    /// let f = () in let f = function #f -> $f in ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.LotsOfFsUse`` () =
      this.GotoDefinitionTestWithSimpleFile "f (*loc-43*)" (Some("function f -> (*loc-42*)", "f"))

    /// let f x = match x with | Suc $x | x -> x
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.OrPatLeft`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-44*)" (Some("| Suc x (*loc-44*)", "x"))

    /// let f x = match x with | Suc x | $x -> x
    [<Test >]
    member public this.``GotoDefinition.Simple.Tricky.OrPatRight`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-45*)" (Some("| Suc x (*loc-44*)", "x"))  // NOTE: or-patterns bind at first occurrence of the variable

    /// let f x = match x with | Suc #y & z -> $y
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.AndPat`` () =
      this.GotoDefinitionTestWithSimpleFile "y (*loc-46*)" (Some("| Suc y & z -> (*loc-47*)", "y"))

    /// let f xs = match xs with | #x :: xs -> $x
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.ConsPat`` () =
      this.GotoDefinitionTestWithSimpleFile "x (*loc-48*)" (Some("| x :: xs -> (*loc-49*)", "x"))

    /// let f p = match p with (#y, z) -> $y
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.PairPat`` () =
      this.GotoDefinitionTestWithSimpleFile "y (*loc-50*)" (Some("| (y : int, z) -> (*loc-51*)", "y"))

    /// fun xs -> match xs with x :: #xs when $xs <> [] -> x :: xs
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.ConsPatWhenClauseInWhen`` () =
      this.GotoDefinitionTestWithSimpleFile "xs <> [] -> (*loc-52*)" (Some("| x :: xs (*loc-54*)", "xs"))

    /// fun xs -> match xs with #x :: xs when xs <> [] -> $x :: xs
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.ConsPatWhenClauseInWhenRhsX`` () =
      this.GotoDefinitionTestWithSimpleFile "x :: xs (*loc-53*)" (Some("| x :: xs (*loc-54*)", "x"))

    /// fun xs -> match xs with x :: #xs when xs <> [] -> x :: $xs
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.ConsPatWhenClauseInWhenRhsXs`` () =
      this.GotoDefinitionTestWithSimpleFile "xs (*loc-53*)" (Some("| x :: xs (*loc-54*)", "xs"))

    /// let x = "$x"
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.InStringFails`` () =
      this.GotoDefinitionTestWithSimpleFile "x(*loc-72*)" None

    /// let x = "hello
    ///          $x
    ///         "
    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.InMultiLineStringFails`` () =
      this.GotoDefinitionTestWithSimpleFile "x(*loc-73*)" None

    [<Test>]
    member public this.``GotoDefinition.Simple.Tricky.QuotedKeyword`` () =
      this.GotoDefinitionTestWithSimpleFile "let`` = (*loc-74*)" (Some("let rec ``let`` = (*loc-74*)", "``let``"))

    /// module $Too = let foo = ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Module.DefModname`` () =
      this.GotoDefinitionTestWithSimpleFile "Too = (*loc-55*)" (Some("module Too = (*loc-55*)", "Too"))

    /// module Too = $foo = ()
    [<Test>]
    member public this.``GotoDefinition.Simple.Module.DefMember`` () =
      this.GotoDefinitionTestWithSimpleFile "foo = 0 (*loc-56*)" (Some("let foo = 0 (*loc-56*)", "foo"))

    /// module #Too = foo = ()
    /// module Bar = open $Too
    [<Test>]
    member public this.``GotoDefinition.Simple.Module.Open`` () =
      this.GotoDefinitionTestWithSimpleFile "Too (*loc-57*)" (Some("module Too = (*loc-55*)", "Too"))

    /// module #Too = foo = ()
    /// $Too.foo
    [<Test>]
    member public this.``GotoDefinition.Simple.Module.QualifiedModule`` () =
      this.GotoDefinitionTestWithSimpleFile "Too.foo (*loc-58*)" (Some("module Too = (*loc-55*)", "Too"))

    /// module Too = #foo = ()
    /// Too.$foo
    [<Test>]
    member public this.``GotoDefinition.Simple.Module.QualifiedMember`` () =
      this.GotoDefinitionTestWithSimpleFile "foo (*loc-58*)" (Some("let foo = 0 (*loc-56*)", "foo"))

    /// type Parity = Even | Odd
    /// let (|$Even|Odd|) x = if x % 0 = 0 then Even else Odd
    [<Test>]
    member public this.``GotoDefinition.Simple.ActivePat.ConsDefLHS`` () =
      this.GotoDefinitionTestWithSimpleFile "Even|Odd|) x = (*loc-59*)" (Some("let (|Even|Odd|) x = (*loc-59*)", "|Even|Odd|"))

    /// type Parity = Even | Odd
    /// let (|#Even|Odd|) x = if x % 0 = 0 then $Even else Odd
    [<Test>]
    member public this.``GotoDefinition.Simple.ActivePat.ConsDefRhs`` () =
      this.GotoDefinitionTestWithSimpleFile "Even (*loc-60*)" (Some("let (|Even|Odd|) x = (*loc-59*)", "|Even|Odd|"))

    /// type Parity = Even | Odd
    /// let (|#Even|Odd|) x = if x % 0 = 0 then Even else Odd
    /// let foo x =
    ///   match x with
    ///   | $Even -> 1
    ///   | Odd  -> 0
    [<Test>]
    member public this.``GotoDefinition.Simple.ActivePat.PatUse`` () =
      this.GotoDefinitionTestWithSimpleFile "Even -> 1 (*loc-61*)" (Some("let (|Even|Odd|) x = (*loc-59*)", "|Even|Odd|"))

    /// let patval = (|Even|Odd|) (*loc-61b*)
    [<Test>]
    member public this.``GotoDefinition.Simple.ActivePat.PatUseValue`` () =
      this.GotoDefinitionTestWithSimpleFile "en|Odd|) (*loc-61b*)" (Some("let (|Even|Odd|) x = (*loc-59*)", "|Even|Odd|"))

    [<Test>]
    [<Ignore("This is currently broken for dev enlistments.")>]
    member public this.``GotoDefinition.Library.InitialTest`` () =
      this.GotoDefinitionTestWithLib "map (*loc-1*)" (Some("map", "lis.fs"))

    // ********** Tests of OO Stuff **********

    /// type #Class$ () =
    ///   member c.Method () = ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.ClassNameDef`` () =
      this.GotoDefinitionTestWithSimpleFile " () = (*loc-62*)" (Some("type Class () = (*loc-62*)", "Class"))

    /// type Class () =
    ///   member c.#Method$ () = ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.ILMethodDef`` () =
      this.GotoDefinitionTestWithSimpleFile " () = () (*loc-63*)" (Some("member c.Method () = () (*loc-63*)", "c.Method"))

    /// type Class () =
    ///   member #c$.Method () = ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.ThisDef`` () =
      this.GotoDefinitionTestWithSimpleFile ".Method () = () (*loc-63*)" (Some("member c.Method () = () (*loc-63*)", "c"))

    /// type Class () =
    ///   static member #Foo$ () = ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.StaticMethodDef`` () =
      this.GotoDefinitionTestWithSimpleFile " () = () (*loc-64*)" (Some("static member Foo () = () (*loc-64*)", "Foo"))

    /// type #Class () =
    ///   member Method () = ()
    /// let c = Class$ ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.ConstructorUse`` () =
      this.GotoDefinitionTestWithSimpleFile " () (*loc-65*)" (Some("type Class () = (*loc-62*)", "Class"))

    /// type Class () =
    ///   member #Method () = ()
    /// let c = Class ()
    /// c.Method$ ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.MethodInvocation`` () =
      this.GotoDefinitionTestWithSimpleFile " () (*loc-66*)" (Some("member c.Method () = () (*loc-63*)", "c.Method"))

    /// type Class () =
    ///   static member #Foo () = ()
    /// Class.Foo$ ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.StaticMethodInvocation`` () =
      this.GotoDefinitionTestWithSimpleFile " () (*loc-67*)" (Some("static member Foo () = () (*loc-64*)", "Foo"))

    /// type Class () =
    ///   member c.Method# () = c.Method$ ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.MethodSelfInvocation`` () =
      this.GotoDefinitionTestWithSimpleFile " () (*loc-68*)" (Some("member c.Method  () = c.Method () (*loc-68*)", "c.Method"))

    /// type Class () =
    ///   member c.Method1 ()  = c.Method2$ ()
    ///   member #c.Method2 () = c.Method1 ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.MethodToMethodForward`` () =
      this.GotoDefinitionTestWithSimpleFile " () (*loc-69*)" (Some("member c.Method2 () = c.Method1 () (*loc-70*)", "c.Method2"))

    /// type Class () =
    ///   member c.Method () = ()
    /// type Class' () =
    ///   member c.Method () =
    ///     let #c = Class ()
    ///     c$.Method ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.ShadowThis`` () =
      this.GotoDefinitionTestWithSimpleFile ".Method () (*loc-71*)" (Some("let c = Class ()", "c"))

    /// type Class () =
    ///   member #c.Method () = ()
    /// type Class' () =
    ///   member c.Method () =
    ///     let c = Class ()
    ///     c.Method$ ()
    [<Test>]
    member public this.``GotoDefinition.ObjectOriented.ShadowThisMethodInvocation`` () =
      this.GotoDefinitionTestWithSimpleFile " () (*loc-71*)" (Some("member c.Method () = () (*loc-63*)", "c.Method"))

    [<Test>]
    member this.``GotoDefinition.ObjectOriented.StructConstructor`` () =
      let lines = 
        [ "#light"
          "[<Struct>]"
          "type Astruct ="
          "  val a : int"
          "  new(a) = { a = a }" 
          "let a = Astruct(0)"
        ]
      
      let (_,_, file) = this.CreateSingleFileProject(lines)

      MoveCursorToStartOfMarker (file, "Astruct(0)")
      let res = GotoDefinitionAtCursor file |> fun x -> x.ToOption() |>  Option.map (fun (res, _) -> res.iStartLine + 1, res.iStartIndex + 1)
      AssertEqual(Some(5, 3), res)

    // ********** GetCompleteIdentifierIsland tests **********

    /// takes a string with a `$` representing the cursor position, gets the
    /// GotoDefinition identifier at that position and compares against expected
    /// result

    member this.GetCompleteIdTest tolerate (s : string)(exp : string option) : unit =
      let n = s.IndexOf '$'
      let s = s.Remove (n, 1)
      match (Microsoft.VisualStudio.FSharp.LanguageService.QuickParse.GetCompleteIdentifierIsland tolerate s n, exp) with
      | (Some (s1, _, _), Some s2) -> 
        printfn "%s" "Received result, as expected."
        Assert.AreEqual (s1, s2)
      | (None,         None)    -> 
        printfn "%s" "Received no result, as expected."
      | (Some _,       None)    -> 
        Assert.Fail("Received result, but none was expected!")
      | (None,         Some _)  -> 
        Assert.Fail("Expected result, but didn't receive one!")

    [<Test>]
    [<Category("PerfCheck")>]
    member public this.``GetCompleteIdTest.TrivialBefore`` () =
      for tolerate in [true;false] do
          this.GetCompleteIdTest tolerate "let $ThisIsAnIdentifier = ()" (Some "ThisIsAnIdentifier")

    [<Test>]
    member public this.``GetCompleteIdTest.TrivialMiddle`` () =
      for tolerate in [true;false] do
          this.GetCompleteIdTest tolerate "let This$IsAnIdentifier = ()" (Some "ThisIsAnIdentifier")

    [<Test>]
    member public this.``GetCompleteIdTest.TrivialEnd`` () =
      this.GetCompleteIdTest true "let ThisIsAnIdentifier$ = ()" (Some "ThisIsAnIdentifier")
      this.GetCompleteIdTest false "let ThisIsAnIdentifier$ = ()" None

    [<Test>]
    member public this.``GetCompleteIdTest.GetsUpToDot1`` () =
      for tolerate in [true;false] do
          this.GetCompleteIdTest tolerate "let ThisIsAnIdentifier = Te$st.Moo.Foo.bar" (Some "Test")

    [<Test>]
    member public this.``GetCompleteIdTest.GetsUpToDot2`` () =
      for tolerate in [true;false] do
          this.GetCompleteIdTest tolerate "let ThisIsAnIdentifier = Test.Mo$o.Foo.bar" (Some "Test.Moo")

    [<Test>]
    member public this.``GetCompleteIdTest.GetsUpToDot3`` () =
      for tolerate in [true;false] do
          this.GetCompleteIdTest tolerate "let ThisIsAnIdentifier = Test.Moo.Fo$o.bar" (Some "Test.Moo.Foo")

    [<Test>]
    member public this.``GetCompleteIdTest.GetsUpToDot4`` () =
      for tolerate in [true;false] do
          this.GetCompleteIdTest tolerate "let ThisIsAnIdentifier = Test.Moo.Foo.ba$r" (Some "Test.Moo.Foo.bar")

    [<Test>]
    member public this.``GetCompleteIdTest.GetsUpToDot5`` () =
      this.GetCompleteIdTest true "let ThisIsAnIdentifier = Test.Moo.Foo.bar$" (Some "Test.Moo.Foo.bar")
      this.GetCompleteIdTest false "let ThisIsAnIdentifier = Test.Moo.Foo.bar$" None

    [<Test>]
    member public this.``GetCompleteIdTest.GetOperator`` () =
      for tolerate in [true;false] do
          this.GetCompleteIdTest tolerate "let ThisIsAnIdentifier = 3 +$ 4" None


    [<Test>]
    member public this.``Identifier.IsConstructor.Bug2516``() =
        let fileContents = """
            module GotoDefinition
            type One(*Mark1*) = One
            let f (x : One(*Mark2*)) = 2"""
        let definitionCode = "type One(*Mark1*) = One"
        this.VerifyGoToDefnSuccessAtStartOfMarker(fileContents,"(*Mark1*)",definitionCode) 
        
    [<Test>]
    member public this.``Identifier.IsTypeName.Bug2516``() =
        let fileContents = """
            module GotoDefinition
            type One(*Mark1*) = One
            let f (x : One(*Mark2*)) = 2"""
        let definitionCode = "type One(*Mark1*) = One"
        this.VerifyGoToDefnSuccessAtStartOfMarker(fileContents,"(*Mark2*)",definitionCode)       
       
    [<Test>]
    member public this.``ModuleName.OnDefinitionSite.Bug2517``() =
        let fileContents = """
            namespace GotoDefinition
            module Foo(*Mark*) =
            let x = ()"""
        let definitionCode = "module Foo(*Mark*) ="
        this.VerifyGoToDefnSuccessAtStartOfMarker(fileContents,"(*Mark*)",definitionCode) 

    [<Test>]
    /// GotoDef on abbreviation
    member public this.``GotoDefinition.Abbreviation.Bug193064``() =
        let fileContents = """
            type X = int
            let f (x:X) = x(*Marker*) """
        let definitionCode = "let f (x:X) = x(*Marker*)"
        this.VerifyGoToDefnSuccessAtStartOfMarker(fileContents,"x(*Marker*)",definitionCode) 

    [<Test>]
    /// Verify the GotoDefinition on UoM yield does NOT jump out error dialog, 
    /// will do nothing in automation lab machine or GTD SI.fs on dev machine with enlistment.
    member public this.``GotoDefinition.UnitOfMeasure.Bug193064``() =
        let fileContents = """
            open Microsoft.FSharp.Data.UnitSystems.SI
            UnitSymbols.A(*Marker*)"""
        this.VerifyGoToDefnNoErrorDialogAtStartOfMarker(fileContents,"A(*Marker*)", "type A = ampere")


// Allow languageService tests to run under different contextes
namespace UnitTests.Tests.LanguageService.GotoDefinition
open UnitTests.Tests.LanguageService
open UnitTests.TestLib.LanguageService
open UnitTests.TestLib.ProjectSystem
open NUnit.Framework
open Salsa.Salsa

// context msbuild
[<TestFixture>] 
[<Category("LanguageService.MSBuild")>]
type ``MSBuild`` = 
   inherit GotoDefinitionTests
   new() = { inherit GotoDefinitionTests(VsOpts = fst (Models.MSBuild())); }

// Context project system
[<TestFixture>]
[<Category("LanguageService.ProjectSystem")>]
type ``ProjectSystem`` = 
    inherit GotoDefinitionTests
    new() = { inherit GotoDefinitionTests(VsOpts = LanguageServiceExtension.ProjectSystem); }  
