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
7 changes: 7 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ Ensure all required fields in the template are filled out appropriately when cre
- MSBuild integration in `ReferenceCopTask.cs`
- Roslyn integration in `ReferenceCopAnalyzer.cs`

#### Adding Roslyn Diagnostics
When adding new diagnostic descriptors for the Roslyn analyzer:
1. Define the `DiagnosticDescriptor` in `DiagnosticDescriptors.cs` as a public static readonly field
2. Register it in the `SupportedDiagnostics` property of `ReferenceCopAnalyzer.cs`
3. Use `Diagnostic.Create(DiagnosticDescriptors.YourDescriptor, Location.None)` when reporting
4. Never create inline `DiagnosticDescriptor` instances in analyzer code

### Code Generation and Modification Guidelines

When adding or modifying code in the ReferenceCop project, follow these guidelines:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ L10N.Integrations/phantom*
# CAKE
tools/

# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts/

# VS Code settings
.vscode/mcp.json

Expand Down
19 changes: 19 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ Thanks for checking out the project! We're just getting started, so any help wil
- Test your changes before submitting.
- Update docs if needed

## Debugging

To attach a debugger during development:

1. **For MSBuild task debugging:**
- Add `<LaunchDebugger>MSBuild</LaunchDebugger>` to your project file
- Build the project - the debugger will launch automatically

2. **For Roslyn analyzer debugging:**
- Add `<LaunchDebugger>Roslyn</LaunchDebugger>` to your project file
- Add `<CompilerVisibleProperty Include="LaunchDebugger" />` to make the property visible to the analyzer
- Build the project - the debugger will launch automatically

See examples in the playground project files for reference.

## End-to-End Testing

Use the `playground` directory for end-to-end validation of your changes. See the [playground README](playground/README.md) for detailed instructions on how to test ReferenceCop changes in a real project environment.

## Need help?

No worries! If you're stuck or have questions, just open an issue or create a discussion straight on GitHub :)
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,42 @@ Here is an example of a `ReferenceCop.config` file:

In case you'd like to enable ReferenceCop and apply the same configuration to multiple projects in a directory, you can do that in a `Directory.Build.props` file by [Customizing the build by folder](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022).

### Advanced Configuration

ReferenceCop supports the following optional settings in the `ReferenceCop.config` file:

#### UseExperimentalDetectors

Enables experimental rule detectors that may provide additional validation capabilities. These detectors are under development and may change in future versions.

**Default:** `false`

**Example:**
```xml
<ReferenceCopConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/mfogliatto/ReferenceCop/main/ReferenceCopConfig.xsd">
<UseExperimentalDetectors>true</UseExperimentalDetectors>
<Rules>
<!-- Your rules here -->
</Rules>
</ReferenceCopConfig>
```

#### EnableDebugMessages

Enables detailed debug messages during build and analysis, useful for troubleshooting rule evaluation and detector behavior.

**Default:** `false`

**Example:**
```xml
<ReferenceCopConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/mfogliatto/ReferenceCop/main/ReferenceCopConfig.xsd">
<EnableDebugMessages>true</EnableDebugMessages>
<Rules>
<!-- Your rules here -->
</Rules>
</ReferenceCopConfig>
```

## License

ReferenceCop is licensed under the MIT license. See LICENSE for more information.
2 changes: 2 additions & 0 deletions ReferenceCopConfig.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<xs:element name="ReferenceCopConfig">
<xs:complexType>
<xs:sequence>
<xs:element name="UseExperimentalDetectors" type="xs:boolean" minOccurs="0" />
<xs:element name="EnableDebugMessages" type="xs:boolean" minOccurs="0" />
<xs:element name="Rules">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
Expand Down
2 changes: 2 additions & 0 deletions playground/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Standard .NET ignores for the test projects
**/bin/
**/obj/
# Local NuGet packages cache
local-packages/
4 changes: 4 additions & 0 deletions playground/TestProject/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@
</AdditionalFiles>
</ItemGroup>

