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
50 changes: 50 additions & 0 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: SonarQube Analysis

permissions:
contents: read

on:
push:
branches:
- develop


jobs:
build:
name: Build and analyze
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'zulu'
- name: Cache SonarQube packages
uses: actions/cache@v4
with:
path: ${{ runner.temp }}\cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarQube scanner
id: cache-sonar-scanner
uses: actions/cache@v4
with:
path: ${{ runner.temp }}\scanner
key: ${{ runner.os }}-sonar-scanner
restore-keys: ${{ runner.os }}-sonar-scanner
- name: Install SonarQube scanner
if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
shell: powershell
run: |
New-Item -Path ${{ runner.temp }}\scanner -ItemType Directory
dotnet tool update dotnet-sonarscanner --tool-path ${{ runner.temp }}\scanner
- name: Build and analyze
shell: powershell
run: |
${{ runner.temp }}\scanner\dotnet-sonarscanner begin /k:"HandyS11_ProjGraph_e194b77c-4ce9-44cc-b6ad-4e5960271a22" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="${{ secrets.SONAR_HOST_URL }}"
dotnet build
${{ runner.temp }}\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
1 change: 1 addition & 0 deletions ProjGraph.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<Folder Name="/Solution Items/.github/workflows/">
<File Path=".github/workflows/ci.yml" />
<File Path=".github/workflows/publish.yml" />
<File Path=".github/workflows/sonarqube.yml"/>
</Folder>
<Folder Name="/src/">
<Project Path="src/ProjGraph.Core/ProjGraph.Core.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.101",
"version": "10.0.102",
"rollForward": "latestMajor",
"allowPrerelease": true
}
Expand Down
47 changes: 46 additions & 1 deletion src/ProjGraph.Cli/Commands/ErdCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using ProjGraph.Lib.Rendering;
using ProjGraph.Lib.Services;
using ProjGraph.Lib.Services.EfAnalysis;
using Spectre.Console;
using Spectre.Console.Cli;
using System.ComponentModel;
Expand All @@ -8,19 +8,49 @@

namespace ProjGraph.Cli.Commands;

