Skip to content

Conversation

@HandyS11
Copy link
Owner

No description provided.

Copilot AI review requested due to automatic review settings January 19, 2026 15:13
@HandyS11 HandyS11 merged commit 8c012b4 into develop Jan 19, 2026
5 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request focuses on refactoring and improving code quality across the ProjGraph solution. The main purpose is to reorganize the Entity Framework analysis service into a more maintainable structure with better separation of concerns, add comprehensive XML documentation, and introduce interface-based design for better testability.

Changes:

  • Refactored EfAnalysisService by splitting it into multiple focused classes within a new EfAnalysis namespace
  • Added comprehensive XML documentation comments across all public APIs
  • Introduced IGraphService interface for dependency injection
  • Removed the includePackages parameter from GetProjectGraph API
  • Updated SDK version in global.json
  • Added SonarQube workflow for code quality analysis

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/ProjGraph.Lib/Services/EfAnalysisService.cs Deleted - code refactored into multiple files in EfAnalysis folder
src/ProjGraph.Lib/Services/EfAnalysis/*.cs New files - decomposed EfAnalysisService into focused components (EntityAnalyzer, RelationshipAnalyzer, NavigationPropertyAnalyzer, etc.)
src/ProjGraph.Lib/Services/GraphService.cs Added IGraphService interface implementation and XML documentation
src/ProjGraph.Lib/Interfaces/IGraphService.cs New interface for graph service to enable dependency injection
src/ProjGraph.Mcp/Program.cs Updated to use IGraphService interface and removed includePackages parameter
src/ProjGraph.Lib/Rendering/*.cs Added XML documentation comments
src/ProjGraph.Lib/Parsers/*.cs Added XML documentation comments
src/ProjGraph.Core/Models/*.cs Added XML documentation and explicit enum values
src/ProjGraph.Cli/Commands/*.cs Added XML documentation comments
tests/ProjGraph.Tests.*/**.cs Updated imports to use new EfAnalysis namespace
global.json Updated SDK version from 10.0.101 to 10.0.102
.github/workflows/sonarqube.yml Added new workflow for SonarQube code quality analysis

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Assert parameters match spec
var parameters = method.GetParameters();
parameters.Should().Contain(p => p.Name == "path" && p.ParameterType == typeof(string));
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the includePackages parameter test assertion appears to be intentional as part of removing this feature from the GetProjectGraph tool. However, this represents a breaking change to the API. Consider adding a comment or documentation to explain why this parameter was removed, or verify that this change is documented in release notes.

Copilot uses AI. Check for mistakes.
Comment on lines 40 to +42
public string GetProjectGraph(
[Description("Absolute path to the project or solution file.")] string path,
[Description("Include NuGet packages?")] bool includePackages = false)
[Description("Absolute path to the project or solution file.")]
string path)
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the includePackages parameter from GetProjectGraph represents a breaking change to the public API. Users who were calling this method with this parameter will experience compilation errors. If this is intentional, ensure proper versioning (e.g., major version bump) and migration documentation are in place.

Copilot uses AI. Check for mistakes.
/// It ensures that each project is only processed once by maintaining a set of discovered project paths.
/// If a project fails to parse, it is skipped, and the error is logged for debugging purposes.
/// </remarks>
private static HashSet<string> DiscoverProjectsRecursively(string rootProjectPath)
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type of DiscoverProjectsRecursively has been changed from IEnumerable<string> to HashSet<string>. While this is more accurate to the implementation, it changes the public signature. If this method is used by external code, this represents a breaking change. Consider whether this method should remain private or if the return type change is acceptable for the API contract.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +26
Library = 0,

/// <summary>
/// Represents an executable project.
/// </summary>
Executable = 1,

/// <summary>
/// Represents a test project.
/// </summary>
Test = 2,

/// <summary>
/// Represents a project of other types.
/// </summary>
Other = 3
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While adding explicit values to enum members (0, 1, 2, 3) is generally a good practice for stability, this changes the binary representation if the previous implicit values were different. Verify that this doesn't affect serialization, database storage, or any external systems that depend on these enum values.

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +100
foreach (var arg in currentType.GetAttributes()
.Where(attribute => attribute.AttributeClass?.Name == "PrimaryKeyAttribute")
.SelectMany(attribute => attribute.ConstructorArguments))
{
if (arg.Value is string pkName)
{
primaryKeyNames.Add(pkName);
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Suggested change
foreach (var arg in currentType.GetAttributes()
.Where(attribute => attribute.AttributeClass?.Name == "PrimaryKeyAttribute")
.SelectMany(attribute => attribute.ConstructorArguments))
{
if (arg.Value is string pkName)
{
primaryKeyNames.Add(pkName);
}
foreach (var pkName in currentType.GetAttributes()
.Where(attribute => attribute.AttributeClass?.Name == "PrimaryKeyAttribute")
.SelectMany(attribute => attribute.ConstructorArguments)
.Select(arg => arg.Value)
.OfType<string>())
{
primaryKeyNames.Add(pkName);

Copilot uses AI. Check for mistakes.
Comment on lines +238 to +243
foreach (var entityFile in entityFiles.Values.Distinct())
{
var entityCode = await File.ReadAllTextAsync(entityFile);
syntaxTrees.Add(CSharpSyntaxTree.ParseText(entityCode));
}

Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Suggested change
foreach (var entityFile in entityFiles.Values.Distinct())
{
var entityCode = await File.ReadAllTextAsync(entityFile);
syntaxTrees.Add(CSharpSyntaxTree.ParseText(entityCode));
}
var entitySyntaxTreesTasks = entityFiles.Values
.Distinct()
.Select(async entityFile =>
{
var entityCode = await File.ReadAllTextAsync(entityFile);
return CSharpSyntaxTree.ParseText(entityCode);
});
var entitySyntaxTrees = await Task.WhenAll(entitySyntaxTreesTasks);
syntaxTrees.AddRange(entitySyntaxTrees);

Copilot uses AI. Check for mistakes.
Comment on lines +261 to +266
foreach (var entityFile in entityFiles.Values.Distinct())
{
var entityCode = await File.ReadAllTextAsync(entityFile);
var entityTree = CSharpSyntaxTree.ParseText(entityCode);
var entityRoot = await entityTree.GetRootAsync();

Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Suggested change
foreach (var entityFile in entityFiles.Values.Distinct())
{
var entityCode = await File.ReadAllTextAsync(entityFile);
var entityTree = CSharpSyntaxTree.ParseText(entityCode);
var entityRoot = await entityTree.GetRootAsync();
var entityRoots = await Task.WhenAll(
entityFiles.Values
.Distinct()
.Select(async entityFile =>
{
var entityCode = await File.ReadAllTextAsync(entityFile);
var entityTree = CSharpSyntaxTree.ParseText(entityCode);
return await entityTree.GetRootAsync();
}));
foreach (var entityRoot in entityRoots)
{

Copilot uses AI. Check for mistakes.
@HandyS11 HandyS11 deleted the refactor/improveCodeQuality branch January 19, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants