Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using StellaOps.Signals.Models;
namespace StellaOps.Signals.Parsing;
/// <summary>
/// Result produced by a callgraph parser.
/// </summary>
public sealed record CallgraphParseResult(
IReadOnlyList<CallgraphNode> Nodes,
IReadOnlyList<CallgraphEdge> Edges,
string FormatVersion);

View File

@@ -0,0 +1,17 @@
using System;
namespace StellaOps.Signals.Parsing;
/// <summary>
/// Exception thrown when a parser is not registered for the requested language.
/// </summary>
public sealed class CallgraphParserNotFoundException : Exception
{
public CallgraphParserNotFoundException(string language)
: base($"No callgraph parser registered for language '{language}'.")
{
Language = language;
}
public string Language { get; }
}

View File

@@ -0,0 +1,14 @@
using System;
namespace StellaOps.Signals.Parsing;
/// <summary>
/// Exception thrown when a callgraph artifact is invalid.
/// </summary>
public sealed class CallgraphParserValidationException : Exception
{
public CallgraphParserValidationException(string message)
: base(message)
{
}
}

View File

@@ -0,0 +1,21 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Signals.Parsing;
/// <summary>
/// Parses raw callgraph artifacts into normalized structures.
/// </summary>
public interface ICallgraphParser
{
/// <summary>
/// Language identifier handled by the parser (e.g., java, nodejs).
/// </summary>
string Language { get; }
/// <summary>
/// Parses the supplied artifact stream.
/// </summary>
Task<CallgraphParseResult> ParseAsync(Stream artifactStream, CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
namespace StellaOps.Signals.Parsing;
/// <summary>
/// Resolves callgraph parsers for specific languages.
/// </summary>
public interface ICallgraphParserResolver
{
/// <summary>
/// Resolves a parser for the supplied language.
/// </summary>
ICallgraphParser Resolve(string language);
}
internal sealed class CallgraphParserResolver : ICallgraphParserResolver
{
private readonly IReadOnlyDictionary<string, ICallgraphParser> parsersByLanguage;
public CallgraphParserResolver(IEnumerable<ICallgraphParser> parsers)
{
ArgumentNullException.ThrowIfNull(parsers);
var map = new Dictionary<string, ICallgraphParser>(StringComparer.OrdinalIgnoreCase);
foreach (var parser in parsers)
{
map[parser.Language] = parser;
}
parsersByLanguage = map;
}
public ICallgraphParser Resolve(string language)
{
ArgumentException.ThrowIfNullOrWhiteSpace(language);
if (parsersByLanguage.TryGetValue(language, out var parser))
{
return parser;
}
throw new CallgraphParserNotFoundException(language);
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Signals.Models;
namespace StellaOps.Signals.Parsing;
/// <summary>
/// Simple JSON-based callgraph parser used for initial language coverage.
/// </summary>
internal sealed class SimpleJsonCallgraphParser : ICallgraphParser
{
private readonly JsonSerializerOptions serializerOptions;
public SimpleJsonCallgraphParser(string language)
{
ArgumentException.ThrowIfNullOrWhiteSpace(language);
Language = language;
serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
}
public string Language { get; }
public async Task<CallgraphParseResult> ParseAsync(Stream artifactStream, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(artifactStream);
var payload = await JsonSerializer.DeserializeAsync<RawCallgraphPayload>(
artifactStream,
serializerOptions,
cancellationToken).ConfigureAwait(false);
if (payload is null)
{
throw new CallgraphParserValidationException("Callgraph artifact payload is empty.");
}
if (payload.Graph is null)
{
throw new CallgraphParserValidationException("Callgraph artifact is missing 'graph' section.");
}
if (payload.Graph.Nodes is null || payload.Graph.Nodes.Count == 0)
{
throw new CallgraphParserValidationException("Callgraph artifact must include at least one node.");
}
if (payload.Graph.Edges is null)
{
payload.Graph.Edges = new List<RawCallgraphEdge>();
}
var nodes = new List<CallgraphNode>(payload.Graph.Nodes.Count);
foreach (var node in payload.Graph.Nodes)
{
if (string.IsNullOrWhiteSpace(node.Id))
{
throw new CallgraphParserValidationException("Callgraph node is missing an id.");
}
nodes.Add(new CallgraphNode(
Id: node.Id.Trim(),
Name: node.Name ?? node.Id.Trim(),
Kind: node.Kind ?? "function",
Namespace: node.Namespace,
File: node.File,
Line: node.Line));
}
var edges = new List<CallgraphEdge>(payload.Graph.Edges.Count);
foreach (var edge in payload.Graph.Edges)
{
if (string.IsNullOrWhiteSpace(edge.Source) || string.IsNullOrWhiteSpace(edge.Target))
{
throw new CallgraphParserValidationException("Callgraph edge requires both source and target.");
}
edges.Add(new CallgraphEdge(edge.Source.Trim(), edge.Target.Trim(), edge.Type ?? "call"));
}
var formatVersion = string.IsNullOrWhiteSpace(payload.FormatVersion) ? "1.0" : payload.FormatVersion.Trim();
return new CallgraphParseResult(nodes, edges, formatVersion);
}
private sealed class RawCallgraphPayload
{
public string? FormatVersion { get; set; }
public RawCallgraphGraph? Graph { get; set; }
}
private sealed class RawCallgraphGraph
{
public List<RawCallgraphNode>? Nodes { get; set; }
public List<RawCallgraphEdge>? Edges { get; set; }
}
private sealed class RawCallgraphNode
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Kind { get; set; }
public string? Namespace { get; set; }
public string? File { get; set; }
public int? Line { get; set; }
}
private sealed class RawCallgraphEdge
{
public string? Source { get; set; }
public string? Target { get; set; }
public string? Type { get; set; }
}
}