<Target Name="PrintReferenceCopVersion" BeforeTargets="CoreCompile">
<Message Text="ReferenceCop Version: $(ReferenceCopVersion)" Importance="high" />
</Target>

</Project>
2 changes: 2 additions & 0 deletions playground/TestProject/ReferenceCop.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ReferenceCopConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/mfogliatto/ReferenceCop/main/ReferenceCopConfig.xsd">
<UseExperimentalDetectors>true</UseExperimentalDetectors>
<EnableDebugMessages>true</EnableDebugMessages>
<Rules>
<!-- Example 1: Block references to assemblies matching a pattern -->
<AssemblyName>
Expand Down
9 changes: 9 additions & 0 deletions playground/TestProject/SampleApp/SampleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ProjectTag>App</ProjectTag>

<!-- Uncomment to attach debugger during build -->
<!-- <LaunchDebugger>MSBuild</LaunchDebugger> -->
<!-- <LaunchDebugger>Roslyn</LaunchDebugger> -->
</PropertyGroup>

<!-- Required for passing LaunchDebugger property to Roslyn Analyzer -->
<ItemGroup>
<CompilerVisibleProperty Include="LaunchDebugger" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SampleLibrary\SampleLibrary.csproj" />
</ItemGroup>
Expand Down
10 changes: 9 additions & 1 deletion playground/TestProject/SampleLibrary/SampleLibrary.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@
<Nullable>enable</Nullable>
<!-- Tag this project as a library -->
<ProjectTag>Library</ProjectTag>

<!-- Uncomment to attach debugger during build -->
<!-- <LaunchDebugger>MSBuild</LaunchDebugger> -->
<!--<LaunchDebugger>Roslyn</LaunchDebugger>-->
</PropertyGroup>

<!-- Required for passing LaunchDebugger property to Roslyn Analyzer -->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<CompilerVisibleProperty Include="LaunchDebugger" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="globalPackagesFolder" value="./local-packages" />
</config>

