tools/apiview/parsers/csharp-api-parser/CSharpAPIParser/TreeToken/CodeFileBuilder.cs (758 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using APIView.Analysis;
using APIView.Model.V2;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.SymbolDisplay;
using System.Collections.Immutable;
using System.ComponentModel;
using ApiView;
using System.Diagnostics.CodeAnalysis;
namespace CSharpAPIParser.TreeToken
{
public class CodeFileBuilder
{
private static readonly char[] _newlineChars = new char[] { '\r', '\n' };
SymbolDisplayFormat _defaultDisplayFormat = new SymbolDisplayFormat(
SymbolDisplayGlobalNamespaceStyle.Omitted,
delegateStyle: SymbolDisplayDelegateStyle.NameAndSignature,
extensionMethodStyle: SymbolDisplayExtensionMethodStyle.StaticMethod,
propertyStyle: SymbolDisplayPropertyStyle.ShowReadWriteDescriptor,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral |
SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
SymbolDisplayMiscellaneousOptions.RemoveAttributeSuffix |
SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier,
kindOptions: SymbolDisplayKindOptions.IncludeMemberKeyword,
parameterOptions: SymbolDisplayParameterOptions.IncludeDefaultValue |
SymbolDisplayParameterOptions.IncludeExtensionThis |
SymbolDisplayParameterOptions.IncludeName |
SymbolDisplayParameterOptions.IncludeParamsRefOut |
SymbolDisplayParameterOptions.IncludeType,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeConstraints |
SymbolDisplayGenericsOptions.IncludeTypeConstraints |
SymbolDisplayGenericsOptions.IncludeTypeParameters |
SymbolDisplayGenericsOptions.IncludeVariance,
memberOptions: SymbolDisplayMemberOptions.IncludeExplicitInterface |
SymbolDisplayMemberOptions.IncludeConstantValue |
SymbolDisplayMemberOptions.IncludeModifiers |
SymbolDisplayMemberOptions.IncludeParameters |
SymbolDisplayMemberOptions.IncludeType
);
private IAssemblySymbol? _assembly;
public ICodeFileBuilderSymbolOrderProvider SymbolOrderProvider { get; set; } = new CodeFileBuilderSymbolOrderProvider();
public const string CurrentVersion = "29.2";
private IEnumerable<INamespaceSymbol> EnumerateNamespaces(IAssemblySymbol assemblySymbol)
{
var stack = new Stack<INamespaceSymbol>();
stack.Push(assemblySymbol.GlobalNamespace);
while (stack.TryPop(out var currentNamespace))
{
if (HasAnyPublicTypes(currentNamespace))
{
yield return currentNamespace;
}
foreach (var subNamespace in currentNamespace.GetNamespaceMembers())
{
stack.Push(subNamespace);
}
}
}
public CodeFile Build(IAssemblySymbol assemblySymbol, bool runAnalysis, List<DependencyInfo>? dependencies)
{
_assembly = assemblySymbol;
var analyzer = new Analyzer();
if (runAnalysis)
{
analyzer.VisitAssembly(assemblySymbol);
}
var codeFile = new CodeFile()
{
Language = "C#",
ParserVersion = CurrentVersion,
PackageName = assemblySymbol.Name,
PackageVersion = assemblySymbol.Identity.Version.ToString()
};
if (dependencies != null)
{
BuildDependencies(codeFile.ReviewLines, dependencies);
}
BuildInternalsVisibleToAttributes(codeFile.ReviewLines, assemblySymbol);
foreach (var namespaceSymbol in SymbolOrderProvider.OrderNamespaces(EnumerateNamespaces(assemblySymbol)))
{
if (namespaceSymbol.IsGlobalNamespace)
{
foreach (var namedTypeSymbol in SymbolOrderProvider.OrderTypes(namespaceSymbol.GetTypeMembers()))
{
BuildType(codeFile.ReviewLines, namedTypeSymbol, false);
}
}
else
{
BuildNamespace(codeFile.ReviewLines, namespaceSymbol);
}
}
codeFile.Diagnostics = analyzer.Results.ToArray();
return codeFile;
}
public static void BuildInternalsVisibleToAttributes(List<ReviewLine> reviewLines, IAssemblySymbol assemblySymbol)
{
var assemblyAttributes = assemblySymbol.GetAttributes()
.Where(a =>
a.AttributeClass?.Name == "InternalsVisibleToAttribute" &&
!a.ConstructorArguments[0].Value?.ToString()?.Contains(".Tests") == true &&
!a.ConstructorArguments[0].Value?.ToString()?.Contains(".Perf") == true &&
!a.ConstructorArguments[0].Value?.ToString()?.Contains("DynamicProxyGenAssembly2") == true);
if (assemblyAttributes != null && assemblyAttributes.Any())
{
var internalVisibleLine = new ReviewLine()
{
LineId = "InternalsVisibleTo",
Tokens = [
ReviewToken.CreateStringLiteralToken("Exposes internals to:")
]
};
reviewLines.Add(internalVisibleLine);
foreach (AttributeData attribute in assemblyAttributes)
{
if (attribute.ConstructorArguments.Length > 0)
{
var param = attribute.ConstructorArguments[0].Value?.ToString();
if (!String.IsNullOrEmpty(param))
{
var firstComma = param?.IndexOf(',');
param = firstComma > 0 ? param?[..(int)firstComma] : param;
reviewLines.Add(new ReviewLine()
{
LineId = attribute.AttributeClass?.Name,
Tokens = [
ReviewToken.CreateStringLiteralToken(param)
]
});
}
}
}
// Add an empty line after internals visible to section
reviewLines.Add(new ReviewLine() { RelatedToLine = internalVisibleLine.LineId });
}
}
public static void BuildDependencies(List<ReviewLine> reviewLines, List<DependencyInfo> dependencies)
{
if (dependencies != null && dependencies.Any())
{
//Dependencies
var headerLine = new ReviewLine()
{
LineId = "Dependencies"
};
var depToken = ReviewToken.CreateStringLiteralToken("Dependencies:");
depToken.NavigationDisplayName = "Dependencies";
depToken.RenderClasses.Add("dependencies");
headerLine.Tokens.Add(depToken);
reviewLines.Add(headerLine);
foreach (DependencyInfo dependency in dependencies)
{
var versionToken = ReviewToken.CreateStringLiteralToken($"-{dependency.Version}");
versionToken.SkipDiff = true;
var dependencyLine = new ReviewLine()
{
LineId = dependency.Name,
Tokens = [
ReviewToken.CreateStringLiteralToken(dependency.Name, false),
versionToken
]
};
reviewLines.Add(dependencyLine);
}
reviewLines.Add(new ReviewLine() { RelatedToLine = headerLine.LineId });
}
}
private void BuildNamespace(List<ReviewLine> reviewLines, INamespaceSymbol namespaceSymbol)
{
bool isHidden = HasOnlyHiddenTypes(namespaceSymbol);
var namespaceLine = new ReviewLine()
{
LineId = namespaceSymbol.GetId(),
Tokens = [
ReviewToken.CreateKeywordToken("namespace")
],
IsHidden = isHidden
};
BuildNamespaceName(namespaceLine, namespaceSymbol);
var nameSpaceToken = namespaceLine.Tokens.LastOrDefault();
if (nameSpaceToken != null)
{
nameSpaceToken.RenderClasses.Add("namespace");
nameSpaceToken.NavigationDisplayName = namespaceSymbol.ToDisplayString();
}
namespaceLine.Tokens.Last().HasSuffixSpace = true;
namespaceLine.Tokens.Add(ReviewToken.CreatePunctuationToken("{"));
// Add each members in the namespace
foreach (var namedTypeSymbol in SymbolOrderProvider.OrderTypes(namespaceSymbol.GetTypeMembers()).OrderBy(s => s.GetId()))
{
BuildType(namespaceLine.Children, namedTypeSymbol, isHidden);
}
reviewLines.Add(namespaceLine);
reviewLines.Add(new ReviewLine()
{
Tokens = [
ReviewToken.CreateStringLiteralToken("}")
],
IsHidden = isHidden,
IsContextEndLine = true
});
//Add an empty line in the review after current name space.
reviewLines.Add(new ReviewLine() { IsHidden = isHidden, RelatedToLine = namespaceLine.LineId});
}
private void BuildNamespaceName(ReviewLine namespaceLine, INamespaceSymbol namespaceSymbol)
{
if (!namespaceSymbol.ContainingNamespace.IsGlobalNamespace)
{
BuildNamespaceName(namespaceLine, namespaceSymbol.ContainingNamespace);
var punctuation = ReviewToken.CreatePunctuationToken(".", false);
namespaceLine.Tokens.Add(punctuation);
}
DisplayName(namespaceLine, namespaceSymbol, namespaceSymbol);
}
private bool HasAnyPublicTypes(INamespaceSymbol subNamespaceSymbol)
{
return subNamespaceSymbol.GetTypeMembers().Any(IsAccessible);
}
private bool HasOnlyHiddenTypes(INamespaceSymbol namespaceSymbol)
{
return namespaceSymbol.GetTypeMembers().All(t => IsHiddenFromIntellisense(t) || !IsAccessible(t));
}
private void BuildType(List<ReviewLine> reviewLines, INamedTypeSymbol namedType, bool inHiddenScope)
{
if (!IsAccessible(namedType))
{
return;
}
bool isHidden = IsHiddenFromIntellisense(namedType) || inHiddenScope;
var reviewLine = new ReviewLine()
{
LineId = namedType.GetId(),
IsHidden = isHidden
};
// Build documentation, attributes, visibility, and name
BuildDocumentation(reviewLines, namedType, isHidden, namedType.GetId());
BuildAttributes(reviewLines, namedType.GetAttributes(), isHidden, namedType.GetId());
BuildVisibility(reviewLine.Tokens, namedType);
switch (namedType.TypeKind)
{
case TypeKind.Class:
BuildClassModifiers(reviewLine.Tokens, namedType);
reviewLine.Tokens.Add(ReviewToken.CreateKeywordToken(SyntaxKind.ClassKeyword));
break;
case TypeKind.Delegate:
reviewLine.Tokens.Add(ReviewToken.CreateKeywordToken(SyntaxKind.DelegateKeyword));
break;
case TypeKind.Enum:
reviewLine.Tokens.Add(ReviewToken.CreateKeywordToken(SyntaxKind.EnumKeyword));
break;
case TypeKind.Interface:
reviewLine.Tokens.Add(ReviewToken.CreateKeywordToken(SyntaxKind.InterfaceKeyword));
break;
case TypeKind.Struct:
if (namedType.IsReadOnly)
{
reviewLine.Tokens.Add(ReviewToken.CreateKeywordToken(SyntaxKind.ReadOnlyKeyword));
}
reviewLine.Tokens.Add(ReviewToken.CreateKeywordToken(SyntaxKind.StructKeyword));
break;
}
DisplayName(reviewLine, namedType, namedType);
// Add navigation short name and render classes to Type name token. Navigation tree is built dynamically based on these properties
var typeToken = reviewLine.Tokens.FirstOrDefault(t => t.Kind == TokenKind.TypeName && string.IsNullOrEmpty(t.NavigateToId));
if (typeToken != null)
{
typeToken.NavigationDisplayName = namedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
typeToken.RenderClasses.Add(namedType.TypeKind.ToString().ToLowerInvariant());
}
if (namedType.TypeKind == TypeKind.Delegate)
{
reviewLine.Tokens.Last().HasSuffixSpace = false;
reviewLine.Tokens.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.SemicolonToken));
reviewLines.Add(reviewLine);
return;
}
reviewLine.Tokens.Last().HasSuffixSpace = true;
BuildBaseType(reviewLine, namedType);
reviewLine.Tokens.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.OpenBraceToken));
foreach (var namedTypeSymbol in SymbolOrderProvider.OrderTypes(namedType.GetTypeMembers()))
{
BuildType(reviewLine.Children, namedTypeSymbol, isHidden);
}
foreach (var member in SymbolOrderProvider.OrderMembers(namedType.GetMembers()))
{
if (member.Kind == SymbolKind.NamedType || member.IsImplicitlyDeclared || !IsAccessible(member)) continue;
if (member is IMethodSymbol method)
{
if (method.MethodKind == MethodKind.PropertyGet ||
method.MethodKind == MethodKind.PropertySet ||
method.MethodKind == MethodKind.EventAdd ||
method.MethodKind == MethodKind.EventRemove ||
method.MethodKind == MethodKind.EventRaise)
{
continue;
}
}
BuildMember(reviewLine.Children, member, isHidden);
}
reviewLines.Add(reviewLine);
reviewLines.Add(new ReviewLine()
{
Tokens = [
ReviewToken.CreateStringLiteralToken("}")
],
IsHidden = isHidden,
IsContextEndLine = true
});
reviewLines.Add(new ReviewLine() { IsHidden = isHidden, RelatedToLine = reviewLine.LineId });
}
private void BuildDocumentation(List<ReviewLine> reviewLines, ISymbol symbol, bool isHidden, string relatedTo)
{
var lines = symbol.GetDocumentationCommentXml()?.Trim().Split(_newlineChars);
if (lines != null)
{
if (lines.All(string.IsNullOrWhiteSpace))
{
return;
}
foreach (var line in lines)
{
var docToken = ReviewToken.CreateCommentToken("// " + line.Trim());
docToken.IsDocumentation = true;
reviewLines.Add(new ReviewLine()
{
Tokens = [docToken],
IsHidden = isHidden,
RelatedToLine = relatedTo
});
}
}
}
private static void BuildClassModifiers(List<ReviewToken> tokenList, INamedTypeSymbol namedType)
{
if (namedType.IsAbstract)
{
tokenList.Add(ReviewToken.CreateKeywordToken(SyntaxKind.AbstractKeyword));
}
if (namedType.IsStatic)
{
tokenList.Add(ReviewToken.CreateKeywordToken(SyntaxKind.StaticKeyword));
}
if (namedType.IsSealed)
{
tokenList.Add(ReviewToken.CreateKeywordToken(SyntaxKind.SealedKeyword));
}
}
private void BuildBaseType(ReviewLine reviewLine, INamedTypeSymbol namedType)
{
bool first = true;
if (namedType.BaseType != null &&
namedType.BaseType.SpecialType == SpecialType.None)
{
reviewLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.ColonToken));
first = false;
DisplayName(reviewLine, namedType.BaseType);
reviewLine.Tokens.Last().HasSuffixSpace = true;
}
foreach (var typeInterface in namedType.Interfaces)
{
if (!IsAccessible(typeInterface)) continue;
if (!first)
{
reviewLine.Tokens.Last().HasSuffixSpace = false;
reviewLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.CommaToken));
}
else
{
reviewLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.ColonToken));
first = false;
}
DisplayName(reviewLine, typeInterface);
reviewLine.Tokens.Last().HasSuffixSpace = true;
}
}
private void BuildMember(List<ReviewLine> reviewLines, ISymbol member, bool inHiddenScope)
{
bool isHidden = IsHiddenFromIntellisense(member) || inHiddenScope;
var reviewLine = new ReviewLine()
{
LineId = member.GetId(),
IsHidden = isHidden
};
BuildDocumentation(reviewLines, member, isHidden, member.GetId());
BuildAttributes(reviewLines, member.GetAttributes(), isHidden, member.GetId());
reviewLines.Add(reviewLine);
DisplayName(reviewLine, member);
reviewLine.Tokens.Last().HasSuffixSpace = false;
// Set member sub kind class for render class styling
var memToken = reviewLine.Tokens.FirstOrDefault(m => m.Kind == TokenKind.MemberName);
if (memToken != null)
{
memToken.RenderClasses.Add(member.Kind.ToString().ToLowerInvariant());
}
if (member.Kind == SymbolKind.Field && member.ContainingType.TypeKind == TypeKind.Enum)
{
reviewLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.CommaToken));
}
else if (member.Kind != SymbolKind.Property)
{
reviewLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.SemicolonToken));
}
}
private void BuildAttributes(List<ReviewLine> reviewLines, ImmutableArray<AttributeData> attributes, bool isHidden, string relatedTo)
{
const string attributeSuffix = "Attribute";
foreach (var attribute in attributes)
{
if (attribute.AttributeClass != null)
{
if ((!IsAccessible(attribute.AttributeClass) &&
attribute.AttributeClass.Name != "FriendAttribute" &&
attribute.AttributeClass.ContainingNamespace.ToString() != "System.Diagnostics.CodeAnalysis")
|| IsSkippedAttribute(attribute.AttributeClass))
{
continue;
}
var attributeLine = new ReviewLine()
{
// GetId() is not unique for attribute class. for e.g. attribute class id is something like "System.FlagsAttribute"
// So, using a unique id for attribute line
LineId = $"{attribute.AttributeClass.GetId()}.{relatedTo}",
IsHidden = isHidden
};
if (attribute.AttributeClass.DeclaredAccessibility == Accessibility.Internal || attribute.AttributeClass.DeclaredAccessibility == Accessibility.Friend)
{
attributeLine.AddToken(ReviewToken.CreateKeywordToken("internal"));
}
attributeLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.OpenBracketToken, false));
var name = attribute.AttributeClass.Name;
if (name.EndsWith(attributeSuffix))
{
name = name.Substring(0, name.Length - attributeSuffix.Length);
}
attributeLine.AddToken(ReviewToken.CreateTypeNameToken(name, false));
if (attribute.ConstructorArguments.Any())
{
attributeLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.OpenParenToken, false));
bool first = true;
foreach (var argument in attribute.ConstructorArguments)
{
if (!first)
{
attributeLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.CommaToken));
}
else
{
first = false;
}
BuildTypedConstant(attributeLine, argument);
}
foreach (var argument in attribute.NamedArguments)
{
if (!first)
{
attributeLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.CommaToken));
}
else
{
first = false;
}
attributeLine.AddToken(ReviewToken.CreateTextToken(argument.Key));
attributeLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.EqualsToken));
BuildTypedConstant(attributeLine, argument.Value);
}
attributeLine.Tokens.Last().HasSuffixSpace = false;
attributeLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.CloseParenToken));
}
attributeLine.Tokens.Last().HasSuffixSpace = false;
attributeLine.AddToken(ReviewToken.CreatePunctuationToken(SyntaxKind.CloseBracketToken));
attributeLine.RelatedToLine = relatedTo;
//Add current attribute line to review lines
reviewLines.Add(attributeLine);
}
}
}
private bool IsSkippedAttribute(INamedTypeSymbol attributeAttributeClass)
{
switch (attributeAttributeClass.Name)
{
case "DebuggerStepThroughAttribute":
case "AsyncStateMachineAttribute":
case "IteratorStateMachineAttribute":
case "DefaultMemberAttribute":
case "AsyncIteratorStateMachineAttribute":
case "EditorBrowsableAttribute":
case "NullableAttribute":
case "NullableContextAttribute":
case "RequiresUnreferencedCodeAttribute":
case "RequiresDynamicCodeAttribute":
return true;
default:
return false;
}
}
private bool IsHiddenFromIntellisense(ISymbol member) =>
member.GetAttributes().Any(d => d.AttributeClass?.Name == "EditorBrowsableAttribute"
&& (EditorBrowsableState)d.ConstructorArguments[0].Value! == EditorBrowsableState.Never);
private bool IsDecoratedWithAttribute(ISymbol member, string attributeName) =>
member.GetAttributes().Any(d => d.AttributeClass?.Name == attributeName);
private void BuildTypedConstant(ReviewLine reviewLine, TypedConstant typedConstant)
{
var tokenList = reviewLine.Tokens;
if (typedConstant.IsNull)
{
tokenList.Add(ReviewToken.CreateKeywordToken(SyntaxKind.NullKeyword, false));
}
else if (typedConstant.Kind == TypedConstantKind.Enum)
{
new CodeFileBuilderEnumFormatter(tokenList).Format(typedConstant.Type, typedConstant.Value);
}
else if (typedConstant.Kind == TypedConstantKind.Type)
{
tokenList.Add(ReviewToken.CreateKeywordToken(SyntaxKind.TypeOfKeyword, false));
tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.OpenParenToken, false));
DisplayName(reviewLine, (ITypeSymbol)typedConstant.Value!);
reviewLine.Tokens.Last().HasSuffixSpace = false;
tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.CloseParenToken, false));
}
else if (typedConstant.Kind == TypedConstantKind.Array)
{
tokenList.Add(ReviewToken.CreateKeywordToken(SyntaxKind.NewKeyword));
tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.OpenBracketToken, false));
tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.CloseBracketToken));
tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.OpenBraceToken));
bool first = true;
foreach (var value in typedConstant.Values)
{
if (!first)
{
tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.CommaToken));
}
else
{
first = false;
}
BuildTypedConstant(reviewLine, value);
reviewLine.Tokens.Last().HasSuffixSpace = false;
}
tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.CloseBraceToken, false));
}
else
{
if (typedConstant.Value is string s)
{
tokenList.Add(ReviewToken.CreateStringLiteralToken(ObjectDisplay.FormatLiteral(s, ObjectDisplayOptions.UseQuotes | ObjectDisplayOptions.EscapeNonPrintableCharacters), false));
}
else
{
tokenList.Add(ReviewToken.CreateLiteralToken(ObjectDisplay.FormatPrimitive(typedConstant.Value, ObjectDisplayOptions.None), false));
}
}
}
private void BuildVisibility(List<ReviewToken> tokenList, ISymbol symbol)
{
tokenList.Add(ReviewToken.CreateKeywordToken(ToEffectiveAccessibility(symbol.DeclaredAccessibility)));
}
private void DisplayName(ReviewLine reviewLine, ISymbol symbol, ISymbol? definedSymbol = null)
{
var reviewLineTokens = reviewLine.Tokens;
if (NeedsAccessibility(symbol))
{
reviewLineTokens.Add(ReviewToken.CreateKeywordToken(ToEffectiveAccessibility(symbol.DeclaredAccessibility)));
}
if (symbol is IPropertySymbol propSymbol && propSymbol.DeclaredAccessibility != Accessibility.Internal)
{
SymbolDisplayPart previous = default(SymbolDisplayPart);
var parts = propSymbol.ToDisplayParts(_defaultDisplayFormat);
for (int i = 0; i < parts.Length; i++)
{
// Skip internal setters
if (parts[i].Kind == SymbolDisplayPartKind.Keyword && parts[i].ToString() == "internal")
{
while (i < parts.Length && parts[i].ToString() != "}")
{
i++;
}
}
var previousToken = reviewLine.Tokens.LastOrDefault();
//Add a new code line as child if there is a line break
if (parts[i].Kind == SymbolDisplayPartKind.LineBreak)
{
var subLine = new ReviewLine()
{
LineId = definedSymbol.GetId(),
};
reviewLine.Children.Add(subLine);
reviewLineTokens = subLine.Tokens;
}
var token = MapToken(definedSymbol: definedSymbol!, symbolDisplayPart: parts[i],
previousSymbolDisplayPart: previous, previousToken);
if (token != null)
{
reviewLineTokens.Add(token);
}
previous = parts[i];
}
}
else
{
SymbolDisplayPart previous = default(SymbolDisplayPart);
foreach (var symbolDisplayPart in symbol.ToDisplayParts(_defaultDisplayFormat))
{
var previousToken = reviewLine.Tokens.LastOrDefault();
var token = MapToken(definedSymbol: definedSymbol!, symbolDisplayPart: symbolDisplayPart,
previousSymbolDisplayPart: previous, previousToken: previousToken);
if (token != null)
{
reviewLineTokens.Add(token);
}
previous = symbolDisplayPart;
}
}
}
private bool NeedsAccessibility(ISymbol symbol)
{
return symbol switch
{
INamespaceSymbol => false,
INamedTypeSymbol => false,
IFieldSymbol fieldSymbol => fieldSymbol.ContainingType.TypeKind != TypeKind.Enum,
IMethodSymbol methodSymbol => !methodSymbol.ExplicitInterfaceImplementations.Any() &&
methodSymbol.ContainingType.TypeKind != TypeKind.Interface,
IPropertySymbol propertySymbol => !propertySymbol.ExplicitInterfaceImplementations.Any() &&
propertySymbol.ContainingType.TypeKind != TypeKind.Interface,
_ => true
};
}
private ReviewToken? MapToken(ISymbol definedSymbol, SymbolDisplayPart symbolDisplayPart, SymbolDisplayPart previousSymbolDisplayPart, ReviewToken? previousToken)
{
string? navigateToId = null;
var symbol = symbolDisplayPart.Symbol;
if (symbol is INamedTypeSymbol &&
(definedSymbol == null || !SymbolEqualityComparer.Default.Equals(definedSymbol, symbol)) &&
SymbolEqualityComparer.Default.Equals(_assembly, symbol.ContainingAssembly))
{
navigateToId = symbol.GetId();
}
var tokenValue = symbolDisplayPart.ToString();
ReviewToken? token = null;
switch (symbolDisplayPart.Kind)
{
case SymbolDisplayPartKind.TypeParameterName:
case SymbolDisplayPartKind.AliasName:
case SymbolDisplayPartKind.AssemblyName:
case SymbolDisplayPartKind.ClassName:
case SymbolDisplayPartKind.DelegateName:
case SymbolDisplayPartKind.EnumName:
case SymbolDisplayPartKind.ErrorTypeName:
case SymbolDisplayPartKind.InterfaceName:
case SymbolDisplayPartKind.StructName:
token = ReviewToken.CreateTypeNameToken(tokenValue, false);
break;
case SymbolDisplayPartKind.Keyword:
token = ReviewToken.CreateKeywordToken(tokenValue, false);
break;
case SymbolDisplayPartKind.StringLiteral:
token = ReviewToken.CreateStringLiteralToken(tokenValue, false);
break;
case SymbolDisplayPartKind.Punctuation:
token = ReviewToken.CreatePunctuationToken(tokenValue, false);
break;
case SymbolDisplayPartKind.Space:
if (previousToken != null)
{
previousToken.HasSuffixSpace = true;
}
break;
case SymbolDisplayPartKind.PropertyName:
case SymbolDisplayPartKind.EventName:
case SymbolDisplayPartKind.FieldName:
case SymbolDisplayPartKind.MethodName:
case SymbolDisplayPartKind.Operator:
case SymbolDisplayPartKind.EnumMemberName:
case SymbolDisplayPartKind.ExtensionMethodName:
case SymbolDisplayPartKind.ConstantName:
token = ReviewToken.CreateMemberNameToken(tokenValue, false);
break;
default:
token = ReviewToken.CreateTextToken(tokenValue, hasSuffixSpace: false);
break;
}
if (token != null && !String.IsNullOrWhiteSpace(navigateToId))
{
token.NavigateToId = navigateToId!;
}
return token;
}
private Accessibility ToEffectiveAccessibility(Accessibility accessibility)
{
switch (accessibility)
{
case Accessibility.ProtectedAndInternal:
return Accessibility.Internal;
case Accessibility.ProtectedOrInternal:
return Accessibility.Protected;
default:
return accessibility;
}
}
private bool IsAccessible(ISymbol s)
{
switch (s.DeclaredAccessibility)
{
case Accessibility.Protected:
case Accessibility.ProtectedOrInternal:
case Accessibility.Public:
return true;
case Accessibility.Internal:
return s.GetAttributes().Any(a => a.AttributeClass?.Name == "FriendAttribute");
default:
return IsAccessibleExplicitInterfaceImplementation(s);
}
}
private bool IsAccessibleExplicitInterfaceImplementation(ISymbol s)
{
return s switch
{
IMethodSymbol methodSymbol => methodSymbol.ExplicitInterfaceImplementations.Any(i => IsAccessible(i.ContainingType)),
IPropertySymbol propertySymbol => propertySymbol.ExplicitInterfaceImplementations.Any(i => IsAccessible(i.ContainingType)),
_ => false
};
}
internal class CodeFileBuilderEnumFormatter : AbstractSymbolDisplayVisitor
{
private readonly List<ReviewToken> _tokenList;
public CodeFileBuilderEnumFormatter(List<ReviewToken> tokenList) : base(null, SymbolDisplayFormat.FullyQualifiedFormat, false, null, 0, false)
{
_tokenList = tokenList;
}
protected override AbstractSymbolDisplayVisitor MakeNotFirstVisitor(bool inNamespaceOrType = false)
{
return this;
}
protected override void AddLiteralValue(SpecialType type, object value)
{
_tokenList.Add(ReviewToken.CreateLiteralToken(ObjectDisplay.FormatPrimitive(value, ObjectDisplayOptions.None)));
}
protected override void AddExplicitlyCastedLiteralValue(INamedTypeSymbol namedType, SpecialType type, object value)
{
_tokenList.Add(ReviewToken.CreateLiteralToken(ObjectDisplay.FormatPrimitive(value, ObjectDisplayOptions.None)));
}
protected override void AddSpace()
{
var lastToken = _tokenList.LastOrDefault();
if (lastToken != null)
{
lastToken.HasSuffixSpace = true;
}
}
protected override void AddBitwiseOr()
{
if(_tokenList.Count > 0)
_tokenList.Last().HasSuffixSpace = true;
_tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.BarToken));
}
public override void VisitField(IFieldSymbol symbol)
{
_tokenList.Add(ReviewToken.CreateTypeNameToken(symbol.Type.Name, false));
_tokenList.Add(ReviewToken.CreatePunctuationToken(SyntaxKind.DotToken, false));
_tokenList.Add(ReviewToken.CreateMemberNameToken(symbol.Name, false));
}
public void Format(ITypeSymbol? type, object? typedConstantValue)
{
AddNonNullConstantValue(type, typedConstantValue);
}
}
}
}