|
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using static CSharpSyntaxTokens;
using static SyntaxFactory;
internal sealed partial class CSharpCodeGenerationService(LanguageServices languageServices)
: AbstractCodeGenerationService<CSharpCodeGenerationContextInfo>(languageServices)
{
public override CodeGenerationOptions DefaultOptions
=> CSharpCodeGenerationOptions.Default;
public override CodeGenerationOptions GetCodeGenerationOptions(IOptionsReader options)
=> new CSharpCodeGenerationOptions(options);
public override CSharpCodeGenerationContextInfo GetInfo(CodeGenerationContext context, CodeGenerationOptions options, ParseOptions parseOptions)
=> new(context, (CSharpCodeGenerationOptions)options, this, ((CSharpParseOptions)parseOptions).LanguageVersion);
public override CodeGenerationDestination GetDestination(SyntaxNode node)
=> CSharpCodeGenerationHelpers.GetDestination(node);
protected override IComparer<SyntaxNode> GetMemberComparer()
=> CSharpDeclarationComparer.WithoutNamesInstance;
protected override IList<bool>? GetAvailableInsertionIndices(SyntaxNode destination, CancellationToken cancellationToken)
{
if (destination is TypeDeclarationSyntax typeDeclaration)
{
return GetInsertionIndices(typeDeclaration, cancellationToken);
}
// TODO(cyrusn): This will make is so that we can't generate into an enum, namespace, or
// compilation unit, if it overlaps a hidden region. We can consider relaxing that
// restriction in the future.
return null;
}
private static IList<bool> GetInsertionIndices(TypeDeclarationSyntax destination, CancellationToken cancellationToken)
=> destination.GetInsertionIndices(cancellationToken);
public override async Task<Document> AddEventAsync(
CodeGenerationSolutionContext context, INamedTypeSymbol destination, IEventSymbol @event, CancellationToken cancellationToken)
{
var newDocument = await base.AddEventAsync(
context, destination, @event, cancellationToken).ConfigureAwait(false);
var namedType = @event.Type as INamedTypeSymbol;
if (namedType?.AssociatedSymbol != null)
{
// This is a VB event that declares its own type. i.e. "Public Event E(x As Object)"
// We also have to generate "public void delegate EEventHandler(object x)"
var compilation = await newDocument.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
var newDestinationSymbol = destination.GetSymbolKey(cancellationToken).Resolve(compilation, cancellationToken: cancellationToken).Symbol;
var newContext = context with { Solution = newDocument.Project.Solution };
if (newDestinationSymbol?.ContainingType != null)
{
return await AddNamedTypeAsync(newContext, newDestinationSymbol.ContainingType, namedType, cancellationToken).ConfigureAwait(false);
}
else if (newDestinationSymbol?.ContainingNamespace != null)
{
return await AddNamedTypeAsync(newContext, newDestinationSymbol.ContainingNamespace, namedType, cancellationToken).ConfigureAwait(false);
}
}
return newDocument;
}
protected override TDeclarationNode AddEvent<TDeclarationNode>(TDeclarationNode destination, IEventSymbol @event, CSharpCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken)
{
CheckDeclarationNode<TypeDeclarationSyntax>(destination);
return Cast<TDeclarationNode>(EventGenerator.AddEventTo(Cast<TypeDeclarationSyntax>(destination), @event, info, availableIndices, cancellationToken));
}
protected override TDeclarationNode AddField<TDeclarationNode>(TDeclarationNode destination, IFieldSymbol field, CSharpCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken)
{
CheckDeclarationNode<EnumDeclarationSyntax, TypeDeclarationSyntax, CompilationUnitSyntax>(destination);
if (destination is EnumDeclarationSyntax)
{
return Cast<TDeclarationNode>(EnumMemberGenerator.AddEnumMemberTo(Cast<EnumDeclarationSyntax>(destination), field, info, cancellationToken));
}
else if (destination is TypeDeclarationSyntax)
{
return Cast<TDeclarationNode>(FieldGenerator.AddFieldTo(Cast<TypeDeclarationSyntax>(destination), field, info, availableIndices, cancellationToken));
}
else
{
return Cast<TDeclarationNode>(FieldGenerator.AddFieldTo(Cast<CompilationUnitSyntax>(destination), field, info, availableIndices, cancellationToken));
}
}
protected override TDeclarationNode AddMethod<TDeclarationNode>(TDeclarationNode destination, IMethodSymbol method, CSharpCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken)
{
// https://github.com/dotnet/roslyn/issues/44425: Add handling for top level statements
if (destination is GlobalStatementSyntax)
{
return destination;
}
CheckDeclarationNode<TypeDeclarationSyntax, CompilationUnitSyntax, BaseNamespaceDeclarationSyntax>(destination);
// Synthesized methods for properties/events are not things we actually generate
// declarations for.
if (method.AssociatedSymbol is IEventSymbol)
{
return destination;
}
// we will ignore the method if the associated property can be generated.
if (method.AssociatedSymbol is IPropertySymbol property)
{
if (PropertyGenerator.CanBeGenerated(property))
{
return destination;
}
}
var csharpOptions = info;
if (destination is TypeDeclarationSyntax typeDeclaration)
{
if (method.IsConstructor())
{
return Cast<TDeclarationNode>(ConstructorGenerator.AddConstructorTo(
typeDeclaration, method, csharpOptions, availableIndices, cancellationToken));
}
if (method.IsDestructor())
{
return Cast<TDeclarationNode>(DestructorGenerator.AddDestructorTo(typeDeclaration, method, csharpOptions, availableIndices, cancellationToken));
}
if (method.MethodKind == MethodKind.Conversion)
{
return Cast<TDeclarationNode>(ConversionGenerator.AddConversionTo(
typeDeclaration, method, csharpOptions, availableIndices, cancellationToken));
}
if (method.MethodKind == MethodKind.UserDefinedOperator)
{
return Cast<TDeclarationNode>(OperatorGenerator.AddOperatorTo(
typeDeclaration, method, csharpOptions, availableIndices, cancellationToken));
}
return Cast<TDeclarationNode>(MethodGenerator.AddMethodTo(
typeDeclaration, method, csharpOptions, availableIndices, cancellationToken));
}
if (method.IsConstructor() ||
method.IsDestructor())
{
return destination;
}
if (destination is CompilationUnitSyntax compilationUnit)
{
return Cast<TDeclarationNode>(
MethodGenerator.AddMethodTo(compilationUnit, method, csharpOptions, availableIndices, cancellationToken));
}
var ns = Cast<BaseNamespaceDeclarationSyntax>(destination);
return Cast<TDeclarationNode>(
MethodGenerator.AddMethodTo(ns, method, csharpOptions, availableIndices, cancellationToken));
}
protected override TDeclarationNode AddProperty<TDeclarationNode>(TDeclarationNode destination, IPropertySymbol property, CSharpCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken)
{
CheckDeclarationNode<TypeDeclarationSyntax, CompilationUnitSyntax>(destination);
// Can't generate a property with parameters. So generate the setter/getter individually.
if (!PropertyGenerator.CanBeGenerated(property))
{
var members = new List<ISymbol>();
if (property.GetMethod != null)
{
var getMethod = property.GetMethod;
if (property is CodeGenerationSymbol codeGenSymbol)
{
foreach (var annotation in codeGenSymbol.GetAnnotations())
{
getMethod = annotation.AddAnnotationToSymbol(getMethod);
}
}
members.Add(getMethod);
}
if (property.SetMethod != null)
{
var setMethod = property.SetMethod;
if (property is CodeGenerationSymbol codeGenSymbol)
{
foreach (var annotation in codeGenSymbol.GetAnnotations())
{
setMethod = annotation.AddAnnotationToSymbol(setMethod);
}
}
members.Add(setMethod);
}
if (members.Count > 1)
{
info = CreateContextInfoForMultipleMembers(info);
}
return AddMembers(destination, members, availableIndices, info, cancellationToken);
}
if (destination is TypeDeclarationSyntax)
{
return Cast<TDeclarationNode>(PropertyGenerator.AddPropertyTo(
Cast<TypeDeclarationSyntax>(destination), property, info, availableIndices, cancellationToken));
}
else
{
return Cast<TDeclarationNode>(PropertyGenerator.AddPropertyTo(
Cast<CompilationUnitSyntax>(destination), property, info, availableIndices, cancellationToken));
}
}
protected override TDeclarationNode AddNamedType<TDeclarationNode>(TDeclarationNode destination, INamedTypeSymbol namedType, CSharpCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken)
{
CheckDeclarationNode<TypeDeclarationSyntax, BaseNamespaceDeclarationSyntax, CompilationUnitSyntax>(destination);
var csharpInfo = info;
if (destination is TypeDeclarationSyntax typeDeclaration)
{
return Cast<TDeclarationNode>(NamedTypeGenerator.AddNamedTypeTo(this, typeDeclaration, namedType, csharpInfo, availableIndices, cancellationToken));
}
else if (destination is BaseNamespaceDeclarationSyntax namespaceDeclaration)
{
return Cast<TDeclarationNode>(NamedTypeGenerator.AddNamedTypeTo(this, namespaceDeclaration, namedType, csharpInfo, availableIndices, cancellationToken));
}
else
{
return Cast<TDeclarationNode>(NamedTypeGenerator.AddNamedTypeTo(this, Cast<CompilationUnitSyntax>(destination), namedType, csharpInfo, availableIndices, cancellationToken));
}
}
protected override TDeclarationNode AddNamespace<TDeclarationNode>(TDeclarationNode destination, INamespaceSymbol @namespace, CSharpCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken)
{
CheckDeclarationNode<CompilationUnitSyntax, BaseNamespaceDeclarationSyntax>(destination);
if (destination is CompilationUnitSyntax compilationUnit)
{
return Cast<TDeclarationNode>(NamespaceGenerator.AddNamespaceTo(this, compilationUnit, @namespace, info, availableIndices, cancellationToken));
}
else
{
return Cast<TDeclarationNode>(NamespaceGenerator.AddNamespaceTo(this, Cast<BaseNamespaceDeclarationSyntax>(destination), @namespace, info, availableIndices, cancellationToken));
}
}
public override TDeclarationNode AddParameters<TDeclarationNode>(
TDeclarationNode destination,
IEnumerable<IParameterSymbol> parameters,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
var currentParameterList = destination.GetParameterList();
var parameterCount = currentParameterList != null ? currentParameterList.Parameters.Count : 0;
var seenOptional = currentParameterList != null && parameterCount > 0 && currentParameterList.Parameters[^1].Default != null;
var isFirstParam = parameterCount == 0;
var editor = new SyntaxEditor(destination, this.LanguageServices.SolutionServices);
foreach (var parameter in parameters)
{
var parameterSyntax = ParameterGenerator.GetParameter(parameter, info, isExplicit: false, isFirstParam: isFirstParam, seenOptional: seenOptional);
AddParameterEditor.AddParameter(
CSharpSyntaxFacts.Instance,
editor,
destination,
parameterCount,
parameterSyntax,
cancellationToken);
parameterCount++;
isFirstParam = false;
seenOptional = seenOptional || parameterSyntax.Default != null;
}
var finalMember = editor.GetChangedRoot();
return Cast<TDeclarationNode>(finalMember);
}
public override TDeclarationNode AddAttributes<TDeclarationNode>(
TDeclarationNode destination,
IEnumerable<AttributeData> attributes,
SyntaxToken? target,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
if (target.HasValue && !target.Value.IsValidAttributeTarget())
{
throw new ArgumentException("target");
}
var attributeSyntaxList = AttributeGenerator.GenerateAttributeLists(attributes.ToImmutableArray(), info, target).ToArray();
return destination switch
{
MemberDeclarationSyntax member => Cast<TDeclarationNode>(member.AddAttributeLists(attributeSyntaxList)),
AccessorDeclarationSyntax accessor => Cast<TDeclarationNode>(accessor.AddAttributeLists(attributeSyntaxList)),
CompilationUnitSyntax compilationUnit => Cast<TDeclarationNode>(compilationUnit.AddAttributeLists(attributeSyntaxList)),
ParameterSyntax parameter => Cast<TDeclarationNode>(parameter.AddAttributeLists(attributeSyntaxList)),
TypeParameterSyntax typeParameter => Cast<TDeclarationNode>(typeParameter.AddAttributeLists(attributeSyntaxList)),
_ => destination,
};
}
protected override TDeclarationNode AddMembers<TDeclarationNode>(TDeclarationNode destination, IEnumerable<SyntaxNode> members)
{
CheckDeclarationNode<EnumDeclarationSyntax, TypeDeclarationSyntax, BaseNamespaceDeclarationSyntax, CompilationUnitSyntax>(destination);
if (destination is EnumDeclarationSyntax enumDeclaration)
{
enumDeclaration.EnsureOpenAndCloseBraceTokens();
return Cast<TDeclarationNode>(enumDeclaration.AddMembers(members.Cast<EnumMemberDeclarationSyntax>().ToArray()));
}
else if (destination is TypeDeclarationSyntax typeDeclaration)
{
typeDeclaration = typeDeclaration.EnsureOpenAndCloseBraceTokens();
return Cast<TDeclarationNode>(typeDeclaration.AddMembers(members.Cast<MemberDeclarationSyntax>().ToArray()));
}
else if (destination is BaseNamespaceDeclarationSyntax namespaceDeclaration)
{
return Cast<TDeclarationNode>(namespaceDeclaration.AddMembers(members.Cast<MemberDeclarationSyntax>().ToArray()));
}
else
{
return Cast<TDeclarationNode>(Cast<CompilationUnitSyntax>(destination)
.AddMembers(members.Cast<MemberDeclarationSyntax>().ToArray()));
}
}
public override TDeclarationNode RemoveAttribute<TDeclarationNode>(
TDeclarationNode destination,
AttributeData attributeToRemove,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
if (attributeToRemove.ApplicationSyntaxReference == null)
{
throw new ArgumentException("attributeToRemove");
}
var attributeSyntaxToRemove = attributeToRemove.ApplicationSyntaxReference.GetSyntax(cancellationToken);
return RemoveAttribute(destination, attributeSyntaxToRemove, info, cancellationToken);
}
public override TDeclarationNode RemoveAttribute<TDeclarationNode>(
TDeclarationNode destination,
SyntaxNode attributeToRemove,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
if (attributeToRemove == null)
{
throw new ArgumentException("attributeToRemove");
}
// Removed node could be AttributeSyntax or AttributeListSyntax.
int positionOfRemovedNode;
SyntaxTriviaList triviaOfRemovedNode;
switch (destination)
{
case MemberDeclarationSyntax member:
{
// Handle all members including types.
var newAttributeLists = RemoveAttributeFromAttributeLists(member.GetAttributes(), attributeToRemove, out positionOfRemovedNode, out triviaOfRemovedNode);
var newMember = member.WithAttributeLists(newAttributeLists);
return Cast<TDeclarationNode>(AppendTriviaAtPosition(newMember, positionOfRemovedNode - destination.FullSpan.Start, triviaOfRemovedNode));
}
case AccessorDeclarationSyntax accessor:
{
// Handle accessors
var newAttributeLists = RemoveAttributeFromAttributeLists(accessor.AttributeLists, attributeToRemove, out positionOfRemovedNode, out triviaOfRemovedNode);
var newAccessor = accessor.WithAttributeLists(newAttributeLists);
return Cast<TDeclarationNode>(AppendTriviaAtPosition(newAccessor, positionOfRemovedNode - destination.FullSpan.Start, triviaOfRemovedNode));
}
case CompilationUnitSyntax compilationUnit:
{
// Handle global attributes
var newAttributeLists = RemoveAttributeFromAttributeLists(compilationUnit.AttributeLists, attributeToRemove, out positionOfRemovedNode, out triviaOfRemovedNode);
var newCompilationUnit = compilationUnit.WithAttributeLists(newAttributeLists);
return Cast<TDeclarationNode>(AppendTriviaAtPosition(newCompilationUnit, positionOfRemovedNode - destination.FullSpan.Start, triviaOfRemovedNode));
}
case ParameterSyntax parameter:
{
// Handle parameters
var newAttributeLists = RemoveAttributeFromAttributeLists(parameter.AttributeLists, attributeToRemove, out positionOfRemovedNode, out triviaOfRemovedNode);
var newParameter = parameter.WithAttributeLists(newAttributeLists);
return Cast<TDeclarationNode>(AppendTriviaAtPosition(newParameter, positionOfRemovedNode - destination.FullSpan.Start, triviaOfRemovedNode));
}
case TypeParameterSyntax typeParameter:
{
var newAttributeLists = RemoveAttributeFromAttributeLists(typeParameter.AttributeLists, attributeToRemove, out positionOfRemovedNode, out triviaOfRemovedNode);
var newTypeParameter = typeParameter.WithAttributeLists(newAttributeLists);
return Cast<TDeclarationNode>(AppendTriviaAtPosition(newTypeParameter, positionOfRemovedNode - destination.FullSpan.Start, triviaOfRemovedNode));
}
}
return destination;
}
private static SyntaxList<AttributeListSyntax> RemoveAttributeFromAttributeLists(
SyntaxList<AttributeListSyntax> attributeLists,
SyntaxNode attributeToRemove,
out int positionOfRemovedNode,
out SyntaxTriviaList triviaOfRemovedNode)
{
foreach (var attributeList in attributeLists)
{
var attributes = attributeList.Attributes;
if (attributes.Contains(attributeToRemove))
{
IEnumerable<SyntaxTrivia> trivia;
IEnumerable<AttributeListSyntax> newAttributeLists;
if (attributes.Count == 1)
{
// Remove the entire attribute list.
ComputePositionAndTriviaForRemoveAttributeList(attributeList, (SyntaxTrivia t) => t.IsKind(SyntaxKind.EndOfLineTrivia), out positionOfRemovedNode, out trivia);
newAttributeLists = attributeLists.Where(aList => aList != attributeList);
}
else
{
// Remove just the given attribute from the attribute list.
ComputePositionAndTriviaForRemoveAttributeFromAttributeList(attributeToRemove, (SyntaxToken t) => t.IsKind(SyntaxKind.CommaToken), out positionOfRemovedNode, out trivia);
var newAttributes = SeparatedList(attributes.Where(a => a != attributeToRemove));
var newAttributeList = attributeList.WithAttributes(newAttributes);
newAttributeLists = attributeLists.Select(attrList => attrList == attributeList ? newAttributeList : attrList);
}
triviaOfRemovedNode = trivia.ToSyntaxTriviaList();
return [.. newAttributeLists];
}
}
throw new ArgumentException("attributeToRemove");
}
public override TDeclarationNode AddStatements<TDeclarationNode>(
TDeclarationNode destinationMember,
IEnumerable<SyntaxNode> statements,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
if (destinationMember is BaseMethodDeclarationSyntax methodDeclaration)
{
return AddStatementsToBaseMethodDeclaration(destinationMember, statements, methodDeclaration);
}
else if (destinationMember is MemberDeclarationSyntax)
{
// not currently supported
return destinationMember;
}
else if (destinationMember is LocalFunctionStatementSyntax localFunctionStatement)
{
return AddStatementsToLocalFunctionStatement(destinationMember, statements, localFunctionStatement);
}
else if (destinationMember is AnonymousFunctionExpressionSyntax anonymousFunctionSyntax)
{
return AddStatementsToAnonymousFunctions(destinationMember, statements, anonymousFunctionSyntax);
}
else if (destinationMember is AccessorDeclarationSyntax accessorDeclaration)
{
return (accessorDeclaration.Body == null) ? destinationMember : Cast<TDeclarationNode>(accessorDeclaration.AddBodyStatements([.. StatementGenerator.GenerateStatements(statements)]));
}
else if (destinationMember is CompilationUnitSyntax compilationUnit && info.Context.BestLocation is null)
{
// This path supports top-level statement insertion. It only applies when best location is unspecified
// so the fallback code below can handle cases where the insertion location is provided.
//
// Insert the new global statement(s) at the end of any current global statements.
// This code relies on 'LastIndexOf' returning -1 when no matching element is found.
var insertionIndex = compilationUnit.Members.LastIndexOf(memberDeclaration => memberDeclaration.IsKind(SyntaxKind.GlobalStatement)) + 1;
var wrappedStatements = StatementGenerator.GenerateStatements(statements).Select(GlobalStatement).ToArray();
return Cast<TDeclarationNode>(compilationUnit.WithMembers(compilationUnit.Members.InsertRange(insertionIndex, wrappedStatements)));
}
else if (destinationMember is StatementSyntax statement && statement.IsParentKind(SyntaxKind.GlobalStatement))
{
// We are adding a statement to a global statement in script, where the CompilationUnitSyntax is not a
// statement container. If the global statement is not already a block, create a block which can hold
// both the original statement and any new statements we are adding to it.
var block = statement as BlockSyntax ?? Block(statement);
return Cast<TDeclarationNode>(block.AddStatements([.. StatementGenerator.GenerateStatements(statements)]));
}
else
{
return AddStatementsWorker(destinationMember, statements, info, cancellationToken);
}
}
private static TDeclarationNode AddStatementsWorker<TDeclarationNode>(
TDeclarationNode destinationMember,
IEnumerable<SyntaxNode> statements,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode
{
var location = info.Context.BestLocation;
CheckLocation(destinationMember, location);
var token = location.FindToken(cancellationToken);
var block = token.Parent.GetAncestorsOrThis<BlockSyntax>().FirstOrDefault();
if (block != null)
{
var blockStatements = block.Statements.ToSet();
var containingStatement = token.GetAncestors<StatementSyntax>().Single(blockStatements.Contains);
var index = block.Statements.IndexOf(containingStatement);
var newStatements = statements.OfType<StatementSyntax>().ToArray();
BlockSyntax newBlock;
if (info.Context.BeforeThisLocation != null)
{
var newContainingStatement = containingStatement.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(out var strippedTrivia);
newStatements[0] = newStatements[0].WithLeadingTrivia(strippedTrivia);
newBlock = block.ReplaceNode(containingStatement, newContainingStatement);
newBlock = newBlock.WithStatements(newBlock.Statements.InsertRange(index, newStatements));
}
else
{
newBlock = block.WithStatements(block.Statements.InsertRange(index + 1, newStatements));
}
return destinationMember.ReplaceNode(block, newBlock);
}
throw new ArgumentException(WorkspaceExtensionsResources.No_available_location_found_to_add_statements_to);
}
private static TDeclarationNode AddStatementsToBaseMethodDeclaration<TDeclarationNode>(
TDeclarationNode destinationMember, IEnumerable<SyntaxNode> statements, BaseMethodDeclarationSyntax baseMethodDeclaration) where TDeclarationNode : SyntaxNode
{
var body = baseMethodDeclaration.Body;
// If the member has an expression body, convert to a block first.
// TODO: property determine if the expr should become a return statement or not.
baseMethodDeclaration.ExpressionBody?.TryConvertToBlock(
baseMethodDeclaration.SemicolonToken, createReturnStatementForExpression: false, out body);
if (body is null)
return destinationMember;
var finalMember = baseMethodDeclaration
.WithExpressionBody(null)
.WithSemicolonToken(default)
.WithBody(AddStatementsToBlock(body, statements));
return Cast<TDeclarationNode>(finalMember);
}
public static BlockSyntax AddStatementsToBlock(BlockSyntax block, IEnumerable<SyntaxNode> statements)
{
var statementsArray = StatementGenerator.GenerateStatements(statements);
if (statementsArray.Count > 0)
{
var closeBraceTrivia = block.CloseBraceToken.LeadingTrivia;
var lastEndIf = closeBraceTrivia.LastOrDefault(t => t.GetStructure() is EndIfDirectiveTriviaSyntax);
if (lastEndIf != default)
{
var splitIndex = closeBraceTrivia.IndexOf(lastEndIf) + 1;
statementsArray = statementsArray.Replace(
statementsArray[0],
statementsArray[0].WithPrependedLeadingTrivia(closeBraceTrivia.Take(splitIndex)));
block = block.WithCloseBraceToken(
block.CloseBraceToken.WithLeadingTrivia(closeBraceTrivia.Skip(splitIndex)));
}
}
return block.WithStatements(block.Statements.AddRange(statementsArray));
}
private static TDeclarationNode AddStatementsToLocalFunctionStatement<TDeclarationNode>(
TDeclarationNode destinationMember, IEnumerable<SyntaxNode> statements, LocalFunctionStatementSyntax localFunctionStatement) where TDeclarationNode : SyntaxNode
{
var body = localFunctionStatement.Body;
// If the member has an expression body, convert to a block first.
// TODO: property determine if the expr should become a return statement or not.
localFunctionStatement.ExpressionBody?.TryConvertToBlock(
localFunctionStatement.SemicolonToken, createReturnStatementForExpression: false, out body);
if (body is null)
return destinationMember;
var finalMember = localFunctionStatement
.WithExpressionBody(null)
.WithSemicolonToken(default)
.WithBody(AddStatementsToBlock(body, statements));
return Cast<TDeclarationNode>(finalMember);
}
private static TDeclarationNode AddStatementsToAnonymousFunctions<TDeclarationNode>(
TDeclarationNode destinationMember, IEnumerable<SyntaxNode> statements, AnonymousFunctionExpressionSyntax anonymousFunctionSyntax) where TDeclarationNode : SyntaxNode
{
if (anonymousFunctionSyntax.ExpressionBody is ExpressionSyntax expressionBody)
{
var semicolonToken = SemicolonToken;
if (expressionBody.TryConvertToStatement(semicolonToken, createReturnStatementForExpression: false, out var statement))
{
var block = Block(statement);
anonymousFunctionSyntax = anonymousFunctionSyntax.WithBlock(block).WithExpressionBody(null);
}
}
var body = anonymousFunctionSyntax.Block;
if (body is null)
return destinationMember;
var finalMember = anonymousFunctionSyntax
.WithExpressionBody(null)
.WithBody(AddStatementsToBlock(body, statements));
return Cast<TDeclarationNode>(finalMember);
}
public override SyntaxNode CreateEventDeclaration(
IEventSymbol @event, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
return EventGenerator.GenerateEventDeclaration(@event, destination, info, cancellationToken);
}
public override SyntaxNode CreateFieldDeclaration(IFieldSymbol field, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
return destination == CodeGenerationDestination.EnumType
? EnumMemberGenerator.GenerateEnumMemberDeclaration(field, destination: null, info, cancellationToken)
: FieldGenerator.GenerateFieldDeclaration(field, info, cancellationToken);
}
// TODO: Change to not return null (https://github.com/dotnet/roslyn/issues/58243)
public override SyntaxNode? CreateMethodDeclaration(
IMethodSymbol method, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
// Synthesized methods for properties/events are not things we actually generate
// declarations for.
if (method.AssociatedSymbol is IEventSymbol)
{
return null;
}
// we will ignore the method if the associated property can be generated.
if (method.AssociatedSymbol is IPropertySymbol property)
{
if (PropertyGenerator.CanBeGenerated(property))
{
return null;
}
}
var csharpOptions = info;
if (method.IsDestructor())
{
return DestructorGenerator.GenerateDestructorDeclaration(method, csharpOptions, cancellationToken);
}
if (method.IsConstructor())
{
return ConstructorGenerator.GenerateConstructorDeclaration(method, csharpOptions, cancellationToken);
}
if (method.IsUserDefinedOperator())
{
return OperatorGenerator.GenerateOperatorDeclaration(method, destination, csharpOptions, cancellationToken);
}
if (method.IsConversion())
{
return ConversionGenerator.GenerateConversionDeclaration(method, destination, csharpOptions, cancellationToken);
}
if (method.IsLocalFunction())
{
return MethodGenerator.GenerateLocalFunctionDeclaration(method, destination, csharpOptions, cancellationToken);
}
return MethodGenerator.GenerateMethodDeclaration(method, destination, csharpOptions, cancellationToken);
}
public override SyntaxNode CreatePropertyDeclaration(
IPropertySymbol property, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
return PropertyGenerator.GeneratePropertyOrIndexer(
property, destination, info, cancellationToken);
}
public override SyntaxNode CreateNamedTypeDeclaration(
INamedTypeSymbol namedType, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
return NamedTypeGenerator.GenerateNamedTypeDeclaration(this, namedType, destination, info, cancellationToken);
}
public override SyntaxNode CreateNamespaceDeclaration(
INamespaceSymbol @namespace, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
return NamespaceGenerator.GenerateNamespaceDeclaration(this, @namespace, destination, info, cancellationToken);
}
private static TDeclarationNode UpdateDeclarationModifiers<TDeclarationNode>(TDeclarationNode declaration, Func<SyntaxTokenList, SyntaxTokenList> computeNewModifiersList)
=> declaration switch
{
BaseTypeDeclarationSyntax typeDeclaration => Cast<TDeclarationNode>(typeDeclaration.WithModifiers(computeNewModifiersList(typeDeclaration.Modifiers))),
BaseFieldDeclarationSyntax fieldDeclaration => Cast<TDeclarationNode>(fieldDeclaration.WithModifiers(computeNewModifiersList(fieldDeclaration.Modifiers))),
BaseMethodDeclarationSyntax methodDeclaration => Cast<TDeclarationNode>(methodDeclaration.WithModifiers(computeNewModifiersList(methodDeclaration.Modifiers))),
BasePropertyDeclarationSyntax propertyDeclaration => Cast<TDeclarationNode>(propertyDeclaration.WithModifiers(computeNewModifiersList(propertyDeclaration.Modifiers))),
_ => declaration,
};
public override TDeclarationNode UpdateDeclarationModifiers<TDeclarationNode>(TDeclarationNode declaration, IEnumerable<SyntaxToken> newModifiers, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
return UpdateDeclarationModifiers(declaration, _ => [.. newModifiers]);
}
public override TDeclarationNode UpdateDeclarationAccessibility<TDeclarationNode>(TDeclarationNode declaration, Accessibility newAccessibility, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
return UpdateDeclarationModifiers(declaration, modifiersList => UpdateDeclarationAccessibility(modifiersList, newAccessibility, info));
}
private static SyntaxTokenList UpdateDeclarationAccessibility(SyntaxTokenList modifiersList, Accessibility newAccessibility, CSharpCodeGenerationContextInfo info)
{
using var _ = ArrayBuilder<SyntaxToken>.GetInstance(out var newModifierTokens);
CSharpCodeGenerationHelpers.AddAccessibilityModifiers(newAccessibility, newModifierTokens, info, Accessibility.NotApplicable);
if (newModifierTokens.Count == 0)
{
return modifiersList;
}
// TODO: Move more APIs to use pooled ArrayBuilder
// https://github.com/dotnet/roslyn/issues/34960
return GetUpdatedDeclarationAccessibilityModifiers(
newModifierTokens, modifiersList,
modifier => SyntaxFacts.IsAccessibilityModifier(modifier.Kind()));
}
public override TDeclarationNode UpdateDeclarationType<TDeclarationNode>(TDeclarationNode declaration, ITypeSymbol newType, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
if (declaration is not CSharpSyntaxNode syntaxNode)
{
return declaration;
}
TypeSyntax newTypeSyntax;
switch (syntaxNode.Kind())
{
case SyntaxKind.DelegateDeclaration:
// Handle delegate declarations.
var delegateDeclarationSyntax = (DelegateDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(delegateDeclarationSyntax.ReturnType.GetLeadingTrivia())
.WithTrailingTrivia(delegateDeclarationSyntax.ReturnType.GetTrailingTrivia());
return Cast<TDeclarationNode>(delegateDeclarationSyntax.WithReturnType(newTypeSyntax));
case SyntaxKind.MethodDeclaration:
// Handle method declarations.
var methodDeclarationSyntax = (MethodDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(methodDeclarationSyntax.ReturnType.GetLeadingTrivia())
.WithTrailingTrivia(methodDeclarationSyntax.ReturnType.GetTrailingTrivia());
return Cast<TDeclarationNode>(methodDeclarationSyntax.WithReturnType(newTypeSyntax));
case SyntaxKind.OperatorDeclaration:
// Handle operator declarations.
var operatorDeclarationSyntax = (OperatorDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(operatorDeclarationSyntax.ReturnType.GetLeadingTrivia())
.WithTrailingTrivia(operatorDeclarationSyntax.ReturnType.GetTrailingTrivia());
return Cast<TDeclarationNode>(operatorDeclarationSyntax.WithReturnType(newTypeSyntax));
case SyntaxKind.ConversionOperatorDeclaration:
// Handle conversion operator declarations.
var conversionOperatorDeclarationSyntax = (ConversionOperatorDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(conversionOperatorDeclarationSyntax.Type.GetLeadingTrivia())
.WithTrailingTrivia(conversionOperatorDeclarationSyntax.Type.GetTrailingTrivia());
return Cast<TDeclarationNode>(conversionOperatorDeclarationSyntax.WithType(newTypeSyntax));
case SyntaxKind.PropertyDeclaration:
// Handle properties.
var propertyDeclaration = (PropertyDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(propertyDeclaration.Type.GetLeadingTrivia())
.WithTrailingTrivia(propertyDeclaration.Type.GetTrailingTrivia());
return Cast<TDeclarationNode>(propertyDeclaration.WithType(newTypeSyntax));
case SyntaxKind.EventDeclaration:
// Handle events.
var eventDeclarationSyntax = (EventDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(eventDeclarationSyntax.Type.GetLeadingTrivia())
.WithTrailingTrivia(eventDeclarationSyntax.Type.GetTrailingTrivia());
return Cast<TDeclarationNode>(eventDeclarationSyntax.WithType(newTypeSyntax));
case SyntaxKind.IndexerDeclaration:
// Handle indexers.
var indexerDeclarationSyntax = (IndexerDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(indexerDeclarationSyntax.Type.GetLeadingTrivia())
.WithTrailingTrivia(indexerDeclarationSyntax.Type.GetTrailingTrivia());
return Cast<TDeclarationNode>(indexerDeclarationSyntax.WithType(newTypeSyntax));
case SyntaxKind.Parameter:
// Handle parameters.
var parameterSyntax = (ParameterSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax();
if (parameterSyntax.Type != null)
{
newTypeSyntax = newTypeSyntax
.WithLeadingTrivia(parameterSyntax.Type.GetLeadingTrivia())
.WithTrailingTrivia(parameterSyntax.Type.GetTrailingTrivia());
}
return Cast<TDeclarationNode>(parameterSyntax.WithType(newTypeSyntax));
case SyntaxKind.IncompleteMember:
// Handle incomplete members.
var incompleteMemberSyntax = (IncompleteMemberSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax();
if (incompleteMemberSyntax.Type != null)
{
newTypeSyntax = newTypeSyntax
.WithLeadingTrivia(incompleteMemberSyntax.Type.GetLeadingTrivia())
.WithTrailingTrivia(incompleteMemberSyntax.Type.GetTrailingTrivia());
}
return Cast<TDeclarationNode>(incompleteMemberSyntax.WithType(newTypeSyntax));
case SyntaxKind.ArrayType:
// Handle array type.
var arrayTypeSyntax = (ArrayTypeSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(arrayTypeSyntax.ElementType.GetLeadingTrivia())
.WithTrailingTrivia(arrayTypeSyntax.ElementType.GetTrailingTrivia());
return Cast<TDeclarationNode>(arrayTypeSyntax.WithElementType(newTypeSyntax));
case SyntaxKind.PointerType:
// Handle pointer type.
var pointerTypeSyntax = (PointerTypeSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(pointerTypeSyntax.ElementType.GetLeadingTrivia())
.WithTrailingTrivia(pointerTypeSyntax.ElementType.GetTrailingTrivia());
return Cast<TDeclarationNode>(pointerTypeSyntax.WithElementType(newTypeSyntax));
case SyntaxKind.VariableDeclaration:
// Handle variable declarations.
var variableDeclarationSyntax = (VariableDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(variableDeclarationSyntax.Type.GetLeadingTrivia())
.WithTrailingTrivia(variableDeclarationSyntax.Type.GetTrailingTrivia());
return Cast<TDeclarationNode>(variableDeclarationSyntax.WithType(newTypeSyntax));
case SyntaxKind.CatchDeclaration:
// Handle catch declarations.
var catchDeclarationSyntax = (CatchDeclarationSyntax)syntaxNode;
newTypeSyntax = newType.GenerateTypeSyntax()
.WithLeadingTrivia(catchDeclarationSyntax.Type.GetLeadingTrivia())
.WithTrailingTrivia(catchDeclarationSyntax.Type.GetTrailingTrivia());
return Cast<TDeclarationNode>(catchDeclarationSyntax.WithType(newTypeSyntax));
default:
return declaration;
}
}
public override TDeclarationNode UpdateDeclarationMembers<TDeclarationNode>(TDeclarationNode declaration, IList<ISymbol> newMembers, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
if (declaration is MemberDeclarationSyntax memberDeclaration)
{
return Cast<TDeclarationNode>(NamedTypeGenerator.UpdateNamedTypeDeclaration(this, memberDeclaration, newMembers, info, cancellationToken));
}
if (declaration is CSharpSyntaxNode syntaxNode)
{
switch (syntaxNode.Kind())
{
case SyntaxKind.CompilationUnit:
case SyntaxKind.NamespaceDeclaration:
case SyntaxKind.FileScopedNamespaceDeclaration:
return Cast<TDeclarationNode>(NamespaceGenerator.UpdateCompilationUnitOrNamespaceDeclaration(this, syntaxNode, newMembers, info, cancellationToken));
}
}
return declaration;
}
}
|