<packageSources>
<clear />
<!-- Direct reference to ReferenceCop package build output -->
<add key="local-build" value="../../src/ReferenceCop.Package/bin/Debug" />
<add key="local-build" value="../src/ReferenceCop.Package/bin/Debug" />
<!-- NuGet.org for other dependencies -->
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
Expand Down
10 changes: 10 additions & 0 deletions playground/playground.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Solution>
<Folder Name="/Solution Items/">
<File Path="nuget.config" />
<File Path="README.md" />
<File Path="TestProject/Directory.Build.props" />
<File Path="TestProject/ReferenceCop.config" />
</Folder>
<Project Path="TestProject/SampleApp/SampleApp.csproj" />
<Project Path="TestProject/SampleLibrary/SampleLibrary.csproj" />
</Solution>
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="Microsoft.Build" Version="16.8.0" />
<PackageVersion Include="Microsoft.Build.Framework" Version="16.8.0" />
Expand Down
104 changes: 104 additions & 0 deletions src/ReferenceCop.Benchmarks/AssemblyNameViolationDetectorBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
namespace ReferenceCop.Benchmarks
{
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Microsoft.CodeAnalysis;

[MemoryDiagnoser]
[RankColumn]
public class AssemblyNameViolationDetectorBenchmarks
{
private AssemblyNameViolationDetector detector;
private List<ReferenceEvaluationContext<AssemblyIdentity>> references;

[Params(10, 50, 100)]
public int RuleCount { get; set; }

[Params(10, 100, 500)]
public int ReferenceCount { get; set; }

[GlobalSetup]
public void Setup()
{
var config = this.CreateConfig(this.RuleCount);
this.detector = new AssemblyNameViolationDetector(new PatternMatchComparer(), config);
this.references = this.CreateReferences(this.ReferenceCount);
}

[Benchmark(Baseline = true)]
public int Original()
{
return this.detector.GetViolationsFrom(this.references).Count();
}

[Benchmark]
public int Experimental()
{
return this.detector.GetViolationsFromExperimental(this.references).Count();
}

private ReferenceCopConfig CreateConfig(int ruleCount)
{
var config = new ReferenceCopConfig();
var rules = new List<ReferenceCopConfig.Rule>();

// Create a mix of exact match and pattern rules (70% exact, 30% patterns)
for (int i = 0; i < ruleCount; i++)
{
string pattern;
if (i % 10 < 7)
{
// Exact match
pattern = $"ExactMatch.Assembly{i}";
}
else
{
// Pattern match
pattern = $"Pattern.Assembly{i}.*";
}

rules.Add(new ReferenceCopConfig.AssemblyName
{
Pattern = pattern,
Severity = ReferenceCopConfig.Rule.ViolationSeverity.Warning,
Description = $"Reference to {pattern} is discouraged",
});
}

config.Rules = rules;
return config;
}

private List<ReferenceEvaluationContext<AssemblyIdentity>> CreateReferences(int count)
{
var references = new List<ReferenceEvaluationContext<AssemblyIdentity>>();

for (int i = 0; i < count; i++)
{
string assemblyName;

// Create a mix where some match rules and some don't
if (i % 5 == 0)
{
// Exact match (will match exact rules)
assemblyName = $"ExactMatch.Assembly{i % this.RuleCount}";
}
else if (i % 5 == 1)
{
// Pattern match (will match pattern rules)
assemblyName = $"Pattern.Assembly{i % this.RuleCount}.SubAssembly";
}
else
{
// No match
assemblyName = $"NoMatch.Assembly{i}";
}

references.Add(ReferenceEvaluationContextFactory.Create(new AssemblyIdentity(assemblyName)));
}

return references;
}
}
}
12 changes: 12 additions & 0 deletions src/ReferenceCop.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace ReferenceCop.Benchmarks
{
using BenchmarkDotNet.Running;

public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run<AssemblyNameViolationDetectorBenchmarks>();
}
}
}
27 changes: 27 additions & 0 deletions src/ReferenceCop.Benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ReferenceCop Benchmarks

This project contains performance benchmarks for the ReferenceCop library using BenchmarkDotNet.

## Running the Benchmarks

To run the benchmarks, execute the following command from the repository root:

```powershell
dotnet run -c Release --project src/ReferenceCop.Benchmarks/ReferenceCop.Benchmarks.csproj
```

## AssemblyNameViolationDetectorBenchmarks

Compares the performance of the original `GetViolationsFrom` method against the optimized `GetViolationsFromExperimental` method.

## Understanding the Results

BenchmarkDotNet will display:
- **Mean**: Average execution time
- **Error**: Standard error of all measurements
- **StdDev**: Standard deviation of all measurements
- **Rank**: Relative ranking of methods
- **Gen0/1/2**: Garbage collection statistics
- **Allocated**: Total memory allocated

Look for the **Rank** column and **Mean** times to compare performance.
17 changes: 17 additions & 0 deletions src/ReferenceCop.Benchmarks/ReferenceCop.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ReferenceCop\ReferenceCop.csproj" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions src/ReferenceCop.MSBuild/BuildEngineExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ public static void LogViolation(this IBuildEngine self, Violation violation, str
}
}

public static void LogDebugMessage(this IBuildEngine self, string message)
{
var warningEvent = new BuildWarningEventArgs(
subcategory: SenderName,
code: "RC9999",
file: default,
lineNumber: default,
columnNumber: default,
endLineNumber: default,
endColumnNumber: default,
message: $"[DEBUG]: {message}",
helpKeyword: default,
senderName: SenderName);
self.LogWarningEvent(warningEvent);
}

internal static void LogErrorEvent(this IBuildEngine self, Exception ex)
{
var errorEvent = CreateErrorEventFor(ex);
Expand Down
Loading