提交 66975f76 编写于 作者: B brettv

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)
上级 3bc5d530
// 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<string>() { 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<SyntaxNode> GetMethodLevelMembers(SyntaxNode root)
{
var list = new List<SyntaxNode>();
......
// 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<ISymbol> 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);
}
}
}
}
// 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<bool> SaveAsync(Document document, CancellationToken cancellationToken);
protected async Task<bool> SaveAsync(
Document document,
ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>> 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<AbstractSyntaxTreeInfo> LoadAsync(
Document document,
Func<ObjectReader, VersionStamp, AbstractSyntaxTreeInfo> reader,
ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>> cache,
string persistenceName,
string serializationFormat,
CancellationToken cancellationToken)
{
var infoTable = cache.GetValue(document.Project.Solution.BranchId, _ => new ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>());
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<DocumentId, AbstractSyntaxTreeInfo>());
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<DocumentId, AbstractSyntaxTreeInfo> GetInfoTable(
BranchId branchId,
Workspace workspace,
ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>> cache)
{
return cache.GetValue(branchId, id =>
{
if (id == workspace.PrimaryBranchId)
{
workspace.DocumentClosed += (sender, e) =>
{
if (!e.Document.IsFromPrimaryBranch())
{
return;
}
ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo> 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<DocumentId, AbstractSyntaxTreeInfo>();
});
}
}
}
// 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 = "<SyntaxTreeInfoDeclarationPersistence>";
private const string SerializationFormat = "1";
/// <summary>
/// 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.
/// </summary>
private static readonly ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>> cache =
new ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>>();
public IEnumerable<DeclaredSymbolInfo> DeclaredSymbolInfos { get; private set; }
public SyntaxTreeDeclarationInfo(VersionStamp version, IEnumerable<DeclaredSymbolInfo> 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<bool> SaveAsync(Document document, CancellationToken cancellationToken)
{
return SaveAsync(document, cache, PersistenceName, SerializationFormat, cancellationToken);
}
public static Task<bool> PrecalculatedAsync(Document document, CancellationToken cancellationToken)
{
return PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken);
}
public static async Task<SyntaxTreeDeclarationInfo> 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;
}
}
}
......@@ -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 = "<SyntaxTreeInfoIdentifierPersistence>";
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.
/// </summary>
private static readonly ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, SyntaxTreeIdentifierInfo>> cache =
new ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, SyntaxTreeIdentifierInfo>>();
private static readonly ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>> cache =
new ConditionalWeakTable<BranchId, ConditionalWeakTable<DocumentId, AbstractSyntaxTreeInfo>>();
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<bool> PrecalculatedAsync(Document document, CancellationToken cancellationToken)
{
return PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken);
......@@ -101,96 +84,29 @@ public static Task<bool> PrecalculatedAsync(Document document, CancellationToken
public static async Task<SyntaxTreeIdentifierInfo> 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<bool> SaveAsync(Document document, CancellationToken cancellationToken)
public override Task<bool> 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<DocumentId, SyntaxTreeIdentifierInfo> 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<DocumentId, SyntaxTreeIdentifierInfo>();
});
}
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<DocumentId, SyntaxTreeIdentifierInfo> 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;
}
}
}
......@@ -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<int> positions, CancellationToken cancellationToken)
{
......
......@@ -20,6 +20,7 @@ internal partial class SyntaxTreeInfo
/// </summary>
private static readonly ConditionalWeakTable<Document, SyntaxTreeIdentifierInfo> identifierSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeIdentifierInfo>();
private static readonly ConditionalWeakTable<Document, SyntaxTreeContextInfo> contextSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeContextInfo>();
private static readonly ConditionalWeakTable<Document, SyntaxTreeDeclarationInfo> declaredSymbolsSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeDeclarationInfo>();
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<SyntaxTreeContextInfo> GetContextInfoAsync(Document document, CancellationToken cancellationToken)
private static async Task<T> GetInfoAsync<T>(
Document document,
ConditionalWeakTable<Document, T> cache,
Func<Document, CancellationToken, Task<T>> generator,
Func<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo, SyntaxTreeDeclarationInfo>, 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<SyntaxTreeIdentifierInfo> GetIdentifierInfoAsync(Document document, CancellationToken cancellationToken)
public static Task<SyntaxTreeContextInfo> 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<SyntaxTreeIdentifierInfo> 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<SyntaxTreeDeclarationInfo> 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<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo>> CreateInfoAsync(Document document, CancellationToken cancellationToken)
private static async Task<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo, SyntaxTreeDeclarationInfo>> CreateInfoAsync(Document document, CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var ignoreCase = syntaxFacts != null && !syntaxFacts.IsCaseSensitive;
......@@ -140,6 +140,8 @@ public static async Task<SyntaxTreeIdentifierInfo> GetIdentifierInfoAsync(Docume
var predefinedTypes = (int)PredefinedType.None;
var predefinedOperators = (int)PredefinedOperator.None;
var declaredSymbolInfos = new List<DeclaredSymbolInfo>();
if (syntaxFacts != null)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
......@@ -156,6 +158,12 @@ public static async Task<SyntaxTreeIdentifierInfo> 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<SyntaxTreeIdentifierInfo> GetIdentifierInfoAsync(Docume
containsThisConstructorInitializer,
containsBaseConstructorInitializer,
containsElementAccess,
containsIndexerMemberCref));
containsIndexerMemberCref),
new SyntaxTreeDeclarationInfo(
version,
declaredSymbolInfos));
}
finally
{
......
......@@ -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);
......
// 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<bool> IsForkedDocumentWithSyntaxChangesAsync(this Docum
var currentDocumentVersion = await currentDocument.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false);
return !documentVersion.Equals(currentDocumentVersion);
}
public static async Task<IEnumerable<DeclaredSymbolInfo>> GetDeclaredSymbolInfosAsync(this Document document, CancellationToken cancellationToken)
{
var declarationInfo = await SyntaxTreeInfo.GetDeclarationInfoAsync(document, cancellationToken).ConfigureAwait(false);
return declarationInfo.DeclaredSymbolInfos;
}
}
}
......@@ -372,6 +372,9 @@
<Compile Include="CodeRefactorings\CodeRefactoringContext.cs" />
<Compile Include="Differencing\LongestCommonImmutableArraySubsequence.cs" />
<Compile Include="Differencing\SequenceEdit.cs" />
<Compile Include="FindSymbols\DeclaredSymbolInfo.cs" />
<Compile Include="FindSymbols\SyntaxTree\AbstractSyntaxTreeInfo.cs" />
<Compile Include="FindSymbols\SyntaxTree\SyntaxTreeDeclarationInfo.cs" />
<Compile Include="LinkedFileDiffMerging\LinkedFileMergeSessionResult.cs" />
<Compile Include="LinkedFileDiffMerging\DefaultDocumentTextDifferencingService.cs" />
<Compile Include="Log\AggregateLogger.cs" />
......
' 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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册