feat(ruby): Implement RubyManifestParser for parsing gem groups and dependencies
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

feat(ruby): Add RubyVendorArtifactCollector to collect vendor artifacts

test(deno): Add golden tests for Deno analyzer with various fixtures

test(deno): Create Deno module and package files for testing

test(deno): Implement Deno lock and import map for dependency management

test(deno): Add FFI and worker scripts for Deno testing

feat(ruby): Set up Ruby workspace with Gemfile and dependencies

feat(ruby): Add expected output for Ruby workspace tests

feat(signals): Introduce CallgraphManifest model for signal processing
This commit is contained in:
master
2025-11-10 09:27:03 +02:00
parent 69c59defdc
commit 56c687253f
87 changed files with 2462 additions and 542 deletions

View File

@@ -0,0 +1 @@
export const dayjs = () => ({ iso: () => "2024-09-01" });

View File

@@ -0,0 +1,3 @@
export function dayjs() {
return { iso: () => "2024-09-01" };
}

View File

@@ -0,0 +1,3 @@
module.exports = function dayjs() {
return { iso: () => "2024-09-01" };
};

View File

@@ -0,0 +1,11 @@
{
"name": "dayjs",
"version": "1.11.12",
"exports": {
".": {
"deno": "./deno.mod.ts",
"import": "./esm/index.js",
"default": "./lib/index.js"
}
}
}

View File

@@ -0,0 +1,24 @@
// Deterministic Deno workspace exercising vendor, npm, FFI, worker, and fetch flows.
{
"importMap": "./import_map.json",
"lock": {
"enabled": true,
"path": "./deno.lock"
},
"nodeModulesDir": false,
"tasks": {
"serve": "deno run --allow-net ./src/main.ts"
},
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
},
"vendor": {
"enabled": true,
"path": "./vendor"
},
"fmt": {
"useTabs": false,
"lineWidth": 100
}
}

View File

@@ -0,0 +1,28 @@
{
"version": "4",
"remote": {
"https://deno.land/std@0.207.0/http/server.ts": "sha256-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
"https://cdn.example.com/dynamic/mod.ts": "sha256-feedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedface",
"https://api.example.com/data.json": "sha256-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
},
"redirects": {
"https://deno.land/std/http/server.ts": "https://deno.land/std@0.207.0/http/server.ts"
},
"npm": {
"specifiers": {
"npm:dayjs@1": "dayjs@1.11.12"
},
"packages": {
"dayjs@1.11.12": {
"integrity": "sha512-sample-dayjs-integrity",
"dependencies": {
"tslib": "tslib@2.6.3"
}
},
"tslib@2.6.3": {
"integrity": "sha512-sample-tslib",
"dependencies": {}
}
}
}
}

View File

@@ -0,0 +1,12 @@
{
"imports": {
"app/": "./src/",
"ffi/": "./src/ffi/",
"workers/": "./src/workers/",
"npmDynamic": "npm:dayjs@1",
"nodeFs": "node:fs",
"nodeCrypto": "node:crypto",
"nodeWorker": "node:worker_threads",
"denoFfi": "deno:ffi"
}
}

View File

@@ -0,0 +1,6 @@
export function openBridge() {
const lib = Deno.dlopen("./ffi/libexample.so", {
add: { parameters: ["i32", "i32"], result: "i32" }
});
lib.close();
}

View File

@@ -0,0 +1,41 @@
import dayjs from "npmDynamic";
import { serve } from "https://deno.land/std@0.207.0/http/server.ts";
import { Worker } from "nodeWorker";
import { dlopen } from "denoFfi";
import "workers/metrics.ts";
const dynamicTarget = "https://cdn.example.com/dynamic/mod.ts";
const fetchTarget = "https://api.example.com/data.json";
async function spinWorkers() {
const worker = new Worker(new URL("./workers/child.ts", import.meta.url), { type: "module" });
worker.postMessage({ kind: "child", payload: "ping" });
const shared = new SharedWorker(new URL("./workers/shared.ts", import.meta.url), { type: "module" });
shared.port.postMessage({ kind: "shared", payload: "metrics" });
}
function loadFfi() {
const lib = dlopen("./ffi/libexample.so", {
add: { parameters: ["i32", "i32"], result: "i32" }
});
try {
return lib.symbols;
} finally {
lib.close();
}
}
export async function main() {
await spinWorkers();
loadFfi();
await import(dynamicTarget);
await fetch(fetchTarget);
await serve(() => new Response(dayjs().format()), { hostname: "127.0.0.1", port: 8088 });
}
if (import.meta.main) {
await main();
}

View File

@@ -0,0 +1,4 @@
self.onmessage = (event) => {
const payload = event.data ?? {};
self.postMessage({ ...payload, worker: "child" });
};

View File

@@ -0,0 +1,3 @@
addEventListener("message", (event) => {
console.log("metric", event.data);
});

View File

@@ -0,0 +1,6 @@
onconnect = (event) => {
const [port] = event.ports;
port.onmessage = (message) => {
port.postMessage({ kind: "shared", payload: message.data });
};
};

View File

@@ -0,0 +1,3 @@
export async function serve(handler: () => Response, _options?: { hostname?: string; port?: number }) {
return handler();
}

