// 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.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; using static CSharpSyntaxTokens; using static SyntaxFactory; internal static class CSharpCodeGenerationHelpers { public static TDeclarationSyntax ConditionallyAddFormattingAnnotationTo<TDeclarationSyntax>( TDeclarationSyntax result, SyntaxList<MemberDeclarationSyntax> members) where TDeclarationSyntax : MemberDeclarationSyntax { return members.Count == 1 ? result.WithAdditionalAnnotations(Formatter.Annotation) : result; } internal static void AddAccessibilityModifiers( Accessibility accessibility, ArrayBuilder<SyntaxToken> tokens, CSharpCodeGenerationContextInfo info, Accessibility defaultAccessibility) { if (!info.Context.GenerateDefaultAccessibility && accessibility == defaultAccessibility) { return; } switch (accessibility) { case Accessibility.Public: tokens.Add(PublicKeyword); break; case Accessibility.Protected: tokens.Add(ProtectedKeyword); break; case Accessibility.Private: tokens.Add(PrivateKeyword); break; case Accessibility.ProtectedAndInternal: tokens.Add(PrivateKeyword); tokens.Add(ProtectedKeyword); break; case Accessibility.Internal: tokens.Add(InternalKeyword); break; case Accessibility.ProtectedOrInternal: tokens.Add(ProtectedKeyword); tokens.Add(InternalKeyword); break; } } public static TypeDeclarationSyntax AddMembersTo( TypeDeclarationSyntax destination, SyntaxList<MemberDeclarationSyntax> members, CancellationToken cancellationToken) { var syntaxTree = destination.SyntaxTree; destination = ReplaceUnterminatedConstructs(destination); var node = ConditionallyAddFormattingAnnotationTo( destination.EnsureOpenAndCloseBraceTokens().WithMembers(members), members); // Make sure the generated syntax node has same parse option. // e.g. If add syntax member to a C# 5 destination, we should return a C# 5 syntax node. var tree = node.SyntaxTree.WithRootAndOptions(node, syntaxTree.Options); return (TypeDeclarationSyntax)tree.GetRoot(cancellationToken); } private static TypeDeclarationSyntax ReplaceUnterminatedConstructs(TypeDeclarationSyntax destination) { const string MultiLineCommentTerminator = "*/"; var lastToken = destination.GetLastToken(); var updatedToken = lastToken.ReplaceTrivia(lastToken.TrailingTrivia, (t1, t2) => { if (t1.Kind() == SyntaxKind.MultiLineCommentTrivia) { var text = t1.ToString(); if (!text.EndsWith(MultiLineCommentTerminator, StringComparison.Ordinal)) { return SyntaxTrivia(SyntaxKind.MultiLineCommentTrivia, text + MultiLineCommentTerminator); } } else if (t1.Kind() == SyntaxKind.SkippedTokensTrivia) { return ReplaceUnterminatedConstructs(t1); } return t1; }); return destination.ReplaceToken(lastToken, updatedToken); } private static SyntaxTrivia ReplaceUnterminatedConstructs(SyntaxTrivia skippedTokensTrivia) { var syntax = (SkippedTokensTriviaSyntax?)skippedTokensTrivia.GetStructure(); Contract.ThrowIfNull(syntax); var tokens = syntax.Tokens; var updatedTokens = TokenList(tokens.Select(ReplaceUnterminatedConstruct)); var updatedSyntax = syntax.WithTokens(updatedTokens); return Trivia(updatedSyntax); } private static SyntaxToken ReplaceUnterminatedConstruct(SyntaxToken token) { if (token.IsVerbatimStringLiteral()) { var tokenText = token.ToString(); if (tokenText.Length <= 2 || tokenText.Last() != '"') { tokenText += '"'; return Literal(token.LeadingTrivia, tokenText, token.ValueText, token.TrailingTrivia); } } else if (token.IsRegularStringLiteral()) { var tokenText = token.ToString(); if (tokenText.Length <= 1 || tokenText.Last() != '"') { tokenText += '"'; return Literal(token.LeadingTrivia, tokenText, token.ValueText, token.TrailingTrivia); } } return token; } public static MemberDeclarationSyntax? FirstMember(SyntaxList<MemberDeclarationSyntax> members) => members.FirstOrDefault(); public static MemberDeclarationSyntax? FirstMethod(SyntaxList<MemberDeclarationSyntax> members) => members.FirstOrDefault(m => m is MethodDeclarationSyntax); public static MemberDeclarationSyntax? LastField(SyntaxList<MemberDeclarationSyntax> members) => members.LastOrDefault(m => m is FieldDeclarationSyntax); public static MemberDeclarationSyntax? LastConstructor(SyntaxList<MemberDeclarationSyntax> members) => members.LastOrDefault(m => m is ConstructorDeclarationSyntax); public static MemberDeclarationSyntax? LastMethod(SyntaxList<MemberDeclarationSyntax> members) => members.LastOrDefault(m => m is MethodDeclarationSyntax); public static MemberDeclarationSyntax? LastOperator(SyntaxList<MemberDeclarationSyntax> members) => members.LastOrDefault(m => m is OperatorDeclarationSyntax or ConversionOperatorDeclarationSyntax); public static SyntaxList<TDeclaration> Insert<TDeclaration>( SyntaxList<TDeclaration> declarationList, TDeclaration declaration, CSharpCodeGenerationContextInfo info, IList<bool>? availableIndices, Func<SyntaxList<TDeclaration>, TDeclaration?>? after = null, Func<SyntaxList<TDeclaration>, TDeclaration?>? before = null) where TDeclaration : SyntaxNode { var index = GetInsertionIndex( declarationList, declaration, info, availableIndices, CSharpDeclarationComparer.WithoutNamesInstance, CSharpDeclarationComparer.WithNamesInstance, after, before); availableIndices?.Insert(index, true); if (index != 0 && declarationList[index - 1].ContainsDiagnostics && AreBracesMissing(declarationList[index - 1])) { return declarationList.Insert(index, declaration.WithLeadingTrivia(ElasticCarriageReturnLineFeed)); } return declarationList.Insert(index, declaration); } private static bool AreBracesMissing<TDeclaration>(TDeclaration declaration) where TDeclaration : SyntaxNode => declaration.ChildTokens().Where(t => t.Kind() is SyntaxKind.OpenBraceToken or SyntaxKind.CloseBraceToken && t.IsMissing).Any(); public static SyntaxNode? GetContextNode( Location location, CancellationToken cancellationToken) { var contextLocation = location; var contextTree = contextLocation != null && contextLocation.IsInSource ? contextLocation.SourceTree : null; return contextTree?.GetRoot(cancellationToken).FindToken(contextLocation!.SourceSpan.Start).Parent; } public static ExplicitInterfaceSpecifierSyntax? GenerateExplicitInterfaceSpecifier( IEnumerable<ISymbol> implementations) { var implementation = implementations.FirstOrDefault(); if (implementation == null) { return null; } if (implementation.ContainingType.GenerateTypeSyntax() is not NameSyntax name) { return null; } return ExplicitInterfaceSpecifier(name); } public static CodeGenerationDestination GetDestination(SyntaxNode destination) { if (destination != null) { return destination.Kind() switch { SyntaxKind.ClassDeclaration => CodeGenerationDestination.ClassType, SyntaxKind.CompilationUnit => CodeGenerationDestination.CompilationUnit, SyntaxKind.EnumDeclaration => CodeGenerationDestination.EnumType, SyntaxKind.InterfaceDeclaration => CodeGenerationDestination.InterfaceType, SyntaxKind.FileScopedNamespaceDeclaration => CodeGenerationDestination.Namespace, SyntaxKind.NamespaceDeclaration => CodeGenerationDestination.Namespace, SyntaxKind.StructDeclaration => CodeGenerationDestination.StructType, _ => CodeGenerationDestination.Unspecified, }; } return CodeGenerationDestination.Unspecified; } public static TSyntaxNode ConditionallyAddDocumentationCommentTo<TSyntaxNode>( TSyntaxNode node, ISymbol symbol, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode { if (!info.Context.GenerateDocumentationComments || node.GetLeadingTrivia().Any(t => t.IsDocComment())) { return node; } var result = TryGetDocumentationComment(symbol, "///", out var comment, cancellationToken) ? node.WithPrependedLeadingTrivia(ParseLeadingTrivia(comment)) .WithPrependedLeadingTrivia(ElasticMarker) : node; return result; } /// <summary> /// Try use the existing syntax node and generate a new syntax node for the given <param name="symbol"/>. /// Note: the returned syntax node might be modified, which means its parent information might be missing. /// </summary> public static T? GetReuseableSyntaxNodeForSymbol<T>(ISymbol symbol, CSharpCodeGenerationContextInfo info) where T : SyntaxNode { Contract.ThrowIfNull(symbol); if (info.Context.ReuseSyntax && symbol.DeclaringSyntaxReferences.Length == 1) { var reusableSyntaxNode = symbol.DeclaringSyntaxReferences[0].GetSyntax(); if (symbol is IFieldSymbol && typeof(T) == typeof(FieldDeclarationSyntax) && reusableSyntaxNode is VariableDeclaratorSyntax variableDeclaratorNode && reusableSyntaxNode.Parent is VariableDeclarationSyntax variableDeclarationNode && reusableSyntaxNode.Parent.Parent is FieldDeclarationSyntax fieldDeclarationNode) { return RemoveLeadingDirectiveTrivia( fieldDeclarationNode.WithDeclaration( variableDeclarationNode.WithVariables([variableDeclaratorNode]))) as T; } return RemoveLeadingDirectiveTrivia(reusableSyntaxNode) as T; } return null; } } |