Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Compiler/Service/ItemKey.fs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ and [<Sealed>] ItemKeyStoreBuilder() =
| Item.MethodGroup (_, [ info ], _)
| Item.CtorGroup (_, [ info ]) ->
match info with
| FSMeth (_, ty, vref, _) when vref.IsConstructor -> writeType true ty
| FSMeth (_, _, vref, _) -> writeValRef vref
| ILMeth (_, info, _) ->
info.ILMethodRef.ArgTypes |> List.iter writeILType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
<Compile Include="Signatures\SigGenerationRoundTripTests.fs" />
<Compile Include="FSharpChecker\CommonWorkflows.fs" />
<Compile Include="FSharpChecker\SymbolUse.fs" />
<Compile Include="FSharpChecker\FindReferences.fs" />
<None Include="**\*.cs;**\*.fs;**\*.fsx;**\*.fsi" Exclude="@(Compile)">
<Link>%(RelativeDir)\TestSource\%(Filename)%(Extension)</Link>
</None>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
module FSharp.Compiler.ComponentTests.FSharpChecker.FindReferences

open FSharp.Compiler.CodeAnalysis
open Xunit
open FSharp.Test.ProjectGeneration

type Occurence = Definition | InType | Use

let deriveOccurence (su:FSharpSymbolUse) =
if su.IsFromDefinition
then Definition
elif su.IsFromType
then InType
elif su.IsFromUse
then Use
else failwith $"Unexpected type of occurence (for this test), symbolUse = {su}"

/// https://github.com/dotnet/fsharp/issues/13199
let reproSourceCode = """
type MyType() =
member x.DoNothing(d:MyType) = ()
let a = MyType()
let b = new MyType()
a.DoNothing(b)
"""
let impFile() = { sourceFile "First" [] with ExtraSource = reproSourceCode }
let createProject() = SyntheticProject.Create(impFile())

[<Fact>]
let ``Finding usage of type via GetUsesOfSymbolInFile should also find it's constructors`` () =
createProject().Workflow
{
checkFile "First" (fun (typeCheckResult: FSharpCheckFileResults) ->

let symbolUse = typeCheckResult.GetSymbolUseAtLocation(7, 11, "type MyType() =", ["MyType"]).Value
let references =
typeCheckResult.GetUsesOfSymbolInFile(symbolUse.Symbol)
|> Array.sortBy (fun su -> su.Range.StartLine)
|> Array.map (fun su -> su.Range.StartLine, su.Range.StartColumn, su.Range.EndColumn, deriveOccurence su)

Assert.Equal<(int*int*int*Occurence)>(
[| 7,5,11,Definition
8,25,31,InType
10,8,14,Use
11,12,18,Use
|],references) )
}


[<Fact>]
let ``Finding usage of type via FindReference should also find it's constructors`` () =
createProject().Workflow
{
placeCursor "First" 7 11 "type MyType() =" ["MyType"]
findAllReferences "First" (fun (ranges:list<FSharp.Compiler.Text.range>) ->
let ranges =
ranges
|> List.sortBy (fun r -> r.StartLine)
|> List.map (fun r -> r.StartLine, r.StartColumn, r.EndColumn)
|> Array.ofSeq

Assert.Equal<(int*int*int)>(
[| 7,5,11 // Typedef itself
8,25,31 // Usage within type
10,8,14 // "a= ..." constructor
11,12,18 // "b= ..." constructor
|],ranges) )

}

[<Fact>]
let ``Finding usage of type via FindReference works across files`` () =
let secondFile = { sourceFile "Second" ["First"] with ExtraSource = """
open ModuleFirst
let secondA = MyType()
let secondB = new MyType()
secondA.DoNothing(secondB)
"""}
let original = createProject()
let project = {original with SourceFiles = original.SourceFiles @ [secondFile]}
project.Workflow
{
placeCursor "First" 7 11 "type MyType() =" ["MyType"]
findAllReferences "Second" (fun (ranges:list<FSharp.Compiler.Text.range>) ->
let ranges =
ranges
|> List.sortBy (fun r -> r.StartLine)
|> List.map (fun r -> r.StartLine, r.StartColumn, r.EndColumn)
|> Array.ofSeq

Assert.Equal<(int*int*int)>(
[| 9,14,20 // "secondA = ..." constructor
10,18,24 // "secondB = ..." constructor
|],ranges) )

}

46 changes: 43 additions & 3 deletions tests/FSharp.Test.Utilities/ProjectGeneration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,19 @@ module ProjectOperations =

type WorkflowContext =
{ Project: SyntheticProject
Signatures: Map<string, string> }
Signatures: Map<string, string>
Cursor : FSharp.Compiler.CodeAnalysis.FSharpSymbolUse option }

type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpChecker) =

let checker = defaultArg checker (FSharpChecker.Create())
let checker =
defaultArg
checker
(FSharpChecker.Create(
keepAllBackgroundSymbolUses = false,
enableBackgroundItemKeyStoreAndSemanticClassification = true,
enablePartialTypeChecking = true
))

let mapProject f workflow =
async {
Expand Down Expand Up @@ -392,7 +400,8 @@ type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpCh

return
{ Project = initialProject
Signatures = Map signatures }
Signatures = Map signatures
Cursor = None }
}

member this.Run(workflow: Async<WorkflowContext>) =
Expand Down Expand Up @@ -442,6 +451,37 @@ type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpCh
return { ctx with Signatures = ctx.Signatures.Add(fileId, newSignature) }
}

/// Find a symbol using the provided range, mimicing placing a cursor on it in IDE scenarios
[<CustomOperation "placeCursor">]
member this.PlaceCursor(workflow: Async<WorkflowContext>, fileId, line, colAtEndOfNames, fullLine, symbolNames) =
async {
let! ctx = workflow
let! results = checkFile fileId ctx.Project checker
let typeCheckResults = getTypeCheckResult results

let su = typeCheckResults.GetSymbolUseAtLocation(line,colAtEndOfNames,fullLine,symbolNames)

return {ctx with Cursor = su}
}



/// Find all references within a single file, results are provided to the 'processResults' function
[<CustomOperation "findAllReferences">]
member this.FindAllReferences(workflow: Async<WorkflowContext>, fileId: string, processResults) =
async{
let! ctx = workflow
let po = ctx.Project.GetProjectOptions checker
let s = ctx.Cursor |> Option.defaultWith (fun () -> failwith $"Please place cursor at a valid location via {nameof(this.PlaceCursor)} first")
let file = ctx.Project.Find fileId
let absFileName = ctx.Project.ProjectDir ++ file.FileName
let! results = checker.FindBackgroundReferencesInFile(absFileName,po, s.Symbol)

processResults (results |> Seq.toList)

return ctx
}

/// Parse and type check given file and process the results using `processResults` function.
[<CustomOperation "checkFile">]
member this.CheckFile(workflow: Async<WorkflowContext>, fileId: string, processResults) =
Expand Down