Skip to content

Commit

Permalink
Merge pull request #89 from VeyronSakai/feature/inject-attribute-code…
Browse files Browse the repository at this point in the history
…-fix

Add InjectAttributeCodeFixProvider
  • Loading branch information
VeyronSakai authored Apr 18, 2024
2 parents d3e8121 + 60410e0 commit 841189a
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ csharp_preserve_single_line_statements = true
# IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0060.severity = warning

resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlighting = none

[src/{Compilers,ExpressionEvaluator,Scripting}/**Test**/*.{cs,vb}]

# IDE0060: Remove unused parameter
Expand Down Expand Up @@ -279,6 +281,7 @@ dotnet_diagnostic.IDE2005.severity = warning
# csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental
dotnet_diagnostic.IDE2006.severity = warning


[src/{VisualStudio}/**/*.{cs,vb}]
# CA1822: Make member static
# There is a risk of accidentally breaking an internal API that partners rely on though IVT.
Expand Down
2 changes: 1 addition & 1 deletion VContainerAnalyzer.Test/FieldAnalyzerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task EmptySourceCode_NoDiagnosticReport()
[Test]
public async ValueTask Analyze_FieldInjection_ReportDiagnostic()
{
var source = Helper.ReadCodes("FieldInjectionClass.cs", "EmptyClassStub.cs");
var source = Helper.GetFileContentTexts("FieldInjectionClass.cs", "EmptyClassStub.cs");
var analyzer = new FieldAnalyzer();
var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);

Expand Down
36 changes: 30 additions & 6 deletions VContainerAnalyzer.Test/Helper.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
// Copyright (c) 2020-2024 VeyronSakai.
// This software is released under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace VContainerAnalyzer.Test;

public static class Helper
internal static class Helper
{
private const string VContainerDirectory = "VContainer";
private const string TestDataDirPath = "../../../TestData";
private const string VContainerDirectoryName = "VContainer";

private static IEnumerable<string> VContainerSourcePaths { get; } =
GetVContainerFiles.Select(file => $"{Path.Combine(VContainerDirectory, file)}").ToArray();
GetVContainerFiles.Select(file => $"{Path.Combine(VContainerDirectoryName, file)}").ToArray();

private static IEnumerable<string> GetVContainerFiles =>
[
Expand All @@ -25,11 +30,30 @@ public static class Helper
"InjectAttribute.cs",
];

public static string[] ReadCodes(params string[] sources)
internal static string[] GetFileContentTexts(params string[] sourcePaths)
{
const string TestDataDirPath = "../../../TestData";
return sources
return sourcePaths
.Concat(VContainerSourcePaths)
.Select(file => File.ReadAllText($"{TestDataDirPath}/{file}", Encoding.UTF8)).ToArray();
}

internal static string GetJoinedFilesContentText(params string[] sources)
{
var fileBodyBuilder = new StringBuilder();
var usingStatementsBuilder = new StringBuilder();

foreach (var filePath in sources.Concat(VContainerSourcePaths))
{
var fileContent = File.ReadAllText($"{TestDataDirPath}/{filePath}", Encoding.UTF8);
var tree = CSharpSyntaxTree.ParseText(fileContent);
var root = tree.GetRoot();
var usingDirectives = root.DescendantNodes().OfType<UsingDirectiveSyntax>().ToList();
var newRoot = root.RemoveNodes(usingDirectives, SyntaxRemoveOptions.KeepNoTrivia);
var usingStatements = string.Join(Environment.NewLine, usingDirectives.Select(u => u.ToFullString()));
fileBodyBuilder.Append(newRoot?.ToFullString());
usingStatementsBuilder.Append(usingStatements);
}

return usingStatementsBuilder.Append(fileBodyBuilder).ToString();
}
}
27 changes: 27 additions & 0 deletions VContainerAnalyzer.Test/InjectAttributeCodeFixProviderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2020-2024 VeyronSakai.
// This software is released under the MIT License.

using System.Threading.Tasks;
using NUnit.Framework;
using Verify =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier<VContainerAnalyzer.Analyzers.FieldAnalyzer,
VContainerAnalyzer.CodeFixProviders.InjectAttributeCodeFixProvider>;

namespace VContainerAnalyzer.Test;

[TestFixture]
public class InjectAttributeCodeFixProviderTest
{
[Test]
public async Task TypeNameContainingLowercase_CodeFixed()
{
var source = Helper.GetJoinedFilesContentText("FieldInjectionClass.cs", "EmptyClassStub.cs");
var fixedSource = Helper.GetJoinedFilesContentText("FieldInjectionClassFixed.txt", "EmptyClassStub.cs");

var expected = Verify.Diagnostic()
.WithSpan(22, 10, 22, 16)
.WithArguments("_field1");

await Verify.VerifyCodeFixAsync(source, expected, fixedSource);
}
}
2 changes: 1 addition & 1 deletion VContainerAnalyzer.Test/PropertyAnalyzerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task EmptySourceCode_NoDiagnosticReport()
[Test]
public async ValueTask Analyze_PropertyInjection_ReportDiagnostic()
{
var source = Helper.ReadCodes("PropertyInjectionClass.cs", "EmptyClassStub.cs");
var source = Helper.GetFileContentTexts("PropertyInjectionClass.cs", "EmptyClassStub.cs");
var analyzer = new PropertyAnalyzer();
var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);

Expand Down
13 changes: 13 additions & 0 deletions VContainerAnalyzer.Test/TestData/FieldInjectionClassFixed.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) 2020-2024 VeyronSakai.
// This software is released under the MIT License.

using VContainer;

namespace VContainerAnalyzer.Test.TestData
{
public class FieldInjectionClass
{
private EmptyClassStub _field1;
private EmptyClassStub _field2;
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2020-2024 VeyronSakai.
// This software is released under the MIT License.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
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.Syntax;
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace VContainerAnalyzer.CodeFixProviders;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(InjectAttributeCodeFixProvider)), Shared]
public sealed class InjectAttributeCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(Rules.Rule0002.Id);

public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

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

var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var declaration = root?
.FindToken(diagnosticSpan.Start)
.Parent?
.AncestorsAndSelf()
.OfType<MemberDeclarationSyntax>()
.FirstOrDefault();

if (declaration == null)
{
return;
}

if (declaration is not FieldDeclarationSyntax && declaration is not PropertyDeclarationSyntax)
{
return;
}

context.RegisterCodeFix(
CodeAction.Create(
"Remove InjectAttribute",
cancellationToken =>
RemoveInjectAttribute(context.Document, declaration, cancellationToken),
FixableDiagnosticIds.Single()),
context.Diagnostics);
}

private static async Task<Document> RemoveInjectAttribute(Document document, MemberDeclarationSyntax declaration,
CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var newAttributeLists = new List<AttributeListSyntax>();

foreach (var attributeList in declaration.AttributeLists)
{
var nodesToRemove = new List<AttributeSyntax>();

foreach (var attribute in attributeList.Attributes)
{
var attributeType = model?.GetTypeInfo(attribute).Type;
if (attributeType != null && attributeType.IsVContainerInjectAttribute())
{
nodesToRemove.Add(attribute);
}
}

var newAttributes = attributeList.RemoveNodes(nodesToRemove, SyntaxRemoveOptions.KeepNoTrivia);
if (newAttributes.Attributes.Any())
{
newAttributeLists.Add(newAttributes);
}
}

var newDeclaration = declaration
.WithAttributeLists(SyntaxFactory.List(newAttributeLists))
.WithLeadingTrivia(declaration.GetLeadingTrivia());

var newRoot = root?.ReplaceNode(declaration, newDeclaration);
return newRoot == null ? document : document.WithSyntaxRoot(newRoot);
}
}

0 comments on commit 841189a

Please sign in to comment.