| File: System\Windows\Forms\CSharp\CodeFixes\AddDesignerSerializationVisibility\AddDesignerSerializationVisibilityCodeFixProvider.cs | Web Access |
| Project: src\winforms\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; } }