Refactor code structure for improved readability and maintainability; removed redundant code blocks and optimized function calls.
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled

This commit is contained in:
master
2025-11-20 07:50:52 +02:00
parent 616ec73133
commit 10212d67c0
473 changed files with 316758 additions and 388 deletions

View File

@@ -0,0 +1,120 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using StellaOps.Scanner.Analyzers.Lang;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime;
/// <summary>
/// Optional harness that executes the emitted Deno runtime shim when an entrypoint is provided via environment variable.
/// This keeps runtime capture opt-in and offline-friendly.
/// </summary>
internal static class DenoRuntimeTraceRunner
{
private const string EntrypointEnvVar = "STELLA_DENO_ENTRYPOINT";
private const string BinaryEnvVar = "STELLA_DENO_BINARY";
private const string RuntimeFileName = "deno-runtime.ndjson";
public static async Task<bool> TryExecuteAsync(
LanguageAnalyzerContext context,
ILogger? logger,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
var entrypoint = Environment.GetEnvironmentVariable(EntrypointEnvVar);
if (string.IsNullOrWhiteSpace(entrypoint))
{
logger?.LogDebug("Deno runtime trace skipped: {EnvVar} not set", EntrypointEnvVar);
return false;
}
var entrypointPath = Path.GetFullPath(Path.Combine(context.RootPath, entrypoint));
if (!File.Exists(entrypointPath))
{
logger?.LogWarning("Deno runtime trace skipped: entrypoint '{Entrypoint}' missing", entrypointPath);
return false;
}
var shimPath = Path.Combine(context.RootPath, DenoRuntimeShim.FileName);
if (!File.Exists(shimPath))
{
await DenoRuntimeShim.WriteAsync(context.RootPath, cancellationToken).ConfigureAwait(false);
}
var binary = Environment.GetEnvironmentVariable(BinaryEnvVar);
if (string.IsNullOrWhiteSpace(binary))
{
binary = "deno";
}
var startInfo = new ProcessStartInfo
{
FileName = binary,
WorkingDirectory = context.RootPath,
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
startInfo.ArgumentList.Add("run");
startInfo.ArgumentList.Add("--cached-only");
startInfo.ArgumentList.Add("--allow-read");
startInfo.ArgumentList.Add("--allow-env");
startInfo.ArgumentList.Add("--quiet");
startInfo.ArgumentList.Add(shimPath);
startInfo.Environment[EntrypointEnvVar] = entrypointPath;
try
{
using var process = Process.Start(startInfo);
if (process is null)
{
logger?.LogWarning("Deno runtime trace skipped: failed to start 'deno' process");
return false;
}
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
if (process.ExitCode != 0)
{
var stderr = await process.StandardError.ReadToEndAsync().ConfigureAwait(false);
logger?.LogWarning(
"Deno runtime trace failed with exit code {ExitCode}. stderr: {Error}",
process.ExitCode,
Truncate(stderr));
return false;
}
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
logger?.LogWarning(ex, "Deno runtime trace skipped: {Message}", ex.Message);
return false;
}
var runtimePath = Path.Combine(context.RootPath, RuntimeFileName);
if (!File.Exists(runtimePath))
{
logger?.LogWarning(
"Deno runtime trace finished but did not emit {RuntimeFile}",
RuntimeFileName);
return false;
}
return true;
}
private static string Truncate(string? value, int maxLength = 400)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
return value.Length <= maxLength ? value : value[..maxLength];
}
}

View File

@@ -0,0 +1,33 @@
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
internal sealed record NodeEntrypoint(
string Path,
string? BinName,
string? MainField,
string? ModuleField,
string ConditionSet)
{
public static NodeEntrypoint Create(string path, string? binName, string? mainField, string? moduleField, IEnumerable<string>? conditions)
{
ArgumentException.ThrowIfNullOrWhiteSpace(path);
var conditionSet = NormalizeConditions(conditions);
return new NodeEntrypoint(path, binName, mainField, moduleField, conditionSet);
}
private static string NormalizeConditions(IEnumerable<string>? conditions)
{
if (conditions is null)
{
return string.Empty;
}
var distinct = conditions
.Where(static c => !string.IsNullOrWhiteSpace(c))
.Select(static c => c.Trim())
.Distinct(StringComparer.Ordinal)
.OrderBy(static c => c, StringComparer.Ordinal);
return string.Join(',', distinct);
}
}

