namespace StellaOps.Scanner.Analyzers.Lang.Deno.Internal; internal sealed class DenoLockFile { private DenoLockFile( string absolutePath, string relativePath, string version, ImmutableDictionary remoteEntries, ImmutableDictionary redirectEntries, ImmutableDictionary npmPackages, ImmutableDictionary npmSpecifiers) { AbsolutePath = Path.GetFullPath(absolutePath); RelativePath = DenoPathUtilities.NormalizeRelativePath(relativePath); Version = version; RemoteEntries = remoteEntries; Redirects = redirectEntries; NpmPackages = npmPackages; NpmSpecifiers = npmSpecifiers; } public string AbsolutePath { get; } public string RelativePath { get; } public string Version { get; } public ImmutableDictionary RemoteEntries { get; } public ImmutableDictionary Redirects { get; } public ImmutableDictionary NpmPackages { get; } public ImmutableDictionary NpmSpecifiers { get; } public static bool TryLoad(string absolutePath, string relativePath, out DenoLockFile? lockFile) { lockFile = null; if (string.IsNullOrWhiteSpace(absolutePath)) { return false; } try { using var stream = File.OpenRead(absolutePath); using var json = JsonDocument.Parse(stream, new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip, }); var root = json.RootElement; var version = root.TryGetProperty("version", out var versionElement) && versionElement.ValueKind == JsonValueKind.String ? versionElement.GetString() ?? "unknown" : "unknown"; var remote = ParseRemoteEntries(root); var redirects = ParseRedirects(root); var (npmPackages, npmSpecifiers) = ParseNpmEntries(root); lockFile = new DenoLockFile( absolutePath, relativePath, version, remote, redirects, npmPackages, npmSpecifiers); return true; } catch (IOException) { return false; } catch (JsonException) { return false; } } private static ImmutableDictionary ParseRemoteEntries(JsonElement root) { if (!root.TryGetProperty("remote", out var remoteElement) || remoteElement.ValueKind != JsonValueKind.Object) { return ImmutableDictionary.Empty; } var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); foreach (var entry in remoteElement.EnumerateObject()) { if (entry.Value.ValueKind == JsonValueKind.String) { builder[entry.Name] = entry.Value.GetString() ?? string.Empty; } } return builder.ToImmutable(); } private static ImmutableDictionary ParseRedirects(JsonElement root) { if (!root.TryGetProperty("redirects", out var redirectsElement) || redirectsElement.ValueKind != JsonValueKind.Object) { return ImmutableDictionary.Empty; } var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); foreach (var entry in redirectsElement.EnumerateObject()) { if (entry.Value.ValueKind == JsonValueKind.String) { builder[entry.Name] = entry.Value.GetString() ?? string.Empty; } } return builder.ToImmutable(); } private static (ImmutableDictionary Packages, ImmutableDictionary Specifiers) ParseNpmEntries(JsonElement root) { if (!root.TryGetProperty("npm", out var npmElement) || npmElement.ValueKind != JsonValueKind.Object) { return (ImmutableDictionary.Empty, ImmutableDictionary.Empty); } var packages = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); if (npmElement.TryGetProperty("packages", out var packagesElement) && packagesElement.ValueKind == JsonValueKind.Object) { foreach (var packageEntry in packagesElement.EnumerateObject()) { if (packageEntry.Value.ValueKind != JsonValueKind.Object) { continue; } var package = DenoLockNpmPackage.Create(packageEntry.Name, packageEntry.Value); if (package is not null) { packages[package.EntryId] = package; } } } var specifiers = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); if (npmElement.TryGetProperty("specifiers", out var specifiersElement) && specifiersElement.ValueKind == JsonValueKind.Object) { foreach (var specifier in specifiersElement.EnumerateObject()) { if (specifier.Value.ValueKind == JsonValueKind.String) { specifiers[specifier.Name] = specifier.Value.GetString() ?? string.Empty; } } } return (packages.ToImmutable(), specifiers.ToImmutable()); } } internal sealed class DenoLockNpmPackage { private DenoLockNpmPackage( string entryId, string integrity, ImmutableDictionary dependencies) { EntryId = entryId; Integrity = integrity; Dependencies = dependencies; } public string EntryId { get; } public string Integrity { get; } public ImmutableDictionary Dependencies { get; } public static DenoLockNpmPackage? Create(string entryId, JsonElement element) { if (string.IsNullOrWhiteSpace(entryId) || element.ValueKind != JsonValueKind.Object) { return null; } var integrity = element.TryGetProperty("integrity", out var integrityElement) && integrityElement.ValueKind == JsonValueKind.String ? integrityElement.GetString() ?? string.Empty : string.Empty; var dependencies = ImmutableDictionary.Empty; if (element.TryGetProperty("dependencies", out var dependenciesElement) && dependenciesElement.ValueKind == JsonValueKind.Object) { var builder = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); foreach (var dependency in dependenciesElement.EnumerateObject()) { if (dependency.Value.ValueKind == JsonValueKind.String) { builder[dependency.Name] = dependency.Value.GetString() ?? string.Empty; } } dependencies = builder.ToImmutable(); } return new DenoLockNpmPackage(entryId, integrity, dependencies); } }