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
61 changes: 57 additions & 4 deletions .github/instructions/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,28 @@ Please ensure all code follows StyleCop-based conventions as defined in the proj
2. **Ordering Rules**:
- Using directives should be placed inside namespaces
- System using directives should be listed first
3. **Layout Rules**: All files must end with a newline
3. **Layout Rules**:
- All files must end with a newline
- Use 4 spaces for indentation, not tabs
- Maintain consistent indentation when adding or modifying code blocks
4. **File Organization**:
- Each type (class, interface, struct, enum) should have its own file
- The file name should match the type name exactly
- Nested types can be in the same file as their parent type

5. **Whitespace Rules**:
- No trailing whitespace at the end of lines (SA1028)
- No empty lines with whitespace characters
- Use a single blank line to separate logical code blocks
- Use a single space after keywords like `if`, `for`, `while`, etc.
- Use a single space before opening braces

6. **Indentation Guidelines**:
- Always preserve the existing indentation style when modifying code
- Match the surrounding code's indentation level precisely when adding new code
- Ensure that all opening and closing braces maintain proper alignment
- For method parameters and arguments that span multiple lines, align parameters with the first parameter
- Do not mix tabs and spaces for indentation

When adding or modifying code, adhere to these style conventions for consistency across the project.

Expand All @@ -95,9 +116,8 @@ When adding or modifying code, adhere to these style conventions for consistency
`result.Should().Be(expectedValue).Because("the discount should apply");`

4. **Test Structure**:
Explicitly separate test phases with comments:
```

Explicitly separate test phases with comments and maintain proper indentation:
```csharp
// Arrange.
var service = Substitute.For<IService>();

