File: System\Windows\Forms\CSharp\CodeFixes\AddDesignerSerializationVisibility\AddDesignerSerializationVisibilityCodeFixProvider.cs
Web Access
Project: src\src\System.Windows.Forms.Analyzers.CodeFixes.CSharp\System.Windows.Forms.Analyzers.CodeFixes.CSharp.csproj (System.Windows.Forms.Analyzers.CodeFixes.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Immutable;
using System.Composition;
using System.Windows.Forms.Analyzers.CodeFixes.Resources;
using System.Windows.Forms.Analyzers.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
 
namespace System.Windows.Forms.CSharp.CodeFixes.AddDesignerSerializationVisibility;
 
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddDesignerSerializationVisibilityCodeFixProvider)), Shared]
internal sealed class AddDesignerSerializationVisibilityCodeFixProvider : CodeFixProvider
{
    private const string SystemComponentModelName = "System.ComponentModel";
    private const string DesignerSerializationVisibilityAttributeName = "DesignerSerializationVisibility";
 
    public sealed override ImmutableArray<string> FixableDiagnosticIds
        => [DiagnosticIDs.MissingPropertySerializationConfiguration];
 
    public sealed override FixAllProvider GetFixAllProvider()
        => WellKnownFixAllProviders.BatchFixer;
 
    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        // Cannot be null - otherwise we wouldn't have a diagnostic of that ID.
        SyntaxNode root = (await context
            .Document
            .GetSyntaxRootAsync(context.CancellationToken)
            .ConfigureAwait(false))!;
 
        Diagnostic diagnostic = context.Diagnostics.First();
        TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;
 
        // Find the type declaration identified by the diagnostic.
        PropertyDeclarationSyntax? propertyDeclaration = root.FindToken(diagnosticSpan.Start)
            .Parent!
            .AncestorsAndSelf()
            .OfType<PropertyDeclarationSyntax>()
            .First();
 
        // Register a code action that will invoke the fix.
        context.RegisterCodeFix(
            CodeAction.Create(
                title: SR.AddDesignerSerializationVisibilityCodeFixTitle,
                createChangedDocument: c => AddDesignerSerializationAttribute(context.Document, propertyDeclaration, c),
                equivalenceKey: nameof(SR.AddDesignerSerializationVisibilityCodeFixTitle)),
            diagnostic);
    }
 
    private static async Task<Document> AddDesignerSerializationAttribute(
        Document document,
        PropertyDeclarationSyntax propertyDeclarationSyntax,
        CancellationToken cancellationToken)
    {
        if (propertyDeclarationSyntax is null)
        {
            return document;
        }
 
        // Let's make sure, the attribute we want to add is not already there:
        if (propertyDeclarationSyntax.AttributeLists
            .SelectMany(al => al.Attributes)
            .Any(a => a.Name.ToString() == DesignerSerializationVisibilityAttributeName))
        {
            // Already there, nothing to do.
            return document;
        }
 
        // Let's check, if we already have the using directive or if we need to add it:
        // (Remember: We can't throw here, as we are in a code fixer. But this also cannot be null.)
        SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!;
 
        // Generate the Attribute we need to put on the property
        AttributeSyntax designerSerializationVisibilityAttribute = SyntaxFactory.Attribute(
            SyntaxFactory.ParseName(DesignerSerializationVisibilityAttributeName),
            SyntaxFactory.ParseAttributeArgumentList("(DesignerSerializationVisibility.Hidden)"));
 
        SyntaxTriviaList leadingTrivia = propertyDeclarationSyntax.GetLeadingTrivia();
        PropertyDeclarationSyntax newProperty = propertyDeclarationSyntax.WithoutLeadingTrivia();
 
        // Add the attribute to the property:
        newProperty = newProperty
            .AddAttributeLists(
                SyntaxFactory.AttributeList(
                    SyntaxFactory.SingletonSeparatedList(designerSerializationVisibilityAttribute)));
 
        // Add the leading trivia back:
        newProperty = newProperty.WithLeadingTrivia(leadingTrivia);
 
        // Produce a new root, which has the updated property with the attribute.
        root = root.ReplaceNode(propertyDeclarationSyntax, newProperty);
 
        // Let's check if we already have the using directive:
        if (root.DescendantNodes()
            .OfType<UsingDirectiveSyntax>()
            .Any(u => u?.Name?.ToString() == SystemComponentModelName))
        {
            document = document.WithSyntaxRoot(root);
            return document;
        }
 
        // Let's check if we have _a_ using directive:
        bool hasUsings = root.DescendantNodes()
            .OfType<UsingDirectiveSyntax>()
            .FirstOrDefault() is not null;
 
        // Get the compilation unit:
        CompilationUnitSyntax compilationUnit = root
            .DescendantNodesAndSelf()
            .OfType<CompilationUnitSyntax>()
            .First();
 
        CompilationUnitSyntax originalCompilationUnit = compilationUnit;
 
        if (!hasUsings)
        {
            // We need to add a new line before the namespace/file-scoped-namespace declaration:
            SyntaxTriviaList? firstNodesLeadingTrivia = compilationUnit
                .DescendantNodes()
                .FirstOrDefault()
                ?.GetLeadingTrivia();
 
            compilationUnit = firstNodesLeadingTrivia is null
                ? compilationUnit
                    .WithLeadingTrivia(SyntaxFactory.CarriageReturnLineFeed)
                : compilationUnit
                    .WithLeadingTrivia(firstNodesLeadingTrivia.Value
                        .Add(SyntaxFactory.CarriageReturnLineFeed));
        }
 
        UsingDirectiveSyntax usingDirective = SyntaxFactory
            .UsingDirective(SyntaxFactory.ParseName(SystemComponentModelName));
 
        usingDirective = usingDirective
            .NormalizeWhitespace()
            .WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed)
            .WithAdditionalAnnotations(Formatter.Annotation);
 
        // Generate the new document:
        document = document.WithSyntaxRoot(
            root.ReplaceNode(
                originalCompilationUnit,
                compilationUnit.AddUsings(usingDirective)));
 
        return document;
    }
}