From 66975f76a14db8efaed9d7d3eecc9a11788f2f5a Mon Sep 17 00:00:00 2001 From: brettv Date: Fri, 16 Jan 2015 14:32:53 -0800 Subject: [PATCH] Speed up NavigateTo performance. Add a new internal type DeclaredSymbolInfo which is populated by the syntax trees for NavigateTo to use instead of realizing the compilation/ISymbols. (changeset 1399184) --- .../CSharpSyntaxFactsService.cs | 275 +++++++++++++++++- .../FindSymbols/DeclaredSymbolInfo.cs | 88 ++++++ .../SyntaxTree/AbstractSyntaxTreeInfo.cs | 124 ++++++++ .../SyntaxTree/SyntaxTreeDeclarationInfo.cs | 79 +++++ .../SyntaxTree/SyntaxTreeIdentifierInfo.cs | 120 ++------ .../SyntaxTreeIdentifierInfo_Set.cs | 2 +- .../FindSymbols/SyntaxTree/SyntaxTreeInfo.cs | 75 +++-- .../SyntaxFactsService/ISyntaxFactsService.cs | 3 + .../Shared/Extensions/DocumentExtensions.cs | 8 +- .../Core/Portable/Workspaces.csproj | 3 + .../VisualBasicSyntaxFactsService.vb | 156 +++++++++- 11 files changed, 794 insertions(+), 139 deletions(-) create mode 100644 src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs create mode 100644 src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractSyntaxTreeInfo.cs create mode 100644 src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeDeclarationInfo.cs diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index b04c3079522..f86780713ad 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -1,14 +1,15 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Composition; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -739,6 +740,276 @@ public bool IsTopLevelNodeWithMembers(SyntaxNode node) node is EnumDeclarationSyntax; } + public bool TryGetDeclaredSymbolInfo(SyntaxNode node, out DeclaredSymbolInfo declaredSymbolInfo) + { + switch (node.Kind()) + { + case SyntaxKind.ClassDeclaration: + var classDecl = (ClassDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(classDecl.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Class, classDecl.Identifier.Span); + return true; + case SyntaxKind.ConstructorDeclaration: + var ctorDecl = (ConstructorDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo( + ctorDecl.Identifier.ValueText, + GetNodeName(node.Parent), + DeclaredSymbolInfoKind.Constructor, + ctorDecl.Identifier.Span, + parameterCount: (ushort)(ctorDecl.ParameterList?.Parameters.Count ?? 0)); + return true; + case SyntaxKind.DelegateDeclaration: + var delegateDecl = (DelegateDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(delegateDecl.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Delegate, delegateDecl.Identifier.Span); + return true; + case SyntaxKind.EnumDeclaration: + var enumDecl = (EnumDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(enumDecl.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Enum, enumDecl.Identifier.Span); + return true; + case SyntaxKind.EnumMemberDeclaration: + var enumMember = (EnumMemberDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(enumMember.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.EnumMember, enumMember.Identifier.Span); + return true; + case SyntaxKind.EventDeclaration: + var eventDecl = (EventDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(ExpandExplicitInterfaceName(eventDecl.Identifier.ValueText, eventDecl.ExplicitInterfaceSpecifier), GetNodeName(node.Parent), DeclaredSymbolInfoKind.Event, eventDecl.Identifier.Span); + return true; + case SyntaxKind.IndexerDeclaration: + var indexerDecl = (IndexerDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(WellKnownMemberNames.Indexer, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Indexer, indexerDecl.ThisKeyword.Span); + return true; + case SyntaxKind.InterfaceDeclaration: + var interfaceDecl = (InterfaceDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(interfaceDecl.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Interface, interfaceDecl.Identifier.Span); + return true; + case SyntaxKind.MethodDeclaration: + var method = (MethodDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo( + ExpandExplicitInterfaceName(method.Identifier.ValueText, method.ExplicitInterfaceSpecifier), + GetNodeName(node.Parent), + DeclaredSymbolInfoKind.Method, + method.Identifier.Span, + parameterCount: (ushort)(method.ParameterList?.Parameters.Count ?? 0), + typeParameterCount: (ushort)(method.TypeParameterList?.Parameters.Count ?? 0)); + return true; + case SyntaxKind.PropertyDeclaration: + var property = (PropertyDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(ExpandExplicitInterfaceName(property.Identifier.ValueText, property.ExplicitInterfaceSpecifier), GetNodeName(node.Parent), DeclaredSymbolInfoKind.Property, property.Identifier.Span); + return true; + case SyntaxKind.StructDeclaration: + var structDecl = (StructDeclarationSyntax)node; + declaredSymbolInfo = new DeclaredSymbolInfo(structDecl.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Struct, structDecl.Identifier.Span); + return true; + case SyntaxKind.VariableDeclarator: + // could either be part of a field declaration or an event field declaration + var variableDeclarator = (VariableDeclaratorSyntax)node; + var variableDeclaration = variableDeclarator.Parent as VariableDeclarationSyntax; + var fieldDeclaration = variableDeclaration?.Parent as BaseFieldDeclarationSyntax; + if (fieldDeclaration != null) + { + var kind = fieldDeclaration is EventFieldDeclarationSyntax + ? DeclaredSymbolInfoKind.Event + : fieldDeclaration.Modifiers.Any(m => m.Kind() == SyntaxKind.ConstKeyword) + ? DeclaredSymbolInfoKind.Constant + : DeclaredSymbolInfoKind.Field; + + declaredSymbolInfo = new DeclaredSymbolInfo(variableDeclarator.Identifier.ValueText, GetNodeName(fieldDeclaration.Parent), kind, variableDeclarator.Identifier.Span); + return true; + } + + break; + } + + declaredSymbolInfo = default(DeclaredSymbolInfo); + return false; + } + + private static string ExpandExplicitInterfaceName(string identifier, ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier) + { + if (explicitInterfaceSpecifier == null) + { + return identifier; + } + else + { + var builder = new StringBuilder(); + ExpandTypeName(explicitInterfaceSpecifier.Name, builder); + builder.Append('.'); + builder.Append(identifier); + return builder.ToString(); + } + } + + private static void ExpandTypeName(TypeSyntax type, StringBuilder builder) + { + switch (type.Kind()) + { + case SyntaxKind.AliasQualifiedName: + var alias = (AliasQualifiedNameSyntax)type; + builder.Append(alias.Alias.Identifier.ValueText); + break; + case SyntaxKind.ArrayType: + var array = (ArrayTypeSyntax)type; + ExpandTypeName(array.ElementType, builder); + for (int i = 0; i < array.RankSpecifiers.Count; i++) + { + var rankSpecifier = array.RankSpecifiers[i]; + builder.Append(rankSpecifier.OpenBracketToken.Text); + for (int j = 1; j < rankSpecifier.Sizes.Count; j++) + { + builder.Append(','); + } + + builder.Append(rankSpecifier.CloseBracketToken.Text); + } + + break; + case SyntaxKind.GenericName: + var generic = (GenericNameSyntax)type; + builder.Append(generic.Identifier.ValueText); + if (generic.TypeArgumentList != null) + { + var arguments = generic.TypeArgumentList.Arguments; + builder.Append(generic.TypeArgumentList.LessThanToken.Text); + for (int i = 0; i < arguments.Count; i++) + { + if (i != 0) + { + builder.Append(','); + } + + ExpandTypeName(arguments[i], builder); + } + + builder.Append(generic.TypeArgumentList.GreaterThanToken.Text); + } + + break; + case SyntaxKind.IdentifierName: + var identifierName = (IdentifierNameSyntax)type; + builder.Append(identifierName.Identifier.ValueText); + break; + case SyntaxKind.NullableType: + var nullable = (NullableTypeSyntax)type; + ExpandTypeName(nullable.ElementType, builder); + builder.Append(nullable.QuestionToken.Text); + break; + case SyntaxKind.OmittedTypeArgument: + // do nothing since it was omitted, but don't reach the default block + break; + case SyntaxKind.PointerType: + var pointer = (PointerTypeSyntax)type; + ExpandTypeName(pointer.ElementType, builder); + builder.Append(pointer.AsteriskToken.Text); + break; + case SyntaxKind.PredefinedType: + var predefined = (PredefinedTypeSyntax)type; + builder.Append(predefined.Keyword.Text); + break; + case SyntaxKind.QualifiedName: + var qualified = (QualifiedNameSyntax)type; + ExpandTypeName(qualified.Left, builder); + builder.Append(qualified.DotToken.Text); + ExpandTypeName(qualified.Right, builder); + break; + default: + Debug.Assert(false, "Unexpected type syntax " + type.Kind()); + break; + } + } + + private static string GetNodeName(SyntaxNode node) + { + string name; + TypeParameterListSyntax typeParameterList; + switch (node.Kind()) + { + case SyntaxKind.ClassDeclaration: + var classDecl = (ClassDeclarationSyntax)node; + name = classDecl.Identifier.ValueText; + typeParameterList = classDecl.TypeParameterList; + break; + case SyntaxKind.CompilationUnit: + name = string.Empty; + typeParameterList = null; + break; + case SyntaxKind.DelegateDeclaration: + var delegateDecl = (DelegateDeclarationSyntax)node; + name = delegateDecl.Identifier.ValueText; + typeParameterList = delegateDecl.TypeParameterList; + break; + case SyntaxKind.EnumDeclaration: + name = ((EnumDeclarationSyntax)node).Identifier.ValueText; + typeParameterList = null; + break; + case SyntaxKind.IdentifierName: + name = ((IdentifierNameSyntax)node).Identifier.ValueText; + typeParameterList = null; + break; + case SyntaxKind.InterfaceDeclaration: + var interfaceDecl = (InterfaceDeclarationSyntax)node; + name = interfaceDecl.Identifier.ValueText; + typeParameterList = interfaceDecl.TypeParameterList; + break; + case SyntaxKind.MethodDeclaration: + var methodDecl = (MethodDeclarationSyntax)node; + name = methodDecl.Identifier.ValueText; + typeParameterList = methodDecl.TypeParameterList; + break; + case SyntaxKind.NamespaceDeclaration: + name = GetNodeName(((NamespaceDeclarationSyntax)node).Name); + typeParameterList = null; + break; + case SyntaxKind.QualifiedName: + var qualified = (QualifiedNameSyntax)node; + name = GetNodeName(qualified.Left) + "." + GetNodeName(qualified.Right); + typeParameterList = null; + break; + case SyntaxKind.StructDeclaration: + var structDecl = (StructDeclarationSyntax)node; + name = structDecl.Identifier.ValueText; + typeParameterList = structDecl.TypeParameterList; + break; + default: + Debug.Assert(false, "Unexpected node type " + node.Kind()); + return null; + } + + // check for nested classes + var names = new List() { name + ExpandTypeParameterList(typeParameterList) }; + var parent = node.Parent; + while (parent is TypeDeclarationSyntax) + { + var currentParent = (TypeDeclarationSyntax)parent; + names.Add(currentParent.Identifier.ValueText + ExpandTypeParameterList(currentParent.TypeParameterList)); + parent = currentParent.Parent; + } + + names.Reverse(); + return string.Join(".", names); + } + + private static string ExpandTypeParameterList(TypeParameterListSyntax typeParameterList) + { + if (typeParameterList != null && typeParameterList.Parameters.Count > 0) + { + var builder = new StringBuilder(); + builder.Append('<'); + builder.Append(typeParameterList.Parameters[0].Identifier.ValueText); + for (int i = 1; i < typeParameterList.Parameters.Count; i++) + { + builder.Append(','); + builder.Append(typeParameterList.Parameters[i].Identifier.ValueText); + } + + builder.Append('>'); + return builder.ToString(); + } + else + { + return null; + } + } + public List GetMethodLevelMembers(SyntaxNode root) { var list = new List(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs new file mode 100644 index 00000000000..4c3de5e29ca --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal enum DeclaredSymbolInfoKind : byte + { + Class, + Constant, + Constructor, + Delegate, + Enum, + EnumMember, + Event, + Field, + Indexer, + Interface, + Method, + Module, + Property, + Struct + } + + internal struct DeclaredSymbolInfo + { + public string Name { get; } + public string Container { get; } + public DeclaredSymbolInfoKind Kind { get; } + public TextSpan Span { get; } + public ushort ParameterCount { get; } + public ushort TypeParameterCount { get; } + + public DeclaredSymbolInfo(string name, string container, DeclaredSymbolInfoKind kind, TextSpan span, ushort parameterCount = 0, ushort typeParameterCount = 0) + : this() + { + Name = name; + Container = container; + Kind = kind; + Span = span; + ParameterCount = parameterCount; + TypeParameterCount = typeParameterCount; + } + + public async Task GetSymbolAsync(Document document, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var node = root.FindNode(Span); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var symbol = semanticModel.GetDeclaredSymbol(node, cancellationToken); + return symbol; + } + + internal void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteString(Container); + writer.WriteByte((byte)Kind); + writer.WriteInt32(Span.Start); + writer.WriteInt32(Span.Length); + writer.WriteUInt16(ParameterCount); + writer.WriteUInt16(TypeParameterCount); + } + + internal static DeclaredSymbolInfo ReadFrom(ObjectReader reader) + { + try + { + var name = reader.ReadString(); + var container = reader.ReadString(); + var kind = (DeclaredSymbolInfoKind)reader.ReadByte(); + var spanStart = reader.ReadInt32(); + var spanLength = reader.ReadInt32(); + var parameterCount = reader.ReadUInt16(); + var typeParameterCount = reader.ReadUInt16(); + + return new DeclaredSymbolInfo(name, container, kind, new TextSpan(spanStart, spanLength), parameterCount, typeParameterCount); + } + catch + { + return default(DeclaredSymbolInfo); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractSyntaxTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractSyntaxTreeInfo.cs new file mode 100644 index 00000000000..b688e6d07a7 --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractSyntaxTreeInfo.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal abstract class AbstractSyntaxTreeInfo : AbstractPersistableState, IObjectWritable + { + protected AbstractSyntaxTreeInfo(VersionStamp version) + : base(version) + { + } + + public abstract void WriteTo(ObjectWriter writer); + + public abstract Task SaveAsync(Document document, CancellationToken cancellationToken); + + protected async Task SaveAsync( + Document document, + ConditionalWeakTable> cache, + string persistenceName, + string serializationFormat, + CancellationToken cancellationToken) + { + var workspace = document.Project.Solution.Workspace; + var infoTable = GetInfoTable(document.Project.Solution.BranchId, workspace, cache); + + // if it is forked document + if (await document.IsForkedDocumentWithSyntaxChangesAsync(cancellationToken).ConfigureAwait(false)) + { + infoTable.Remove(document.Id); + infoTable.GetValue(document.Id, _ => this); + return false; + } + + // okay, cache this info if it is from opened document or persistence failed. + var persisted = await SaveAsync(document, persistenceName, serializationFormat, this, cancellationToken).ConfigureAwait(false); + if (!persisted || document.IsOpen()) + { + var primaryInfoTable = GetInfoTable(workspace.PrimaryBranchId, workspace, cache); + primaryInfoTable.Remove(document.Id); + primaryInfoTable.GetValue(document.Id, _ => this); + } + + return persisted; + } + + protected static async Task LoadAsync( + Document document, + Func reader, + ConditionalWeakTable> cache, + string persistenceName, + string serializationFormat, + CancellationToken cancellationToken) + { + var infoTable = cache.GetValue(document.Project.Solution.BranchId, _ => new ConditionalWeakTable()); + var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); + + // first look to see if we already have the info in the cache + AbstractSyntaxTreeInfo info; + if (infoTable.TryGetValue(document.Id, out info) && info.Version == version) + { + return info; + } + + // cache is invalid. remove it + infoTable.Remove(document.Id); + + // check primary cache to see whether we have valid info there + var primaryInfoTable = cache.GetValue(document.Project.Solution.Workspace.PrimaryBranchId, _ => new ConditionalWeakTable()); + if (primaryInfoTable.TryGetValue(document.Id, out info) && info.Version == version) + { + return info; + } + + // check whether we can get it from peristence service + info = await LoadAsync(document, persistenceName, serializationFormat, reader, cancellationToken).ConfigureAwait(false); + if (info != null) + { + // save it in the cache. persisted info is always from primary branch. no reason to save it to the branched document cache. + primaryInfoTable.Remove(document.Id); + primaryInfoTable.GetValue(document.Id, _ => info); + return info; + } + + // well, we don't have this information. + return null; + } + + private static ConditionalWeakTable GetInfoTable( + BranchId branchId, + Workspace workspace, + ConditionalWeakTable> cache) + { + return cache.GetValue(branchId, id => + { + if (id == workspace.PrimaryBranchId) + { + workspace.DocumentClosed += (sender, e) => + { + if (!e.Document.IsFromPrimaryBranch()) + { + return; + } + + ConditionalWeakTable infoTable; + if (cache.TryGetValue(e.Document.Project.Solution.BranchId, out infoTable)) + { + // remove closed document from primary branch from live cache. + infoTable.Remove(e.Document.Id); + } + }; + } + + return new ConditionalWeakTable(); + }); + } + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeDeclarationInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeDeclarationInfo.cs new file mode 100644 index 00000000000..2d045938307 --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeDeclarationInfo.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal class SyntaxTreeDeclarationInfo : AbstractSyntaxTreeInfo + { + private const string PersistenceName = ""; + private const string SerializationFormat = "1"; + + /// + /// in memory cache will hold onto any info related to opened documents in primary branch or all documents in forked branch + /// + /// this is not snapshot based so multiple versions of snapshots can re-use same data as long as it is relevant. + /// + private static readonly ConditionalWeakTable> cache = + new ConditionalWeakTable>(); + + public IEnumerable DeclaredSymbolInfos { get; private set; } + + public SyntaxTreeDeclarationInfo(VersionStamp version, IEnumerable declaredSymbolInfos) + : base(version) + { + DeclaredSymbolInfos = declaredSymbolInfos; + } + + public override void WriteTo(ObjectWriter writer) + { + writer.WriteInt32(DeclaredSymbolInfos.Count()); + foreach (var declaredSymbolInfo in DeclaredSymbolInfos) + { + declaredSymbolInfo.WriteTo(writer); + } + } + + public override Task SaveAsync(Document document, CancellationToken cancellationToken) + { + return SaveAsync(document, cache, PersistenceName, SerializationFormat, cancellationToken); + } + + public static Task PrecalculatedAsync(Document document, CancellationToken cancellationToken) + { + return PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken); + } + + public static async Task LoadAsync(Document document, CancellationToken cancellationToken) + { + var info = await LoadAsync(document, ReadFrom, cache, PersistenceName, SerializationFormat, cancellationToken).ConfigureAwait(false); + return (SyntaxTreeDeclarationInfo)info; + } + + private static SyntaxTreeDeclarationInfo ReadFrom(ObjectReader reader, VersionStamp version) + { + try + { + var declaredSymbolCount = reader.ReadInt32(); + var declaredSymbols = new DeclaredSymbolInfo[declaredSymbolCount]; + for (int i = 0; i < declaredSymbolCount; i++) + { + declaredSymbols[i] = DeclaredSymbolInfo.ReadFrom(reader); + } + + return new SyntaxTreeDeclarationInfo(version, declaredSymbols); + } + catch (Exception) + { + } + + return null; + } + } +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo.cs index fa968c1f503..c3d4307bf55 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo.cs @@ -4,13 +4,12 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { - internal partial class SyntaxTreeIdentifierInfo : AbstractPersistableState, IObjectWritable + internal partial class SyntaxTreeIdentifierInfo : AbstractSyntaxTreeInfo { private const string PersistenceName = ""; private const string SerializationFormat = "1"; @@ -20,8 +19,8 @@ internal partial class SyntaxTreeIdentifierInfo : AbstractPersistableState, IObj /// /// this is not snapshot based so multiple versions of snapshots can re-use same data as long as it is relevant. /// - private static readonly ConditionalWeakTable> cache = - new ConditionalWeakTable>(); + private static readonly ConditionalWeakTable> cache = + new ConditionalWeakTable>(); private readonly VersionStamp version; private readonly BloomFilter identifierFilter; @@ -35,12 +34,12 @@ internal partial class SyntaxTreeIdentifierInfo : AbstractPersistableState, IObj { if (identifierFilter == null) { - throw new ArgumentNullException("identifierFilter"); + throw new ArgumentNullException(nameof(identifierFilter)); } if (escapedIdentifierFilter == null) { - throw new ArgumentNullException("escapedIdentifierFilter"); + throw new ArgumentNullException(nameof(escapedIdentifierFilter)); } this.version = version; @@ -72,28 +71,12 @@ public bool ProbablyContainsEscapedIdentifier(string identifier) return escapedIdentifierFilter.ProbablyContains(identifier); } - public void WriteTo(ObjectWriter writer) + public override void WriteTo(ObjectWriter writer) { this.identifierFilter.WriteTo(writer); this.escapedIdentifierFilter.WriteTo(writer); } - private static SyntaxTreeIdentifierInfo ReadFrom(ObjectReader reader, VersionStamp version) - { - try - { - var identifierFilter = BloomFilter.ReadFrom(reader); - var escapedIdentifierFilter = BloomFilter.ReadFrom(reader); - - return new SyntaxTreeIdentifierInfo(version, identifierFilter, escapedIdentifierFilter); - } - catch (Exception) - { - } - - return null; - } - public static Task PrecalculatedAsync(Document document, CancellationToken cancellationToken) { return PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken); @@ -101,96 +84,29 @@ public static Task PrecalculatedAsync(Document document, CancellationToken public static async Task LoadAsync(Document document, CancellationToken cancellationToken) { - var workspace = document.Project.Solution.Workspace; - var infoTable = GetInfoTable(document.Project.Solution.BranchId, workspace); - var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - - // first look to see if we already have the info in the live cache - SyntaxTreeIdentifierInfo info; - if (infoTable.TryGetValue(document.Id, out info) && info.Version == version) - { - return info; - } - - // we know cached information is invalid, delete it. - infoTable.Remove(document.Id); - - // now, check primary cache to see whether we have a hit - var primaryInfoTable = GetInfoTable(workspace.PrimaryBranchId, workspace); - if (primaryInfoTable.TryGetValue(document.Id, out info) && info.Version == version) - { - return info; - } - - // check whether we can re-use persisted data - info = await LoadAsync(document, PersistenceName, SerializationFormat, ReadFrom, cancellationToken).ConfigureAwait(false); - if (info != null) - { - // check whether we need to cache it - if (document.IsOpen()) - { - Contract.Requires(!await document.IsForkedDocumentWithSyntaxChangesAsync(cancellationToken).ConfigureAwait(false)); - primaryInfoTable.Remove(document.Id); - primaryInfoTable.GetValue(document.Id, _ => info); - } - - return info; - } - - return null; + var info = await LoadAsync(document, ReadFrom, cache, PersistenceName, SerializationFormat, cancellationToken).ConfigureAwait(false); + return (SyntaxTreeIdentifierInfo)info; } - public async Task SaveAsync(Document document, CancellationToken cancellationToken) + public override Task SaveAsync(Document document, CancellationToken cancellationToken) { - var workspace = document.Project.Solution.Workspace; - var infoTable = GetInfoTable(document.Project.Solution.BranchId, workspace); - - // if it is forked document - if (await document.IsForkedDocumentWithSyntaxChangesAsync(cancellationToken).ConfigureAwait(false)) - { - infoTable.Remove(document.Id); - infoTable.GetValue(document.Id, _ => this); - return false; - } - - // okay, cache this info if it is from opened document or persistence failed. - var persisted = await SaveAsync(document, PersistenceName, SerializationFormat, this, cancellationToken).ConfigureAwait(false); - if (!persisted || document.IsOpen()) - { - var primaryInfoTable = GetInfoTable(workspace.PrimaryBranchId, workspace); - primaryInfoTable.Remove(document.Id); - primaryInfoTable.GetValue(document.Id, _ => this); - } - - return persisted; + return SaveAsync(document, cache, PersistenceName, SerializationFormat, cancellationToken); } - private static ConditionalWeakTable GetInfoTable(BranchId branchId, Workspace workspace) + private static SyntaxTreeIdentifierInfo ReadFrom(ObjectReader reader, VersionStamp version) { - return cache.GetValue(branchId, id => + try { - if (id == workspace.PrimaryBranchId) - { - workspace.DocumentClosed += OnDocumentClosed; - } - - return new ConditionalWeakTable(); - }); - } + var identifierFilter = BloomFilter.ReadFrom(reader); + var escapedIdentifierFilter = BloomFilter.ReadFrom(reader); - private static void OnDocumentClosed(object sender, DocumentEventArgs e) - { - if (!e.Document.IsFromPrimaryBranch()) - { - return; + return new SyntaxTreeIdentifierInfo(version, identifierFilter, escapedIdentifierFilter); } - - ConditionalWeakTable infoTable; - if (cache.TryGetValue(e.Document.Project.Solution.BranchId, out infoTable)) + catch (Exception) { - // remove closed document from primary branch from live cache. - infoTable.Remove(e.Document.Id); } + + return null; } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo_Set.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo_Set.cs index 2c8d2d99240..c62fd832c3d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo_Set.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo_Set.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - internal partial class SyntaxTreeIdentifierInfo : AbstractPersistableState, IObjectWritable + internal partial class SyntaxTreeIdentifierInfo : AbstractSyntaxTreeInfo { public static bool TryGetIdentifierLocations(Document document, VersionStamp version, string identifier, List positions, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeInfo.cs index 72a700ddc5a..93b9a34225a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeInfo.cs @@ -20,6 +20,7 @@ internal partial class SyntaxTreeInfo /// private static readonly ConditionalWeakTable identifierSnapshotCache = new ConditionalWeakTable(); private static readonly ConditionalWeakTable contextSnapshotCache = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable declaredSymbolsSnapshotCache = new ConditionalWeakTable(); public static async Task PrecalculateAsync(Document document, CancellationToken cancellationToken) { @@ -52,7 +53,8 @@ private static async Task PrecalculateBasicInfoAsync(Document document, Cancella { // we already have information. move on if (await SyntaxTreeIdentifierInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) && - await SyntaxTreeContextInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false)) + await SyntaxTreeContextInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) && + await SyntaxTreeDeclarationInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false)) { return; } @@ -61,62 +63,60 @@ await SyntaxTreeContextInfo.PrecalculatedAsync(document, cancellationToken).Conf await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false); await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false); + await data.Item3.SaveAsync(document, cancellationToken).ConfigureAwait(false); } - public static async Task GetContextInfoAsync(Document document, CancellationToken cancellationToken) + private static async Task GetInfoAsync( + Document document, + ConditionalWeakTable cache, + Func> generator, + Func, T> selector, + CancellationToken cancellationToken) + where T : class { - SyntaxTreeContextInfo info; - if (contextSnapshotCache.TryGetValue(document, out info)) + T info; + if (cache.TryGetValue(document, out info)) { return info; } - info = await SyntaxTreeContextInfo.LoadAsync(document, cancellationToken).ConfigureAwait(false); + info = await generator(document, cancellationToken).ConfigureAwait(false); if (info != null) { - return contextSnapshotCache.GetValue(document, _ => info); + return cache.GetValue(document, _ => info); } - // alright, we don't have cached information, re-calcuate them here. + // alright, we don't have cached information, re-calculate them here. var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); - // okay, persist this info. + // okay, persist this info await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false); await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false); + await data.Item3.SaveAsync(document, cancellationToken).ConfigureAwait(false); - info = data.Item2; - return contextSnapshotCache.GetValue(document, _ => info); + info = selector(data); + return cache.GetValue(document, _ => info); } - public static async Task GetIdentifierInfoAsync(Document document, CancellationToken cancellationToken) + public static Task GetContextInfoAsync(Document document, CancellationToken cancellationToken) { - SyntaxTreeIdentifierInfo info; - if (identifierSnapshotCache.TryGetValue(document, out info)) - { - return info; - } - - info = await SyntaxTreeIdentifierInfo.LoadAsync(document, cancellationToken).ConfigureAwait(false); - if (info != null) - { - return identifierSnapshotCache.GetValue(document, _ => info); - } - - // alright, we don't have cached information, re-calcuate them here. - var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); + return GetInfoAsync(document, contextSnapshotCache, SyntaxTreeContextInfo.LoadAsync, tuple => tuple.Item2, cancellationToken); + } - // okay, persist this info. - await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false); - await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false); + public static Task GetIdentifierInfoAsync(Document document, CancellationToken cancellationToken) + { + return GetInfoAsync(document, identifierSnapshotCache, SyntaxTreeIdentifierInfo.LoadAsync, tuple => tuple.Item1, cancellationToken); + } - info = data.Item1; - return identifierSnapshotCache.GetValue(document, _ => info); + public static Task GetDeclarationInfoAsync(Document document, CancellationToken cancellationToken) + { + return GetInfoAsync(document, declaredSymbolsSnapshotCache, SyntaxTreeDeclarationInfo.LoadAsync, tuple => tuple.Item3, cancellationToken); } // The probability of getting a false positive when calling ContainsIdentifier. private const double FalsePositiveProbability = 0.0001; - private static async Task> CreateInfoAsync(Document document, CancellationToken cancellationToken) + private static async Task> CreateInfoAsync(Document document, CancellationToken cancellationToken) { var syntaxFacts = document.GetLanguageService(); var ignoreCase = syntaxFacts != null && !syntaxFacts.IsCaseSensitive; @@ -140,6 +140,8 @@ public static async Task GetIdentifierInfoAsync(Docume var predefinedTypes = (int)PredefinedType.None; var predefinedOperators = (int)PredefinedOperator.None; + var declaredSymbolInfos = new List(); + if (syntaxFacts != null) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -156,6 +158,12 @@ public static async Task GetIdentifierInfoAsync(Docume containsQueryExpression = containsQueryExpression || syntaxFacts.IsQueryExpression(node); containsElementAccess = containsElementAccess || syntaxFacts.IsElementAccessExpression(node); containsIndexerMemberCref = containsIndexerMemberCref || syntaxFacts.IsIndexerMemberCRef(node); + + DeclaredSymbolInfo declaredSymbolInfo; + if (syntaxFacts.TryGetDeclaredSymbolInfo(node, out declaredSymbolInfo)) + { + declaredSymbolInfos.Add(declaredSymbolInfo); + } } else { @@ -208,7 +216,10 @@ public static async Task GetIdentifierInfoAsync(Docume containsThisConstructorInitializer, containsBaseConstructorInitializer, containsElementAccess, - containsIndexerMemberCref)); + containsIndexerMemberCref), + new SyntaxTreeDeclarationInfo( + version, + declaredSymbolInfos)); } finally { diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index f4ddd790ae4..09be749f6f5 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; @@ -110,6 +111,8 @@ internal interface ISyntaxFactsService : ILanguageService bool IsTopLevelNodeWithMembers(SyntaxNode node); bool HasIncompleteParentMember(SyntaxNode node); + bool TryGetDeclaredSymbolInfo(SyntaxNode node, out DeclaredSymbolInfo declaredSymbolInfo); + SyntaxNode GetContainingTypeDeclaration(SyntaxNode root, int position); SyntaxNode GetContainingMemberDeclaration(SyntaxNode root, int position); SyntaxNode GetContainingVariableDeclaratorOfFieldDeclaration(SyntaxNode node); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs index 2991295c9d8..ddecf67c601 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.SemanticModelWorkspaceService; @@ -135,5 +135,11 @@ public static async Task IsForkedDocumentWithSyntaxChangesAsync(this Docum var currentDocumentVersion = await currentDocument.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); return !documentVersion.Equals(currentDocumentVersion); } + + public static async Task> GetDeclaredSymbolInfosAsync(this Document document, CancellationToken cancellationToken) + { + var declarationInfo = await SyntaxTreeInfo.GetDeclarationInfoAsync(document, cancellationToken).ConfigureAwait(false); + return declarationInfo.DeclaredSymbolInfos; + } } } diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 3bd890b7b73..2e15fdcfd59 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -372,6 +372,9 @@ + + + diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index 36d692b9d63..929ac04c0a5 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -1,9 +1,10 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Composition +Imports System.Text Imports System.Threading Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Host +Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.LanguageServices Imports Microsoft.CodeAnalysis.Text @@ -710,6 +711,159 @@ Namespace Microsoft.CodeAnalysis.VisualBasic TypeOf node Is EnumBlockSyntax End Function + Public Function TryGetDeclaredSymbolInfo(node As SyntaxNode, ByRef declaredSymbolInfo As DeclaredSymbolInfo) As Boolean Implements ISyntaxFactsService.TryGetDeclaredSymbolInfo + Select Case node.Kind() + Case SyntaxKind.ClassBlock + Dim classDecl = CType(node, ClassBlockSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo(classDecl.Begin.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Class, classDecl.Begin.Identifier.Span) + Return True + Case SyntaxKind.ConstructorBlock + Dim constructor = CType(node, ConstructorBlockSyntax) + Dim typeBlock = CType(constructor.Parent, TypeBlockSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo( + typeBlock.Begin.Identifier.ValueText, + GetNodeName(node.Parent), + DeclaredSymbolInfoKind.Constructor, + constructor.Begin.NewKeyword.Span, + parameterCount:=CType(If(constructor.Begin.ParameterList?.Parameters.Count, 0), UShort)) + Return True + Case SyntaxKind.DelegateFunctionStatement, SyntaxKind.DelegateSubStatement + Dim delegateDecl = CType(node, DelegateStatementSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo(delegateDecl.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Delegate, delegateDecl.Identifier.Span) + Return True + Case SyntaxKind.EnumBlock + Dim enumDecl = CType(node, EnumBlockSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo(enumDecl.EnumStatement.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Enum, enumDecl.EnumStatement.Identifier.Span) + Return True + Case SyntaxKind.EnumMemberDeclaration + Dim enumMember = CType(node, EnumMemberDeclarationSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo(enumMember.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.EnumMember, enumMember.Identifier.Span) + Return True + Case SyntaxKind.EventStatement + Dim eventDecl = CType(node, EventStatementSyntax) + Dim eventParent = If(TypeOf node.Parent Is EventBlockSyntax, node.Parent.Parent, node.Parent) + declaredSymbolInfo = New DeclaredSymbolInfo(eventDecl.Identifier.ValueText, GetNodeName(eventParent), DeclaredSymbolInfoKind.Event, eventDecl.Identifier.Span) + Return True + Case SyntaxKind.FunctionBlock, SyntaxKind.SubBlock + Dim funcDecl = CType(node, MethodBlockSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo( + funcDecl.Begin.Identifier.ValueText, + GetNodeName(node.Parent), + DeclaredSymbolInfoKind.Method, + funcDecl.Begin.Identifier.Span, + parameterCount:=CType(If(funcDecl.Begin.ParameterList?.Parameters.Count, 0), UShort), + typeParameterCount:=CType(If(funcDecl.Begin.TypeParameterList?.Parameters.Count, 0), UShort)) + Return True + Case SyntaxKind.InterfaceBlock + Dim interfaceDecl = CType(node, InterfaceBlockSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo(interfaceDecl.Begin.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Interface, interfaceDecl.Begin.Identifier.Span) + Return True + Case SyntaxKind.ModifiedIdentifier + Dim modifiedIdentifier = CType(node, ModifiedIdentifierSyntax) + Dim variableDeclarator = TryCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax) + Dim fieldDecl = TryCast(variableDeclarator?.Parent, FieldDeclarationSyntax) + If fieldDecl IsNot Nothing Then + Dim kind = If(fieldDecl.Modifiers.Any(Function(m) m.Kind() = SyntaxKind.ConstKeyword), + DeclaredSymbolInfoKind.Constant, + DeclaredSymbolInfoKind.Field) + declaredSymbolInfo = New DeclaredSymbolInfo(modifiedIdentifier.Identifier.ValueText, GetNodeName(fieldDecl.Parent), kind, modifiedIdentifier.Identifier.Span) + Return True + End If + Case SyntaxKind.ModuleBlock + Dim moduleDecl = CType(node, ModuleBlockSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo(moduleDecl.Begin.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Module, moduleDecl.Begin.Identifier.Span) + Return True + Case SyntaxKind.PropertyStatement + Dim propertyDecl = CType(node, PropertyStatementSyntax) + Dim propertyParent = If(TypeOf node.Parent Is PropertyBlockSyntax, node.Parent.Parent, node.Parent) + declaredSymbolInfo = New DeclaredSymbolInfo(propertyDecl.Identifier.ValueText, GetNodeName(propertyParent), DeclaredSymbolInfoKind.Property, propertyDecl.Identifier.Span) + Return True + Case SyntaxKind.StructureBlock + Dim structDecl = CType(node, StructureBlockSyntax) + declaredSymbolInfo = New DeclaredSymbolInfo(structDecl.Begin.Identifier.ValueText, GetNodeName(node.Parent), DeclaredSymbolInfoKind.Struct, structDecl.Begin.Identifier.Span) + Return True + End Select + + declaredSymbolInfo = Nothing + Return False + End Function + + Private Shared Function GetNodeName(node As SyntaxNode) As String + Dim name As String + Dim typeParameterList As TypeParameterListSyntax + Select Case node.Kind() + Case SyntaxKind.ClassBlock + Dim classDecl = CType(node, ClassBlockSyntax) + name = classDecl.Begin.Identifier.ValueText + typeParameterList = classDecl.Begin.TypeParameterList + Case SyntaxKind.CompilationUnit + name = String.Empty + typeParameterList = Nothing + Case SyntaxKind.EnumBlock + name = CType(node, EnumBlockSyntax).EnumStatement.Identifier.ValueText + typeParameterList = Nothing + Case SyntaxKind.IdentifierName + name = CType(node, IdentifierNameSyntax).Identifier.ValueText + typeParameterList = Nothing + Case SyntaxKind.InterfaceBlock + Dim interfaceDecl = CType(node, InterfaceBlockSyntax) + name = interfaceDecl.Begin.Identifier.ValueText + typeParameterList = interfaceDecl.Begin.TypeParameterList + Case SyntaxKind.FunctionBlock, SyntaxKind.SubBlock + Dim methodDecl = CType(node, MethodBlockSyntax) + name = methodDecl.Begin.Identifier.ValueText + typeParameterList = methodDecl.Begin.TypeParameterList + Case SyntaxKind.ModuleBlock + Dim moduleDecl = CType(node, ModuleBlockSyntax) + name = moduleDecl.Begin.Identifier.ValueText + typeParameterList = moduleDecl.Begin.TypeParameterList + Case SyntaxKind.NamespaceBlock + name = GetNodeName(CType(node, NamespaceBlockSyntax).NamespaceStatement.Name) + typeParameterList = Nothing + Case SyntaxKind.QualifiedName + Dim qualified = CType(node, QualifiedNameSyntax) + name = GetNodeName(qualified.Left) + "." + GetNodeName(qualified.Right) + typeParameterList = Nothing + Case SyntaxKind.StructureBlock + Dim structDecl = CType(node, StructureBlockSyntax) + name = structDecl.Begin.Identifier.ValueText + typeParameterList = structDecl.Begin.TypeParameterList + Case Else + Debug.Assert(False, "Unexpected node type " + node.Kind().ToString()) + Return Nothing + End Select + + ' check for nested classes + Dim names = New List(Of String)() + names.Add(name + ExpandTypeParameterList(typeParameterList)) + Dim parent = node.Parent + While TypeOf parent Is TypeBlockSyntax + Dim currentParent = CType(parent, TypeBlockSyntax) + names.Add(currentParent.Begin.Identifier.ValueText + ExpandTypeParameterList(currentParent.Begin.TypeParameterList)) + parent = currentParent.Parent + End While + + names.Reverse() + Return String.Join(".", names) + End Function + + Private Shared Function ExpandTypeParameterList(typeParameterList As TypeParameterListSyntax) As String + If typeParameterList IsNot Nothing AndAlso typeParameterList.Parameters.Count > 0 Then + Dim builder = New StringBuilder() + builder.Append("(Of ") + builder.Append(typeParameterList.Parameters(0).Identifier.ValueText) + For i = 1 To typeParameterList.Parameters.Count - 1 + builder.Append(","c) + builder.Append(typeParameterList.Parameters(i).Identifier.ValueText) + Next + + builder.Append(")"c) + Return builder.ToString() + Else + Return Nothing + End If + End Function + Private Sub AppendMethodLevelMembers(node As SyntaxNode, list As List(Of SyntaxNode)) For Each member In node.GetMembers() If IsTopLevelNodeWithMembers(member) Then -- GitLab