From ea5b109670b95e8b76e2a51e59dc82d8e23de0ff Mon Sep 17 00:00:00 2001 From: ijklam <43789618+Tangent-90@users.noreply.github.com> Date: Wed, 29 May 2024 00:19:07 +0800 Subject: [PATCH 1/5] extract fix #14375 --- .../Service/ServiceAssemblyContent.fs | 17 ++-- .../Completion/CompletionProvider.fs | 82 +++++++++++-------- 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/src/Compiler/Service/ServiceAssemblyContent.fs b/src/Compiler/Service/ServiceAssemblyContent.fs index 2b54ed7f229..c3f99e620c4 100644 --- a/src/Compiler/Service/ServiceAssemblyContent.fs +++ b/src/Compiler/Service/ServiceAssemblyContent.fs @@ -108,14 +108,14 @@ type IAssemblyContentCache = module AssemblyContent = - let UnresolvedSymbol (topRequireQualifiedAccessParent: ShortIdents option) (cleanedIdents: ShortIdents) (fullName: string) = + let UnresolvedSymbol (nearestRequireQualifiedAccessParent: ShortIdents option) (cleanedIdents: ShortIdents) (fullName: string) ns = let getNamespace (idents: ShortIdents) = if idents.Length > 1 then Some idents[..idents.Length - 2] else None let ns = - topRequireQualifiedAccessParent + nearestRequireQualifiedAccessParent |> Option.bind getNamespace - |> Option.orElseWith (fun () -> getNamespace cleanedIdents) + |> Option.orElse ns |> Option.defaultValue [||] let displayName = @@ -130,10 +130,12 @@ module AssemblyContent = parent.FormatEntityFullName entity |> Option.map (fun (fullName, cleanIdents) -> let topRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix + let nearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix + { FullName = fullName CleanedIdents = cleanIdents Namespace = ns - NearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix + NearestRequireQualifiedAccessParent = nearestRequireQualifiedAccessParent TopRequireQualifiedAccessParent = topRequireQualifiedAccessParent AutoOpenParent = parent.AutoOpen |> Option.map parent.FixParentModuleSuffix Symbol = entity @@ -149,11 +151,12 @@ module AssemblyContent = match entity with | FSharpSymbolPatterns.Attribute -> EntityKind.Attribute | _ -> EntityKind.Type - UnresolvedSymbol = UnresolvedSymbol topRequireQualifiedAccessParent cleanIdents fullName + UnresolvedSymbol = UnresolvedSymbol nearestRequireQualifiedAccessParent cleanIdents fullName ns }) let traverseMemberFunctionAndValues ns (parent: Parent) (membersFunctionsAndValues: seq) = let topRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix + let nearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess true |> Option.map parent.FixParentModuleSuffix let autoOpenParent = parent.AutoOpen |> Option.map parent.FixParentModuleSuffix membersFunctionsAndValues |> Seq.filter (fun x -> not x.IsInstanceMember && not x.IsPropertyGetterMethod && not x.IsPropertySetterMethod) @@ -163,12 +166,12 @@ module AssemblyContent = { FullName = fullName CleanedIdents = cleanedIdents Namespace = ns - NearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess true |> Option.map parent.FixParentModuleSuffix + NearestRequireQualifiedAccessParent = nearestRequireQualifiedAccessParent TopRequireQualifiedAccessParent = topRequireQualifiedAccessParent AutoOpenParent = autoOpenParent Symbol = func Kind = fun _ -> EntityKind.FunctionOrValue func.IsActivePattern - UnresolvedSymbol = UnresolvedSymbol topRequireQualifiedAccessParent cleanedIdents fullName } + UnresolvedSymbol = UnresolvedSymbol nearestRequireQualifiedAccessParent cleanedIdents fullName ns } [ yield! func.TryGetFullDisplayName() |> Option.map (fun fullDisplayName -> processIdents func.FullName (fullDisplayName.Split '.')) diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index a18987ac48c..03c02ee7563 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -235,14 +235,10 @@ type internal FSharpCompletionProvider let glyph = Tokenizer.FSharpGlyphToRoslynGlyph(declarationItem.Glyph, declarationItem.Accessibility) - let namespaceName, filterText = - match declarationItem.NamespaceToOpen, declarationItem.NameInList.Split '.' with - // There is no namespace to open and the item name does not contain dots, so we don't need to pass special FilterText to Roslyn. - | None, [| _ |] -> null, null - | Some namespaceToOpen, idents -> namespaceToOpen, Array.last idents - // Either we have a namespace to open ("DateTime (open System)") or item name contains dots ("Array.map"), or both. - // We are passing last part of long ident as FilterText. - | None, idents -> null, Array.last idents + let namespaceName = + match declarationItem.NamespaceToOpen with + | None -> null + | Some namespaceToOpen -> namespaceToOpen let completionItem = FSharpCommonCompletionItem @@ -251,7 +247,7 @@ type internal FSharpCompletionProvider null, rules = noCommitOnSpaceRules, glyph = Nullable glyph, - filterText = filterText, + filterText = declarationItem.NameInList, inlineDescription = namespaceName ) .AddProperty(FullNamePropName, declarationItem.FullName) @@ -434,40 +430,58 @@ type internal FSharpCompletionProvider | true, ns -> let! sourceText = document.GetTextAsync(cancellationToken) - let textWithItemCommitted = - sourceText.WithChanges(TextChange(item.Span, nameInCode)) + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") + let completionInsertRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, item.Span, sourceText) + + let isNamespaceOrModuleInserted = + checkFileResults.OpenDeclarations + |> Array.exists (fun i -> + Range.rangeContainsPos i.AppliedScope completionInsertRange.Start + && i.Modules |> List.distinct |> List.exists (fun i -> + (i.IsNamespace || i.IsFSharpModule) && + match i.Namespace with + | Some x -> $"{x}.{i.DisplayName}" = ns + | _ -> i.DisplayName = ns + ) + ) + + if isNamespaceOrModuleInserted then + return CompletionChange.Create(TextChange(item.Span, nameInCode)) + else + let textWithItemCommitted = + sourceText.WithChanges(TextChange(item.Span, nameInCode)) - let line = sourceText.Lines.GetLineFromPosition(item.Span.Start) + let line = sourceText.Lines.GetLineFromPosition(item.Span.Start) - let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpCompletionProvider)) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpCompletionProvider)) - let fullNameIdents = - fullName - |> ValueOption.map (fun x -> x.Split '.') - |> ValueOption.defaultValue [||] + let fullNameIdents = + fullName + |> ValueOption.map (fun x -> x.Split '.') + |> ValueOption.defaultValue [||] - let insertionPoint = - if settings.CodeFixes.AlwaysPlaceOpensAtTopLevel then - OpenStatementInsertionPoint.TopLevel - else - OpenStatementInsertionPoint.Nearest + let insertionPoint = + if settings.CodeFixes.AlwaysPlaceOpensAtTopLevel then + OpenStatementInsertionPoint.TopLevel + else + OpenStatementInsertionPoint.Nearest - let ctx = - ParsedInput.FindNearestPointToInsertOpenDeclaration line.LineNumber parseResults.ParseTree fullNameIdents insertionPoint + let ctx = + ParsedInput.FindNearestPointToInsertOpenDeclaration line.LineNumber parseResults.ParseTree fullNameIdents insertionPoint - let finalSourceText, changedSpanStartPos = - OpenDeclarationHelper.insertOpenDeclaration textWithItemCommitted ctx ns + let finalSourceText, changedSpanStartPos = + OpenDeclarationHelper.insertOpenDeclaration textWithItemCommitted ctx ns - let fullChangingSpan = TextSpan.FromBounds(changedSpanStartPos, item.Span.End) + let fullChangingSpan = TextSpan.FromBounds(changedSpanStartPos, item.Span.End) - let changedSpan = - TextSpan.FromBounds(changedSpanStartPos, item.Span.End + (finalSourceText.Length - sourceText.Length)) + let changedSpan = + TextSpan.FromBounds(changedSpanStartPos, item.Span.End + (finalSourceText.Length - sourceText.Length)) - let changedText = finalSourceText.ToString(changedSpan) + let changedText = finalSourceText.ToString(changedSpan) - return - CompletionChange - .Create(TextChange(fullChangingSpan, changedText)) - .WithNewPosition(Nullable(changedSpan.End)) + return + CompletionChange + .Create(TextChange(fullChangingSpan, changedText)) + .WithNewPosition(Nullable(changedSpan.End)) } |> CancellableTask.start cancellationToken From 4805844d5eed2adbdea113dcbd3d08cded4f2922 Mon Sep 17 00:00:00 2001 From: ijklam <43789618+Tangent-90@users.noreply.github.com> Date: Wed, 29 May 2024 00:50:18 +0800 Subject: [PATCH 2/5] release note --- docs/release-notes/.FSharp.Compiler.Service/8.0.400.md | 1 + docs/release-notes/.VisualStudio/17.11.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md index 29dac7317ee..c1c833355e1 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md @@ -26,3 +26,4 @@ * Improve error of Active Pattern case Argument Count Not Match ([PR #16846](https://github.com/dotnet/fsharp/pull/16846)) * AsyncLocal diagnostics context. ([PR #16779](https://github.com/dotnet/fsharp/pull/16779)) * Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822)) +* Showing and inserting correct name of entities from unopened namespace/module ([Issue #14375](https://github.com/dotnet/fsharp/issues/14375), [PR #17261](https://github.com/dotnet/fsharp/pull/17261)) diff --git a/docs/release-notes/.VisualStudio/17.11.md b/docs/release-notes/.VisualStudio/17.11.md index d02f192462e..0ec76ebc55b 100644 --- a/docs/release-notes/.VisualStudio/17.11.md +++ b/docs/release-notes/.VisualStudio/17.11.md @@ -5,3 +5,4 @@ ### Changed * Use AsyncLocal diagnostics context. ([PR #16779])(https://github.com/dotnet/fsharp/pull/16779)) +* Do not insert duplicated opens for completions from "Unopened namespaces". Can filter completions from "Unopened namespaces" by class/module name. ([PR #17261](https://github.com/dotnet/fsharp/pull/17261)) From 951d08184485c1e527d2cd74e53b503310385ea0 Mon Sep 17 00:00:00 2001 From: ijklam <43789618+Tangent-90@users.noreply.github.com> Date: Wed, 29 May 2024 00:54:41 +0800 Subject: [PATCH 3/5] format --- .../Completion/CompletionProvider.fs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index 03c02ee7563..bfe12f1c7c4 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -431,19 +431,21 @@ type internal FSharpCompletionProvider let! sourceText = document.GetTextAsync(cancellationToken) let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") - let completionInsertRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, item.Span, sourceText) - + + let completionInsertRange = + RoslynHelpers.TextSpanToFSharpRange(document.FilePath, item.Span, sourceText) + let isNamespaceOrModuleInserted = checkFileResults.OpenDeclarations - |> Array.exists (fun i -> + |> Array.exists (fun i -> Range.rangeContainsPos i.AppliedScope completionInsertRange.Start - && i.Modules |> List.distinct |> List.exists (fun i -> - (i.IsNamespace || i.IsFSharpModule) && - match i.Namespace with - | Some x -> $"{x}.{i.DisplayName}" = ns - | _ -> i.DisplayName = ns - ) - ) + && i.Modules + |> List.distinct + |> List.exists (fun i -> + (i.IsNamespace || i.IsFSharpModule) + && match i.Namespace with + | Some x -> $"{x}.{i.DisplayName}" = ns + | _ -> i.DisplayName = ns)) if isNamespaceOrModuleInserted then return CompletionChange.Create(TextChange(item.Span, nameInCode)) @@ -467,7 +469,11 @@ type internal FSharpCompletionProvider OpenStatementInsertionPoint.Nearest let ctx = - ParsedInput.FindNearestPointToInsertOpenDeclaration line.LineNumber parseResults.ParseTree fullNameIdents insertionPoint + ParsedInput.FindNearestPointToInsertOpenDeclaration + line.LineNumber + parseResults.ParseTree + fullNameIdents + insertionPoint let finalSourceText, changedSpanStartPos = OpenDeclarationHelper.insertOpenDeclaration textWithItemCommitted ctx ns From de04dc4945cc3e89246e5151ae73a6bb502e66b0 Mon Sep 17 00:00:00 2001 From: ijklam <43789618+Tangent-90@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:18:57 +0800 Subject: [PATCH 4/5] wrap spacial identifiers of unresolved symbols in backticks --- .../Service/ServiceAssemblyContent.fs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Service/ServiceAssemblyContent.fs b/src/Compiler/Service/ServiceAssemblyContent.fs index c3f99e620c4..d16530b785b 100644 --- a/src/Compiler/Service/ServiceAssemblyContent.fs +++ b/src/Compiler/Service/ServiceAssemblyContent.fs @@ -13,6 +13,19 @@ open Internal.Utilities.Library open FSharp.Compiler.Diagnostics open FSharp.Compiler.IO open FSharp.Compiler.Symbols +open FSharp.Compiler.Syntax + +module Utils = + let replaceLastIdentToDisplayName idents (displayName: string) = + match idents |> Array.tryFindIndexBack (fun i -> displayName.StartsWith(i, System.StringComparison.Ordinal)) with + | Some x when x = idents.Length - 1 -> idents |> Array.replace (idents.Length - 1) displayName + | Some x -> + let newIdents = Array.zeroCreate (x + 1) + Array.Copy(idents, newIdents, x) + newIdents[x] <- displayName + newIdents + | _ -> idents + type IsAutoOpen = bool @@ -80,10 +93,9 @@ type Parent = else ident) let removeModuleSuffix (idents: ShortIdents) = - if entity.IsFSharpModule && idents.Length > 0 then + if (entity.IsFSharpModule || PrettyNaming.DoesIdentifierNeedBackticks entity.DisplayName) && idents.Length > 0 then let lastIdent = idents[idents.Length - 1] - if lastIdent <> entity.DisplayName then - idents |> Array.replace (idents.Length - 1) entity.DisplayName + if lastIdent <> entity.DisplayName then Utils.replaceLastIdentToDisplayName idents entity.DisplayName else idents else idents @@ -117,10 +129,11 @@ module AssemblyContent = |> Option.bind getNamespace |> Option.orElse ns |> Option.defaultValue [||] - + |> Array.map PrettyNaming.NormalizeIdentifierBackticks + let displayName = let nameIdents = if cleanedIdents.Length > ns.Length then cleanedIdents |> Array.skip ns.Length else cleanedIdents - nameIdents |> String.concat "." + nameIdents |> Array.map PrettyNaming.NormalizeIdentifierBackticks |> String.concat "." { FullName = fullName DisplayName = displayName @@ -174,7 +187,15 @@ module AssemblyContent = UnresolvedSymbol = UnresolvedSymbol nearestRequireQualifiedAccessParent cleanedIdents fullName ns } [ yield! func.TryGetFullDisplayName() - |> Option.map (fun fullDisplayName -> processIdents func.FullName (fullDisplayName.Split '.')) + |> Option.map (fun fullDisplayName -> + let idents = (fullDisplayName.Split '.') + let lastIdent = idents[idents.Length - 1] + let idents = + match Option.attempt (fun _ -> func.DisplayName) with + | Some shortDisplayName when PrettyNaming.DoesIdentifierNeedBackticks shortDisplayName && lastIdent <> shortDisplayName -> + Utils.replaceLastIdentToDisplayName idents shortDisplayName + | _ -> idents + processIdents func.FullName idents) |> Option.toList (* for [] From 749b57141337dee0bcf1e870db09b0ca78e8440e Mon Sep 17 00:00:00 2001 From: ijklam <43789618+Tangent-90@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:32:57 +0800 Subject: [PATCH 5/5] fix; add test --- .../Service/ServiceAssemblyContent.fs | 20 ++--- .../AssemblyContentProviderTests.fs | 85 +++++++++++++++++++ 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/src/Compiler/Service/ServiceAssemblyContent.fs b/src/Compiler/Service/ServiceAssemblyContent.fs index d16530b785b..f1b3bf2ee16 100644 --- a/src/Compiler/Service/ServiceAssemblyContent.fs +++ b/src/Compiler/Service/ServiceAssemblyContent.fs @@ -120,15 +120,17 @@ type IAssemblyContentCache = module AssemblyContent = - let UnresolvedSymbol (nearestRequireQualifiedAccessParent: ShortIdents option) (cleanedIdents: ShortIdents) (fullName: string) ns = + let UnresolvedSymbol (topRequireQualifiedAccessParent: ShortIdents option) (cleanedIdents: ShortIdents) (fullName: string) ns = let getNamespace (idents: ShortIdents) = if idents.Length > 1 then Some idents[..idents.Length - 2] else None + // 1. get namespace/module to open from topRequireQualifiedAccessParent + // 2. if the topRequireQualifiedAccessParent is None, use the namespace, as we don't know whether an ident is namespace/module or not let ns = - nearestRequireQualifiedAccessParent - |> Option.bind getNamespace + topRequireQualifiedAccessParent + |> Option.bind getNamespace |> Option.orElse ns - |> Option.defaultValue [||] + |> Option.defaultWith (fun _ -> Array.empty) |> Array.map PrettyNaming.NormalizeIdentifierBackticks let displayName = @@ -143,12 +145,11 @@ module AssemblyContent = parent.FormatEntityFullName entity |> Option.map (fun (fullName, cleanIdents) -> let topRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix - let nearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix { FullName = fullName CleanedIdents = cleanIdents Namespace = ns - NearestRequireQualifiedAccessParent = nearestRequireQualifiedAccessParent + NearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix TopRequireQualifiedAccessParent = topRequireQualifiedAccessParent AutoOpenParent = parent.AutoOpen |> Option.map parent.FixParentModuleSuffix Symbol = entity @@ -164,12 +165,11 @@ module AssemblyContent = match entity with | FSharpSymbolPatterns.Attribute -> EntityKind.Attribute | _ -> EntityKind.Type - UnresolvedSymbol = UnresolvedSymbol nearestRequireQualifiedAccessParent cleanIdents fullName ns + UnresolvedSymbol = UnresolvedSymbol topRequireQualifiedAccessParent cleanIdents fullName ns }) let traverseMemberFunctionAndValues ns (parent: Parent) (membersFunctionsAndValues: seq) = let topRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix - let nearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess true |> Option.map parent.FixParentModuleSuffix let autoOpenParent = parent.AutoOpen |> Option.map parent.FixParentModuleSuffix membersFunctionsAndValues |> Seq.filter (fun x -> not x.IsInstanceMember && not x.IsPropertyGetterMethod && not x.IsPropertySetterMethod) @@ -179,12 +179,12 @@ module AssemblyContent = { FullName = fullName CleanedIdents = cleanedIdents Namespace = ns - NearestRequireQualifiedAccessParent = nearestRequireQualifiedAccessParent + NearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess true |> Option.map parent.FixParentModuleSuffix TopRequireQualifiedAccessParent = topRequireQualifiedAccessParent AutoOpenParent = autoOpenParent Symbol = func Kind = fun _ -> EntityKind.FunctionOrValue func.IsActivePattern - UnresolvedSymbol = UnresolvedSymbol nearestRequireQualifiedAccessParent cleanedIdents fullName ns } + UnresolvedSymbol = UnresolvedSymbol topRequireQualifiedAccessParent cleanedIdents fullName ns } [ yield! func.TryGetFullDisplayName() |> Option.map (fun fullDisplayName -> diff --git a/tests/FSharp.Compiler.Service.Tests/AssemblyContentProviderTests.fs b/tests/FSharp.Compiler.Service.Tests/AssemblyContentProviderTests.fs index f99f0b55aef..f20ec76253b 100644 --- a/tests/FSharp.Compiler.Service.Tests/AssemblyContentProviderTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/AssemblyContentProviderTests.fs @@ -142,3 +142,88 @@ let ``TopRequireQualifiedAccessParent property should be valid``() = let actual = source |> getSymbolMap getTopRequireQualifiedAccessParentName assertAreEqual (expectedResult, actual) + + +[] +let ``Check Unresolved Symbols``() = + let source = """ +namespace ``1 2 3`` + +module Test = + module M1 = + let v1 = 1 + + module M11 = + let v11 = 1 + + module M111 = + let v111 = 1 + + [] + module M12 = + let v12 = 1 + + module M121 = + let v121 = 1 + + [] + module M1211 = + let v1211 = 1 + + type A = + static member val B = 0 + static member C() = () + static member (++) s s2 = s + "/" + s2 + + type B = + abstract D: int -> int + + let ``a.b.c`` = "999" + + type E = { x: int; y: int } + type F = + | A = 1 + | B = 2 + type G = + | A of int + | B of string + + let (|Is1|_|) x = x = 1 + let (++) s s2 = s + "/" + s2 + """ + + let expectedResult = + [ + "1 2 3.Test", "open ``1 2 3`` - Test"; + "1 2 3.Test.M1", "open ``1 2 3`` - Test.M1"; + "1 2 3.Test.M1.(++)", "open ``1 2 3`` - Test.M1.``(++)``"; + "1 2 3.Test.M1.A", "open ``1 2 3`` - Test.M1.A"; + "1 2 3.Test.M1.A.(++)", "open ``1 2 3`` - Test.M1.A.``(++)``"; + "1 2 3.Test.M1.A.B", "open ``1 2 3`` - Test.M1.A.B"; + "1 2 3.Test.M1.A.C", "open ``1 2 3`` - Test.M1.A.C"; + "1 2 3.Test.M1.A.op_PlusPlus", "open ``1 2 3`` - Test.M1.A.op_PlusPlus"; + "1 2 3.Test.M1.B", "open ``1 2 3`` - Test.M1.B"; + "1 2 3.Test.M1.E", "open ``1 2 3`` - Test.M1.E"; + "1 2 3.Test.M1.F", "open ``1 2 3`` - Test.M1.F"; + "1 2 3.Test.M1.G", "open ``1 2 3`` - Test.M1.G"; + "1 2 3.Test.M1.M11", "open ``1 2 3`` - Test.M1.M11"; + "1 2 3.Test.M1.M11.M111", "open ``1 2 3`` - Test.M1.M11.M111"; + "1 2 3.Test.M1.M11.M111.v111", "open ``1 2 3`` - Test.M1.M11.M111.v111"; + "1 2 3.Test.M1.M11.v11", "open ``1 2 3`` - Test.M1.M11.v11"; + "1 2 3.Test.M1.M12", "open ``1 2 3`` - Test.M1.M12"; + "1 2 3.Test.M1.M12.M121", "open ``1 2 3``.Test.M1 - M12.M121"; + "1 2 3.Test.M1.M12.M121.M1211", "open ``1 2 3``.Test.M1 - M12.M121.M1211"; + "1 2 3.Test.M1.M12.M121.M1211.v1211", "open ``1 2 3``.Test.M1 - M12.M121.M1211.v1211"; + "1 2 3.Test.M1.M12.M121.v121", "open ``1 2 3``.Test.M1 - M12.M121.v121"; + "1 2 3.Test.M1.M12.v12", "open ``1 2 3``.Test.M1 - M12.v12"; + "1 2 3.Test.M1.``a.b.c``", "open ``1 2 3`` - Test.M1.``a.b.c``"; + "1 2 3.Test.M1.op_PlusPlus", "open ``1 2 3`` - Test.M1.op_PlusPlus"; + "1 2 3.Test.M1.v1", "open ``1 2 3`` - Test.M1.v1"; + ] + |> Map.ofList + + let actual = source |> getSymbolMap (fun i -> + let ns = i.UnresolvedSymbol.Namespace |> String.concat "." + $"open {ns} - {i.UnresolvedSymbol.DisplayName}") + + assertAreEqual (expectedResult, actual)