Skip to content

Commit 9ff8eb6

Browse files
author
fabien.menager
committed
Add a code fixer and a code refactorer to transform factory methods into constructors
1 parent 76eff10 commit 9ff8eb6

22 files changed

Lines changed: 1319 additions & 1 deletion

File tree

samples/ReadmeSample/ReadmeSample.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
<ItemGroup>
1515
<ProjectReference Include="..\..\src\EntityFrameworkCore.Projectables.Generator\EntityFrameworkCore.Projectables.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
16+
<ProjectReference Include="..\..\src\EntityFrameworkCore.Projectables.CodeFixes\EntityFrameworkCore.Projectables.CodeFixes.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
1617
<ProjectReference Include="..\..\src\EntityFrameworkCore.Projectables\EntityFrameworkCore.Projectables.csproj" />
1718
</ItemGroup>
1819

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.CodeActions;
5+
using Microsoft.CodeAnalysis.CodeFixes;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
8+
namespace EntityFrameworkCore.Projectables.CodeFixes;
9+
10+
/// <summary>
11+
/// Code fix provider for <c>EFP0012</c>.
12+
/// Offers two fixes on a <c>[Projectable]</c> factory method that can be a constructor:
13+
/// <list type="number">
14+
/// <item><description>Convert the factory method to a constructor (current document).</description></item>
15+
/// <item><description>Convert the factory method to a constructor <em>and</em> update all
16+
/// callers throughout the solution.</description></item>
17+
/// </list>
18+
/// </summary>
19+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FactoryMethodToConstructorCodeFixProvider))]
20+
[Shared]
21+
public sealed class FactoryMethodToConstructorCodeFixProvider : CodeFixProvider
22+
{
23+
/// <inheritdoc />
24+
public override ImmutableArray<string> FixableDiagnosticIds => ["EFP0012"];
25+
26+
/// <inheritdoc />
27+
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
28+
29+
/// <inheritdoc />
30+
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
31+
{
32+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
33+
if (root is null)
34+
{
35+
return;
36+
}
37+
38+
var diagnostic = context.Diagnostics[0];
39+
var node = root.FindNode(diagnostic.Location.SourceSpan);
40+
var method = node.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault();
41+
if (method is null)
42+
{
43+
return;
44+
}
45+
46+
if (!FactoryMethodTransformationHelper.TryGetFactoryMethodPattern(method, out var containingType, out _))
47+
{
48+
return;
49+
}
50+
51+
context.RegisterCodeFix(
52+
CodeAction.Create(
53+
title: "Convert [Projectable] factory method to constructor",
54+
createChangedDocument: ct =>
55+
FactoryMethodTransformationHelper.ConvertToConstructorAsync(
56+
context.Document, method, containingType!, ct),
57+
equivalenceKey: "EFP0012_FactoryToConstructor"),
58+
diagnostic);
59+
60+
context.RegisterCodeFix(
61+
CodeAction.Create(
62+
title: "Convert [Projectable] factory method to constructor (and update callers)",
63+
createChangedSolution: ct =>
64+
FactoryMethodTransformationHelper.ConvertToConstructorAndUpdateCallersAsync(
65+
context.Document, method, containingType!, ct),
66+
equivalenceKey: "EFP0012_FactoryToConstructorWithCallers"),
67+
diagnostic);
68+
}
69+
}
70+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Composition;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CodeActions;
4+
using Microsoft.CodeAnalysis.CodeRefactorings;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
7+
namespace EntityFrameworkCore.Projectables.CodeFixes;
8+
9+
/// <summary>
10+
/// Code refactoring provider that converts a <c>[Projectable]</c> factory method whose body is
11+
/// an object-initializer expression (<c>=> new T { … }</c>) into a <c>[Projectable]</c>
12+
/// constructor of the same class.
13+
/// <para>
14+
/// Two refactoring actions are offered:
15+
/// <list type="number">
16+
/// <item><description>Convert the factory method to a constructor (current document only).</description></item>
17+
/// <item><description>Convert the factory method to a constructor <em>and</em> replace all
18+
/// callers throughout the solution with <c>new T(…)</c> invocations.</description></item>
19+
/// </list>
20+
/// </para>
21+
/// <para>
22+
/// This provider is complementary to <see cref="FactoryMethodToConstructorCodeFixProvider"/>,
23+
/// which fixes the <c>EFP0012</c> diagnostic. The refactoring provider remains useful when
24+
/// the diagnostic is suppressed.
25+
/// </para>
26+
/// <para>
27+
/// A public parameterless constructor is automatically inserted when the class does not already
28+
/// have one, preserving the implicit default constructor that would otherwise be lost.
29+
/// </para>
30+
/// </summary>
31+
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(FactoryMethodToConstructorCodeRefactoringProvider))]
32+
[Shared]
33+
public sealed class FactoryMethodToConstructorCodeRefactoringProvider : CodeRefactoringProvider
34+
{
35+
/// <inheritdoc />
36+
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
37+
{
38+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
39+
if (root is null)
40+
{
41+
return;
42+
}
43+
44+
var node = root.FindNode(context.Span);
45+
var method = node.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault();
46+
if (method is null)
47+
{
48+
return;
49+
}
50+
51+
if (!FactoryMethodTransformationHelper.TryGetFactoryMethodPattern(
52+
method, out var containingType, out _))
53+
{
54+
return;
55+
}
56+
57+
context.RegisterRefactoring(
58+
CodeAction.Create(
59+
title: "Convert [Projectable] factory method to constructor",
60+
createChangedDocument: ct =>
61+
FactoryMethodTransformationHelper.ConvertToConstructorAsync(
62+
context.Document, method, containingType!, ct),
63+
equivalenceKey: "EFP_FactoryToConstructor"));
64+
65+
context.RegisterRefactoring(
66+
CodeAction.Create(
67+
title: "Convert [Projectable] factory method to constructor (and update callers)",
68+
createChangedSolution: ct =>
69+
FactoryMethodTransformationHelper.ConvertToConstructorAndUpdateCallersAsync(
70+
context.Document, method, containingType!, ct),
71+
equivalenceKey: "EFP_FactoryToConstructorWithCallers"));
72+
}
73+
}

0 commit comments

Comments
 (0)