/// <summary>
/// Represents the command for generating an Entity Relationship Diagram (ERD) from a DbContext file.
/// </summary>
/// <remarks>
/// The <see cref="ErdCommand"/> class is an asynchronous command that utilizes the `Settings` class
/// to configure the path to the DbContext file and the optional DbContext name. It processes the input
/// and generates a Mermaid ERD diagram based on the analyzed DbContext.
/// </remarks>
public sealed class ErdCommand : AsyncCommand<ErdCommand.Settings>
{
/// <summary>
/// Represents the settings for the `ErdCommand`.
/// </summary>
/// <remarks>
/// This class contains the configuration options for the `ErdCommand`, including the path to the DbContext file
/// and the optional DbContext name. It also provides validation for the input settings.
/// </remarks>
public sealed class Settings : CommandSettings
{
/// <summary>
/// Gets or sets the path to a .cs file containing a DbContext.
/// If not specified, the current directory will be searched for DbContext files.
/// </summary>
[CommandArgument(0, "[path]")]
[Description(
"Path to a .cs file containing a DbContext (optional, searches current directory if not specified)")]
public string? Path { get; init; }

/// <summary>
/// Gets or sets the name of the DbContext to analyze.
/// This is an optional parameter.
/// </summary>
[CommandOption("-c|--context")]
[Description("The name of the DbContext to analyze (optional)")]
public string? ContextName { get; init; }

/// <summary>
/// Validates the settings provided for the command.
/// Ensures that the specified path exists, is a .cs file, or is left empty to search the current directory.
/// </summary>
/// <returns>
/// A <see cref="ValidationResult"/> indicating whether the settings are valid.
/// </returns>
public override ValidationResult Validate()
{
if (string.IsNullOrWhiteSpace(Path))
Expand All @@ -42,6 +72,21 @@ public override ValidationResult Validate()
}
}

/// <summary>
/// Executes the command asynchronously, analyzing the specified DbContext file and generating a Mermaid ERD diagram.
/// </summary>
/// <param name="context">
/// The command context containing information about the execution environment.
/// </param>
/// <param name="settings">
/// The settings for the command, including the path to the DbContext file and the optional DbContext name.
/// </param>
/// <param name="cancellationToken">
/// A token to monitor for cancellation requests.
/// </param>
/// <returns>
/// An integer representing the exit code of the command. Returns 0 if successful, 1 otherwise.
/// </returns>
public override async Task<int> ExecuteAsync(
CommandContext context,
Settings settings,
Expand Down
45 changes: 45 additions & 0 deletions src/ProjGraph.Cli/Commands/VisualizeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,48 @@

namespace ProjGraph.Cli.Commands;

/// <summary>
/// Represents the command for visualizing the structure of a solution or project file.
/// </summary>
/// <remarks>
/// The <see cref="VisualizeCommand"/> class is an asynchronous command that uses the <see cref="Settings"/> class
/// to configure the path to the solution or project file and the desired output format. It processes the input
/// and renders the structure in the specified format (tree or mermaid).
/// </remarks>
public sealed class VisualizeCommand : AsyncCommand<VisualizeCommand.Settings>
{
/// <summary>
/// Represents the settings for the `VisualizeCommand`.
/// </summary>
/// <remarks>
/// This class contains the configuration options for the `VisualizeCommand`, including the path to the solution or project file
/// and the desired output format. It also provides validation for the input settings.
/// </remarks>
public sealed class Settings : CommandSettings
{
/// <summary>
/// Gets or sets the path to the .sln, .slnx, or .csproj file to be analyzed.
/// </summary>
[CommandArgument(0, "<PATH>")]
[Description("The path to the .sln, .slnx, or .csproj file")]
public string Path { get; init; } = string.Empty;

/// <summary>
/// Gets or sets the output format for the visualization.
/// Supported formats are "tree" and "mermaid".
/// </summary>
[CommandOption("-f|--format")]
[Description("The output format (tree, mermaid)")]
[DefaultValue("tree")]
public string Format { get; init; } = "tree";

/// <summary>
/// Validates the settings provided for the command.
/// Ensures that the specified path exists, is valid, and that the format is either "tree" or "mermaid".
/// </summary>
/// <returns>
/// A <see cref="ValidationResult"/> indicating whether the settings are valid.
/// </returns>
public override ValidationResult Validate()
{
if (string.IsNullOrWhiteSpace(Path))
Expand All @@ -43,6 +72,22 @@ public override ValidationResult Validate()
}
}

/// <summary>
/// Executes the command asynchronously, analyzing the specified solution or project file and rendering its structure
/// in the specified format (tree or mermaid).
/// </summary>
/// <param name="context">
/// The command context containing information about the execution environment.
/// </param>
/// <param name="settings">
/// The settings for the command, including the path to the solution or project file and the desired output format.
/// </param>
/// <param name="cancellationToken">
/// A token to monitor for cancellation requests.
/// </param>
/// <returns>
/// An integer representing the exit code of the command. Returns 0 if successful, 1 otherwise.
/// </returns>
public override async Task<int> ExecuteAsync(
CommandContext context,
Settings settings,
Expand Down
78 changes: 78 additions & 0 deletions src/ProjGraph.Cli/Rendering/TreeRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,21 @@

namespace ProjGraph.Cli.Rendering;

/// <summary>
/// Provides methods for rendering a solution graph as a tree structure in the console.
/// </summary>
/// <remarks>
/// The <see cref="TreeRenderer"/> class includes methods to render the solution graph's header,
/// projects, dependencies, and any detected cyclic dependencies with proper formatting and color coding.
/// </remarks>
public static class TreeRenderer
{
/// <summary>
/// Renders the solution graph by displaying its header, projects, dependencies, and any detected cyclic dependencies.
/// </summary>
/// <param name="graph">
/// The solution graph containing the projects and dependencies to be rendered.
/// </param>
public static void Render(SolutionGraph graph)
{
RenderHeader(graph);
Expand Down Expand Up @@ -33,13 +46,32 @@ public static void Render(SolutionGraph graph)
RenderCycleWarning(cyclicProjectIds);
}

/// <summary>
/// Renders the header of the solution graph with proper formatting and styling.
/// </summary>
/// <param name="graph">
/// The solution graph containing the projects and dependencies to be displayed.
/// The graph's name will be used as the title of the header.
/// </param>
private static void RenderHeader(SolutionGraph graph)
{
var graphName = Markup.Escape(graph.Name.Trim());
AnsiConsole.Write(new Rule($"[yellow]Dependency Graph: {graphName}[/]") { Justification = Justify.Left });
AnsiConsole.MarkupLine("[bold blue]Projects[/]");
}

/// <summary>
/// Renders a project in the solution graph with appropriate formatting and color coding.
/// </summary>
/// <param name="project">The project to be rendered, represented as a <see cref="Project"/> object.</param>
/// <param name="isLastProject">
/// A boolean indicating whether the current project is the last in the list of projects.
/// Used to determine the connector style.
/// </param>
/// <param name="cyclicProjectIds">
/// A set of project IDs that are part of a circular dependency.
/// If the project is part of this set, it will be highlighted in red.
/// </param>
private static void RenderProject(Project project, bool isLastProject, HashSet<Guid> cyclicProjectIds)
{
var pPrefix = isLastProject ? "└── " : "├── ";
Expand All @@ -50,6 +82,19 @@ private static void RenderProject(Project project, bool isLastProject, HashSet<G
AnsiConsole.MarkupLine($"{pPrefix}{typeIcon} [{color}]{projectName}[/]");
}

/// <summary>
/// Renders the dependencies of a given project in the solution graph with proper formatting and color coding.
/// </summary>
/// <param name="graph">The solution graph containing all projects and their dependencies.</param>
/// <param name="project">The project whose dependencies are to be rendered.</param>
/// <param name="isLastProject">
/// A boolean indicating whether the current project is the last in the list of projects.
/// Used to determine the indentation style.
/// </param>
/// <param name="cyclicProjectIds">
/// A set of project IDs that are part of a circular dependency.
/// Dependencies in this set will be highlighted in red.
/// </param>
private static void RenderDependencies(
SolutionGraph graph,
Project project,
Expand All @@ -72,6 +117,22 @@ private static void RenderDependencies(
}
}

/// <summary>
/// Renders a dependency in the project graph with appropriate formatting and color coding.
/// </summary>
/// <param name="dependency">The project that represents the dependency to be rendered.</param>
/// <param name="isLastProject">
/// A boolean indicating whether the current project is the last in the list of projects.
/// Used to determine the indentation style.
/// </param>
/// <param name="isLastDep">
/// A boolean indicating whether the current dependency is the last in the list of dependencies.
/// Used to determine the connector style.
/// </param>
/// <param name="cyclicProjectIds">
/// A set of project IDs that are part of a circular dependency.
/// If the dependency is part of this set, it will be highlighted in red.
/// </param>
private static void RenderDependency(
Project dependency,
bool isLastProject,
Expand All @@ -86,6 +147,16 @@ private static void RenderDependency(
AnsiConsole.MarkupLine($"{dPrefix}{dConnector}[italic {depColor}]→ {depName}[/]");
}

/// <summary>
/// Retrieves the appropriate icon representation for a given project type.
/// </summary>
/// <param name="type">The type of the project, represented as a <see cref="ProjectType"/> enum.</param>
/// <returns>
/// A string containing an emoji that represents the project type:
/// - "🚀" for executable projects.
/// - "🧪" for test projects.
/// - "📦" for other types of projects.
/// </returns>
private static string GetProjectTypeIcon(ProjectType type)
{
return type switch
Expand All @@ -96,6 +167,13 @@ private static string GetProjectTypeIcon(ProjectType type)
};
}

/// <summary>
/// Renders a warning message if there are any cyclic dependencies detected in the project graph.
/// </summary>
/// <param name="cyclicProjectIds">
/// A set of project IDs that are part of a circular dependency.
/// If the set is not empty, a warning message will be displayed.
/// </param>
private static void RenderCycleWarning(HashSet<Guid> cyclicProjectIds)
{
if (cyclicProjectIds.Count is not 0)
Expand Down
17 changes: 17 additions & 0 deletions src/ProjGraph.Core/Models/Dependency.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
namespace ProjGraph.Core.Models;

/// <summary>
/// Represents the type of dependency in a project.
/// </summary>
public enum DependencyType
{
/// <summary>
/// Represents a project-to-project reference.
/// </summary>
ProjectReference = 0,

/// <summary>
/// Represents a package reference.
/// </summary>
PackageReference = 1
}

/// <summary>
/// Represents a dependency between two entities in a project.
/// </summary>
/// <param name="SourceId">The unique identifier of the source entity.</param>
/// <param name="TargetId">The unique identifier of the target entity.</param>
/// <param name="Type">The type of dependency (e.g., project or package reference).</param>
public record Dependency(
Guid SourceId,
Guid TargetId,
DependencyType Type
);

Loading
Loading