Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/AutoCtor.CodeFixes/AddAutoConstructCodeFixer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Composition;
Comment on lines +1 to +6

namespace AutoCtor.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class AddAutoConstructCodeFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(Diagnostics.ACTR008_AddAutoConstruct.Id);

public override FixAllProvider? GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
.ConfigureAwait(false);
if (root is null)
return;

var token = root.FindToken(context.Diagnostics[0].Location.SourceSpan.Start);
if (token.Parent is not TypeDeclarationSyntax typeDeclaration)
return;

context.RegisterCodeFix(
CodeAction.Create(
title: "Add [AutoConstruct]",
createChangedDocument: ct => ApplyFixAsync(context.Document, typeDeclaration, ct),
equivalenceKey: "AddAutoConstruct"),
context.Diagnostics[0]);
}

private static async Task<Document> ApplyFixAsync(
Document document,
TypeDeclarationSyntax typeDeclaration,
CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken)
.ConfigureAwait(false);

if (root is null)
return document;

root = (CompilationUnitSyntax)new AutoCtorRewriter(typeDeclaration, null)
.Visit(root);
return document.WithSyntaxRoot(root);
}
}
13 changes: 3 additions & 10 deletions src/AutoCtor.CodeFixes/AutoCtor.CodeFixes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@
<PackageProjectUrl>https://github.com/distantcam/AutoCtor</PackageProjectUrl>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;RS2008</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;RS2008</NoWarn>
</PropertyGroup>

<ItemGroup>
<AssemblyMetadata Include="GitSha" Value="$(GitSha)" />
<AssemblyMetadata Include="PackageProjectUrl" Value="$(PackageProjectUrl)" />
Expand All @@ -30,8 +22,9 @@
</ItemGroup>

<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)..\Shared\Constants\Diagnostics.cs" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)Shared\*.md" />
<Compile Include="$(MSBuildThisFileDirectory)..\Shared\Constants\*.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\Shared\Helpers\Utilities.cs" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)..\Shared\*.md" />
</ItemGroup>

</Project>
160 changes: 160 additions & 0 deletions src/AutoCtor.CodeFixes/AutoCtorRewriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace AutoCtor.CodeFixes;

internal sealed class AutoCtorRewriter(
TypeDeclarationSyntax typeDeclaration,
ConstructorDeclarationSyntax? ctorDeclaration
) : CSharpSyntaxRewriter
{
// The target type and all containing types – the only types that need 'partial'.
private readonly ImmutableArray<BaseTypeDeclarationSyntax> _targetAndAncestors =
typeDeclaration
.AncestorsAndSelf()
.OfType<BaseTypeDeclarationSyntax>()
.ToImmutableArray();

public override SyntaxNode? VisitCompilationUnit(CompilationUnitSyntax node)
{
node = (CompilationUnitSyntax)base.VisitCompilationUnit(node)!;

// Add using AutoCtor;
if (!node.Usings.Any(u => u.Name is IdentifierNameSyntax { Identifier.Text: "AutoCtor" }
|| u.Name is AliasQualifiedNameSyntax { Name.Identifier.Text: "AutoCtor" }))
{
var usingName = IdentifierName("AutoCtor");
var usingDirective = UsingDirective(usingName);
node = node.AddUsings(usingDirective);
}

return node;
}

public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node)
{
var isTargetOrAncestor = _targetAndAncestors.Any(a => a.IsEquivalentTo(node));

// Remove matching constructor
// Add [AutoConstruct] attribute
if (node.IsEquivalentTo(typeDeclaration))
{
node = RemoveCtor(node);
if (!HasAutoConstructAttribute(node))
{
var attrList = CreateAutoConstructAttributeList();
node = node.AddAttributeLists(attrList);
}
}

// Add partial modifier only to the target type and its containing types
if (isTargetOrAncestor && !node.Modifiers.Any(SyntaxKind.PartialKeyword))
{
var partialToken = CreatePartialToken(node.Modifiers.Count == 0);
node = node.AddModifiers(partialToken);
}

return base.VisitClassDeclaration(node);
}

public override SyntaxNode? VisitStructDeclaration(StructDeclarationSyntax node)
{
var isTargetOrAncestor = _targetAndAncestors.Any(a => a.IsEquivalentTo(node));

// Remove matching constructor
// Add [AutoConstruct] attribute
if (node.IsEquivalentTo(typeDeclaration))
{
node = RemoveCtor(node);
if (!HasAutoConstructAttribute(node))
{
var attrList = CreateAutoConstructAttributeList();
node = node.AddAttributeLists(attrList);
}
}

// Add partial modifier only to the target type and its containing types
if (isTargetOrAncestor && !node.Modifiers.Any(SyntaxKind.PartialKeyword))
{
var partialToken = CreatePartialToken(node.Modifiers.Count == 0);
node = node.AddModifiers(partialToken);
}

return base.VisitStructDeclaration(node);
}

public override SyntaxNode? VisitRecordDeclaration(RecordDeclarationSyntax node)
{
var isTargetOrAncestor = _targetAndAncestors.Any(a => a.IsEquivalentTo(node));

// Remove matching constructor
// Add [AutoConstruct] attribute
if (node.IsEquivalentTo(typeDeclaration))
{
node = RemoveCtor(node);
if (!HasAutoConstructAttribute(node))
{
var attrList = CreateAutoConstructAttributeList();
node = node.AddAttributeLists(attrList);
}
}

// Add partial modifier only to the target type and its containing types
if (isTargetOrAncestor && !node.Modifiers.Any(SyntaxKind.PartialKeyword))
{
var partialToken = CreatePartialToken(node.Modifiers.Count == 0);
node = node.AddModifiers(partialToken);
}

return base.VisitRecordDeclaration(node);
}

private TRoot RemoveCtor<TRoot>(TRoot node) where TRoot : SyntaxNode
{
if (ctorDeclaration is null)
return node;
var ctor = node.ChildNodes().First(ctor => ctor.IsEquivalentTo(ctorDeclaration));
return node.RemoveNode(ctor, SyntaxRemoveOptions.KeepUnbalancedDirectives)!;
}

private static SyntaxToken CreatePartialToken(bool noSpace)
{
return Token(
SyntaxTriviaList.Empty,
SyntaxKind.PartialKeyword,
noSpace ? SyntaxTriviaList.Empty : SyntaxTriviaList.Create(Space));
}

private static AttributeListSyntax CreateAutoConstructAttributeList()
{
var attrName = IdentifierName("AutoConstruct");
var attr = Attribute(attrName);
return AttributeList(SingletonSeparatedList(attr))
.WithTrailingTrivia(ElasticEndOfLine("\n"));
}

private static bool HasAutoConstructAttribute(TypeDeclarationSyntax typeDeclaration)
{
foreach (var attributeList in typeDeclaration.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
var simplifiedName = attribute.Name switch
{
IdentifierNameSyntax i => i,
QualifiedNameSyntax q => q.Right,
AliasQualifiedNameSyntax a => a.Name,
_ => null
};

if (simplifiedName?.Identifier.Text == "AutoConstruct")
return true;
}
}

return false;
}
}
Loading