feat(scanner): Implement Deno analyzer and associated tests
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Added Deno analyzer with comprehensive metadata and evidence structure.
- Created a detailed implementation plan for Sprint 130 focusing on Deno analyzer.
- Introduced AdvisoryAiGuardrailOptions for managing guardrail configurations.
- Developed GuardrailPhraseLoader for loading blocked phrases from JSON files.
- Implemented tests for AdvisoryGuardrailOptions binding and phrase loading.
- Enhanced telemetry for Advisory AI with metrics tracking.
- Added VexObservationProjectionService for querying VEX observations.
- Created extensive tests for VexObservationProjectionService functionality.
- Introduced Ruby language analyzer with tests for simple and complex workspaces.
- Added Ruby application fixtures for testing purposes.
This commit is contained in:
master
2025-11-12 10:01:54 +02:00
parent 0e8655cbb1
commit babb81af52
75 changed files with 3346 additions and 187 deletions

View File

@@ -6942,6 +6942,35 @@ internal static class CommandHandlers
return;
}
if (report.Observation is { } observation)
{
var bundler = string.IsNullOrWhiteSpace(observation.BundlerVersion)
? "n/a"
: observation.BundlerVersion;
AnsiConsole.MarkupLine(
"[grey]Observation[/] bundler={0} • packages={1} • runtimeEdges={2}",
Markup.Escape(bundler),
observation.PackageCount,
observation.RuntimeEdgeCount);
AnsiConsole.MarkupLine(
"[grey]Capabilities[/] exec={0} net={1} serialization={2}",
observation.UsesExec ? "[green]on[/]" : "[red]off[/]",
observation.UsesNetwork ? "[green]on[/]" : "[red]off[/]",
observation.UsesSerialization ? "[green]on[/]" : "[red]off[/]");
if (observation.SchedulerCount > 0)
{
var schedulerLabel = observation.Schedulers.Count > 0
? string.Join(", ", observation.Schedulers)
: observation.SchedulerCount.ToString(CultureInfo.InvariantCulture);
AnsiConsole.MarkupLine("[grey]Schedulers[/] {0}", Markup.Escape(schedulerLabel));
}
AnsiConsole.WriteLine();
}
var table = new Table().Border(TableBorder.Rounded);
table.AddColumn("Package");
table.AddColumn("Version");
@@ -7088,14 +7117,19 @@ internal static class CommandHandlers
[JsonPropertyName("packages")]
public IReadOnlyList<RubyInspectEntry> Packages { get; }
private RubyInspectReport(IReadOnlyList<RubyInspectEntry> packages)
[JsonPropertyName("observation")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public RubyObservationSummary? Observation { get; }
private RubyInspectReport(IReadOnlyList<RubyInspectEntry> packages, RubyObservationSummary? observation)
{
Packages = packages;
Observation = observation;
}
public static RubyInspectReport Create(IEnumerable<LanguageComponentSnapshot>? snapshots)
{
var source = snapshots ?? Array.Empty<LanguageComponentSnapshot>();
var source = snapshots?.ToArray() ?? Array.Empty<LanguageComponentSnapshot>();
var entries = source
.Where(static snapshot => string.Equals(snapshot.Type, "gem", StringComparison.OrdinalIgnoreCase))
@@ -7104,7 +7138,9 @@ internal static class CommandHandlers
.ThenBy(static entry => entry.Version ?? string.Empty, StringComparer.OrdinalIgnoreCase)
.ToArray();
return new RubyInspectReport(entries);
var observation = RubyObservationSummary.TryCreate(source);
return new RubyInspectReport(entries, observation);
}
}
@@ -7149,6 +7185,41 @@ internal static class CommandHandlers
}
}
private sealed record RubyObservationSummary(
[property: JsonPropertyName("packageCount")] int PackageCount,
[property: JsonPropertyName("runtimeEdgeCount")] int RuntimeEdgeCount,
[property: JsonPropertyName("bundlerVersion")] string? BundlerVersion,
[property: JsonPropertyName("usesExec")] bool UsesExec,
[property: JsonPropertyName("usesNetwork")] bool UsesNetwork,
[property: JsonPropertyName("usesSerialization")] bool UsesSerialization,
[property: JsonPropertyName("schedulerCount")] int SchedulerCount,
[property: JsonPropertyName("schedulers")] IReadOnlyList<string> Schedulers)
{
public static RubyObservationSummary? TryCreate(IEnumerable<LanguageComponentSnapshot> snapshots)
{
var observation = snapshots.FirstOrDefault(static snapshot =>
string.Equals(snapshot.Type, "ruby-observation", StringComparison.OrdinalIgnoreCase));
if (observation is null)
{
return null;
}
var metadata = RubyMetadataHelpers.Clone(observation.Metadata);
var schedulers = RubyMetadataHelpers.GetList(metadata, "ruby.observation.capability.scheduler_list");
return new RubyObservationSummary(
RubyMetadataHelpers.GetInt(metadata, "ruby.observation.packages") ?? 0,
RubyMetadataHelpers.GetInt(metadata, "ruby.observation.runtime_edges") ?? 0,
RubyMetadataHelpers.GetString(metadata, "ruby.observation.bundler_version"),
RubyMetadataHelpers.GetBool(metadata, "ruby.observation.capability.exec") ?? false,
RubyMetadataHelpers.GetBool(metadata, "ruby.observation.capability.net") ?? false,
RubyMetadataHelpers.GetBool(metadata, "ruby.observation.capability.serialization") ?? false,
RubyMetadataHelpers.GetInt(metadata, "ruby.observation.capability.schedulers") ?? schedulers.Count,
schedulers);
}
}
private sealed class RubyResolveReport
{
[JsonPropertyName("scanId")]
@@ -7343,6 +7414,22 @@ internal static class CommandHandlers
return null;
}
public static int? GetInt(IDictionary<string, string?> metadata, string key)
{
var value = GetString(metadata, key);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed))
{
return parsed;
}
return null;
}
}
private sealed record LockValidationEntry(