Skip to content

Commit

Permalink
Merge pull request #18 from protomorphine/feat/record-params
Browse files Browse the repository at this point in the history
Feat/record params
  • Loading branch information
protomorphine authored Apr 23, 2024
2 parents 5c48831 + 0d1354e commit 535ec2b
Show file tree
Hide file tree
Showing 25 changed files with 684 additions and 341 deletions.
48 changes: 44 additions & 4 deletions ImmutableAnalyzer/ImmutableAnalyzer.Sample/Examples.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global

using System;
using System.Collections.Generic;

namespace ImmutableAnalyzer.Sample;

// If you don't see warnings, build the Analyzers Project.
Expand All @@ -15,23 +17,61 @@ public enum UserRole
[Immutable]
public class UserDto
{
/// <summary>
/// IM0002 - immutable class property can't have a public set accessor.
/// </summary>
public int Id { get; set; }

public string Name { get; init; } = string.Empty;

/// <summary>
/// IM0001 - immutable class property can't have a mutable type.
/// </summary>
public List<int> FriendsIds { get; init; } = new();

public UserRole Role { get; set; } = UserRole.Customer;
/// <summary>
/// immutable class property can be an enum.
/// </summary>
public UserRole Role { get; init; } = UserRole.Customer;

/// <summary>
/// immutable class property can be an array of immutable type.
/// </summary>
public OrganizationDto[] Organization { get; init; } = Array.Empty<OrganizationDto>();

public OrganizationDto Organization { get; init; } = new();
/// <summary>
/// IM0001 - immutable class property can't be an array of mutable type.
/// </summary>
public PhoneNumber[] PhoneNumbers { get; init; } = Array.Empty<PhoneNumber>();
}

[Immutable]
public class OrganizationDto
{
public string Name { get; set; } = string.Empty;
public string Name { get; init; } = string.Empty;

/// <summary>
/// IM0001 - immutable class property can't have a mutable type.
/// IM0002 - immutable class property can't have a public set accessor.
/// </summary>
public IList<string> PhoneNumbers { get; set; } = new List<string>();

/// <summary>
/// IM0001 - immutable class property can't have a mutable type.
/// IM0002 - immutable class property can't have a public set accessor.
/// </summary>
public IDictionary<int, string> Departments { get; set; } = new Dictionary<int, string>();
}
}

public class PhoneNumber
{
public string CountryCode { get; set; } = string.Empty;

public string Number { get; set; } = string.Empty;
}

/// <summary>
/// IM0003 - immutable record can't have a mutable parameter.
/// </summary>
[Immutable]
public record PetInfo(List<string> Toys);
27 changes: 27 additions & 0 deletions ImmutableAnalyzer/ImmutableAnalyzer.Tests/AnalyzerTestFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;

namespace ImmutableAnalyzer.Tests;

