/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpUseCollectionExpressionForFluentCodeFixProvider.cs
195 строк
10 KB
Cyrus Najmabadi
Merge remote-tracking branch 'upstream/features/collection-expression-arguments' into portIDEFeatures2
12 дек 2025, 14:45
12 дек 2025, 14:45
8598aea
Код
Авторство
О чём код?
// 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.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UseCollectionExpression; using Microsoft.CodeAnalysis.UseCollectionInitializer; namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; using static CSharpCollectionExpressionRewriter; using static CSharpSyntaxTokens; using static CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer; using static SyntaxFactory; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseCollectionExpressionForFluent), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed partial class CSharpUseCollectionExpressionForFluentCodeFixProvider() : AbstractUseCollectionExpressionCodeFixProvider<InvocationExpressionSyntax>( CSharpCodeFixesResources.Use_collection_expression, IDEDiagnosticIds.UseCollectionExpressionForFluentDiagnosticId) { public override ImmutableArray<string> FixableDiagnosticIds { get; } = [IDEDiagnosticIds.UseCollectionExpressionForFluentDiagnosticId]; protected override async Task FixAsync( Document document, SyntaxEditor editor, InvocationExpressionSyntax invocationExpression, ImmutableDictionary<string, string?> properties, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var state = new UpdateExpressionState<ExpressionSyntax, StatementSyntax>( semanticModel, syntaxFacts, invocationExpression, valuePattern: default, initializedSymbol: null); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var expressionType = semanticModel.Compilation.ExpressionOfTType(); if (AnalyzeInvocation(text, state, invocationExpression, expressionType, allowSemanticsChange: true, addMatches: true, cancellationToken) is not { } analysisResult) return; // We want to replace `new[] { 1, 2, 3 }.Concat(x).Add(y).ToArray()` with the new collection expression. To do // this, we go through the following steps. First, we replace the whole expression with `new(x, y) { 1, 2, 3 }` // (a dummy object creation expression). We then call into our helper which replaces expressions with collection // expressions. The reason for the dummy object creation expression is that it serves as an actual node the // rewriting code can attach an initializer to, by which it can figure out appropriate wrapping and indentation // for the collection expression elements. var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); // Get the expressions that we're going to fill the new collection expression with. var allMatches = analysisResult.PreMatches.Concat(analysisResult.PostMatches); var arguments = await GetArgumentsAsync(document, allMatches, cancellationToken).ConfigureAwait(false); var argumentListTrailingTrivia = analysisResult.ExistingInitializer is null ? default : analysisResult.ExistingInitializer.GetFirstToken().GetPreviousToken().TrailingTrivia; var dummyObjectAnnotation = new SyntaxAnnotation(); var dummyObjectCreation = ImplicitObjectCreationExpression( ArgumentList(arguments).WithTrailingTrivia(argumentListTrailingTrivia), initializer: analysisResult.ExistingInitializer) .WithTriviaFrom(invocationExpression) .WithAdditionalAnnotations(dummyObjectAnnotation); var newSemanticDocument = await semanticDocument.WithSyntaxRootAsync( semanticDocument.Root.ReplaceNode(invocationExpression, dummyObjectCreation), cancellationToken).ConfigureAwait(false); dummyObjectCreation = (ImplicitObjectCreationExpressionSyntax)newSemanticDocument.Root.GetAnnotatedNodes(dummyObjectAnnotation).Single(); var preMatches = CreateMatches(dummyObjectCreation.ArgumentList.Arguments, analysisResult.PreMatches, index: 0); var postMatches = CreateMatches(dummyObjectCreation.ArgumentList.Arguments, analysisResult.PostMatches, index: preMatches.Length); var collectionExpression = await CreateCollectionExpressionAsync( newSemanticDocument.Document, dummyObjectCreation, preMatches, postMatches, static o => o.Initializer, static (o, i) => o.WithInitializer(i), cancellationToken).ConfigureAwait(false); editor.ReplaceNode(invocationExpression, collectionExpression); static ImmutableArray<CollectionMatch<ExpressionSyntax>> CreateMatches( SeparatedSyntaxList<ArgumentSyntax> arguments, ImmutableArray<CollectionMatch<ArgumentSyntax>> matches, int index) { using var result = TemporaryArray<CollectionMatch<ExpressionSyntax>>.Empty; for (int i = 0, n = matches.Length; i < n; i++) { var match = matches[i]; var argument = arguments[i + index]; // If we're going to spread a collection expression, just take the values *within* that collection expression // and make them arguments to the collection expression we're creating. if (match.UseSpread && argument.Expression is CollectionExpressionSyntax collectionExpression) { foreach (var element in collectionExpression.Elements) { if (element is SpreadElementSyntax spreadElement) { result.Add(new(spreadElement.Expression, UseSpread: true, UseKeyValue: false)); } else if (element is ExpressionElementSyntax expressionElement) { result.Add(new(expressionElement.Expression, UseSpread: false, UseKeyValue: false)); } } } else { result.Add(new(argument.Expression, match.UseSpread, UseKeyValue: false)); } } return result.ToImmutableAndClear(); } static async Task<SeparatedSyntaxList<ArgumentSyntax>> GetArgumentsAsync( Document document, ImmutableArray<CollectionMatch<ArgumentSyntax>> matches, CancellationToken cancellationToken) { if (matches.IsEmpty) return default; var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder<SyntaxNodeOrToken>.GetInstance(out var nodesAndTokens); // Get the first argument. If it was originally like `Add(arg)` then keep it in that form in `new(arg)`. // However, if it was on it's own line originally, then preserve that in the new form as well. AddOriginallyFirstArgument(matches[0].Node); // Now go through and add the rest of the arguments. for (int i = 1, n = matches.Length; i < n; i++) { var argument = matches[i].Node; var argumentList = (ArgumentListSyntax)argument.GetRequiredParent(); var originalArgumentListChildren = argumentList.Arguments.GetWithSeparators(); var index = originalArgumentListChildren.IndexOf(argument); // if this was not the first argument in its original list. for example: `.Add(1, 2, 3)`, then add its // preceding comma as well. That way we preserve its original relationship in the rewritten code. if (index > 0) { nodesAndTokens.Add(originalArgumentListChildren[index - 1]); nodesAndTokens.Add(argument); } else { nodesAndTokens.Add(CommaToken.WithoutTrivia()); AddOriginallyFirstArgument(argument); } } return SeparatedList<ArgumentSyntax>(nodesAndTokens); void AddOriginallyFirstArgument(ArgumentSyntax firstArgument) { var firstToken = firstArgument.GetFirstToken(); if (text.AreOnSameLine(firstToken.GetPreviousToken(), firstToken)) { nodesAndTokens.Add(firstArgument); } else { nodesAndTokens.Add(firstArgument.WithPrependedLeadingTrivia(EndOfLine(formattingOptions.NewLine))); } } } } }