-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add baseclass codefix for projection & aggregates
- Loading branch information
1 parent
6b09c5e
commit 289d654
Showing
3 changed files
with
151 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
Source/Analyzers/CodeFixes/MissingBaseClassCodeFixProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright (c) Dolittle. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
namespace Dolittle.SDK.Analyzers.CodeFixes; | ||
|
||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MethodVisibilityCodeFixProvider))] | ||
public class MissingBaseClassCodeFixProvider : CodeFixProvider | ||
{ | ||
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIds.MissingBaseClassRuleId); | ||
|
||
public override Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var diagnostic = context.Diagnostics.First(); | ||
var missingClass = diagnostic.Properties["baseClass"]; | ||
if (missingClass is null) | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
|
||
var title = $"Add base class: '{missingClass}'"; | ||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
title: title, | ||
createChangedDocument: c => AddBaseClassAsync(context.Document, diagnostic, missingClass, c), | ||
equivalenceKey: title), | ||
diagnostic); | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
|
||
|
||
async Task<Document> AddBaseClassAsync(Document document, Diagnostic diagnostic, string missingClass, CancellationToken cancellationToken) | ||
{ | ||
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); | ||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
if (root is null || semanticModel is null) return document; | ||
|
||
var node = root.FindNode(diagnostic.Location.SourceSpan); | ||
var classDeclaration = node.FirstAncestorOrSelf<ClassDeclarationSyntax>(); | ||
if (classDeclaration is null) return document; | ||
|
||
var baseClassType = semanticModel.Compilation.GetTypeByMetadataName(missingClass); | ||
if (baseClassType is null) return document; | ||
|
||
var newClassDeclaration = classDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(baseClassType.Name))).NormalizeWhitespace(); | ||
|
||
// Add using directive for the namespace if it's not already there | ||
var namespaceToAdd = baseClassType?.ContainingNamespace?.ToDisplayString(); | ||
if (!string.IsNullOrEmpty(namespaceToAdd) && root is CompilationUnitSyntax compilationUnitSyntax && | ||
!NamespaceImported(compilationUnitSyntax, namespaceToAdd!)) | ||
{ | ||
var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceToAdd!)) | ||
.NormalizeWhitespace(); // Ensure proper formatting | ||
compilationUnitSyntax = compilationUnitSyntax.AddUsings(usingDirective); | ||
root = compilationUnitSyntax; | ||
} | ||
|
||
var newRoot = root.ReplaceNode(classDeclaration, newClassDeclaration); | ||
return document.WithSyntaxRoot(newRoot); | ||
} | ||
|
||
|
||
|
||
bool NamespaceImported(CompilationUnitSyntax root, string namespaceName) | ||
{ | ||
return root.Usings.Any(usingDirective => usingDirective.Name?.ToString() == namespaceName); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
Tests/Analyzers/CodeFixes/MissingBaseClassCodeFixProviderTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// // Copyright (c) Dolittle. All rights reserved. | ||
// // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
// | ||
// using System.Threading.Tasks; | ||
// | ||
// namespace Dolittle.SDK.Analyzers.CodeFixes; | ||
// | ||
// public class MissingBaseClassCodeFixProviderTests : CodeFixProviderTests<AttributeIdentityAnalyzer, MissingBaseClassCodeFixProvider> | ||
// { | ||
// [Fact] | ||
// public async Task ShouldFixAggregateBaseClass() | ||
// { | ||
// var test = @" | ||
// [Dolittle.SDK.Aggregates.AggregateRoot(""c6f87322-be67-4aaf-a9f4-fdc24ac4f0fb"")] | ||
// class SomeAggregateRoot | ||
// { | ||
// public string Name { get; set; } | ||
// }"; | ||
// | ||
// var expected = @"using Dolittle.SDK.Aggregates; | ||
// [Dolittle.SDK.Aggregates.AggregateRoot(""c6f87322-be67-4aaf-a9f4-fdc24ac4f0fb"")] | ||
// class SomeAggregateRoot : AggregateRoot | ||
// { | ||
// public string Name { get; set; } | ||
// }"; | ||
// | ||
// var diagnosticResult = Diagnostic(DescriptorRules.MissingBaseClass) | ||
// .WithSpan(2, 1, 6, 2) | ||
// .WithArguments("SomeAggregateRoot", "Dolittle.SDK.Aggregates.AggregateRoot"); | ||
// | ||
// | ||
// await VerifyCodeFixAsync(test, expected, diagnosticResult); | ||
// } | ||
// | ||
// [Fact] | ||
// public async Task ShouldFixProjectionBaseClass() | ||
// { | ||
// var test = @" | ||
// [Dolittle.SDK.Projections.Projection(""c6f87322-be67-4aaf-a9f4-fdc24ac4f0fb"")] | ||
// class SomeProjection | ||
// { | ||
// public string Name { get; set; } | ||
// }"; | ||
// | ||
// var expected = @"using Dolittle.SDK.Projections; | ||
// [Dolittle.SDK.Projections.Projection(""c6f87322-be67-4aaf-a9f4-fdc24ac4f0fb"")] | ||
// class SomeProjection : ReadModel | ||
// { | ||
// public string Name { get; set; } | ||
// }"; | ||
// | ||
// var diagnosticResult = Diagnostic(DescriptorRules.MissingBaseClass) | ||
// .WithSpan(2, 1, 6, 2) | ||
// .WithArguments("SomeProjection", "Dolittle.SDK.Projections.ReadModel"); | ||
// | ||
// await VerifyCodeFixAsync(test, expected, diagnosticResult); | ||
// } | ||
// } |