View File

@@ -0,0 +1,10 @@
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
internal sealed record NodeImportEdge(
string SourceFile,
string TargetSpecifier,
string Kind,
string Evidence)
{
public string ComparisonKey => string.Concat(SourceFile, "|", TargetSpecifier, "|", Kind);
}

View File

@@ -0,0 +1,91 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Esprima;
using Esprima.Ast;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
internal static class NodeImportWalker
{
public static IReadOnlyList<NodeImportEdge> AnalyzeImports(string sourcePath, string content)
{
ArgumentException.ThrowIfNullOrWhiteSpace(sourcePath);
if (content is null)
{
return Array.Empty<NodeImportEdge>();
}
Script script;
try
{
script = new JavaScriptParser(content, new ParserOptions
{
Tolerant = true,
AdaptRegexp = true,
Source = sourcePath
}).ParseScript();
}
catch (ParserException)
{
return Array.Empty<NodeImportEdge>();
}
var edges = new List<NodeImportEdge>();
Walk(script, sourcePath, edges);
return edges.Count == 0
? Array.Empty<NodeImportEdge>()
: edges.OrderBy(e => e.ComparisonKey, StringComparer.Ordinal).ToArray();
}
private static void Walk(Node node, string sourcePath, List<NodeImportEdge> edges)
{
switch (node)
{
case ImportDeclaration importDecl when !string.IsNullOrWhiteSpace(importDecl.Source?.StringValue):
edges.Add(new NodeImportEdge(sourcePath, importDecl.Source.StringValue!, "import", BuildEvidence(importDecl.Loc)));
break;
case CallExpression call when IsRequire(call) && call.Arguments.FirstOrDefault() is Literal { Value: string target }:
edges.Add(new NodeImportEdge(sourcePath, target, "require", BuildEvidence(call.Loc)));
break;
case ImportExpression importExp when importExp.Source is Literal { Value: string importTarget }:
edges.Add(new NodeImportEdge(sourcePath, importTarget, "import()", BuildEvidence(importExp.Loc)));
break;
}
foreach (var child in node.ChildNodes)
{
Walk(child, sourcePath, edges);
}
}
private static bool IsRequire(CallExpression call)
{
return call.Callee is Identifier id && string.Equals(id.Name, "require", StringComparison.Ordinal)
&& call.Arguments.Count == 1 && call.Arguments[0] is Literal { Value: string };
}
private static string BuildEvidence(Location? loc)
{
if (loc is null)
{
return string.Empty;
}
var json = new JsonObject
{
["start"] = BuildPosition(loc.Start),
["end"] = BuildPosition(loc.End)
};
return json.ToJsonString(new JsonSerializerOptions { WriteIndented = false });
}
private static JsonObject BuildPosition(Position pos)
{
return new JsonObject
{
["line"] = pos.Line,
["column"] = pos.Column
};
}
}

View File