Expand All @@ -108,6 +128,11 @@ var result = sut.Calculate();
result.Should().NotBeNull();
```

When adding test methods, ensure that indentation is consistent with existing code:
- Use 4 spaces for indentation levels
- Align closing braces with their corresponding opening statements
- Maintain proper indentation for nested code blocks within test methods

5. **Framework**:
Use **MSTest** attributes:
- Annotate test classes with `[TestClass]`
Expand Down Expand Up @@ -147,12 +172,40 @@ Ensure all required fields in the template are filled out appropriately when cre
- MSBuild integration in `ReferenceCopTask.cs`
- Roslyn integration in `ReferenceCopAnalyzer.cs`

### Code Generation and Modification Guidelines

When adding or modifying code in the ReferenceCop project, follow these guidelines:

1. **Code Addition**:
- Match existing indentation patterns precisely
- Ensure all nested blocks follow the 4-space indentation rule
- Maintain the same brace style as surrounding code

2. **Code Modification**:
- Preserve the original indentation when replacing or modifying existing code
- When refactoring, ensure that the modified code follows the same indentation patterns
- Do not change indentation of surrounding, unchanged code

3. **Line Wrapping**:
- For method signatures that span multiple lines, indent continuation lines with 4 spaces
- For parameter lists, align parameters with the first parameter
- For chained method calls, use consistent indentation for each line

4. **XML Documentation**:
- Ensure XML doc comments maintain consistent indentation with the code they document
- Indent XML doc elements consistently within the comment block

## Instructions for the AI Agent

- Write clean, idiomatic C# code.
- Follow the coding standards and style described in the sections above.
- Add XML documentation for all public methods.
- Write MSTest tests for all new code.
- Pay careful attention to code indentation:
- Maintain 4-space indentation throughout the codebase
- Align braces correctly according to the existing code style
- Ensure that code additions match the surrounding indentation exactly
- When generating code, verify that indentation remains consistent with existing patterns
- If you need clarification, request it in the `[ASSISTANT NOTE]` section below.

## Reference
Expand Down
23 changes: 0 additions & 23 deletions src/.github/PULL_REQUEST_TEMPLATE.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ReferenceCop.MSBuild.Tests
{
using System.IO;
using System.Linq;
using FluentAssertions;
using Microsoft.Build.Evaluation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand All @@ -24,6 +25,30 @@ public void Setup()
ProjectCollection.GlobalProjectCollection.UnloadAllProjects();
}

[TestMethod]
public void GetProjectReferences_WhenReferenceHasNoWarn_ReturnsNoWarnValue()
{
// Arrange.
string tempProjectPath = this.CreateTempProjectFileWithReferenceNoWarn();

try
{
// Act.
var references = this.provider.GetProjectReferences(tempProjectPath).ToList();

// Assert.
references.Should().ContainSingle();
var reference = references.Single();
reference.Path.Should().Be("TestReferenceWithNoWarn.csproj");
reference.NoWarn.Should().ContainSingle().Which.Should().Be("RC0001;RC0002");
}
finally
{
// Cleanup
File.Delete(tempProjectPath);
}
}

[TestMethod]
public void GetProjectReferences_WhenProjectHasOneReference_ReturnsSingleReference()
{
Expand All @@ -36,7 +61,7 @@ public void GetProjectReferences_WhenProjectHasOneReference_ReturnsSingleReferen
var references = this.provider.GetProjectReferences(tempProjectPath);

// Assert.
references.Should().NotBeNull().And.ContainSingle(r => r == TestReference);
references.Should().NotBeNull().And.ContainSingle(r => r.Path == TestReference);
}
finally
{
Expand Down Expand Up @@ -113,6 +138,21 @@ private string CreateTempProjectFileWithProperty()
<TargetFramework>net6.0</TargetFramework>
<{TestPropertyName}>{TestPropertyValue}</{TestPropertyName}>
</PropertyGroup>
</Project>";
File.WriteAllText(tempFile, projectContent);
return tempFile;
}

private string CreateTempProjectFileWithReferenceNoWarn()
{
string tempFile = Path.GetTempFileName() + ".csproj";
string projectContent = @"<Project>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""TestReferenceWithNoWarn.csproj"" NoWarn=""RC0001;RC0002"" />
</ItemGroup>
</Project>";
File.WriteAllText(tempFile, projectContent);
return tempFile;
Expand Down
19 changes: 10 additions & 9 deletions src/ReferenceCop.MSBuild.Tests/ReferenceCopTaskTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ReferenceCop.MSBuild.Tests
{
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Microsoft.Build.Framework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand All @@ -27,13 +28,13 @@ public void Execute_WhenViolationHasWarningSeverity_ReturnsTrue()

// Setup the fake IProjectMetadataProvider
fakeProjectReferencesProvider.GetProjectReferences(Arg.Any<string>())
.Returns(new List<string> { "ReferenceProject.csproj" });
.Returns(new List<ProjectReferenceInfo> { new ProjectReferenceInfo("ReferenceProject.csproj") });
fakeProjectReferencesProvider.GetPropertyValue(Arg.Any<string>(), Arg.Any<string>())
.Returns("C:\\path\\to\\repo");

tagViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<string>>())
tagViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<ReferenceEvaluationContext<string>>>())
.Returns(new List<Violation> { warningViolation });
pathViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<string>>())
pathViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<ReferenceEvaluationContext<string>>>())
.Returns(new List<Violation>());

fakeConfigLoader.Load().Returns(new ReferenceCopConfig());
Expand Down Expand Up @@ -71,16 +72,16 @@ public void Execute_WhenViolationHasErrorSeverity_ReturnsFalse()

// Setup the fake IProjectReferencesProvider
fakeProjectReferencesProvider.GetProjectReferences(Arg.Any<string>())
.Returns(new List<string> { "ReferenceProject.csproj" });
.Returns(new List<ProjectReferenceInfo> { new ProjectReferenceInfo("ReferenceProject.csproj") });
fakeProjectReferencesProvider.GetPropertyValue(Arg.Any<string>(), Arg.Any<string>())
.Returns("C:\\path\\to\\repo");

var rule = new ReferenceCopConfig.AssemblyName { Severity = ReferenceCopConfig.Rule.ViolationSeverity.Error };
var errorViolation = new Violation(rule, "TestReference");

tagViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<string>>())
tagViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<ReferenceEvaluationContext<string>>>())
.Returns(new List<Violation> { errorViolation });
pathViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<string>>())
pathViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<ReferenceEvaluationContext<string>>>())
.Returns(new List<Violation>());

fakeConfigLoader.Load().Returns(new ReferenceCopConfig());
Expand Down Expand Up @@ -118,7 +119,7 @@ public void Execute_WhenHasBothErrorAndWarningSeverities_ReturnsFalse()

// Setup the fake IProjectReferencesProvider
fakeProjectReferencesProvider.GetProjectReferences(Arg.Any<string>())
.Returns(new List<string> { "ReferenceProject.csproj" });
.Returns(new List<ProjectReferenceInfo> { new ProjectReferenceInfo("ReferenceProject.csproj") });
fakeProjectReferencesProvider.GetPropertyValue(Arg.Any<string>(), Arg.Any<string>())
.Returns("C:\\path\\to\\repo");

Expand All @@ -127,9 +128,9 @@ public void Execute_WhenHasBothErrorAndWarningSeverities_ReturnsFalse()
var errorViolation = new Violation(errorRule, "TestErrorReference");
var warningViolation = new Violation(warningRule, "TestWarningReference");

tagViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<string>>())
tagViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<ReferenceEvaluationContext<string>>>())
.Returns(new List<Violation> { errorViolation });
pathViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<string>>())
pathViolationDetector.GetViolationsFrom(Arg.Any<IEnumerable<ReferenceEvaluationContext<string>>>())
.Returns(new List<Violation> { warningViolation });

fakeConfigLoader.Load().Returns(new ReferenceCopConfig());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public interface IProjectMetadataProvider
/// </summary>
/// <param name="projectFilePath">The path to the project file.</param>
/// <returns>The collection of project references.</returns>
IEnumerable<string> GetProjectReferences(string projectFilePath);
IEnumerable<ProjectReferenceInfo> GetProjectReferences(string projectFilePath);

/// <summary>
/// Gets a resolved property value from a project file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,33 @@ namespace ReferenceCop.MSBuild
public class MSBuildProjectMetadataProvider : IProjectMetadataProvider
{
private const string ProjectReferenceNode = "ProjectReference";
private const string NoWarnMetadata = "NoWarn";

/// <summary>
/// Gets the project references from a project file.
/// </summary>
/// <param name="projectFilePath">The path to the project file.</param>
/// <returns>The collection of project references.</returns>
public IEnumerable<string> GetProjectReferences(string projectFilePath)
public IEnumerable<ProjectReferenceInfo> GetProjectReferences(string projectFilePath)
{
var projectCollection = new ProjectCollection();
var project = projectCollection.LoadProject(projectFilePath);

// Get all ProjectReference items. These are the direct project references.
var projectReferences = project.GetItems(ProjectReferenceNode);

// Extract the Include attribute, which contains the path to the referenced project.
return projectReferences.Select(pr => pr.EvaluatedInclude);
// Extract the Include attribute and NoWarn metadata for each reference
foreach (var pr in projectReferences)
{
string referencePath = pr.EvaluatedInclude;
string noWarnValue = pr.GetMetadataValue(NoWarnMetadata);

IEnumerable<string> noWarnCodes = string.IsNullOrEmpty(noWarnValue)
? new List<string>()
: noWarnValue.Split(',').Select(code => code.Trim());

yield return new ProjectReferenceInfo(referencePath, noWarnCodes);
}
}

/// <summary>
Expand Down
31 changes: 31 additions & 0 deletions src/ReferenceCop.MSBuild/Providers/ProjectReferenceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace ReferenceCop.MSBuild
{
using System.Collections.Generic;

/// <summary>
/// Contains information about a project reference, including the path and NoWarn values.
/// </summary>
public class ProjectReferenceInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="ProjectReferenceInfo"/> class.
/// </summary>
/// <param name="path">The path to the referenced project.</param>
/// <param name="noWarn">The NoWarn values for this reference.</param>
public ProjectReferenceInfo(string path, IEnumerable<string> noWarn = null)
{
this.Path = path;
this.NoWarn = noWarn ?? new List<string>();
}

/// <summary>
/// Gets the path to the referenced project.
/// </summary>
public string Path { get; }

/// <summary>
/// Gets the NoWarn values for this reference.
/// </summary>
public IEnumerable<string> NoWarn { get; }
}
}
22 changes: 14 additions & 8 deletions src/ReferenceCop.MSBuild/ReferenceCopTask.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace ReferenceCop.MSBuild
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Build.Framework;

public class ReferenceCopTask : ITask
Expand All @@ -11,8 +13,8 @@ public class ReferenceCopTask : ITask

private readonly IProjectMetadataProvider projectReferencesProvider;
private readonly Func<string, IConfigurationLoader> configLoaderFactory;
private readonly Func<ReferenceCopConfig, string, IViolationDetector<string>> tagViolationDetectorFactory;
private readonly Func<ReferenceCopConfig, string, string, IViolationDetector<string>> pathViolationDetectorFactory;
private readonly Func<ReferenceCopConfig, string, IViolationDetector<string>> projectTagViolationDetectorFactory;
private readonly Func<ReferenceCopConfig, string, string, IViolationDetector<string>> projectPathViolationDetectorFactory;

/// <summary>
/// Initializes a new instance of the <see cref="ReferenceCopTask"/> class.
Expand Down Expand Up @@ -41,10 +43,10 @@ public ReferenceCopTask(

this.configLoaderFactory = (configFilePaths) => configLoader ?? new XmlConfigurationLoader(configFilePaths);

this.tagViolationDetectorFactory = (config, projectPath) =>
this.projectTagViolationDetectorFactory = (config, projectPath) =>
tagViolationDetector ?? new ProjectTagViolationDetector(config, projectPath, new ProjectTagProvider());

this.pathViolationDetectorFactory = (config, projectPath, repositoryRoot) =>
this.projectPathViolationDetectorFactory = (config, projectPath, repositoryRoot) =>
pathViolationDetector ?? new ProjectPathViolationDetector(config, projectPath, new ProjectPathProvider(repositoryRoot));
}

Expand All @@ -70,10 +72,14 @@ public bool Execute()
var configFilePath = ConfigFilePathsParser.Parse(this.ConfigFilePaths);
var configLoader = this.configLoaderFactory(configFilePath);
var config = configLoader.Load();

var projectReferences = this.projectReferencesProvider.GetProjectReferences(this.ProjectFile.ItemSpec);
var evaluationContexts = projectReferences
.Select(_ => ReferenceEvaluationContextFactory.Create(_.Path, _.NoWarn))
.ToList();

var projectTagViolationDetector = this.tagViolationDetectorFactory(config, this.ProjectFile.ItemSpec);
foreach (var violation in projectTagViolationDetector.GetViolationsFrom(projectReferences))
var projectTagViolationDetector = this.projectTagViolationDetectorFactory(config, this.ProjectFile.ItemSpec);
foreach (var violation in projectTagViolationDetector.GetViolationsFrom(evaluationContexts))
{
if (violation.Rule.Severity == ReferenceCopConfig.Rule.ViolationSeverity.Error)
{
Expand All @@ -85,8 +91,8 @@ public bool Execute()

var repositoryRoot = this.projectReferencesProvider.GetPropertyValue(
this.ProjectFile.ItemSpec, ReferenceCopRepositoryRootProperty);
var projectPathViolationDetector = this.pathViolationDetectorFactory(config, this.ProjectFile.ItemSpec, repositoryRoot);
foreach (var violation in projectPathViolationDetector.GetViolationsFrom(projectReferences))
var projectPathViolationDetector = this.projectPathViolationDetectorFactory(config, this.ProjectFile.ItemSpec, repositoryRoot);
foreach (var violation in projectPathViolationDetector.GetViolationsFrom(evaluationContexts))
{
if (violation.Rule.Severity == ReferenceCopConfig.Rule.ViolationSeverity.Error)
{
Expand Down
Loading
Loading