/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpUseCollectionExpressionForBuilderCodeFixProvider.cs
131 строка
7 KB
Cyrus Najmabadi
fixup
12 дек 2025, 15:21
12 дек 2025, 15:21
5ce0b4a
Код
Авторство
О чём код?
// 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.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UseCollectionExpression; namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; using static CSharpCollectionExpressionRewriter; using static CSharpUseCollectionExpressionForBuilderDiagnosticAnalyzer; using static SyntaxFactory; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseCollectionExpressionForBuilder), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed partial class CSharpUseCollectionExpressionForBuilderCodeFixProvider() : AbstractUseCollectionExpressionCodeFixProvider<InvocationExpressionSyntax>( CSharpCodeFixesResources.Use_collection_expression, IDEDiagnosticIds.UseCollectionExpressionForBuilderDiagnosticId) { public override ImmutableArray<string> FixableDiagnosticIds { get; } = [IDEDiagnosticIds.UseCollectionExpressionForBuilderDiagnosticId]; protected override async Task FixAsync( Document document, SyntaxEditor editor, InvocationExpressionSyntax invocationExpression, ImmutableDictionary<string, string?> properties, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var expressionType = semanticModel.Compilation.ExpressionOfTType(); if (AnalyzeInvocation(semanticModel, invocationExpression, expressionType, allowSemanticsChange: true, cancellationToken) is not { } analysisResult) return; // We want to replace the final invocation (`builder.ToImmutable()`) with `new()`. That way we can call into // the collection-rewriter to swap out that object-creation with the new collection-expression. // First, mark all the nodes we care about, so we can find them once we do the replacement with the // object-creation expression. var dummyObjectAnnotation = new SyntaxAnnotation(); var newDocument = await CreateTrackedDocumentAsync( document, analysisResult, dummyObjectAnnotation, cancellationToken).ConfigureAwait(false); var root = await newDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var dummyObjectCreation = (ImplicitObjectCreationExpressionSyntax)root.GetAnnotatedNodes(dummyObjectAnnotation).Single(); // Move the original match over to this rewritten tree. analysisResult = TrackAnalysisResult(root, analysisResult); // Get the new collection expression. var collectionExpression = await CreateCollectionExpressionAsync( newDocument, dummyObjectCreation, preMatches: [], analysisResult.Matches, static o => o.Initializer, static (o, i) => o.WithInitializer(i), cancellationToken).ConfigureAwait(false); var subEditor = new SyntaxEditor(root, document.Project.Solution.Services); // Remove all the nodes mutating the builder. foreach (var (statement, _, _, _) in analysisResult.Matches) subEditor.RemoveNode(statement); // Remove the actual declaration of the builder. Keep any comments on the builder declaration in case they're // still valid for the final statement. var removalOptions = SyntaxRemoveOptions.KeepUnbalancedDirectives | SyntaxRemoveOptions.AddElasticMarker; if (analysisResult.LocalDeclarationStatement.GetLeadingTrivia().Any(t => t.IsSingleOrMultiLineComment())) removalOptions |= SyntaxRemoveOptions.KeepLeadingTrivia; subEditor.RemoveNode(analysisResult.LocalDeclarationStatement, removalOptions); // Finally, replace the invocation where we convert the builder to a collection with the new collection expression. subEditor.ReplaceNode(dummyObjectCreation, collectionExpression); editor.ReplaceNode(editor.OriginalRoot, subEditor.GetChangedRoot()); return; // Move the nodes in analysisResult over to the tracked result in the root passed in. static AnalysisResult TrackAnalysisResult(SyntaxNode root, AnalysisResult analysisResult) => new(analysisResult.DiagnosticLocation, root.GetCurrentNode(analysisResult.LocalDeclarationStatement)!, root.GetCurrentNode(analysisResult.CreationExpression)!, analysisResult.Matches.SelectAsArray(m => new CollectionMatch<SyntaxNode>(root.GetCurrentNode(m.Node)!, m.UseSpread, m.UseKeyValue)), analysisResult.ChangesSemantics); // Creates a new document with all of the relevant nodes in analysisResult tracked so that we can find them // across mutations we're making. static async Task<Document> CreateTrackedDocumentAsync( Document document, AnalysisResult analysisResult, SyntaxAnnotation annotation, CancellationToken cancellationToken) { using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var nodesToTrack); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); nodesToTrack.Add(analysisResult.LocalDeclarationStatement); nodesToTrack.Add(analysisResult.CreationExpression); foreach (var (statement, _, _, _) in analysisResult.Matches) nodesToTrack.Add(statement); var newRoot = root.TrackNodes(nodesToTrack); var creationExpression = newRoot.GetCurrentNode(analysisResult.CreationExpression)!; var dummyObjectCreation = ImplicitObjectCreationExpression() .WithTriviaFrom(creationExpression) .WithAdditionalAnnotations(annotation); var newDocument = document.WithSyntaxRoot(newRoot.ReplaceNode(creationExpression, dummyObjectCreation)); return newDocument; } } }