@@ -1,4 +1,6 @@
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
using System.Globalization;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
internal sealed class NodePackage
{
@@ -80,6 +82,12 @@ internal sealed class NodePackage
public bool IsYarnPnp { get; }
private readonly List<NodeEntrypoint> _entrypoints = new();
private readonly List<NodeImportEdge> _imports = new();
public IReadOnlyList<NodeEntrypoint> Entrypoints => _entrypoints;
public IReadOnlyList<NodeImportEdge> Imports => _imports;
public string RelativePathNormalized => string.IsNullOrEmpty(RelativePath) ? string.Empty : RelativePath.Replace(Path.DirectorySeparatorChar, '/');
public string ComponentKey => $"purl::{Purl}";
@@ -113,10 +121,43 @@ internal sealed class NodePackage
LanguageEvidenceKind.Metadata,
"package.json:scripts",
locator,
script.Command,
script.Sha256));
}
script.Command,
script.Sha256));
}
foreach (var entrypoint in _entrypoints)
{
var locator = string.IsNullOrEmpty(PackageJsonLocator)
? "package.json#entrypoint"
: $"{PackageJsonLocator}#entrypoint";
var content = string.Join(';', new[]
{
entrypoint.Path,
entrypoint.BinName,
entrypoint.MainField,
entrypoint.ModuleField,
entrypoint.ConditionSet
}.Where(static v => !string.IsNullOrWhiteSpace(v)));
evidence.Add(new LanguageComponentEvidence(
LanguageEvidenceKind.Metadata,
"package.json:entrypoint",
locator,
content,
sha256: null));
}
foreach (var importEdge in _imports.OrderBy(static e => e.ComparisonKey, StringComparer.Ordinal))
{
evidence.Add(new LanguageComponentEvidence(
LanguageEvidenceKind.Source,
"node.import",
importEdge.SourceFile,
importEdge.TargetSpecifier,
sha256: null));
}
return evidence
.OrderBy(static e => e.ComparisonKey, StringComparer.Ordinal)
.ToArray();
@@ -186,6 +227,33 @@ internal sealed class NodePackage
}
}
if (_entrypoints.Count > 0)
{
var paths = _entrypoints
.Select(static ep => ep.Path)
.OrderBy(static p => p, StringComparer.Ordinal)
.ToArray();
entries.Add(new KeyValuePair<string, string?>("entrypoint", string.Join(';', paths)));
var conditionSets = _entrypoints
.Select(static ep => ep.ConditionSet)
.Where(static cs => !string.IsNullOrWhiteSpace(cs))
.Distinct(StringComparer.Ordinal)
.OrderBy(static cs => cs, StringComparer.Ordinal)
.ToArray();
if (conditionSets.Length > 0)
{
entries.Add(new KeyValuePair<string, string?>("entrypoint.conditions", string.Join(';', conditionSets)));
}
}
if (_imports.Count > 0)
{
entries.Add(new KeyValuePair<string, string?>("imports", _imports.Count.ToString(CultureInfo.InvariantCulture)));
}
if (HasInstallScripts)
{
entries.Add(new KeyValuePair<string, string?>("installScripts", "true"));
@@ -230,6 +298,48 @@ internal sealed class NodePackage
.OrderBy(static pair => pair.Key, StringComparer.Ordinal)
.ToArray();
}
public void AddEntrypoint(string path, string conditionSet, string? binName, string? mainField, string? moduleField)
{
if (string.IsNullOrWhiteSpace(path))
{
return;
}
var entry = NodeEntrypoint.Create(path.Replace(Path.DirectorySeparatorChar, '/'), binName, mainField, moduleField, ParseConditionSet(conditionSet));
if (_entrypoints.Any(ep => string.Equals(ep.Path, entry.Path, StringComparison.Ordinal)))
{
return;
}
_entrypoints.Add(entry);
}
public void AddImport(string sourceFile, string targetSpecifier, string kind, string evidence)
{
if (string.IsNullOrWhiteSpace(sourceFile) || string.IsNullOrWhiteSpace(targetSpecifier))
{
return;
}
var edge = new NodeImportEdge(sourceFile.Replace(Path.DirectorySeparatorChar, '/'), targetSpecifier.Trim(), kind.Trim(), evidence);
if (_imports.Any(e => string.Equals(e.ComparisonKey, edge.ComparisonKey, StringComparison.Ordinal)))
{
return;
}
_imports.Add(edge);
}
private static IEnumerable<string> ParseConditionSet(string conditionSet)
{
if (string.IsNullOrWhiteSpace(conditionSet))
{
return Array.Empty<string>();
}
return conditionSet.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
private static string BuildPurl(string name, string version)
{

View File

@@ -61,9 +61,65 @@ internal static class NodePackageCollector
AppendDeclaredPackages(packages, lockData);
AttachImports(context, packages, cancellationToken);
return packages;
}
private static void AttachImports(LanguageAnalyzerContext context, List<NodePackage> packages, CancellationToken cancellationToken)
{
foreach (var package in packages)
{
cancellationToken.ThrowIfCancellationRequested();
var packageRoot = string.IsNullOrEmpty(package.RelativePathNormalized)
? context.RootPath
: Path.Combine(context.RootPath, package.RelativePathNormalized.Replace('/', Path.DirectorySeparatorChar));
if (!Directory.Exists(packageRoot))
{
continue;
}
foreach (var file in EnumerateSourceFiles(packageRoot))
{
cancellationToken.ThrowIfCancellationRequested();
string content;
try
{
content = File.ReadAllText(file);
}
catch (IOException)
{
continue;
}
var imports = NodeImportWalker.AnalyzeImports(context.GetRelativePath(file).Replace(Path.DirectorySeparatorChar, '/'), content);
foreach (var edge in imports)
{
package.AddImport(edge.SourceFile, edge.TargetSpecifier, edge.Kind, edge.Evidence);
}
}
}
}
private static IEnumerable<string> EnumerateSourceFiles(string root)
{
foreach (var extension in new[] { ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx" })
{
foreach (var file in Directory.EnumerateFiles(root, "*" + extension, new EnumerationOptions
{
RecurseSubdirectories = true,
MatchCasing = MatchCasing.CaseInsensitive,
IgnoreInaccessible = true
}))
{
yield return file;
}
}
}
private static void TraverseDirectory(
LanguageAnalyzerContext context,
string directory,

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace StellaOps.Scanner.Emit.Reachability;
public enum ReachabilityState
{
Unknown = 0,
Conditional = 1,
Reachable = 2,
Unreachable = 3
}
public enum ReachabilityEvidenceKind
{
StaticPath,
RuntimeHit,
RuntimeSinkHit,
Guard,
Mitigation
}
public readonly record struct ReachabilityEvidence(
ReachabilityEvidenceKind Kind,
string? Reference = null);
public sealed record ReachabilityLatticeResult(
ReachabilityState State,
double Score);
public static class ReachabilityLattice
{
public static ReachabilityLatticeResult Evaluate(IEnumerable<ReachabilityEvidence> rawEvidence)
{
var evidence = rawEvidence
.Where(e => Enum.IsDefined(typeof(ReachabilityEvidenceKind), e.Kind))
.OrderBy(e => e.Kind)
.ThenBy(e => e.Reference ?? string.Empty, StringComparer.Ordinal)
.ToList();
var hasRuntimeSinkHit = evidence.Any(e => e.Kind is ReachabilityEvidenceKind.RuntimeSinkHit);
var hasRuntimeHit = evidence.Any(e => e.Kind is ReachabilityEvidenceKind.RuntimeHit or ReachabilityEvidenceKind.RuntimeSinkHit);
var hasStaticPath = evidence.Any(e => e.Kind is ReachabilityEvidenceKind.StaticPath);
var guardCount = evidence.Count(e => e.Kind is ReachabilityEvidenceKind.Guard);
var mitigationCount = evidence.Count(e => e.Kind is ReachabilityEvidenceKind.Mitigation);
var score = 0.0;
var state = ReachabilityState.Unknown;
if (hasStaticPath)
{
state = ReachabilityState.Conditional;
score += 0.50;
}
if (hasRuntimeHit)
{
state = ReachabilityState.Reachable;
score += 0.30;
if (hasRuntimeSinkHit)
{
score += 0.10;
}
}
if (!hasRuntimeHit && guardCount > 0)
{
state = state switch
{
ReachabilityState.Reachable => ReachabilityState.Conditional,
ReachabilityState.Conditional => ReachabilityState.Unknown,
_ => state
};
score = Math.Max(score - 0.20 * guardCount, 0);
}
if (!hasRuntimeHit && mitigationCount > 0)
{
state = ReachabilityState.Unreachable;
score = Math.Max(score - 0.30 * mitigationCount, 0);
}
if (state == ReachabilityState.Unknown && score <= 0 && evidence.Count == 0)
{
return new ReachabilityLatticeResult(ReachabilityState.Unknown, 0);
}
var capped = Math.Clamp(score, 0, 1);
var rounded = Math.Round(capped, 2, MidpointRounding.AwayFromZero);
return new ReachabilityLatticeResult(state, rounded);
}
}