From d22f473e3216cdf68b02d50bba2c4d12a9dfd3c9 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sat, 10 Dec 2016 16:15:13 +0300 Subject: [PATCH 01/10] implement FindReferencesService --- CommonHelpers.fs | 9 +++- FSharp.Editor.fsproj | 5 +- FindReferencesService.fs | 110 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 FindReferencesService.fs diff --git a/CommonHelpers.fs b/CommonHelpers.fs index c193082aee7..d58db899f71 100644 --- a/CommonHelpers.fs +++ b/CommonHelpers.fs @@ -187,4 +187,11 @@ module CommonHelpers = let islandColumn = sourceText.Lines.GetLinePositionSpan(classifiedSpan.TextSpan).End.Character Some (islandColumn, [""], classifiedSpan.TextSpan) | _ -> None - | _ -> None \ No newline at end of file + | _ -> None + + /// Fix invalid span if it appears to have redundant suffix and prefix. + let fixupSpan (sourceText: SourceText, span: TextSpan) : TextSpan = + let text = sourceText.GetSubText(span).ToString() + match text.LastIndexOf '.' with + | -1 | 0 -> span + | index -> TextSpan(span.Start + index + 1, text.Length - index - 1) \ No newline at end of file diff --git a/FSharp.Editor.fsproj b/FSharp.Editor.fsproj index b88fedf9d7d..e369772d9f0 100644 --- a/FSharp.Editor.fsproj +++ b/FSharp.Editor.fsproj @@ -61,8 +61,11 @@ Completion\SignatureHelp.fs - GoToDefinition\GoToDefinitionService.fs + Navigation\GoToDefinitionService.fs + + Navigation\FindReferencesService.fs + BlockComment\CommentUncommentService.fs diff --git a/FindReferencesService.fs b/FindReferencesService.fs new file mode 100644 index 00000000000..82ac2eb3412 --- /dev/null +++ b/FindReferencesService.fs @@ -0,0 +1,110 @@ +// 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. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Collections.Immutable +open System.Composition + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Host.Mef +open Microsoft.CodeAnalysis.Editor +open Microsoft.CodeAnalysis.Editor.Host +open Microsoft.CodeAnalysis.Navigation +open Microsoft.CodeAnalysis.FindSymbols +open Microsoft.CodeAnalysis.FindReferences + +open Microsoft.VisualStudio.FSharp.LanguageService + +open Microsoft.FSharp.Compiler.Range +open Microsoft.FSharp.Compiler.SourceCodeServices + +[, FSharpCommonConstants.FSharpLanguageName); Shared>] +type internal FSharpFindReferencesService + [] + ( + checkerProvider: FSharpCheckerProvider, + projectInfoManager: ProjectInfoManager + ) = + + let findReferencedSymbolsAsync(document: Document, position: int, context: FindReferencesContext) : Async = + async { + let! sourceText = document.GetTextAsync(context.CancellationToken) |> Async.AwaitTask + let! textVersion = document.GetTextVersionAsync(context.CancellationToken) |> Async.AwaitTask + let checker = checkerProvider.Checker + let! options = projectInfoManager.TryGetOptionsForDocumentOrProject(document) + match options with + | Some options -> + let! _parseResults, checkResultsAnswer = checker.ParseAndCheckFileInProject(document.FilePath, hash textVersion, sourceText.ToString(), options) + let checkFileResults = + match checkResultsAnswer with + | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" + | FSharpCheckFileAnswer.Succeeded(results) -> results + + let textLine = sourceText.Lines.GetLineFromPosition(position) + let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based + let textLinePos = sourceText.Lines.GetLinePosition(position) + let fcsTextLineNumber = textLinePos.Line + 1 + let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, options.OtherOptions |> Seq.toList) + + let rangeToDocumentSpan (range: range) = + async { + match document.Project.Solution.GetDocumentIdsWithFilePath(range.FileName) |> Seq.tryHead with + | Some docId -> + let doc = document.Project.Solution.GetDocument(docId) + let! sourceText = doc.GetTextAsync(context.CancellationToken) |> Async.AwaitTask + let span = CommonHelpers.fixupSpan(sourceText, CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range)) + return Some (DocumentSpan(doc, span)) + | None -> return None + } + + match CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, context.CancellationToken) with + | Some (islandColumn, qualifiers, _) -> + let! symbolUse = checkFileResults.GetSymbolUseAtLocation(textLineNumber, islandColumn, textLine.ToString(), qualifiers) + match symbolUse with + | Some symbolUse -> + let! declaration = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) + let declarationRange = + match declaration with + | FSharpFindDeclResult.DeclFound range -> Some range + | _ -> None + + let! declarationSpan = + match declarationRange with + | Some range -> rangeToDocumentSpan range + | None -> async.Return (Some (DocumentSpan())) + + let declarationSpan = match declarationSpan with Some x -> x | None -> DocumentSpan() + + let definitionItem = + DefinitionItem.Create( + ImmutableArray.Empty, + [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), + declarationSpan) + + do! context.OnDefinitionFoundAsync(definitionItem) |> Async.AwaitTask + + let! projectCheckResults = checker.ParseAndCheckProject(options) + let! symbolUses = projectCheckResults.GetUsesOfSymbol(symbolUse.Symbol) + for symbolUse in symbolUses do + match declarationRange with + | Some declRange when declRange = symbolUse.RangeAlternate -> () + | _ -> + let! referenceDocSpan = rangeToDocumentSpan symbolUse.RangeAlternate + match referenceDocSpan with + | Some span -> + let referenceItem = SourceReferenceItem(definitionItem, span) + do! context.OnReferenceFoundAsync(referenceItem) |> Async.AwaitTask + | None -> () + | None -> () + | None -> () + | None -> () + + do! context.OnCompletedAsync() |> Async.AwaitTask + } + + interface IStreamingFindReferencesService with + member __.FindReferencesAsync(document, position, context) = + findReferencedSymbolsAsync(document, position, context) + |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + \ No newline at end of file From d6472f67ac1d13d7a13139e7f9776e97a7dcf037 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sun, 11 Dec 2016 00:52:32 +0300 Subject: [PATCH 02/10] fixed exception when trying find references on external symbol --- FindReferencesService.fs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/FindReferencesService.fs b/FindReferencesService.fs index 82ac2eb3412..424ee82213e 100644 --- a/FindReferencesService.fs +++ b/FindReferencesService.fs @@ -72,15 +72,20 @@ type internal FSharpFindReferencesService let! declarationSpan = match declarationRange with | Some range -> rangeToDocumentSpan range - | None -> async.Return (Some (DocumentSpan())) + | None -> async.Return None - let declarationSpan = match declarationSpan with Some x -> x | None -> DocumentSpan() - + let definitionItem = - DefinitionItem.Create( - ImmutableArray.Empty, - [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), - declarationSpan) + match declarationSpan with + | Some span -> + DefinitionItem.Create( + ImmutableArray.Empty, + [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), + span) + | None -> + DefinitionItem.CreateNonNavigableItem( + ImmutableArray.Empty, + [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray()) do! context.OnDefinitionFoundAsync(definitionItem) |> Async.AwaitTask From 439858240c7f8c195ae2c1132cbe757862aa8f96 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Sun, 11 Dec 2016 01:08:22 +0300 Subject: [PATCH 03/10] use file check results to find symbol usages if symbol is private to file --- CommonHelpers.fs | 21 ++++++++++++++++++++- FindReferencesService.fs | 14 ++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CommonHelpers.fs b/CommonHelpers.fs index d58db899f71..31a0e7a7f6d 100644 --- a/CommonHelpers.fs +++ b/CommonHelpers.fs @@ -194,4 +194,23 @@ module CommonHelpers = let text = sourceText.GetSubText(span).ToString() match text.LastIndexOf '.' with | -1 | 0 -> span - | index -> TextSpan(span.Start + index + 1, text.Length - index - 1) \ No newline at end of file + | index -> TextSpan(span.Start + index + 1, text.Length - index - 1) + +[] +module internal Extensions = + open System.IO + + type Path with + static member GetFullPathSafe path = + try Path.GetFullPath path + with _ -> path + + type FSharpSymbol with + member this.IsPrivateToFile = + match this with + | :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember + | :? FSharpEntity as m -> m.Accessibility.IsPrivate + | :? FSharpGenericParameter -> true + | :? FSharpUnionCase as m -> m.Accessibility.IsPrivate + | :? FSharpField as m -> m.Accessibility.IsPrivate + | _ -> false \ No newline at end of file diff --git a/FindReferencesService.fs b/FindReferencesService.fs index 424ee82213e..ebdf7af1884 100644 --- a/FindReferencesService.fs +++ b/FindReferencesService.fs @@ -74,7 +74,6 @@ type internal FSharpFindReferencesService | Some range -> rangeToDocumentSpan range | None -> async.Return None - let definitionItem = match declarationSpan with | Some span -> @@ -88,9 +87,16 @@ type internal FSharpFindReferencesService [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray()) do! context.OnDefinitionFoundAsync(definitionItem) |> Async.AwaitTask - - let! projectCheckResults = checker.ParseAndCheckProject(options) - let! symbolUses = projectCheckResults.GetUsesOfSymbol(symbolUse.Symbol) + + let! symbolUses = + async { + if symbolUse.Symbol.IsPrivateToFile then + return! checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) + else + let! projectCheckResults = checker.ParseAndCheckProject(options) + return! projectCheckResults.GetUsesOfSymbol(symbolUse.Symbol) + } + for symbolUse in symbolUses do match declarationRange with | Some declRange when declRange = symbolUse.RangeAlternate -> () From f97786c2a83a9757e05b3166286cf511cab91596 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Mon, 12 Dec 2016 17:19:43 +0300 Subject: [PATCH 04/10] one file -> many Documents fix FSharpSymbol.IsPrivateToFile --- CommonHelpers.fs | 32 +++++++++++---- CommonRoslynHelpers.fs | 6 +++ FindReferencesService.fs | 88 +++++++++++++++++++++++----------------- 3 files changed, 81 insertions(+), 45 deletions(-) diff --git a/CommonHelpers.fs b/CommonHelpers.fs index 31a0e7a7f6d..3b9597288f3 100644 --- a/CommonHelpers.fs +++ b/CommonHelpers.fs @@ -205,12 +205,28 @@ module internal Extensions = try Path.GetFullPath path with _ -> path - type FSharpSymbol with + type FSharpSymbolUse with member this.IsPrivateToFile = - match this with - | :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember - | :? FSharpEntity as m -> m.Accessibility.IsPrivate - | :? FSharpGenericParameter -> true - | :? FSharpUnionCase as m -> m.Accessibility.IsPrivate - | :? FSharpField as m -> m.Accessibility.IsPrivate - | _ -> false \ No newline at end of file + let isPrivate = + match this.Symbol with + | :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember + | :? FSharpEntity as m -> m.Accessibility.IsPrivate + | :? FSharpGenericParameter -> true + | :? FSharpUnionCase as m -> m.Accessibility.IsPrivate + | :? FSharpField as m -> m.Accessibility.IsPrivate + | _ -> false + + let declarationLocation = + match this.Symbol.SignatureLocation with + | Some x -> Some x + | _ -> + match this.Symbol.DeclarationLocation with + | Some x -> Some x + | _ -> this.Symbol.ImplementationLocation + + let declaredInTheFile = + match declarationLocation with + | Some declRange -> declRange.FileName = this.RangeAlternate.FileName + | _ -> false + + isPrivate && declaredInTheFile \ No newline at end of file diff --git a/CommonRoslynHelpers.fs b/CommonRoslynHelpers.fs index ed4a279dc0d..b0b7059c68d 100644 --- a/CommonRoslynHelpers.fs +++ b/CommonRoslynHelpers.fs @@ -20,6 +20,12 @@ module internal CommonRoslynHelpers = let endPosition = sourceText.Lines.[range.EndLine - 1].Start + range.EndColumn TextSpan(startPosition, endPosition - startPosition) + let TryFSharpRangeToTextSpan(sourceText: SourceText, range: range) : TextSpan option = + try Some(FSharpRangeToTextSpan(sourceText, range)) + with e -> + //Assert.Exception(e) + None + let GetCompletedTaskResult(task: Task<'TResult>) = if task.Status = TaskStatus.RanToCompletion then task.Result diff --git a/FindReferencesService.fs b/FindReferencesService.fs index ebdf7af1884..f0718cd4112 100644 --- a/FindReferencesService.fs +++ b/FindReferencesService.fs @@ -41,56 +41,68 @@ type internal FSharpFindReferencesService | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" | FSharpCheckFileAnswer.Succeeded(results) -> results - let textLine = sourceText.Lines.GetLineFromPosition(position) - let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based - let textLinePos = sourceText.Lines.GetLinePosition(position) - let fcsTextLineNumber = textLinePos.Line + 1 + let textLine = sourceText.Lines.GetLineFromPosition(position).ToString() + let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1 let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, options.OtherOptions |> Seq.toList) - let rangeToDocumentSpan (range: range) = + // File can be included in more than one project, hence single `range` may results with multiple `Document`s. + let rangeToDocumentSpans (range: range) = async { - match document.Project.Solution.GetDocumentIdsWithFilePath(range.FileName) |> Seq.tryHead with - | Some docId -> - let doc = document.Project.Solution.GetDocument(docId) - let! sourceText = doc.GetTextAsync(context.CancellationToken) |> Async.AwaitTask - let span = CommonHelpers.fixupSpan(sourceText, CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range)) - return Some (DocumentSpan(doc, span)) - | None -> return None + if range.Start = range.End then return [||] + else + let! spans = + document.Project.Solution.GetDocumentIdsWithFilePath(range.FileName) + |> Seq.map (fun documentId -> + async { + let doc = document.Project.Solution.GetDocument(documentId) + let! sourceText = doc.GetTextAsync(context.CancellationToken) |> Async.AwaitTask + match CommonRoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with + | Some span -> + let span = CommonHelpers.fixupSpan(sourceText, span) + return Some (DocumentSpan(doc, span)) + | None -> return None + }) + |> Async.Parallel + return spans |> Array.choose id } match CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, context.CancellationToken) with | Some (islandColumn, qualifiers, _) -> - let! symbolUse = checkFileResults.GetSymbolUseAtLocation(textLineNumber, islandColumn, textLine.ToString(), qualifiers) + let! symbolUse = checkFileResults.GetSymbolUseAtLocation(lineNumber, islandColumn, textLine, qualifiers) match symbolUse with | Some symbolUse -> - let! declaration = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) + let! declaration = checkFileResults.GetDeclarationLocationAlternate (lineNumber, islandColumn, textLine, qualifiers, false) let declarationRange = match declaration with | FSharpFindDeclResult.DeclFound range -> Some range | _ -> None - let! declarationSpan = + let! declarationSpans = match declarationRange with - | Some range -> rangeToDocumentSpan range - | None -> async.Return None + | Some range -> rangeToDocumentSpans range + | None -> async.Return [||] - let definitionItem = - match declarationSpan with - | Some span -> - DefinitionItem.Create( - ImmutableArray.Empty, - [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), - span) - | None -> - DefinitionItem.CreateNonNavigableItem( - ImmutableArray.Empty, - [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray()) - - do! context.OnDefinitionFoundAsync(definitionItem) |> Async.AwaitTask + let definitionItems = + match declarationSpans with + | [||] -> + [| DefinitionItem.CreateNonNavigableItem( + ImmutableArray.Empty, + [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), + [TaggedText(TextTags.Assembly, symbolUse.Symbol.Assembly.SimpleName)].ToImmutableArray()) |] + | _ -> + declarationSpans + |> Array.map (fun span -> + DefinitionItem.Create( + ImmutableArray.Empty, + [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), + span)) + + for definitionItem in definitionItems do + do! context.OnDefinitionFoundAsync(definitionItem) |> Async.AwaitTask let! symbolUses = async { - if symbolUse.Symbol.IsPrivateToFile then + if symbolUse.IsPrivateToFile then return! checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) else let! projectCheckResults = checker.ParseAndCheckProject(options) @@ -101,12 +113,14 @@ type internal FSharpFindReferencesService match declarationRange with | Some declRange when declRange = symbolUse.RangeAlternate -> () | _ -> - let! referenceDocSpan = rangeToDocumentSpan symbolUse.RangeAlternate - match referenceDocSpan with - | Some span -> - let referenceItem = SourceReferenceItem(definitionItem, span) - do! context.OnReferenceFoundAsync(referenceItem) |> Async.AwaitTask - | None -> () + let! referenceDocSpans = rangeToDocumentSpans symbolUse.RangeAlternate + match referenceDocSpans with + | [||] -> () + | _ -> + for referenceDocSpan in referenceDocSpans do + for definitionItem in definitionItems do + let referenceItem = SourceReferenceItem(definitionItem, referenceDocSpan) + do! context.OnReferenceFoundAsync(referenceItem) |> Async.AwaitTask | None -> () | None -> () | None -> () From 0a150d2417561f086d24aa7378ac84f1267b200a Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Mon, 12 Dec 2016 22:14:48 +0300 Subject: [PATCH 05/10] solution wide FindReferencesService --- CommonHelpers.fs | 51 +++++++++++++++++++++ CommonRoslynHelpers.fs | 9 ++++ FindReferencesService.fs | 98 ++++++++++++++++++++++++---------------- 3 files changed, 120 insertions(+), 38 deletions(-) diff --git a/CommonHelpers.fs b/CommonHelpers.fs index 3b9597288f3..aaf7a20058c 100644 --- a/CommonHelpers.fs +++ b/CommonHelpers.fs @@ -196,8 +196,14 @@ module CommonHelpers = | -1 | 0 -> span | index -> TextSpan(span.Start + index + 1, text.Length - index - 1) +[] +type internal SymbolDeclarationLocation = + | CurrentDocument + | Projects of Project list * isLocalForProject: bool + [] module internal Extensions = + open System open System.IO type Path with @@ -205,7 +211,52 @@ module internal Extensions = try Path.GetFullPath path with _ -> path + type FSharpSymbol with + member this.IsInternalToProject = + match this with + | :? FSharpParameter -> true + | :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember || not m.Accessibility.IsPublic + | :? FSharpEntity as m -> not m.Accessibility.IsPublic + | :? FSharpGenericParameter -> true + | :? FSharpUnionCase as m -> not m.Accessibility.IsPublic + | :? FSharpField as m -> not m.Accessibility.IsPublic + | _ -> false + type FSharpSymbolUse with + member this.GetDeclarationLocation (currentDocument: Document) : SymbolDeclarationLocation option = + if this.IsPrivateToFile then + Some SymbolDeclarationLocation.CurrentDocument + else + let isSymbolLocalForProject = this.Symbol.IsInternalToProject + + let declarationLocation = + match this.Symbol.ImplementationLocation with + | Some x -> Some x + | None -> this.Symbol.DeclarationLocation + + match declarationLocation with + | Some loc -> + let filePath = Path.GetFullPathSafe loc.FileName + let isScript = String.Equals(Path.GetExtension(filePath), ".fsx", StringComparison.OrdinalIgnoreCase) + if isScript && filePath = currentDocument.FilePath then + Some SymbolDeclarationLocation.CurrentDocument + elif isScript then + // The standalone script might include other files via '#load' + // These files appear in project options and the standalone file + // should be treated as an individual project + Some (SymbolDeclarationLocation.Projects ([currentDocument.Project], isSymbolLocalForProject)) + else + let projects = + currentDocument.Project.Solution.GetDocumentIdsWithFilePath(currentDocument.FilePath) + |> Seq.map (fun x -> x.ProjectId) + |> Seq.distinct + |> Seq.map currentDocument.Project.Solution.GetProject + |> Seq.toList + match projects with + | [] -> None + | projects -> Some (SymbolDeclarationLocation.Projects (projects, isSymbolLocalForProject)) + | None -> None + member this.IsPrivateToFile = let isPrivate = match this.Symbol with diff --git a/CommonRoslynHelpers.fs b/CommonRoslynHelpers.fs index b0b7059c68d..b7eeb6b1c34 100644 --- a/CommonRoslynHelpers.fs +++ b/CommonRoslynHelpers.fs @@ -59,3 +59,12 @@ module internal CommonRoslynHelpers = let severity = if error.Severity = FSharpErrorSeverity.Error then DiagnosticSeverity.Error else DiagnosticSeverity.Warning let descriptor = new DiagnosticDescriptor(id, emptyString, description, error.Subcategory, severity, true, emptyString, String.Empty, null) Diagnostic.Create(descriptor, location) + +[] +module internal RoslynExtensions = + type Project with + /// The list of all other projects within the same solution that reference this project. + member this.GetDependentProjects() = + [ for project in this.Solution.Projects do + if project.ProjectReferences |> Seq.exists (fun ref -> ref.ProjectId = this.Id) then + yield project ] \ No newline at end of file diff --git a/FindReferencesService.fs b/FindReferencesService.fs index f0718cd4112..013c8d3c1be 100644 --- a/FindReferencesService.fs +++ b/FindReferencesService.fs @@ -3,6 +3,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System +open System.Threading open System.Collections.Immutable open System.Composition @@ -26,6 +27,27 @@ type internal FSharpFindReferencesService checkerProvider: FSharpCheckerProvider, projectInfoManager: ProjectInfoManager ) = + + // File can be included in more than one project, hence single `range` may results with multiple `Document`s. + let rangeToDocumentSpans (solution: Solution, range: range, cancellationToken: CancellationToken) = + async { + if range.Start = range.End then return [] + else + let! spans = + solution.GetDocumentIdsWithFilePath(range.FileName) + |> Seq.map (fun documentId -> + async { + let doc = solution.GetDocument(documentId) + let! sourceText = doc.GetTextAsync(cancellationToken) |> Async.AwaitTask + match CommonRoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with + | Some span -> + let span = CommonHelpers.fixupSpan(sourceText, span) + return Some (DocumentSpan(doc, span)) + | None -> return None + }) + |> Async.Parallel + return spans |> Array.choose id |> Array.toList + } let findReferencedSymbolsAsync(document: Document, position: int, context: FindReferencesContext) : Async = async { @@ -45,27 +67,6 @@ type internal FSharpFindReferencesService let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1 let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, options.OtherOptions |> Seq.toList) - // File can be included in more than one project, hence single `range` may results with multiple `Document`s. - let rangeToDocumentSpans (range: range) = - async { - if range.Start = range.End then return [||] - else - let! spans = - document.Project.Solution.GetDocumentIdsWithFilePath(range.FileName) - |> Seq.map (fun documentId -> - async { - let doc = document.Project.Solution.GetDocument(documentId) - let! sourceText = doc.GetTextAsync(context.CancellationToken) |> Async.AwaitTask - match CommonRoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with - | Some span -> - let span = CommonHelpers.fixupSpan(sourceText, span) - return Some (DocumentSpan(doc, span)) - | None -> return None - }) - |> Async.Parallel - return spans |> Array.choose id - } - match CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, context.CancellationToken) with | Some (islandColumn, qualifiers, _) -> let! symbolUse = checkFileResults.GetSymbolUseAtLocation(lineNumber, islandColumn, textLine, qualifiers) @@ -79,19 +80,19 @@ type internal FSharpFindReferencesService let! declarationSpans = match declarationRange with - | Some range -> rangeToDocumentSpans range - | None -> async.Return [||] + | Some range -> rangeToDocumentSpans(document.Project.Solution, range, context.CancellationToken) + | None -> async.Return [] let definitionItems = match declarationSpans with - | [||] -> - [| DefinitionItem.CreateNonNavigableItem( - ImmutableArray.Empty, - [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), - [TaggedText(TextTags.Assembly, symbolUse.Symbol.Assembly.SimpleName)].ToImmutableArray()) |] + | [] -> + [ DefinitionItem.CreateNonNavigableItem( + ImmutableArray.Empty, + [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), + [TaggedText(TextTags.Assembly, symbolUse.Symbol.Assembly.SimpleName)].ToImmutableArray()) ] | _ -> declarationSpans - |> Array.map (fun span -> + |> List.map (fun span -> DefinitionItem.Create( ImmutableArray.Empty, [TaggedText(TextTags.Text, symbolUse.Symbol.FullName)].ToImmutableArray(), @@ -101,21 +102,42 @@ type internal FSharpFindReferencesService do! context.OnDefinitionFoundAsync(definitionItem) |> Async.AwaitTask let! symbolUses = - async { - if symbolUse.IsPrivateToFile then - return! checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) - else - let! projectCheckResults = checker.ParseAndCheckProject(options) - return! projectCheckResults.GetUsesOfSymbol(symbolUse.Symbol) - } + match symbolUse.GetDeclarationLocation document with + | Some SymbolDeclarationLocation.CurrentDocument -> + checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) + | scope -> + let projectsToCheck = + match scope with + | Some (SymbolDeclarationLocation.Projects (declProjects, false)) -> + declProjects |> List.collect (fun x -> x.GetDependentProjects()) + | Some (SymbolDeclarationLocation.Projects (declProjects, true)) -> declProjects + // The symbol is declared in .NET framework, an external assembly or in a C# project within the solution. + // In order to find all its usages we have to check all F# projects. + | _ -> Seq.toList document.Project.Solution.Projects + + async { + let! symbolUses = + projectsToCheck + |> Seq.map (fun project -> + async { + match projectInfoManager.TryGetOptionsForProject(project.Id) with + | Some options -> + let! projectCheckResults = checker.ParseAndCheckProject(options) + return! projectCheckResults.GetUsesOfSymbol(symbolUse.Symbol) + | None -> return [||] + }) + |> Async.Parallel + + return symbolUses |> Array.concat + } for symbolUse in symbolUses do match declarationRange with | Some declRange when declRange = symbolUse.RangeAlternate -> () | _ -> - let! referenceDocSpans = rangeToDocumentSpans symbolUse.RangeAlternate + let! referenceDocSpans = rangeToDocumentSpans(document.Project.Solution, symbolUse.RangeAlternate, context.CancellationToken) match referenceDocSpans with - | [||] -> () + | [] -> () | _ -> for referenceDocSpan in referenceDocSpans do for definitionItem in definitionItems do From 0327eb73b93554970ac936aa37d2e82b58b79c17 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Thu, 15 Dec 2016 10:51:44 +0300 Subject: [PATCH 06/10] fix after merge --- CommonHelpers.fs | 24 +++++++++++++++++++ FSharp.Editor.fsproj | 2 +- .../FindReferencesService.fs | 0 .../GoToDefinitionService.fs | 0 4 files changed, 25 insertions(+), 1 deletion(-) rename FindReferencesService.fs => Navigation/FindReferencesService.fs (100%) rename {GoToDefinition => Navigation}/GoToDefinitionService.fs (100%) diff --git a/CommonHelpers.fs b/CommonHelpers.fs index b41979f9eb9..32fcbf76dc1 100644 --- a/CommonHelpers.fs +++ b/CommonHelpers.fs @@ -197,6 +197,30 @@ module CommonHelpers = | -1 | 0 -> span | index -> TextSpan(span.Start + index + 1, text.Length - index - 1) + let glyphMajorToRoslynGlyph = function + | GlyphMajor.Class + | GlyphMajor.Typedef + | GlyphMajor.Type + | GlyphMajor.Exception -> Glyph.ClassPublic + | GlyphMajor.Constant -> Glyph.ConstantPublic + | GlyphMajor.Delegate -> Glyph.DelegatePublic + | GlyphMajor.Union + | GlyphMajor.Enum -> Glyph.EnumPublic + | GlyphMajor.EnumMember + | GlyphMajor.Variable + | GlyphMajor.FieldBlue -> Glyph.FieldPublic + | GlyphMajor.Event -> Glyph.EventPublic + | GlyphMajor.Interface -> Glyph.InterfacePublic + | GlyphMajor.Method + | GlyphMajor.Method2 -> Glyph.MethodPublic + | GlyphMajor.Module -> Glyph.ModulePublic + | GlyphMajor.NameSpace -> Glyph.Namespace + | GlyphMajor.Property -> Glyph.PropertyPublic + | GlyphMajor.Struct + | GlyphMajor.ValueType -> Glyph.StructurePublic + | GlyphMajor.Error -> Glyph.Error + | _ -> Glyph.None + [] type internal SymbolDeclarationLocation = | CurrentDocument diff --git a/FSharp.Editor.fsproj b/FSharp.Editor.fsproj index ad6a049a07b..5ddeeb23851 100644 --- a/FSharp.Editor.fsproj +++ b/FSharp.Editor.fsproj @@ -42,7 +42,7 @@ - + diff --git a/FindReferencesService.fs b/Navigation/FindReferencesService.fs similarity index 100% rename from FindReferencesService.fs rename to Navigation/FindReferencesService.fs diff --git a/GoToDefinition/GoToDefinitionService.fs b/Navigation/GoToDefinitionService.fs similarity index 100% rename from GoToDefinition/GoToDefinitionService.fs rename to Navigation/GoToDefinitionService.fs From 5d14c8f79ed9bd5d5398cc363ca1af4a5deaefee Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Thu, 15 Dec 2016 12:16:04 +0300 Subject: [PATCH 07/10] fix after merge --- Navigation/FindReferencesService.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Navigation/FindReferencesService.fs b/Navigation/FindReferencesService.fs index 013c8d3c1be..30a1baf6ab3 100644 --- a/Navigation/FindReferencesService.fs +++ b/Navigation/FindReferencesService.fs @@ -67,7 +67,7 @@ type internal FSharpFindReferencesService let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1 let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, options.OtherOptions |> Seq.toList) - match CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, context.CancellationToken) with + match CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, true, context.CancellationToken) with | Some (islandColumn, qualifiers, _) -> let! symbolUse = checkFileResults.GetSymbolUseAtLocation(lineNumber, islandColumn, textLine, qualifiers) match symbolUse with From c2869b716d13c3afa5d306b354cbe1823e7e9908 Mon Sep 17 00:00:00 2001 From: Kevin Ransom Date: Thu, 15 Dec 2016 09:46:26 -0800 Subject: [PATCH 08/10] Revert "fixed: CommonHelpers.tryClassifyAtPosition return wrong result at the end position" This reverts commit 61b6a5c54f080077f821b19c176dc40e50af6762. --- CommonHelpers.fs | 19 +++----------- GoToDefinition/GoToDefinitionService.fs | 33 ++++++++++++++++--------- QuickInfo/QuickInfoProvider.fs | 12 +++++++-- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/CommonHelpers.fs b/CommonHelpers.fs index d1cce824524..af01304c8b1 100644 --- a/CommonHelpers.fs +++ b/CommonHelpers.fs @@ -60,7 +60,7 @@ module CommonHelpers = | FSharpTokenColorKind.PreprocessorKeyword -> ClassificationTypeNames.PreprocessorKeyword | FSharpTokenColorKind.Operator -> ClassificationTypeNames.Operator | FSharpTokenColorKind.TypeName -> ClassificationTypeNames.ClassName - | FSharpTokenColorKind.Default + | FSharpTokenColorKind.Default | _ -> ClassificationTypeNames.Text let private scanSourceLine(sourceTokenizer: FSharpSourceTokenizer, textLine: TextLine, lineContents: string, lexState: FSharpTokenizerLexState) : SourceLineData = @@ -71,11 +71,7 @@ module CommonHelpers = let tokenInfoOption, nextLexState = lineTokenizer.ScanToken(lexState.Value) lexState.Value <- nextLexState if tokenInfoOption.IsSome then - let classificationType = - if tokenInfoOption.Value.CharClass = FSharpTokenCharKind.WhiteSpace then - ClassificationTypeNames.WhiteSpace - else - compilerTokenToRoslynToken(tokenInfoOption.Value.ColorClass) + let classificationType = compilerTokenToRoslynToken(tokenInfoOption.Value.ColorClass) for i = tokenInfoOption.Value.LeftColumn to tokenInfoOption.Value.RightColumn do Array.set colorMap i classificationType tokenInfoOption @@ -163,21 +159,14 @@ module CommonHelpers = Assert.Exception(ex) List() - let tryClassifyAtPosition (documentKey, sourceText: SourceText, filePath, defines, position: int, includeRightColumn: bool, cancellationToken) = + let tryClassifyAtPosition (documentKey, sourceText: SourceText, filePath, defines, position: int, cancellationToken) = let textLine = sourceText.Lines.GetLineFromPosition(position) let textLinePos = sourceText.Lines.GetLinePosition(position) let textLineColumn = textLinePos.Character let classifiedSpanOption = getColorizationData(documentKey, sourceText, textLine.Span, Some(filePath), defines, cancellationToken) - |> Seq.tryFind(fun classifiedSpan -> - if includeRightColumn then - classifiedSpan.ClassificationType <> ClassificationTypeNames.WhiteSpace && - (classifiedSpan.TextSpan.Contains(position) || - // TextSpan.Contains returns `false` for `position` equals its right bound, - // so we have to check if it contains `position - 1`. - (position > 0 && classifiedSpan.TextSpan.Contains(position - 1))) - else classifiedSpan.TextSpan.Contains(position)) + |> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(position)) match classifiedSpanOption with | Some(classifiedSpan) -> diff --git a/GoToDefinition/GoToDefinitionService.fs b/GoToDefinition/GoToDefinitionService.fs index 3e2027550bd..9ac6d7ecdaa 100644 --- a/GoToDefinition/GoToDefinitionService.fs +++ b/GoToDefinition/GoToDefinitionService.fs @@ -53,20 +53,29 @@ type internal FSharpGoToDefinitionService let textLine = sourceText.Lines.GetLineFromPosition(position) let textLinePos = sourceText.Lines.GetLinePosition(position) let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based + let textLineColumn = textLinePos.Character + let tryGotoAtPosition position = + async { + match CommonHelpers.tryClassifyAtPosition(documentKey, sourceText, filePath, defines, position, cancellationToken) with + | Some (islandColumn, qualifiers, _) -> + let! _parseResults, checkFileAnswer = checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> return None + | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> - match CommonHelpers.tryClassifyAtPosition(documentKey, sourceText, filePath, defines, position, true, cancellationToken) with - | Some (islandColumn, qualifiers, _) -> - let! _parseResults, checkFileAnswer = checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> return None - | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> + let! declarations = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) - let! declarations = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) - - match declarations with - | FSharpFindDeclResult.DeclFound(range) -> return Some(range) - | _ -> return None - | None -> return None + match declarations with + | FSharpFindDeclResult.DeclFound(range) -> return Some(range) + | _ -> return None + | None -> return None + } + + // Tolerate being on the right of the identifier + let! attempt1 = tryGotoAtPosition position + match attempt1 with + | None when textLineColumn > 0 -> return! tryGotoAtPosition (position - 1) + | res -> return res } // FSROSLYNTODO: Since we are not integrated with the Roslyn project system yet, the below call diff --git a/QuickInfo/QuickInfoProvider.fs b/QuickInfo/QuickInfoProvider.fs index feb219503f1..467ade58cb2 100644 --- a/QuickInfo/QuickInfoProvider.fs +++ b/QuickInfo/QuickInfoProvider.fs @@ -80,9 +80,17 @@ type internal FSharpQuickInfoProvider let textLine = sourceText.Lines.GetLineFromPosition(position) let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based + let textLinePos = sourceText.Lines.GetLinePosition(position) + let textLineColumn = textLinePos.Character //let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), textLineColumn - 1) let defines = CompilerEnvironment.GetCompilationDefinesForEditing(filePath, options.OtherOptions |> Seq.toList) - let quickParseInfo = CommonHelpers.tryClassifyAtPosition(documentId, sourceText, filePath, defines, position, false, cancellationToken) + let tryClassifyAtPosition position = + CommonHelpers.tryClassifyAtPosition(documentId, sourceText, filePath, defines, position, cancellationToken) + + let quickParseInfo = + match tryClassifyAtPosition position with + | None when textLineColumn > 0 -> tryClassifyAtPosition (position - 1) + | res -> res match quickParseInfo with | Some (islandColumn, qualifiers, textSpan) -> @@ -99,7 +107,7 @@ type internal FSharpQuickInfoProvider async { let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) - let classification = CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, false, cancellationToken) + let classification = CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, cancellationToken) match classification with | Some _ -> From 283924dc781955c2182e813b84805b4d85d951f9 Mon Sep 17 00:00:00 2001 From: Vasily Kirichenko Date: Thu, 15 Dec 2016 23:45:44 +0300 Subject: [PATCH 09/10] Block Structure service (#2011) * update Microsoft.CodeAnalysis.xxx packages to 2.0.0-rc * add BlockStructureService * BlockStructureService works --- FSharp.Editor.fsproj | 1 + Structure/BlockStructureService.fs | 119 +++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 Structure/BlockStructureService.fs diff --git a/FSharp.Editor.fsproj b/FSharp.Editor.fsproj index 0f11b39621f..7aba850dfeb 100644 --- a/FSharp.Editor.fsproj +++ b/FSharp.Editor.fsproj @@ -45,6 +45,7 @@ + diff --git a/Structure/BlockStructureService.fs b/Structure/BlockStructureService.fs new file mode 100644 index 00000000000..8a497434802 --- /dev/null +++ b/Structure/BlockStructureService.fs @@ -0,0 +1,119 @@ +// 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. + +namespace rec Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Collections.Concurrent +open System.Collections.Generic +open System.Collections.Immutable +open System.Threading +open System.Threading.Tasks +open System.Linq +open System.Runtime.CompilerServices +open System.Windows +open System.Windows.Controls +open System.Windows.Media + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Completion +open Microsoft.CodeAnalysis.Classification +open Microsoft.CodeAnalysis.Editor +open Microsoft.CodeAnalysis.Editor.Shared.Utilities +open Microsoft.CodeAnalysis.Formatting +open Microsoft.CodeAnalysis.Host +open Microsoft.CodeAnalysis.Host.Mef +open Microsoft.CodeAnalysis.Options +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.Structure + +open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.VisualStudio.Text +open Microsoft.VisualStudio.Text.Classification +open Microsoft.VisualStudio.Text.Tagging +open Microsoft.VisualStudio.Text.Formatting +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop + +open Microsoft.FSharp.Compiler +open Microsoft.FSharp.Compiler.Parser +open Microsoft.FSharp.Compiler.Range +open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.FSharp.Compiler.SourceCodeServices.Structure +open System.Windows.Documents + +[, FSharpCommonConstants.FSharpLanguageName); Shared>] +type internal FSharpBlockStructureServiceFactory [](checkerProvider: FSharpCheckerProvider, projectInfoManager: ProjectInfoManager) = + interface ILanguageServiceFactory with + member __.CreateLanguageService(_languageServices) = + upcast FSharpBlockStructureService(checkerProvider.Checker, projectInfoManager) + +type internal FSharpBlockStructureService(checker: FSharpChecker, projectInfoManager: ProjectInfoManager) = + inherit BlockStructureService() + let scopeToBlockType = function + | Scope.Open -> BlockTypes.Imports + | Scope.Namespace + | Scope.Module -> BlockTypes.Namespace + | Scope.Record + | Scope.Tuple + | Scope.Attribute + | Scope.Interface + | Scope.TypeExtension + | Scope.UnionCase + | Scope.EnumCase + | Scope.SimpleType + | Scope.RecordDefn + | Scope.UnionDefn + | Scope.Type -> BlockTypes.Type + | Scope.Member -> BlockTypes.Member + | Scope.LetOrUse + | Scope.Match + | Scope.IfThenElse + | Scope.ThenInIfThenElse + | Scope.ElseInIfThenElse + | Scope.MatchLambda -> BlockTypes.Conditional + | Scope.CompExpr + | Scope.TryInTryWith + | Scope.WithInTryWith + | Scope.TryFinally + | Scope.TryInTryFinally + | Scope.FinallyInTryFinally + | Scope.ObjExpr + | Scope.ArrayOrList + | Scope.CompExprInternal + | Scope.Quote + | Scope.SpecialFunc + | Scope.MatchClause + | Scope.Lambda + | Scope.LetOrUseBang + | Scope.YieldOrReturn + | Scope.YieldOrReturnBang + | Scope.RecordField + | Scope.TryWith -> BlockTypes.Expression + | Scope.Do -> BlockTypes.Statement + | Scope.While + | Scope.For -> BlockTypes.Loop + | Scope.HashDirective -> BlockTypes.PreprocessorRegion + | Scope.Comment + | Scope.XmlDocComment -> BlockTypes.Comment + | _ -> BlockTypes.Nonstructural + + override __.Language = FSharpCommonConstants.FSharpLanguageName + + override __.GetBlockStructureAsync(document, cancellationToken) : Task = + async { + match projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document) with + | Some options -> + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let! fileParseResults = checker.ParseFileInProject(document.FilePath, sourceText.ToString(), options) + match fileParseResults.ParseTree with + | Some parsedInput -> + let ranges = Structure.getOutliningRanges (sourceText.Lines |> Seq.map (fun x -> x.ToString()) |> Seq.toArray) parsedInput + let blockSpans = + ranges + |> Seq.map (fun range -> + BlockSpan(scopeToBlockType range.Scope, true, CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range.Range))) + return BlockStructure(blockSpans.ToImmutableArray()) + | None -> return BlockStructure(ImmutableArray<_>.Empty) + | None -> return BlockStructure(ImmutableArray<_>.Empty) + } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) \ No newline at end of file From 7593a6dfd6af604b693ea598159a575b124f3af2 Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Thu, 15 Dec 2016 17:08:05 -0800 Subject: [PATCH 10/10] Fixes a build caused by merging the find-references PR. (#2024) --- Navigation/FindReferencesService.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Navigation/FindReferencesService.fs b/Navigation/FindReferencesService.fs index 30a1baf6ab3..013c8d3c1be 100644 --- a/Navigation/FindReferencesService.fs +++ b/Navigation/FindReferencesService.fs @@ -67,7 +67,7 @@ type internal FSharpFindReferencesService let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1 let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, options.OtherOptions |> Seq.toList) - match CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, true, context.CancellationToken) with + match CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, context.CancellationToken) with | Some (islandColumn, qualifiers, _) -> let! symbolUse = checkFileResults.GetSymbolUseAtLocation(lineNumber, islandColumn, textLine, qualifiers) match symbolUse with