// 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.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeGeneration; internal abstract partial class AbstractCodeGenerationService<TCodeGenerationContextInfo> : ICodeGenerationService where TCodeGenerationContextInfo : CodeGenerationContextInfo { private readonly ISymbolDeclarationService _symbolDeclarationService; protected AbstractCodeGenerationService( LanguageServices languageServices) { LanguageServices = languageServices; _symbolDeclarationService = languageServices.GetRequiredService<ISymbolDeclarationService>(); } public LanguageServices LanguageServices { get; } public abstract CodeGenerationOptions DefaultOptions { get; } public abstract CodeGenerationOptions GetCodeGenerationOptions(IOptionsReader options); public abstract TCodeGenerationContextInfo GetInfo(CodeGenerationContext context, CodeGenerationOptions options, ParseOptions parseOptions); CodeGenerationContextInfo ICodeGenerationService.GetInfo(CodeGenerationContext context, CodeGenerationOptions options, ParseOptions parseOptions) => GetInfo(context, options, parseOptions); #region ICodeGenerationService public TDeclarationNode AddEvent<TDeclarationNode>(TDeclarationNode destination, IEventSymbol @event, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => WithAnnotations(AddEvent(destination, @event, (TCodeGenerationContextInfo)info, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), info); public TDeclarationNode AddField<TDeclarationNode>(TDeclarationNode destination, IFieldSymbol field, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => WithAnnotations(AddField(destination, field, (TCodeGenerationContextInfo)info, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), info); public TDeclarationNode AddMethod<TDeclarationNode>(TDeclarationNode destination, IMethodSymbol method, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => WithAnnotations(AddMethod(destination, method, (TCodeGenerationContextInfo)info, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), info); public TDeclarationNode AddProperty<TDeclarationNode>(TDeclarationNode destination, IPropertySymbol property, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => WithAnnotations(AddProperty(destination, property, (TCodeGenerationContextInfo)info, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), info); public TDeclarationNode AddNamedType<TDeclarationNode>(TDeclarationNode destination, INamedTypeSymbol namedType, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => WithAnnotations(AddNamedType(destination, namedType, (TCodeGenerationContextInfo)info, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), info); public TDeclarationNode AddNamespace<TDeclarationNode>(TDeclarationNode destination, INamespaceSymbol @namespace, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => WithAnnotations(AddNamespace(destination, @namespace, (TCodeGenerationContextInfo)info, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), info); public TDeclarationNode AddMembers<TDeclarationNode>(TDeclarationNode destination, IEnumerable<ISymbol> members, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => WithAnnotations(AddMembers(destination, members, GetAvailableInsertionIndices(destination, cancellationToken), (TCodeGenerationContextInfo)info, cancellationToken), info); private static TNode WithAnnotations<TNode>(TNode node, CodeGenerationContextInfo info) where TNode : SyntaxNode { return info.Context.AddImports ? node.WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) : node; } public SyntaxNode CreateEventDeclaration(IEventSymbol @event, CodeGenerationDestination destination, CodeGenerationContextInfo info, CancellationToken cancellationToken) => CreateEventDeclaration(@event, destination, (TCodeGenerationContextInfo)info, cancellationToken); public SyntaxNode CreateFieldDeclaration(IFieldSymbol field, CodeGenerationDestination destination, CodeGenerationContextInfo info, CancellationToken cancellationToken) => CreateFieldDeclaration(field, destination, (TCodeGenerationContextInfo)info, cancellationToken); public SyntaxNode? CreateMethodDeclaration(IMethodSymbol method, CodeGenerationDestination destination, CodeGenerationContextInfo info, CancellationToken cancellationToken) => CreateMethodDeclaration(method, destination, (TCodeGenerationContextInfo)info, cancellationToken); public SyntaxNode CreatePropertyDeclaration(IPropertySymbol property, CodeGenerationDestination destination, CodeGenerationContextInfo info, CancellationToken cancellationToken) => CreatePropertyDeclaration(property, destination, (TCodeGenerationContextInfo)info, cancellationToken); public SyntaxNode CreateNamedTypeDeclaration(INamedTypeSymbol namedType, CodeGenerationDestination destination, CodeGenerationContextInfo info, CancellationToken cancellationToken) => CreateNamedTypeDeclaration(namedType, destination, (TCodeGenerationContextInfo)info, cancellationToken); public SyntaxNode CreateNamespaceDeclaration(INamespaceSymbol @namespace, CodeGenerationDestination destination, CodeGenerationContextInfo info, CancellationToken cancellationToken) => CreateNamespaceDeclaration(@namespace, destination, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode AddParameters<TDeclarationNode>(TDeclarationNode destination, IEnumerable<IParameterSymbol> parameters, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => AddParameters(destination, parameters, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode AddAttributes<TDeclarationNode>(TDeclarationNode destination, IEnumerable<AttributeData> attributes, SyntaxToken? target, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => AddAttributes(destination, attributes, target, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode RemoveAttribute<TDeclarationNode>(TDeclarationNode destination, SyntaxNode attributeToRemove, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => RemoveAttribute(destination, attributeToRemove, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode RemoveAttribute<TDeclarationNode>(TDeclarationNode destination, AttributeData attributeToRemove, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => RemoveAttribute(destination, attributeToRemove, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode UpdateDeclarationModifiers<TDeclarationNode>(TDeclarationNode declaration, IEnumerable<SyntaxToken> newModifiers, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => UpdateDeclarationModifiers(declaration, newModifiers, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode UpdateDeclarationAccessibility<TDeclarationNode>(TDeclarationNode declaration, Accessibility newAccessibility, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => UpdateDeclarationAccessibility(declaration, newAccessibility, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode UpdateDeclarationType<TDeclarationNode>(TDeclarationNode declaration, ITypeSymbol newType, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => UpdateDeclarationType(declaration, newType, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode UpdateDeclarationMembers<TDeclarationNode>(TDeclarationNode declaration, IList<ISymbol> newMembers, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => UpdateDeclarationMembers(declaration, newMembers, (TCodeGenerationContextInfo)info, cancellationToken); public TDeclarationNode AddStatements<TDeclarationNode>(TDeclarationNode destination, IEnumerable<SyntaxNode> statements, CodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode => AddStatements(destination, statements, (TCodeGenerationContextInfo)info, cancellationToken); #endregion protected abstract TDeclarationNode AddEvent<TDeclarationNode>(TDeclarationNode destination, IEventSymbol @event, TCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; protected abstract TDeclarationNode AddField<TDeclarationNode>(TDeclarationNode destination, IFieldSymbol field, TCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; protected abstract TDeclarationNode AddMethod<TDeclarationNode>(TDeclarationNode destination, IMethodSymbol method, TCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; protected abstract TDeclarationNode AddProperty<TDeclarationNode>(TDeclarationNode destination, IPropertySymbol property, TCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; protected abstract TDeclarationNode AddNamedType<TDeclarationNode>(TDeclarationNode destination, INamedTypeSymbol namedType, TCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; protected abstract TDeclarationNode AddNamespace<TDeclarationNode>(TDeclarationNode destination, INamespaceSymbol @namespace, TCodeGenerationContextInfo info, IList<bool>? availableIndices, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; protected abstract TDeclarationNode AddMembers<TDeclarationNode>(TDeclarationNode destination, IEnumerable<SyntaxNode> members) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode AddParameters<TDeclarationNode>(TDeclarationNode destinationMember, IEnumerable<IParameterSymbol> parameters, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode AddAttributes<TDeclarationNode>(TDeclarationNode destination, IEnumerable<AttributeData> attributes, SyntaxToken? target, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode RemoveAttribute<TDeclarationNode>(TDeclarationNode destination, SyntaxNode attributeToRemove, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode RemoveAttribute<TDeclarationNode>(TDeclarationNode destination, AttributeData attributeToRemove, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode AddStatements<TDeclarationNode>(TDeclarationNode destinationMember, IEnumerable<SyntaxNode> statements, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode UpdateDeclarationModifiers<TDeclarationNode>(TDeclarationNode declaration, IEnumerable<SyntaxToken> newModifiers, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode UpdateDeclarationAccessibility<TDeclarationNode>(TDeclarationNode declaration, Accessibility newAccessibility, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode UpdateDeclarationType<TDeclarationNode>(TDeclarationNode declaration, ITypeSymbol newType, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract TDeclarationNode UpdateDeclarationMembers<TDeclarationNode>(TDeclarationNode declaration, IList<ISymbol> newMembers, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode; public abstract CodeGenerationDestination GetDestination(SyntaxNode node); public abstract SyntaxNode CreateEventDeclaration(IEventSymbol @event, CodeGenerationDestination destination, TCodeGenerationContextInfo info, CancellationToken cancellationToken); public abstract SyntaxNode CreateFieldDeclaration(IFieldSymbol field, CodeGenerationDestination destination, TCodeGenerationContextInfo info, CancellationToken cancellationToken); // TODO: Change to not return null (https://github.com/dotnet/roslyn/issues/58243) public abstract SyntaxNode? CreateMethodDeclaration(IMethodSymbol method, CodeGenerationDestination destination, TCodeGenerationContextInfo info, CancellationToken cancellationToken); public abstract SyntaxNode CreatePropertyDeclaration(IPropertySymbol property, CodeGenerationDestination destination, TCodeGenerationContextInfo info, CancellationToken cancellationToken); public abstract SyntaxNode CreateNamedTypeDeclaration(INamedTypeSymbol namedType, CodeGenerationDestination destination, TCodeGenerationContextInfo info, CancellationToken cancellationToken); public abstract SyntaxNode CreateNamespaceDeclaration(INamespaceSymbol @namespace, CodeGenerationDestination destination, TCodeGenerationContextInfo info, CancellationToken cancellationToken); protected static T Cast<T>(object value) => (T)value; protected static void CheckDeclarationNode<TDeclarationNode>(SyntaxNode destination) where TDeclarationNode : SyntaxNode { if (destination == null) { throw new ArgumentNullException(nameof(destination)); } if (destination is not TDeclarationNode) { throw new ArgumentException( string.Format(WorkspaceExtensionsResources.Destination_type_must_be_a_0_but_given_one_is_1, typeof(TDeclarationNode).Name, destination.GetType().Name), nameof(destination)); } } protected static void CheckDeclarationNode<TDeclarationNode1, TDeclarationNode2>(SyntaxNode destination) where TDeclarationNode1 : SyntaxNode where TDeclarationNode2 : SyntaxNode { if (destination == null) { throw new ArgumentNullException(nameof(destination)); } if (destination is not TDeclarationNode1 and not TDeclarationNode2) { throw new ArgumentException( string.Format(WorkspaceExtensionsResources.Destination_type_must_be_a_0_or_a_1_but_given_one_is_2, typeof(TDeclarationNode1).Name, typeof(TDeclarationNode2).Name, destination.GetType().Name), nameof(destination)); } } protected static void CheckDeclarationNode<TDeclarationNode1, TDeclarationNode2, TDeclarationNode3>(SyntaxNode destination) where TDeclarationNode1 : SyntaxNode where TDeclarationNode2 : SyntaxNode where TDeclarationNode3 : SyntaxNode { if (destination == null) { throw new ArgumentNullException(nameof(destination)); } if (destination is not TDeclarationNode1 and not TDeclarationNode2 and not TDeclarationNode3) { throw new ArgumentException( string.Format(WorkspaceExtensionsResources.Destination_type_must_be_a_0_1_or_2_but_given_one_is_3, typeof(TDeclarationNode1).Name, typeof(TDeclarationNode2).Name, typeof(TDeclarationNode3).Name, destination.GetType().Name), nameof(destination)); } } protected static void CheckDeclarationNode<TDeclarationNode1, TDeclarationNode2, TDeclarationNode3, TDeclarationNode4>(SyntaxNode destination) where TDeclarationNode1 : SyntaxNode where TDeclarationNode2 : SyntaxNode where TDeclarationNode3 : SyntaxNode where TDeclarationNode4 : SyntaxNode { if (destination is not TDeclarationNode1 and not TDeclarationNode2 and not TDeclarationNode3 and not TDeclarationNode4) { throw new ArgumentException( string.Format(WorkspaceExtensionsResources.Destination_type_must_be_a_0_1_2_or_3_but_given_one_is_4, typeof(TDeclarationNode1).Name, typeof(TDeclarationNode2).Name, typeof(TDeclarationNode3).Name, typeof(TDeclarationNode4).Name, destination.GetType().Name), nameof(destination)); } } private async Task<Document> GetEditAsync( CodeGenerationSolutionContext context, INamespaceOrTypeSymbol destination, Func<SyntaxNode, TCodeGenerationContextInfo, IList<bool>?, CancellationToken, SyntaxNode> declarationTransform, CancellationToken cancellationToken) { var (destinationDeclaration, availableIndices) = FindMostRelevantDeclaration(context.Solution, destination, context.Context.BestLocation, cancellationToken); if (destinationDeclaration == null) throw new ArgumentException(WorkspaceExtensionsResources.Could_not_find_location_to_generation_symbol_into); var destinationTree = destinationDeclaration.SyntaxTree; var oldDocument = context.Solution.GetRequiredDocument(destinationTree); var codeGenOptions = await oldDocument.GetCodeGenerationOptionsAsync(cancellationToken).ConfigureAwait(false); var info = GetInfo(context.Context, codeGenOptions, destinationDeclaration.SyntaxTree.Options); var transformedDeclaration = declarationTransform(destinationDeclaration, info, availableIndices, cancellationToken); var root = await destinationTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var currentRoot = root.ReplaceNode(destinationDeclaration, transformedDeclaration); var newDocument = oldDocument.WithSyntaxRoot(currentRoot); if (context.Context.AddImports) { var addImportsOptions = await newDocument.GetAddImportPlacementOptionsAsync(cancellationToken).ConfigureAwait(false); var service = newDocument.GetRequiredLanguageService<ImportAdderService>(); newDocument = await service.AddImportsAsync( newDocument, [currentRoot.FullSpan], ImportAdderService.Strategy.AddImportsFromSymbolAnnotations, addImportsOptions, cancellationToken).ConfigureAwait(false); } return newDocument; } protected TDeclarationNode AddMembers<TDeclarationNode>( TDeclarationNode destination, IEnumerable<ISymbol> members, IList<bool>? availableIndices, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode { var membersList = members.ToList(); if (membersList.Count > 1) { info = CreateContextInfoForMultipleMembers(info); } // Filter out the members that are implicitly declared. They're implicit, hence we do // not want an explicit declaration. The only exception are fields generated from implicit tuple fields. var filteredMembers = membersList.Where(m => !m.IsImplicitlyDeclared || m.IsTupleField()); return info.Context.AutoInsertionLocation ? AddMembersToAppropriateLocationInDestination(destination, filteredMembers, availableIndices, info, cancellationToken) : AddMembersToEndOfDestination(destination, filteredMembers, info, cancellationToken); } private TDeclarationSyntax AddMembersToEndOfDestination<TDeclarationSyntax>( TDeclarationSyntax destination, IEnumerable<ISymbol> members, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationSyntax : SyntaxNode { var newMembers = new List<SyntaxNode>(); var codeGenerationDestination = GetDestination(destination); foreach (var member in members) { cancellationToken.ThrowIfCancellationRequested(); var newMember = GetNewMember(info, codeGenerationDestination, member, cancellationToken); if (newMember != null) { newMembers.Add(newMember); } } // Metadata as source generates complete declarations and doesn't modify // existing ones. We can take the members to generate, sort them once, // and then add them in that order to the end of the destination. if (!GeneratingEnum(members) && info.Context.SortMembers) { newMembers.Sort(GetMemberComparer()); } return this.AddMembers(destination, newMembers); } private TDeclarationSyntax AddMembersToAppropriateLocationInDestination<TDeclarationSyntax>( TDeclarationSyntax destination, IEnumerable<ISymbol> members, IList<bool>? availableIndices, TCodeGenerationContextInfo info, CancellationToken cancellationToken) where TDeclarationSyntax : SyntaxNode { var currentDestination = destination; foreach (var member in members) { cancellationToken.ThrowIfCancellationRequested(); currentDestination = UpdateDestination(availableIndices, info, currentDestination, member, cancellationToken); } return currentDestination; } private SyntaxNode? GetNewMember(TCodeGenerationContextInfo info, CodeGenerationDestination codeGenerationDestination, ISymbol member, CancellationToken cancellationToken) => member switch { IEventSymbol @event => CreateEventDeclaration(@event, codeGenerationDestination, info, cancellationToken), IFieldSymbol field => CreateFieldDeclaration(field, codeGenerationDestination, info, cancellationToken), IPropertySymbol property => CreatePropertyDeclaration(property, codeGenerationDestination, info, cancellationToken), IMethodSymbol method => CreateMethodDeclaration(method, codeGenerationDestination, info, cancellationToken), INamedTypeSymbol namedType => CreateNamedTypeDeclaration(namedType, codeGenerationDestination, info, cancellationToken), INamespaceSymbol @namespace => CreateNamespaceDeclaration(@namespace, codeGenerationDestination, info, cancellationToken), _ => null, }; private TDeclarationNode UpdateDestination<TDeclarationNode>( IList<bool>? availableIndices, TCodeGenerationContextInfo info, TDeclarationNode currentDestination, ISymbol member, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode { return member switch { IEventSymbol @event => AddEvent(currentDestination, @event, info, availableIndices, cancellationToken), IFieldSymbol field => AddField(currentDestination, field, info, availableIndices, cancellationToken), IPropertySymbol property => AddProperty(currentDestination, property, info, availableIndices, cancellationToken), IMethodSymbol method => AddMethod(currentDestination, method, info, availableIndices, cancellationToken), INamedTypeSymbol namedType => AddNamedType(currentDestination, namedType, info, availableIndices, cancellationToken), INamespaceSymbol @namespace => AddNamespace(currentDestination, @namespace, info, availableIndices, cancellationToken), _ => currentDestination, }; } private static bool GeneratingEnum(IEnumerable<ISymbol> members) { var field = members.OfType<IFieldSymbol>().FirstOrDefault(); return field != null && field.ContainingType.IsEnumType(); } protected abstract IComparer<SyntaxNode> GetMemberComparer(); protected static TCodeGenerationContextInfo CreateContextInfoForMultipleMembers(TCodeGenerationContextInfo info) { // For now we ignore the afterThisLocation/beforeThisLocation if we're adding // multiple members. In the future it would be nice to appropriately handle this. // The difficulty lies with ensuring that we properly understand the position we're // inserting into, even as we change the type by adding multiple members. Not // impossible to figure out, but out of scope right now. return (TCodeGenerationContextInfo)info.WithContext(info.Context.With(afterThisLocation: null, beforeThisLocation: null)); } public virtual Task<Document> AddEventAsync( CodeGenerationSolutionContext context, INamedTypeSymbol destination, IEventSymbol @event, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddEvent(t, @event, opts, ai, ct), cancellationToken); } public Task<Document> AddFieldAsync(CodeGenerationSolutionContext context, INamedTypeSymbol destination, IFieldSymbol field, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddField(t, field, opts, ai, ct), cancellationToken); } public Task<Document> AddPropertyAsync(CodeGenerationSolutionContext context, INamedTypeSymbol destination, IPropertySymbol property, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddProperty(t, property, opts, ai, ct), cancellationToken); } public Task<Document> AddNamedTypeAsync(CodeGenerationSolutionContext context, INamedTypeSymbol destination, INamedTypeSymbol namedType, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddNamedType(t, namedType, opts, ai, ct), cancellationToken); } public Task<Document> AddNamedTypeAsync(CodeGenerationSolutionContext context, INamespaceSymbol destination, INamedTypeSymbol namedType, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddNamedType(t, namedType, opts, ai, ct), cancellationToken); } public Task<Document> AddNamespaceAsync(CodeGenerationSolutionContext context, INamespaceSymbol destination, INamespaceSymbol @namespace, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddNamespace(t, @namespace, opts, ai, ct), cancellationToken); } public Task<Document> AddMethodAsync(CodeGenerationSolutionContext context, INamedTypeSymbol destination, IMethodSymbol method, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddMethod(t, method, opts, ai, ct), cancellationToken); } public Task<Document> AddMembersAsync(CodeGenerationSolutionContext context, INamedTypeSymbol destination, IEnumerable<ISymbol> members, CancellationToken cancellationToken) { return GetEditAsync( context, destination, (t, opts, ai, ct) => AddMembers(t, members, ai, opts, ct), cancellationToken); } public Task<Document> AddNamespaceOrTypeAsync(CodeGenerationSolutionContext context, INamespaceSymbol destination, INamespaceOrTypeSymbol namespaceOrType, CancellationToken cancellationToken) { if (namespaceOrType == null) { throw new ArgumentNullException(nameof(namespaceOrType)); } if (namespaceOrType is INamespaceSymbol namespaceSymbol) { return AddNamespaceAsync(context, destination, namespaceSymbol, cancellationToken); } else { return AddNamedTypeAsync(context, destination, (INamedTypeSymbol)namespaceOrType, cancellationToken); } } protected static void CheckLocation(SyntaxNode destinationMember, [NotNull] Location? location) { if (location == null) { throw new ArgumentException(WorkspaceExtensionsResources.No_location_provided_to_add_statements_to); } if (!location.IsInSource) { throw new ArgumentException(WorkspaceExtensionsResources.Destination_location_was_not_in_source); } if (location.SourceTree != destinationMember.SyntaxTree) { throw new ArgumentException(WorkspaceExtensionsResources.Destination_location_was_from_a_different_tree); } } protected static void ComputePositionAndTriviaForRemoveAttributeList( SyntaxNode attributeList, Func<SyntaxTrivia, bool> isEndOfLineTrivia, out int positionOfRemovedNode, out IEnumerable<SyntaxTrivia> triviaOfRemovedNode) { positionOfRemovedNode = attributeList.FullSpan.Start; var leading = attributeList.GetLeadingTrivia(); var trailing = attributeList.GetTrailingTrivia(); if (trailing.Count >= 1 && isEndOfLineTrivia(trailing.Last())) { // Remove redundant trailing trivia as we are removing the entire attribute list. triviaOfRemovedNode = leading; } else { triviaOfRemovedNode = leading.Concat(trailing); } } protected static void ComputePositionAndTriviaForRemoveAttributeFromAttributeList( SyntaxNode attributeToRemove, Func<SyntaxToken, bool> isComma, out int positionOfRemovedNode, out IEnumerable<SyntaxTrivia> triviaOfRemovedNode) { positionOfRemovedNode = attributeToRemove.FullSpan.Start; var root = attributeToRemove.SyntaxTree.GetRoot(); var previousToken = root.FindToken(attributeToRemove.FullSpan.Start - 1); var leading = isComma(previousToken) ? previousToken.LeadingTrivia : attributeToRemove.GetLeadingTrivia(); var nextToken = root.FindToken(attributeToRemove.FullSpan.End + 1); var trailing = isComma(nextToken) ? nextToken.TrailingTrivia : attributeToRemove.GetTrailingTrivia(); triviaOfRemovedNode = leading.Concat(trailing); } protected static T AppendTriviaAtPosition<T>(T node, int position, SyntaxTriviaList trivia) where T : SyntaxNode { if (trivia.Any()) { var tokenToInsertTrivia = node.FindToken(position); var tokenWithInsertedTrivia = tokenToInsertTrivia.WithLeadingTrivia(trivia.Concat(tokenToInsertTrivia.LeadingTrivia)); return node.ReplaceToken(tokenToInsertTrivia, tokenWithInsertedTrivia); } return node; } protected static SyntaxTokenList GetUpdatedDeclarationAccessibilityModifiers( ArrayBuilder<SyntaxToken> newModifierTokens, SyntaxTokenList modifiersList, Func<SyntaxToken, bool> isAccessibilityModifier) { using var _ = ArrayBuilder<SyntaxToken>.GetInstance(out var updatedModifiersList); var anyAccessModifierSeen = false; foreach (var modifier in modifiersList) { SyntaxToken newModifier; if (isAccessibilityModifier(modifier)) { if (newModifierTokens.Count == 0) continue; newModifier = newModifierTokens[0] .WithLeadingTrivia(modifier.LeadingTrivia) .WithTrailingTrivia(modifier.TrailingTrivia); newModifierTokens.RemoveAt(0); anyAccessModifierSeen = true; } else { if (anyAccessModifierSeen && newModifierTokens.Any()) { updatedModifiersList.AddRange(newModifierTokens); newModifierTokens.Clear(); } newModifier = modifier; } updatedModifiersList.Add(newModifier); } if (!anyAccessModifierSeen) { for (var i = newModifierTokens.Count - 1; i >= 0; i--) updatedModifiersList.Insert(0, newModifierTokens[i]); } else { updatedModifiersList.AddRange(newModifierTokens); } return [.. updatedModifiersList]; } } |