-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor/improve code quality #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…roved entity analysis
There was a problem hiding this 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
EfAnalysisnamespace - Added comprehensive XML documentation comments across all public APIs
- Introduced
IGraphServiceinterface for dependency injection - Removed the
includePackagesparameter 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)); |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
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.
| 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) |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
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.
| /// 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) |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
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.
| 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 |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
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.
| 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); | ||
| } |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
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(...)'.
| 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); |
| foreach (var entityFile in entityFiles.Values.Distinct()) | ||
| { | ||
| var entityCode = await File.ReadAllTextAsync(entityFile); | ||
| syntaxTrees.Add(CSharpSyntaxTree.ParseText(entityCode)); | ||
| } | ||
|
|
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
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(...)'.
| 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); |
| foreach (var entityFile in entityFiles.Values.Distinct()) | ||
| { | ||
| var entityCode = await File.ReadAllTextAsync(entityFile); | ||
| var entityTree = CSharpSyntaxTree.ParseText(entityCode); | ||
| var entityRoot = await entityTree.GetRootAsync(); | ||
|
|
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
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(...)'.
| 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) | |
| { |
No description provided.