Skip to content

Commit

Permalink
Feat/set accessor code fix (#19)
Browse files Browse the repository at this point in the history
* refactor

* feat(code-fix): added new code fix and test refactor
  • Loading branch information
protomorphine authored May 9, 2024
1 parent e52d3c4 commit 624e304
Show file tree
Hide file tree
Showing 19 changed files with 310 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;

namespace ImmutableAnalyzer.Tests;
namespace ImmutableAnalyzer.Tests.Factories;

/// <summary>
/// Simple factory for <see cref="CSharpAnalyzerTest{TAnalyzer,TVerifier}"/>.
/// </summary>
public static class AnalyzerTestFactory
{
/// <summary>
/// Creates test case with reference to .NET6 for given analyzer.
/// </summary>
/// <param name="source">Source code to test.</param>
/// <typeparam name="TAnalyzer">Type of analyzer.</typeparam>
/// <returns>Test case.</returns>
public static CSharpAnalyzerTest<TAnalyzer, XUnitVerifier> CreateCSharpAnalyzerTest<TAnalyzer>(string source)
where TAnalyzer : DiagnosticAnalyzer, new()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
using System;
using System.Linq;

namespace ImmutableAnalyzer.Tests;
namespace ImmutableAnalyzer.Tests.Factories;

/// <summary>
/// Simple factory to create source code to test.
/// </summary>
public static class SourceFactory
{
private const string CommonSource = @"
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Immutable;
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace System.Runtime.CompilerServices
{
[EditorBrowsable(EditorBrowsableState.Never)]
public record IsExternalInit;
}
namespace System.Runtime.CompilerServices
{
[EditorBrowsable(EditorBrowsableState.Never)]
public record IsExternalInit;
}
[AttributeUsage(AttributeTargets.Class)]
public class ImmutableAttribute : Attribute { }
";
[AttributeUsage(AttributeTargets.Class)]
public class ImmutableAttribute : Attribute { }";

/// <summary>
/// Creates source code with immutable class with given property type.
Expand All @@ -35,12 +34,11 @@ public static string ImmutableClassWithProperty(string propertyType, out int lin
{
var source = $@"{CommonSource}
[Immutable]
public class TestImmutableClass
{{
public {propertyType} Id {{ get; init; }}
}}
";
[Immutable]
public class TestImmutableClass
{{
public {propertyType} Id {{ get; init; }}
}}";

(line, column) = GetLineAndColumn(source, propertyType);
return source;
Expand All @@ -57,17 +55,33 @@ public static string ImmutableClassWithPropertyAccessor(string propertySetAccess
{
var source = $@"{CommonSource}
[Immutable]
public class TestImmutableClass
{{
public int Id {{get; {propertySetAccessor};}}
}}
";
[Immutable]
public class TestImmutableClass
{{
public int Id {{ get; {propertySetAccessor}; }}
}}";

(line, column) = GetLineAndColumn(source, propertySetAccessor);
return source;
}

/// <summary>
/// Creates source code with immutable class with getter only.
/// </summary>
/// <returns>String, that represent source code.</returns>
public static string ImmutableClassWithGetOnlyPropertyAccessor()
{
const string source = $@"{CommonSource}
[Immutable]
public class TestImmutableClass
{{
public int Id {{ get; }}
}}";

return source;
}

/// <summary>
/// Creates source code with immutable record with given primary ctor parameter type.
/// </summary>
Expand All @@ -79,9 +93,8 @@ public static string ImmutableRecordWithParameter(string parameterType, out int
{
var source = $@"{CommonSource}
[Immutable]
public record TestImmutableRecord({parameterType} Param);
";
[Immutable]
public record TestImmutableRecord({parameterType} Param);";

(line, column) = GetLineAndColumn(source, parameterType);
return source;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using ImmutableAnalyzer.PropertyAnalyzers.PropertyType;
using ImmutableAnalyzer.Tests.Factories;
using Xunit;
using Verifier =
Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
Expand All @@ -20,8 +21,8 @@ public async Task Class_with_immutable_property_should_be_immutable(string prope
var source = SourceFactory.ImmutableClassWithProperty(property, out _, out _);
var test = AnalyzerTestFactory.CreateCSharpAnalyzerTest<PropertyTypeAnalyzer>(source);

await test.RunAsync().ConfigureAwait(false);
Assert.True(true); // SonarLint S2699
var exception = await Record.ExceptionAsync(() => test.RunAsync());
Assert.Null(exception);
}

[Theory]
Expand All @@ -31,7 +32,7 @@ public async Task Class_with_mutable_property_should_not_be_immutable(string pro
var source = SourceFactory.ImmutableClassWithProperty(property, out var line, out var column);

var expected = Verifier.Diagnostic().WithLocation(line, column).WithArguments(property);
await Verifier.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false);
await Verifier.VerifyAnalyzerAsync(source, expected);
}

[Fact]
Expand All @@ -43,7 +44,7 @@ public async Task Class_with_user_defined_immutable_property_type_should_be_immu
SourceFactory.ImmutableClassWithProperty(propertyType: className, out _, out _) +
PureClassWithImmutableProperty(name: className, "[Immutable]");

await Verifier.VerifyAnalyzerAsync(source).ConfigureAwait(false);
await Verifier.VerifyAnalyzerAsync(source);
}

[Fact]
Expand All @@ -56,7 +57,7 @@ public async Task Class_with_user_defined_mutable_property_type_should_not_be_im
PureClassWithImmutableProperty(name: className);

var expectedDiagnostic = Verifier.Diagnostic().WithLocation(line, column).WithArguments(className);
await Verifier.VerifyAnalyzerAsync(source, expectedDiagnostic).ConfigureAwait(false);
await Verifier.VerifyAnalyzerAsync(source, expectedDiagnostic);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using ImmutableAnalyzer.PropertyAnalyzers.SetAccessor;
using ImmutableAnalyzer.Tests.Factories;
using Xunit;
using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
ImmutableAnalyzer.PropertyAnalyzers.SetAccessor.SetAccessorAnalyzer>;
Expand All @@ -14,30 +15,33 @@ public class SetAccessorAnalyzerTests
[Fact]
public async Task Immutable_class_property_could_not_have_a_public_setter()
{
var source = SourceFactory.ImmutableClassWithPropertyAccessor(
"set", out var line, out var column
);
var source = SourceFactory.ImmutableClassWithPropertyAccessor("set", out var line, out var column);

var expected = Verifier.Diagnostic()
.WithLocation(line, column)
.WithArguments("set");

await Verifier.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false);
await Verifier.VerifyAnalyzerAsync(source, expected);
}

[Fact]
public async Task Immutable_class_property_could_have_init_setter()
{
var source = SourceFactory.ImmutableClassWithPropertyAccessor("init", out _, out _);

await Verifier.VerifyAnalyzerAsync(source).ConfigureAwait(false);
await Verifier.VerifyAnalyzerAsync(source);
}

[Fact]
public async Task Immutable_class_property_could_have_private_setter()
{
var source = SourceFactory.ImmutableClassWithPropertyAccessor("private set", out _, out _);
await Verifier.VerifyAnalyzerAsync(source);
}

await Verifier.VerifyAnalyzerAsync(source).ConfigureAwait(false);
[Fact]
public async Task Immutable_class_property_could_have_no_setter()
{
var source = SourceFactory.ImmutableClassWithGetOnlyPropertyAccessor();
await Verifier.VerifyAnalyzerAsync(source);
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,82 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using ImmutableAnalyzer.PropertyAnalyzers.SetAccessor;
using ImmutableAnalyzer.Tests.Factories;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier<
ImmutableAnalyzer.PropertyAnalyzers.SetAccessor.SetAccessorAnalyzer,
ImmutableAnalyzer.PropertyAnalyzers.SetAccessor.SetAccessorCodeFixProvider
>;
using SetAccessorCodeFixCodeFixTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest<
ImmutableAnalyzer.PropertyAnalyzers.SetAccessor.SetAccessorAnalyzer,
ImmutableAnalyzer.PropertyAnalyzers.SetAccessor.SetAccessorCodeFixProvider,
Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier
>;

namespace ImmutableAnalyzer.Tests.PropertyAnalyzersTests.SetAccessorTests;

public class SetAccessorCodeFixTests
{
[Fact]
public async Task Immutable_class_with_public_set_accessor_should_be_fixed_with_init_keyword()
{
var test = CreateTest(SetAccessorCodeFixProvider.FixStrategy.ToInit);
test.CodeActionValidationMode = CodeActionValidationMode.None;

var exception = await Record.ExceptionAsync(() => test.RunAsync());
Assert.Null(exception);
}

[Fact]
public async Task Immutable_class_with_public_set_accessor_should_be_fixed_with_private_set()
{
var test = CreateTest(SetAccessorCodeFixProvider.FixStrategy.ToPrivate);

var exception = await Record.ExceptionAsync(() => test.RunAsync());
Assert.Null(exception);
}

[Fact]
public async Task Immutable_class_with_public_set_accessor_should_be_removed()
{
var test = CreateTest(SetAccessorCodeFixProvider.FixStrategy.Remove);

var exception = await Record.ExceptionAsync(() => test.RunAsync());
Assert.Null(exception);
}

/// <summary>
/// Creates testcase for <see cref="SetAccessorAnalyzer"/> and <see cref="SetAccessorCodeFixProvider"/>.
/// </summary>
/// <param name="fixStrategy">Code fix strategy.</param>
/// <returns>Tes case.</returns>
/// <exception cref="ArgumentOutOfRangeException">Raises when <see cref="fixStrategy"/> is invalid.</exception>
private static SetAccessorCodeFixCodeFixTest CreateTest(SetAccessorCodeFixProvider.FixStrategy fixStrategy)
{
var source = SourceFactory.ImmutableClassWithPropertyAccessor("set", out var line, out var column);
var fixedSource = SourceFactory.ImmutableClassWithPropertyAccessor("private set", out _, out _);
var expectedDiagnostic = Verifier.Diagnostic().WithLocation(line, column).WithArguments("set");

var fixedSource = fixStrategy switch
{
SetAccessorCodeFixProvider.FixStrategy.Remove =>
SourceFactory.ImmutableClassWithGetOnlyPropertyAccessor(),

SetAccessorCodeFixProvider.FixStrategy.ToInit =>
SourceFactory.ImmutableClassWithPropertyAccessor("init", out _, out _),

SetAccessorCodeFixProvider.FixStrategy.ToPrivate =>
SourceFactory.ImmutableClassWithPropertyAccessor("private set", out _, out _),

_ => throw new ArgumentOutOfRangeException(nameof(fixStrategy), fixStrategy, null)
};

var expected = Verifier.Diagnostic()
.WithLocation(line, column)
.WithArguments("set");
await Verifier.VerifyCodeFixAsync(source, expected, fixedSource).ConfigureAwait(false);
return new SetAccessorCodeFixCodeFixTest
{
TestCode = source,
FixedCode = fixedSource,
CodeActionIndex = (int) fixStrategy,
ExpectedDiagnostics = {expectedDiagnostic}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using ImmutableAnalyzer.ParameterAnalyzers;
using ImmutableAnalyzer.Tests.Factories;
using Xunit;
using Verifier =
Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
Expand All @@ -17,8 +18,8 @@ public async Task Record_with_immutable_property_should_be_immutable(string prop
var source = SourceFactory.ImmutableRecordWithParameter(property, out _, out _);
var test = AnalyzerTestFactory.CreateCSharpAnalyzerTest<ParameterTypeAnalyzer>(source);

await test.RunAsync().ConfigureAwait(false);
Assert.True(true); // SonarLint S2699
var exception = await Record.ExceptionAsync(() => test.RunAsync());
Assert.Null(exception);
}

[Theory]
Expand All @@ -28,6 +29,6 @@ public async Task Record_with_mutable_property_should_not_be_immutable(string pr
var source = SourceFactory.ImmutableRecordWithParameter(property, out var line, out var column);

var expected = Verifier.Diagnostic().WithLocation(line, column).WithArguments(property);
await Verifier.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false);
await Verifier.VerifyAnalyzerAsync(source, expected);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +0,0 @@
## Release 1.2.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------------------------------------------------------------

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ImmutableAnalyzer.Extensions;
/// <summary>
/// Extensions for <see cref="ISymbol"/>
/// </summary>
public static class SymbolExtensions
internal static class SymbolExtensions
{
/// <summary>
/// Checks if <see cref="symbol"/> has <see cref="ImmutableAttribute"/> in any declaring syntax references.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace ImmutableAnalyzer.Extensions;
/// <summary>
/// Extensions for <see cref="ClassDeclarationSyntax"/>
/// </summary>
public static class TypeDeclarationSyntaxExtensions
internal static class TypeDeclarationSyntaxExtensions
{
private const string Attribute = nameof(System.Attribute);

Expand All @@ -31,7 +31,6 @@ public static bool HasAttribute<T>(this TypeDeclarationSyntax typeDeclarationNod

return typeDeclarationNode
.AttributeLists
.AsParallel()
.SelectMany(a => a.Attributes)
.Any(attribute => attribute.Name.ToString() == attributeName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ protected override void AnalyzeParameter(ParameterSyntax node, SyntaxNodeAnalysi
Rule, node.Type!.GetLocation(),
node.Type.ToFullString().Trim()
);

ctx.ReportDiagnostic(diagnostic);
}
}
Loading

0 comments on commit 624e304

Please sign in to comment.