summary refs log tree commit diff
path: root/ReferenceClientProxyImplementation/Patches/Implementations/JSPatches/JsonParseMultilinePatch.cs
blob: b5e7d771b38c6530fd502c9dff7a2db68794344c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using ReferenceClientProxyImplementation.Configuration;

namespace ReferenceClientProxyImplementation.Patches.Implementations.JSPatches;

public partial class JsonParseMultilinePatch(ProxyConfiguration config) : IPatch {
    public int GetOrder() => 1;

    public string GetName() => "Patch null-coalescing expressions in JS";
    public bool Applies(string relativeName, byte[] content) => relativeName.EndsWith(".js");

    public async Task<byte[]> Execute(string relativePath, byte[] content) {
        var stringContent = Encoding.UTF8.GetString(content);

        var matches = JsonParseRegex().Matches(stringContent);
        Console.WriteLine($"Found {matches.Count} JSON.parse calls in {relativePath}");

        

        await Parallel.ForEachAsync(matches, async (match, ct) => {
            string formattedJson = match.Groups[1].Value;
            try {
                var jsonElement = JsonSerializer.Deserialize<JsonElement>(formattedJson.Replace("\\", "\\\\") + "waef");
                formattedJson = JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions { WriteIndented = true });
            } catch (JsonException je) {
                // Console.WriteLine($"STJ: Failed to parse JSON in {relativePath} at index {match.Index}: {je.Message}"); // intentinally broken
                try {
                    formattedJson = await formatJsonWithNodejs(relativePath, match, ct);
                } catch (Exception e) {
                    Console.WriteLine($"Node.js: Failed to parse JSON in {relativePath} at index {match.Index}: {e.Message}");
                    return;
                }
            }
            
            lock (matches) stringContent = stringContent.Replace(match.Value, $"JSON.parse(`{formattedJson.Replace("\\", "\\\\")}`);");
        });

        return Encoding.UTF8.GetBytes(stringContent);
    }
    
    private async Task<string> formatJsonWithNodejs(string relativePath, Match match, CancellationToken ct) {
            // Extract the JSON string from the match
            var id = "dcp_" + Path.GetFileName(relativePath).Replace('.', '_') + "_" + match.Index;
            await File.WriteAllTextAsync($"{Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp"}/{id}.js", $"console.log(JSON.stringify(JSON.parse(`{match.Groups[1].Value.Replace("`", "\\\\\\`")}`), null, 2))");
            var sw = Stopwatch.StartNew();

            var psi = new ProcessStartInfo(config.AssetCache.NodePath, $"{Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp"}/{id}.js") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true };

            using var process = Process.Start(psi);
            if (process == null) {
                throw new InvalidOperationException("Failed to start the formatting process.");
            }

            var stdout = await process.StandardOutput.ReadToEndAsync();
            var stderr = await process.StandardError.ReadToEndAsync();

            await process.WaitForExitAsync();
            // Console.WriteLine($"Formatted {relativeName} in {sw.ElapsedMilliseconds}ms: {process.ExitCode}");

            if (process.ExitCode != 0) {
                Console.WriteLine($"Failed to run {Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp"}/{id}.js in {sw.ElapsedMilliseconds}ms: {process.ExitCode}");
                Console.WriteLine("Standard Output: " + stdout);
                Console.WriteLine("Standard Error: " + stderr);
                throw new Exception($"Failed to execute {Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp"}/{id}.js: {stderr}");
            }

            var formattedJson = stdout.Trim();
            Console.WriteLine($"Parsed JSON({id}) in {sw.ElapsedMilliseconds}ms: {formattedJson.Length} bytes");
            // stringContent = stringContent.Replace(match.Value, $"JSON.parse(`{formattedJson.Replace("\\n", "\\\\n")}`);");
            await File.WriteAllTextAsync($"{config.TestClient.RevisionPath}/patched/assets/{Path.GetFileName(relativePath)}-{match.Index}.json", formattedJson);
            return formattedJson;
    }

    [GeneratedRegex(@"JSON\.parse\(\n\s*'(.*?)',?\s*\);", RegexOptions.Compiled | RegexOptions.Multiline)]
    private static partial Regex JsonParseRegex();
}