public static class AnalyzerTestFactory
{
public static CSharpAnalyzerTest<TAnalyzer, XUnitVerifier> CreateCSharpAnalyzerTest<TAnalyzer>(string source)
where TAnalyzer : DiagnosticAnalyzer, new()
{
var referenceAssembly = new ReferenceAssemblies("net6.0",
new PackageIdentity("Microsoft.NETCore.App.Ref", "6.0.0"),
Path.Combine("ref", "net6.0"));

return new CSharpAnalyzerTest<TAnalyzer, XUnitVerifier>
{
TestState =
{
Sources = {source},
ReferenceAssemblies = referenceAssembly
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.Threading.Tasks;
using ImmutableAnalyzer.PropertyAnalyzers.PropertyType;
using Xunit;
using Verifier =
Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
ImmutableAnalyzer.PropertyAnalyzers.PropertyType.PropertyTypeAnalyzer>;

namespace ImmutableAnalyzer.Tests.PropertyAnalyzersTests.PropertyTypeTests;

/// <summary>
/// Tests for <see cref="PropertyTypeAnalyzer"/>
/// </summary>
public class PropertyTypeAnalyzerTests
{
[Theory]
[ClassData(typeof(TestData.ImmutableTypes))]
[ClassData(typeof(TestData.ImmutableGenericsTypes))]
public async Task Class_with_immutable_property_should_be_immutable(string property)
{
var source = SourceFactory.ImmutableClassWithProperty(property, out _, out _);
var test = AnalyzerTestFactory.CreateCSharpAnalyzerTest<PropertyTypeAnalyzer>(source);

await test.RunAsync().ConfigureAwait(false);
Assert.True(true); // SonarLint S2699
}

[Theory]
[ClassData(typeof(TestData.MutableTypes))]
public async Task Class_with_mutable_property_should_not_be_immutable(string property)
{
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);
}

[Fact]
public async Task Class_with_user_defined_immutable_property_type_should_be_immutable()
{
const string className = "Person";

var source =
SourceFactory.ImmutableClassWithProperty(propertyType: className, out _, out _) +
PureClassWithImmutableProperty(name: className, "[Immutable]");

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

[Fact]
public async Task Class_with_user_defined_mutable_property_type_should_not_be_immutable()
{
const string className = "Person";

var source =
SourceFactory.ImmutableClassWithProperty(propertyType: className, out var line, out var column) +
PureClassWithImmutableProperty(name: className);

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

[Fact]
public async Task Class_with_enum_property_type_should_be_immutable()
{
const string enumName = "TestEnum";
var source =
SourceFactory.ImmutableClassWithProperty(enumName, out _, out _) +
$"public enum {enumName} {{ Value1, Value2, Value3 }}";

await Verifier.VerifyAnalyzerAsync(source);
}

/// <summary>
/// Creates source code of class (which contains 1 property named `Id` with type `long`) with given name and attributes.
/// </summary>
/// <param name="name">Class name.</param>
/// <param name="attribute">Attributes.</param>
/// <returns>Source code of class.</returns>
private static string PureClassWithImmutableProperty(string name, string attribute = "") =>
$@"
{attribute}
public class {name}
{{
public long Id {{ get; init; }}
}}
";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,39 @@
using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
ImmutableAnalyzer.PropertyAnalyzers.SetAccessor.SetAccessorAnalyzer>;

namespace ImmutableAnalyzer.Tests.TypePropertiesTests.SetAccessorTests;
namespace ImmutableAnalyzer.Tests.PropertyAnalyzersTests.SetAccessorTests;

/// <summary>
/// Tests for <see cref="SetAccessorAnalyzer"/>
/// </summary>
public class SetAccessorAnalyzerTests
{
[Theory]
[InlineData("set")]
public async Task ImmutableClassPropertySetAccessor_ShouldReport(string propertyAccessor)
[Fact]
public async Task Immutable_class_property_could_not_have_a_public_setter()
{
var source = SourceFactory.ImmutableClassWithPropertyAccessor(propertyAccessor);
var source = SourceFactory.ImmutableClassWithPropertyAccessor(
"set", out var line, out var column
);

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

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

[Theory]
[InlineData("init")]
[InlineData("private set")]
public async Task ImmutableClassPropertySetAccessor_ShouldNotReport(string propertyAccessor)
[Fact]
public async Task Immutable_class_property_could_have_init_setter()
{
var source = SourceFactory.ImmutableClassWithPropertyAccessor(propertyAccessor);
var source = SourceFactory.ImmutableClassWithPropertyAccessor("init", out _, out _);

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

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

await Verifier.VerifyAnalyzerAsync(source).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
ImmutableAnalyzer.PropertyAnalyzers.SetAccessor.SetAccessorCodeFixProvider
>;

namespace ImmutableAnalyzer.Tests.TypePropertiesTests.SetAccessorTests;
namespace ImmutableAnalyzer.Tests.PropertyAnalyzersTests.SetAccessorTests;

public class SetAccessorCodeFixTests
{
[Fact]
public async Task ImmutableClassPropertyWithPublicSetAccessor_ReplaceWithPrivateSet()
public async Task Immutable_class_with_public_set_accessor_should_be_fixed_with_private_set()
{
var source = SourceFactory.ImmutableClassWithPropertyAccessor("set");
var fixedSource = SourceFactory.ImmutableClassWithPropertyAccessor("private set");
var source = SourceFactory.ImmutableClassWithPropertyAccessor("set", out var line, out var column);
var fixedSource = SourceFactory.ImmutableClassWithPropertyAccessor("private set", out _, out _);

var expected = Verifier.Diagnostic()
.WithLocation(21, 25)
.WithLocation(line, column)
.WithArguments("set");
await Verifier.VerifyCodeFixAsync(source, expected, fixedSource).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using ImmutableAnalyzer.ParameterAnalyzers;
using Xunit;
using Verifier =
Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
ImmutableAnalyzer.ParameterAnalyzers.ParameterTypeAnalyzer>;

namespace ImmutableAnalyzer.Tests.RecordAnalyzersTests;

public class ParameterTypeAnalyzerTests
{
[Theory]
[ClassData(typeof(TestData.ImmutableTypes))]
[ClassData(typeof(TestData.ImmutableGenericsTypes))]
public async Task Record_with_immutable_property_should_be_immutable(string property)
{
var source = SourceFactory.ImmutableRecordWithParameter(property, out _, out _);
var test = AnalyzerTestFactory.CreateCSharpAnalyzerTest<ParameterTypeAnalyzer>(source);

await test.RunAsync().ConfigureAwait(false);
Assert.True(true); // SonarLint S2699
}

[Theory]
[ClassData(typeof(TestData.MutableTypes))]
public async Task Record_with_mutable_property_should_not_be_immutable(string property)
{
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);
}
}
Loading

0 comments on commit 535ec2b

Please sign in to comment.