View File

@@ -0,0 +1,3 @@
---
BUNDLE_GEMFILE: "apps/api/Gemfile"
BUNDLE_PATH: "apps/api/vendor/bundle"

View File

@@ -0,0 +1,12 @@
source "https://rubygems.org"
gem "rails", "~> 7.1.0"
group :development, :test do
gem "pry"
gem "rubocop", require: false
end
group :production, :console do
gem "puma", "~> 6.4"
end

View File

@@ -0,0 +1,19 @@
GEM
remote: https://rubygems.org/
specs:
pry (1.0.0)
puma (6.4.2)
rails (7.1.3)
rubocop (1.60.0)
PLATFORMS
ruby
DEPENDENCIES
pry
puma (~> 6.4)
rails (~> 7.1.0)
rubocop
BUNDLED WITH
2.5.10

View File

@@ -0,0 +1,6 @@
require "rails"
require "puma"
require "bootsnap"
require "sidekiq"
puts "workspace"

View File

@@ -0,0 +1,7 @@
source "https://rubygems.org"
group :jobs do
gem "sidekiq"
end
gem "bootsnap"

View File

@@ -0,0 +1,15 @@
GEM
remote: https://rubygems.org/
specs:
bootsnap (1.18.4)
sidekiq (7.2.4)
PLATFORMS
ruby
DEPENDENCIES
bootsnap
sidekiq
BUNDLED WITH
2.5.10

View File

@@ -21,4 +21,17 @@ public sealed class RubyLanguageAnalyzerTests
cancellationToken: TestContext.Current.CancellationToken,
usageHints: usageHints);
}
[Fact]
public async Task WorkspaceLockfilesAndVendorArtifactsAsync()
{
var fixture = TestPaths.ResolveFixture("lang", "ruby", "workspace");
var golden = Path.Combine(fixture, "expected.json");
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixture,
golden,
new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() },
cancellationToken: TestContext.Current.CancellationToken);
}
}

View File

@@ -48,7 +48,7 @@ public static class JavaClassFileFactory
using var buffer = new MemoryStream();
using var writer = new BigEndianWriter(buffer);
WriteClassFileHeader(writer, constantPoolCount: 18);
WriteClassFileHeader(writer, constantPoolCount: 20);
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
@@ -59,14 +59,16 @@ public static class JavaClassFileFactory
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(resourcePath); // #8
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Class"); // #10
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/ClassLoader"); // #10
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getResource"); // #12
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)Ljava/net/URL;"); // #13
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getSystemClassLoader"); // #12
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()Ljava/lang/ClassLoader;"); // #13
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("dummy"); // #16
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(16); // #17
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getResource"); // #16
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)Ljava/net/URL;"); // #17
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(16); writer.WriteUInt16(17); // #18
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(18); // #19
writer.WriteUInt16(0x0001); // public
writer.WriteUInt16(2); // this class
@@ -76,7 +78,7 @@ public static class JavaClassFileFactory
writer.WriteUInt16(0); // fields
writer.WriteUInt16(1); // methods
WriteResourceLookupMethod(writer, methodNameIndex: 5, descriptorIndex: 6, classConstantIndex: 4, stringIndex: 9, methodRefIndex: 15);
WriteResourceLookupMethod(writer, methodNameIndex: 5, descriptorIndex: 6, systemLoaderMethodRefIndex: 15, stringIndex: 9, getResourceMethodRefIndex: 19);
writer.WriteUInt16(0); // class attributes
@@ -188,7 +190,13 @@ public static class JavaClassFileFactory
writer.WriteBytes(codeBytes);
}
private static void WriteResourceLookupMethod(BigEndianWriter writer, ushort methodNameIndex, ushort descriptorIndex, ushort classConstantIndex, ushort stringIndex, ushort methodRefIndex)
private static void WriteResourceLookupMethod(
BigEndianWriter writer,
ushort methodNameIndex,
ushort descriptorIndex,
ushort systemLoaderMethodRefIndex,
ushort stringIndex,
ushort getResourceMethodRefIndex)
{
writer.WriteUInt16(0x0009);
writer.WriteUInt16(methodNameIndex);
@@ -201,13 +209,13 @@ public static class JavaClassFileFactory
{
codeWriter.WriteUInt16(2);
codeWriter.WriteUInt16(0);
codeWriter.WriteUInt32(8);
codeWriter.WriteByte(0x13); // ldc_w for class literal
codeWriter.WriteUInt16(classConstantIndex);
codeWriter.WriteByte(0x12);
codeWriter.WriteUInt32(10);
codeWriter.WriteByte(0xB8); // invokestatic
codeWriter.WriteUInt16(systemLoaderMethodRefIndex);
codeWriter.WriteByte(0x12); // ldc
codeWriter.WriteByte((byte)stringIndex);
codeWriter.WriteByte(0xB6);
codeWriter.WriteUInt16(methodRefIndex);
codeWriter.WriteByte(0xB6); // invokevirtual
codeWriter.WriteUInt16(getResourceMethodRefIndex);
codeWriter.WriteByte(0x57);
codeWriter.WriteByte(0xB1);
codeWriter.WriteUInt16(0);