mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-22 21:45:58 +00:00
add .NET project file support (.sln, .csproj, .fsproj, .vbproj, .razor, .cshtml)
Adds extract_sln, extract_csproj, and extract_razor extractors. Captures NuGet package refs, project-to-project dependencies, target frameworks, SDK attribute, @using/@inject/@inherits/@model directives, Blazor component refs, and @code methods. Resolves relative project paths to absolute paths so sln/csproj nodes link correctly when the graph is assembled. Closes #515. Co-Authored-By: aksrathore <aksrathore@users.noreply.github.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
aksrathore
Claude Sonnet 4.6
parent
c7a05d67a1
commit
8bcfffdf62
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
||||
<PackageReference Include="FluentValidation" Version="11.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Domain\Domain.csproj" />
|
||||
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
@page "/counter"
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using MyApp.Services
|
||||
@inject ICounterService CounterService
|
||||
@inject NavigationManager Navigation
|
||||
@inherits ComponentBase
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>Current count: @currentCount</p>
|
||||
|
||||
<Button OnClick="IncrementCount">Click me</Button>
|
||||
<WeatherDisplay City="@city" />
|
||||
<DataGrid TItem="CounterRecord" Items="@records" />
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
private string city = "Seattle";
|
||||
private List<CounterRecord> records = new();
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
CounterService.Increment();
|
||||
}
|
||||
|
||||
public async Task LoadData()
|
||||
{
|
||||
records = await CounterService.GetRecords();
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi", "src\WebApi\WebApi.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901} = {B2C3D4E5-F6A7-8901-BCDE-F12345678901}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "src\Domain\Domain.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "tests\Tests\Tests.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Tests for .NET project file extraction (.sln, .csproj, .razor)."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import pytest
|
||||
from graphify.extract import extract_sln, extract_csproj, extract_razor
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
def _labels(r):
|
||||
return [n["label"] for n in r["nodes"]]
|
||||
|
||||
|
||||
def _relations(r):
|
||||
return {e["relation"] for e in r["edges"]}
|
||||
|
||||
|
||||
# ── .sln ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_sln_extracts_projects():
|
||||
r = extract_sln(FIXTURES / "sample.sln")
|
||||
assert "error" not in r
|
||||
labels = set(_labels(r))
|
||||
assert "WebApi" in labels
|
||||
assert "Domain" in labels
|
||||
assert "Tests" in labels
|
||||
|
||||
|
||||
def test_sln_contains_edges():
|
||||
r = extract_sln(FIXTURES / "sample.sln")
|
||||
contains = [e for e in r["edges"] if e["relation"] == "contains"]
|
||||
assert len(contains) == 3
|
||||
|
||||
|
||||
def test_sln_project_dependency():
|
||||
r = extract_sln(FIXTURES / "sample.sln")
|
||||
assert "imports" in _relations(r)
|
||||
|
||||
|
||||
# ── .csproj ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_csproj_packages():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
assert "error" not in r
|
||||
labels = _labels(r)
|
||||
assert any("MediatR" in l for l in labels)
|
||||
assert any("FluentValidation" in l for l in labels)
|
||||
assert any("Swashbuckle" in l for l in labels)
|
||||
|
||||
|
||||
def test_csproj_project_references():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
imports = [e for e in r["edges"] if e["relation"] == "imports"]
|
||||
assert len(imports) == 6 # 4 packages + 2 project refs
|
||||
|
||||
|
||||
def test_csproj_target_framework():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
assert "net8.0" in _labels(r)
|
||||
|
||||
|
||||
def test_csproj_sdk():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
assert "Microsoft.NET.Sdk.Web" in _labels(r)
|
||||
|
||||
|
||||
def test_csproj_invalid_xml():
|
||||
with tempfile.NamedTemporaryFile(suffix=".csproj", mode="w", delete=False) as f:
|
||||
f.write("<Project><Invalid></Project>")
|
||||
f.flush()
|
||||
r = extract_csproj(Path(f.name))
|
||||
assert "error" in r
|
||||
|
||||
|
||||
# ── .razor ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_razor_using_and_inject():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
assert "error" not in r
|
||||
targets = {e["target"] for e in r["edges"] if e["relation"] == "imports"}
|
||||
assert any("microsoft" in t for t in targets)
|
||||
assert any("counterservice" in t.lower() for t in targets)
|
||||
|
||||
|
||||
def test_razor_components():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
targets = {e["target"] for e in r["edges"] if e["relation"] == "calls"}
|
||||
assert any("weatherdisplay" in t for t in targets)
|
||||
assert any("datagrid" in t for t in targets)
|
||||
|
||||
|
||||
def test_razor_page_route():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
assert any("/counter" in l for l in _labels(r))
|
||||
|
||||
|
||||
def test_razor_inherits():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
assert "inherits" in _relations(r)
|
||||
|
||||
|
||||
def test_razor_code_methods():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
labels = _labels(r)
|
||||
assert "IncrementCount" in labels
|
||||
assert "LoadData" in labels
|
||||
|
||||
|
||||
def test_razor_missing_file():
|
||||
r = extract_razor(Path("/nonexistent/file.razor"))
|
||||
assert "error" in r
|
||||
|
||||
|
||||
# ── dispatch & detect integration ────────────────────────────────────────────
|
||||
|
||||
def test_dispatch_table():
|
||||
from graphify.extract import _get_extractor
|
||||
for ext in (".sln", ".csproj", ".fsproj", ".vbproj", ".razor", ".cshtml"):
|
||||
assert _get_extractor(Path(f"foo{ext}")) is not None, f"{ext} not in dispatch"
|
||||
|
||||
|
||||
def test_code_extensions():
|
||||
from graphify.detect import CODE_EXTENSIONS
|
||||
for ext in (".sln", ".csproj", ".fsproj", ".vbproj", ".razor", ".cshtml"):
|
||||
assert ext in CODE_EXTENSIONS, f"{ext} missing"
|
||||
+74
-2
@@ -1,4 +1,4 @@
|
||||
"""Tests for language extractors: Java, C, C++, Ruby, C#, Kotlin, Scala, PHP, Swift, Go, Julia, Fortran, JS/TS."""
|
||||
"""Tests for language extractors: Java, C, C++, Ruby, C#, Kotlin, Scala, PHP, Swift, Go, Julia, Fortran, JS/TS, .NET project files."""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
@@ -6,7 +6,7 @@ from graphify.extract import (
|
||||
extract_java, extract_c, extract_cpp, extract_ruby,
|
||||
extract_csharp, extract_kotlin, extract_scala, extract_php,
|
||||
extract_swift, extract_go, extract_julia, extract_js, extract_fortran,
|
||||
extract_groovy,
|
||||
extract_groovy, extract_sln, extract_csproj, extract_razor,
|
||||
)
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
@@ -1058,3 +1058,75 @@ def test_groovy_spock_no_dangling_edges():
|
||||
node_ids = {n["id"] for n in r["nodes"]}
|
||||
for e in r["edges"]:
|
||||
assert e["source"] in node_ids
|
||||
|
||||
|
||||
# -- .NET project files (.sln, .csproj, .razor) -------------------------------
|
||||
|
||||
def test_sln_no_error():
|
||||
r = extract_sln(FIXTURES / "sample.sln")
|
||||
assert "error" not in r
|
||||
|
||||
def test_sln_finds_projects():
|
||||
r = extract_sln(FIXTURES / "sample.sln")
|
||||
labels = _labels(r)
|
||||
assert any("WebApi" in l for l in labels)
|
||||
assert any("Domain" in l for l in labels)
|
||||
|
||||
def test_sln_contains_edges():
|
||||
r = extract_sln(FIXTURES / "sample.sln")
|
||||
assert "contains" in _relations(r)
|
||||
|
||||
def test_sln_project_dependency_edges():
|
||||
r = extract_sln(FIXTURES / "sample.sln")
|
||||
assert "imports" in _relations(r)
|
||||
|
||||
def test_csproj_no_error():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
assert "error" not in r
|
||||
|
||||
def test_csproj_finds_packages():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
labels = _labels(r)
|
||||
assert any("MediatR" in l for l in labels)
|
||||
assert any("FluentValidation" in l for l in labels)
|
||||
|
||||
def test_csproj_finds_project_references():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
labels = _labels(r)
|
||||
assert any("Domain.csproj" in l for l in labels)
|
||||
|
||||
def test_csproj_finds_target_framework():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
assert any("net8.0" in l for l in _labels(r))
|
||||
|
||||
def test_csproj_finds_sdk():
|
||||
r = extract_csproj(FIXTURES / "sample.csproj")
|
||||
assert any("Microsoft.NET.Sdk.Web" in l for l in _labels(r))
|
||||
|
||||
def test_razor_no_error():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
assert "error" not in r
|
||||
|
||||
def test_razor_finds_using_directives():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
assert "imports" in _relations(r)
|
||||
|
||||
def test_razor_finds_component_references():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
assert "calls" in _relations(r)
|
||||
|
||||
def test_razor_finds_inherits():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
assert "inherits" in _relations(r)
|
||||
|
||||
def test_razor_finds_code_block_methods():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
labels = _labels(r)
|
||||
assert any("IncrementCount" in l for l in labels)
|
||||
assert any("LoadData" in l for l in labels)
|
||||
|
||||
def test_razor_no_dangling_edges():
|
||||
r = extract_razor(FIXTURES / "sample.razor")
|
||||
node_ids = {n["id"] for n in r["nodes"]}
|
||||
for e in r["edges"]:
|
||||
assert e["source"] in node_ids
|
||||
|
||||
Reference in New Issue
Block a user