|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeGeneration;
internal static class CodeGenerationHelpers
{
public static SyntaxNode? GenerateThrowStatement(
SyntaxGenerator factory,
SemanticDocument document,
string exceptionMetadataName)
{
var compilation = document.SemanticModel.Compilation;
var exceptionType = compilation.GetTypeByMetadataName(exceptionMetadataName);
// If we can't find the Exception, we obviously can't generate anything.
if (exceptionType == null)
{
return null;
}
var exceptionCreationExpression = factory.ObjectCreationExpression(exceptionType, arguments: []);
return factory.ThrowStatement(exceptionCreationExpression);
}
[return: NotNullIfNotNull(nameof(syntax))]
public static TSyntaxNode? AddAnnotationsTo<TSyntaxNode>(ISymbol symbol, TSyntaxNode? syntax) where TSyntaxNode : SyntaxNode
=> symbol is CodeGenerationSymbol codeGenerationSymbol
? syntax?.WithAdditionalAnnotations(codeGenerationSymbol.GetAnnotations())
: syntax;
public static TSyntaxNode AddFormatterAndCodeGeneratorAnnotationsTo<TSyntaxNode>(TSyntaxNode node) where TSyntaxNode : SyntaxNode
=> node.WithAdditionalAnnotations(Formatter.Annotation, CodeGenerator.Annotation);
public static void GetNameAndInnermostNamespace(
INamespaceSymbol @namespace,
CodeGenerationContextInfo info,
out string name,
out INamespaceSymbol innermostNamespace)
{
if (info.Context.GenerateMembers && info.Context.MergeNestedNamespaces && @namespace.Name != string.Empty)
{
var names = new List<string>
{
@namespace.Name
};
innermostNamespace = @namespace;
while (true)
{
var members = innermostNamespace.GetMembers().ToList();
if (members is [INamespaceSymbol childNamespace] &&
CodeGenerationNamespaceInfo.GetImports(innermostNamespace).Count == 0)
{
names.Add(childNamespace.Name);
innermostNamespace = childNamespace;
continue;
}
break;
}
name = string.Join(".", names);
}
else
{
name = @namespace.Name;
innermostNamespace = @namespace;
}
}
public static bool IsSpecialType([NotNullWhen(true)] ITypeSymbol? type, SpecialType specialType)
=> type != null && type.SpecialType == specialType;
public static int GetPreferredIndex(int index, IList<bool>? availableIndices, bool forward)
{
if (availableIndices == null)
return index;
if (forward)
{
for (var i = index; i < availableIndices.Count; i++)
{
if (availableIndices[i])
{
return i;
}
}
}
else
{
for (var i = index; i >= 0; i--)
{
if (availableIndices[i])
{
return i;
}
}
}
return -1;
}
public static bool TryGetDocumentationComment(
ISymbol symbol, string commentToken, [NotNullWhen(true)] out string? comment, CancellationToken cancellationToken = default)
{
var xml = symbol.GetDocumentationCommentXml(cancellationToken: cancellationToken);
if (string.IsNullOrEmpty(xml))
{
comment = null;
return false;
}
var commentStarter = string.Concat(commentToken, " ");
var newLineStarter = string.Concat("\n", commentStarter);
// Start the comment with an empty line for visual clarity.
comment = string.Concat(commentStarter, "\r\n", commentStarter, xml!.Replace("\n", newLineStarter));
return true;
}
public static bool TypesMatch(ITypeSymbol? type, object value)
=> type?.SpecialType switch
{
SpecialType.System_SByte => value is sbyte,
SpecialType.System_Byte => value is byte,
SpecialType.System_Int16 => value is short,
SpecialType.System_UInt16 => value is ushort,
SpecialType.System_Int32 => value is int,
SpecialType.System_UInt32 => value is uint,
SpecialType.System_Int64 => value is long,
SpecialType.System_UInt64 => value is ulong,
SpecialType.System_Decimal => value is decimal,
SpecialType.System_Single => value is float,
SpecialType.System_Double => value is double,
_ => false,
};
public static IEnumerable<ISymbol> GetMembers(INamedTypeSymbol namedType)
{
if (namedType.TypeKind != TypeKind.Enum)
{
return namedType.GetMembers();
}
return namedType.GetMembers()
.OfType<IFieldSymbol>()
.OrderBy((f1, f2) =>
{
if (f1.HasConstantValue != f2.HasConstantValue)
{
return f1.HasConstantValue ? 1 : -1;
}
return f1.HasConstantValue
? Comparer<object>.Default.Compare(f1.ConstantValue, f2.ConstantValue!)
: f1.Name.CompareTo(f2.Name);
}).ToList();
}
public static T RemoveLeadingDirectiveTrivia<T>(T node) where T : SyntaxNode
{
var leadingTrivia = node.GetLeadingTrivia().Where(trivia => !trivia.IsDirective);
return node.WithLeadingTrivia(leadingTrivia);
}
public static T? GetReuseableSyntaxNodeForAttribute<T>(AttributeData attribute)
where T : SyntaxNode
{
Contract.ThrowIfNull(attribute);
return attribute.ApplicationSyntaxReference != null
? attribute.ApplicationSyntaxReference.GetSyntax() as T
: null;
}
public static int GetInsertionIndex<TDeclaration>(
SyntaxList<TDeclaration> declarationList,
TDeclaration declaration,
CodeGenerationContextInfo info,
IList<bool>? availableIndices,
IComparer<TDeclaration> comparerWithoutNameCheck,
IComparer<TDeclaration> comparerWithNameCheck,
Func<SyntaxList<TDeclaration>, TDeclaration?>? after = null,
Func<SyntaxList<TDeclaration>, TDeclaration?>? before = null)
where TDeclaration : SyntaxNode
{
Contract.ThrowIfTrue(availableIndices != null && availableIndices.Count != declarationList.Count + 1);
// Try to strictly obey the after option by inserting immediately after the member containing the location
if (info.Context.AfterThisLocation?.SourceTree is { } afterSourceTree &&
afterSourceTree.FilePath == declarationList.FirstOrDefault()?.SyntaxTree.FilePath)
{
var afterMember = declarationList.LastOrDefault(m => m.SpanStart <= info.Context.AfterThisLocation.SourceSpan.Start);
if (afterMember != null)
{
var index = declarationList.IndexOf(afterMember);
index = GetPreferredIndex(index + 1, availableIndices, forward: true);
if (index != -1)
{
return index;
}
}
}
// Try to strictly obey the before option by inserting immediately before the member containing the location
if (info.Context.BeforeThisLocation?.SourceTree is { } beforeSourceTree &&
beforeSourceTree.FilePath == declarationList.FirstOrDefault()?.SyntaxTree.FilePath)
{
var beforeMember = declarationList.FirstOrDefault(m => m.Span.End >= info.Context.BeforeThisLocation.SourceSpan.End);
if (beforeMember != null)
{
var index = declarationList.IndexOf(beforeMember);
index = GetPreferredIndex(index, availableIndices, forward: false);
if (index != -1)
{
return index;
}
}
}
if (info.Context.AutoInsertionLocation)
{
if (declarationList.IsEmpty())
{
return 0;
}
var desiredIndex = TryGetDesiredIndexIfGrouped(
declarationList, declaration, availableIndices,
comparerWithoutNameCheck, comparerWithNameCheck);
if (desiredIndex.HasValue)
{
return desiredIndex.Value;
}
if (after != null)
{
var member = after(declarationList);
if (member != null)
{
var index = declarationList.IndexOf(member);
if (index >= 0)
{
index = GetPreferredIndex(index + 1, availableIndices, forward: true);
if (index != -1)
{
return index;
}
}
}
}
if (before != null)
{
var member = before(declarationList);
if (member != null)
{
var index = declarationList.IndexOf(member);
if (index >= 0)
{
index = GetPreferredIndex(index, availableIndices, forward: false);
if (index != -1)
{
return index;
}
}
}
}
}
// Otherwise, add the declaration to the end.
{
var index = GetPreferredIndex(declarationList.Count, availableIndices, forward: false);
if (index != -1)
{
return index;
}
}
return declarationList.Count;
}
public static int? TryGetDesiredIndexIfGrouped<TDeclarationSyntax>(
SyntaxList<TDeclarationSyntax> declarationList,
TDeclarationSyntax declaration,
IList<bool>? availableIndices,
IComparer<TDeclarationSyntax> comparerWithoutNameCheck,
IComparer<TDeclarationSyntax> comparerWithNameCheck)
where TDeclarationSyntax : SyntaxNode
{
var result = TryGetDesiredIndexIfGroupedWorker(
declarationList, declaration, availableIndices,
comparerWithoutNameCheck, comparerWithNameCheck);
if (result == null)
{
return null;
}
result = GetPreferredIndex(result.Value, availableIndices, forward: true);
if (result == -1)
{
return null;
}
return result;
}
private static int? TryGetDesiredIndexIfGroupedWorker<TDeclarationSyntax>(
SyntaxList<TDeclarationSyntax> declarationList,
TDeclarationSyntax declaration,
IList<bool>? availableIndices,
IComparer<TDeclarationSyntax> comparerWithoutNameCheck,
IComparer<TDeclarationSyntax> comparerWithNameCheck)
where TDeclarationSyntax : SyntaxNode
{
if (!declarationList.IsSorted(comparerWithoutNameCheck))
{
// Existing declarations weren't grouped. Don't try to find a location
// to this declaration into.
return null;
}
// The list was grouped (by type, staticness, accessibility). Try to find a location
// to put the new declaration into.
var result = Array.BinarySearch([.. declarationList], declaration, comparerWithoutNameCheck);
var desiredGroupIndex = result < 0 ? ~result : result;
Debug.Assert(desiredGroupIndex >= 0);
Debug.Assert(desiredGroupIndex <= declarationList.Count);
// Now, walk forward until we hit the last member of this group.
while (desiredGroupIndex < declarationList.Count)
{
// Stop walking forward if we hit an unavailable index.
if (availableIndices != null && !availableIndices[desiredGroupIndex])
{
break;
}
if (0 != comparerWithoutNameCheck.Compare(declaration, declarationList[desiredGroupIndex]))
{
// Found the index of an item not of our group.
break;
}
desiredGroupIndex++;
}
// Now, walk backward until we find the last member with the same name
// as us. We want to keep overloads together, so we'll place ourselves
// after that member.
var currentIndex = desiredGroupIndex;
while (currentIndex > 0)
{
var previousIndex = currentIndex - 1;
// Stop walking backward if we hit an unavailable index.
if (availableIndices != null && !availableIndices[previousIndex])
{
break;
}
if (0 != comparerWithoutNameCheck.Compare(declaration, declarationList[previousIndex]))
{
// Hit the previous group of items.
break;
}
// Still in the same group. If we find something with the same name
// then place ourselves after it.
if (0 == comparerWithNameCheck.Compare(declaration, declarationList[previousIndex]))
{
// Found something with the same name. Generate after this item.
return currentIndex;
}
currentIndex--;
}
// Couldn't find anything with our name. Just place us at the end of this group.
return desiredGroupIndex;
}
// from https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md
public static bool IsCompilerInternalAttribute(AttributeData attribute)
=> attribute.AttributeClass is
{
Name: "NullableAttribute" or "NullableContextAttribute" or "NativeIntegerAttribute" or "DynamicAttribute",
ContainingNamespace:
{
Name: nameof(System.Runtime.CompilerServices),
ContainingNamespace:
{
Name: nameof(System.Runtime),
ContainingNamespace:
{
Name: nameof(System),
ContainingNamespace.IsGlobalNamespace: true,
},
},
},
};
}
|