Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void GetProjectReferences_WhenReferenceHasNoWarn_ReturnsNoWarnValue()
references.Should().ContainSingle();
var reference = references.Single();
reference.Path.Should().Be("TestReferenceWithNoWarn.csproj");
reference.NoWarn.Should().ContainSingle().Which.Should().Be("RC0001;RC0002");
reference.NoWarn.Should().Contain("RC0001").And.Contain("RC0002");
}
finally
{
Expand Down Expand Up @@ -151,7 +151,7 @@ private string CreateTempProjectFileWithReferenceNoWarn()
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""TestReferenceWithNoWarn.csproj"" NoWarn=""RC0001;RC0002"" />
<ProjectReference Include=""TestReferenceWithNoWarn.csproj"" NoWarn=""RC0001,RC0002"" />
</ItemGroup>
</Project>";
File.WriteAllText(tempFile, projectContent);
Expand Down
32 changes: 32 additions & 0 deletions src/ReferenceCop.MSBuild/ReferenceCop.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
<Project>
<UsingTask TaskName="ReferenceCopTask" AssemblyFile="ReferenceCop.MSBuild.dll" />

<!-- Define global properties that will be set later -->
<PropertyGroup>
<ReferenceCop_NoWarnAssemblies></ReferenceCop_NoWarnAssemblies>
</PropertyGroup>

<!-- Define properties that will be visible to Roslyn analyzers -->
<!-- These properties will be prefixed with 'build_property.' when accessed by analyzers -->
<ItemGroup>
<CompilerVisibleProperty Include="ReferenceCop_NoWarnAssemblies" />
</ItemGroup>

<!-- This target runs before compilation and before analyzer execution to collect and expose NoWarn information -->
<Target Name="CollectNoWarnAssemblies" BeforeTargets="CoreCompile;BeforeCompile" AfterTargets="PrepareForBuild">
<!-- Collect NoWarn settings from project references, assembly references, and package references -->
<ItemGroup>
<_ReferenceCopNoWarnItem Include="@(ProjectReference->'%(Identity)')" Condition="'%(ProjectReference.NoWarn)' != ''">
<NoWarn>%(ProjectReference.NoWarn)</NoWarn>
</_ReferenceCopNoWarnItem>
<_ReferenceCopNoWarnItem Include="@(Reference->'%(Identity)')" Condition="'%(Reference.NoWarn)' != ''">
<NoWarn>%(Reference.NoWarn)</NoWarn>
</_ReferenceCopNoWarnItem>
<_ReferenceCopNoWarnItem Include="@(PackageReference->'%(Identity)')" Condition="'%(PackageReference.NoWarn)' != ''">
<NoWarn>%(PackageReference.NoWarn)</NoWarn>
</_ReferenceCopNoWarnItem>
</ItemGroup>

<!-- Set properties that will be visible to analyzers -->
<PropertyGroup>
<ReferenceCop_NoWarnAssemblies>@(_ReferenceCopNoWarnItem->'%(Identity)|%(NoWarn)', ';')</ReferenceCop_NoWarnAssemblies>
</PropertyGroup>
</Target>

<Target Name="RunReferenceCop" BeforeTargets="Build">
<ItemGroup>
<ProjectFile Include="$(MSBuildProjectFullPath)" />
Expand Down
8 changes: 4 additions & 4 deletions src/ReferenceCop.Roslyn.Tests/DiagnosticFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void CreateFor_WhenViolationHasErrorSeverity_CreatesErrorDiagnostic()
var violation = new Violation(rule, referenceName);

// Act
var diagnostic = global::ReferenceCop.DiagnosticFactory.CreateFor(violation);
var diagnostic = DiagnosticFactory.CreateFor(violation);

// Assert
using (new AssertionScope())
Expand Down Expand Up @@ -72,7 +72,7 @@ public void CreateFor_WhenViolationHasWarningSeverity_CreatesWarningDiagnostic()
var violation = new Violation(rule, referenceName);

// Act
var diagnostic = global::ReferenceCop.DiagnosticFactory.CreateFor(violation);
var diagnostic = DiagnosticFactory.CreateFor(violation);

// Assert
using (new AssertionScope())
Expand Down Expand Up @@ -111,7 +111,7 @@ public void CreateFor_WhenViolationHasUnknownSeverity_ReturnsNull()
var violation = new Violation(rule, referenceName);

// Act
var diagnostic = global::ReferenceCop.DiagnosticFactory.CreateFor(violation);
var diagnostic = DiagnosticFactory.CreateFor(violation);

// Assert
diagnostic.Should().BeNull();
Expand All @@ -129,7 +129,7 @@ public void CreateFor_WhenExceptionIsProvided_CreatesErrorDiagnostic()
var exception = new InvalidOperationException(exceptionMessage);

// Act
var diagnostic = global::ReferenceCop.DiagnosticFactory.CreateFor(exception);
var diagnostic = DiagnosticFactory.CreateFor(exception);

// Assert
using (new AssertionScope())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
namespace ReferenceCop.Roslyn.Tests
{
using System.Linq;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ReferenceCop;

[TestClass]
public class NoWarnAssembliesProviderTests
{
[TestMethod]
public void GetNoWarnByAssembly_WhenNullInput_ReturnsEmptyDictionary()
{
// Arrange.
var provider = new NoWarnAssembliesProvider();

// Act.
var result = provider.GetNoWarnByAssembly(null);

// Assert.
using (new AssertionScope())
{
result.Should().NotBeNull();
result.Should().BeEmpty();
}
}

[TestMethod]
public void GetNoWarnByAssembly_WhenEmptyInput_ReturnsEmptyDictionary()
{
// Arrange.
var provider = new NoWarnAssembliesProvider();

// Act.
var result = provider.GetNoWarnByAssembly(string.Empty);

// Assert.
using (new AssertionScope())
{
result.Should().NotBeNull();
result.Should().BeEmpty();
}
}

[TestMethod]
public void GetNoWarnByAssembly_WhenValidInput_ReturnsDictionaryWithEntries()
{
// Arrange.
var provider = new NoWarnAssembliesProvider();
string noWarnAssemblies = "Assembly1|RC0001,RC0002;Assembly2|RC0002";

// Act.
var result = provider.GetNoWarnByAssembly(noWarnAssemblies);

// Assert.
using (new AssertionScope())
{
result.Should().NotBeNull();
result.Should().HaveCount(2);
result.Should().ContainKey("Assembly1");
result.Should().ContainKey("Assembly2");

var assembly1Codes = result["Assembly1"].ToList();
assembly1Codes.Should().HaveCount(2);
assembly1Codes.Should().Contain("RC0001");
assembly1Codes.Should().Contain("RC0002");

var assembly2Codes = result["Assembly2"].ToList();
assembly2Codes.Should().HaveCount(1);
assembly2Codes.Should().Contain("RC0002");
}
}

[TestMethod]
public void GetNoWarnByAssembly_WhenInvalidEntries_IgnoresInvalidEntries()
{
// Arrange.
var provider = new NoWarnAssembliesProvider();
string noWarnAssemblies = "Assembly1|RC0001;InvalidEntry;Assembly2|RC0002";

// Act.
var result = provider.GetNoWarnByAssembly(noWarnAssemblies);

// Assert.
using (new AssertionScope())
{
result.Should().NotBeNull();
result.Should().HaveCount(2);
result.Should().ContainKey("Assembly1");
result.Should().ContainKey("Assembly2");
}
}

[TestMethod]
public void GetNoWarnByAssembly_WhenInputContainsWhitespace_PreservesAssemblyNamesTrimsCodeValues()
{
// Arrange.
var provider = new NoWarnAssembliesProvider();
string noWarnAssemblies = " Assembly1 | RC0001 , RC0002 ; Assembly2 | RC0002 ";

// Act.
var result = provider.GetNoWarnByAssembly(noWarnAssemblies);

// Assert.
using (new AssertionScope())
{
result.Should().NotBeNull();
result.Should().HaveCount(2);
result.Should().ContainKey(" Assembly1 ");
result.Should().ContainKey(" Assembly2 ");

var assembly1Codes = result[" Assembly1 "].ToList();
assembly1Codes.Should().HaveCount(2);
assembly1Codes.Should().Contain("RC0001");
assembly1Codes.Should().Contain("RC0002");
}
}

[TestMethod]
public void GetNoWarnByAssembly_WhenMultipleCodesForSameAssembly_StoresAllCodes()
{
// Arrange.
var provider = new NoWarnAssembliesProvider();
string noWarnAssemblies = "Assembly1|RC0001,RC0002,RC0003";

// Act.
var result = provider.GetNoWarnByAssembly(noWarnAssemblies);

// Assert.
using (new AssertionScope())
{
result.Should().NotBeNull();
result.Should().HaveCount(1);

var codes = result["Assembly1"].ToList();
codes.Should().HaveCount(3);
codes.Should().Contain("RC0001");
codes.Should().Contain("RC0002");
codes.Should().Contain("RC0003");
}
}

[TestMethod]
public void GetNoWarnByAssembly_WhenEntryHasNoCodes_StoresEmptyCollection()
{
// Arrange.
var provider = new NoWarnAssembliesProvider();
string noWarnAssemblies = "Assembly1|";

// Act.
var result = provider.GetNoWarnByAssembly(noWarnAssemblies);

// Assert.
using (new AssertionScope())
{
result.Should().NotBeNull();
result.Should().HaveCount(1);
result["Assembly1"].Should().BeEmpty();
}
}
}
}
2 changes: 1 addition & 1 deletion src/ReferenceCop.Roslyn/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace ReferenceCop
namespace ReferenceCop.Roslyn
{
using Microsoft.CodeAnalysis;

internal class DiagnosticDescriptors
{
public static readonly DiagnosticDescriptor GeneralError = new DiagnosticDescriptor(
"RC0000",

Check warning on line 8 in src/ReferenceCop.Roslyn/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'RC0000' (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 8 in src/ReferenceCop.Roslyn/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'RC0000' (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"General Error",
"An error occurred while executing the analyzer: {0}",
Category,
Expand All @@ -13,7 +13,7 @@
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor IllegalReferenceRule = new DiagnosticDescriptor(
Violation.ViolationSeverityErrorCode,

Check warning on line 16 in src/ReferenceCop.Roslyn/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'RC0001' (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 16 in src/ReferenceCop.Roslyn/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'RC0001' (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"Illegal references",
ViolationMessageTemplates.IllegalReference,
Category,
Expand All @@ -21,7 +21,7 @@
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor DiscouragedReferenceRule = new DiagnosticDescriptor(
Violation.ViolationSeverityWarningCode,

Check warning on line 24 in src/ReferenceCop.Roslyn/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'RC0002' (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 24 in src/ReferenceCop.Roslyn/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'RC0002' (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"Discouraged references",
ViolationMessageTemplates.DiscouragedReference,
Category,
Expand Down
2 changes: 1 addition & 1 deletion src/ReferenceCop.Roslyn/DiagnosticFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace ReferenceCop
namespace ReferenceCop.Roslyn
{
using System;
using Microsoft.CodeAnalysis;
Expand Down
24 changes: 24 additions & 0 deletions src/ReferenceCop.Roslyn/Providers/INoWarnAssembliesProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace ReferenceCop
{
using System.Collections.Generic;

/// <summary>
/// Provider interface for accessing NoWarn assembly configurations.
/// </summary>
public interface INoWarnAssembliesProvider
{
/// <summary>
/// Gets a dictionary of assembly names to their associated NoWarn codes from a raw string.
/// </summary>
/// <param name="noWarnAssembliesString">
/// The raw NoWarn assemblies configuration string.
/// The string follows the format: "AssemblyName1|Code1,Code2;AssemblyName2|Code3,Code4"
/// where:
/// - Each assembly entry is separated by semicolons (;)
/// - Each assembly entry consists of an assembly name and NoWarn codes separated by a pipe character (|)
/// - NoWarn codes are comma-separated
/// </param>
/// <returns>A dictionary where the key is the assembly name and the value is a collection of NoWarn codes.</returns>
Dictionary<string, IEnumerable<string>> GetNoWarnByAssembly(string noWarnAssembliesString);
}
}
71 changes: 71 additions & 0 deletions src/ReferenceCop.Roslyn/Providers/NoWarnAssembliesProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace ReferenceCop
{
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Provider for NoWarn assembly configurations in ReferenceCop.
/// </summary>
public class NoWarnAssembliesProvider : INoWarnAssembliesProvider
{
private static readonly Dictionary<string, IEnumerable<string>> EmptyDictionary = new Dictionary<string, IEnumerable<string>>();

private static readonly char[] AssemblyEntriesSeparator = new[] { ';' };
private static readonly char[] EntryPartsSeparator = new[] { '|' };
private static readonly char[] NoWarnCodesSeparator = new[] { ',' };

/// <summary>
/// Gets a dictionary of assembly names to their associated NoWarn codes from a raw string.
/// </summary>
/// <param name="noWarnAssembliesString">
/// The raw NoWarn assemblies configuration string.
/// The string follows the format: "AssemblyName1|Code1,Code2;AssemblyName2|Code3,Code4"
/// where:
/// - Each assembly entry is separated by semicolons (;).
/// - Each assembly entry consists of an assembly name and NoWarn codes separated by a pipe character (|).
/// - NoWarn codes are comma-separated.
/// </param>
/// <returns>A dictionary where the key is the assembly name and the value is a collection of NoWarn codes.</returns>
public Dictionary<string, IEnumerable<string>> GetNoWarnByAssembly(string noWarnAssembliesString)
{
if (string.IsNullOrEmpty(noWarnAssembliesString))
{
return EmptyDictionary;
}

var entries = noWarnAssembliesString.Split(AssemblyEntriesSeparator, StringSplitOptions.RemoveEmptyEntries);

if (entries.Length == 0)
{
return EmptyDictionary;
}

var result = new Dictionary<string, IEnumerable<string>>(entries.Length);
foreach (var entry in entries)
{
var parts = entry.Split(EntryPartsSeparator, 2);
if (parts.Length == 2)
{
var assemblyName = parts[0];
var noWarnCodesString = parts[1];

if (string.IsNullOrEmpty(noWarnCodesString))
{
result[assemblyName] = Array.Empty<string>();
}
else
{
var noWarnCodes = noWarnCodesString.Split(NoWarnCodesSeparator, StringSplitOptions.RemoveEmptyEntries)
.Select(code => code.Trim())
.ToArray();

result[assemblyName] = noWarnCodes;
}
}
}

return result;
}
}
}
Loading
Loading