From 31fc9762a34f30aae1832994bfad6209a023f03e Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 25 Oct 2026 02:44:32 +0800 Subject: [PATCH] refactor(go): parse module records through one byte contract Parse the original unframed module bytes for every build-info result. Remove the duplicate text facade and string-source branch while retaining observed text, framed bytes, malformed lines, quoting and replacement semantics. Validation: 163 Go parser, image, provider and public CLI/MCP tests passed using local binary fixtures; build, static and documentation checks passed. --- src/go/GoBinaryImage.ts | 7 ++---- src/go/GoModuleText.ts | 33 +++++++------------------- tests/boundary/go/goModuleText.test.ts | 25 +++++++++---------- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/src/go/GoBinaryImage.ts b/src/go/GoBinaryImage.ts index 0f9e773ffd..4595a7518d 100644 --- a/src/go/GoBinaryImage.ts +++ b/src/go/GoBinaryImage.ts @@ -11,7 +11,7 @@ import { } from "../native/metadata/BinaryContainer.js"; import { readBinaryContainer } from "../native/metadata/readBinaryContainer.js"; import type { GoModuleMetadata } from "../domain/go/goBinary.js"; -import { parseGoModuleBytes, parseGoModuleText } from "./GoModuleText.js"; +import { parseGoModuleBytes } from "./GoModuleText.js"; import { goBuildInfoSearch, type GoBuildInfoSearchRange, @@ -212,10 +212,7 @@ export const readGoBinaryImage = (bytes: Buffer): GoBinaryImage => { module_bytes_base64: module.bytes.toString("base64"), version_location: version.location, module_location: module.location, - module: - moduleText === null - ? parseGoModuleBytes(moduleBody) - : parseGoModuleText(moduleText), + module: parseGoModuleBytes(moduleBody), }, ...(sectionNamesLimitation === undefined ? {} diff --git a/src/go/GoModuleText.ts b/src/go/GoModuleText.ts index 6155879108..b3444d2af0 100644 --- a/src/go/GoModuleText.ts +++ b/src/go/GoModuleText.ts @@ -117,25 +117,17 @@ interface ModuleSourceLine { readonly terminated: boolean; } -function* moduleSourceLines( - source: string | Buffer, -): Generator { +function* moduleSourceLines(source: Buffer): Generator { let start = 0; while (start < source.length) { - const newline = - typeof source === "string" - ? source.indexOf("\n", start) - : source.indexOf(10, start); + const newline = source.indexOf(10, start); const end = newline < 0 ? source.length : newline; let line: string | Buffer; - if (typeof source === "string") line = source.slice(start, end); - else { - const bytes = source.subarray(start, end); - try { - line = utf8.decode(bytes); - } catch { - line = bytes; - } + const bytes = source.subarray(start, end); + try { + line = utf8.decode(bytes); + } catch { + line = bytes; } yield { line, terminated: newline >= 0 }; if (newline < 0) return; @@ -147,7 +139,8 @@ const modulePrefixes = ["mod\t", "dep\t", "=>\t"].map((prefix) => Buffer.from(prefix), ); -const parseModuleRecords = (source: string | Buffer): GoModuleMetadata => { +/** Decode an unframed Go module body while retaining non-UTF-8 line bytes exactly. */ +export const parseGoModuleBytes = (source: Buffer): GoModuleMetadata => { let path: string | null = null; let main: GoModule | null = null; const dependencies: GoModule[] = []; @@ -216,11 +209,3 @@ const parseModuleRecords = (source: string | Buffer): GoModuleMetadata => { complete: unparsed_lines.length + unparsed_line_bytes_base64.length === 0, }; }; - -/** Decode Go's module records while preserving unsupported/malformed source lines. */ -export const parseGoModuleText = (text: string): GoModuleMetadata => - parseModuleRecords(text); - -/** Decode an unframed Go module body while retaining non-UTF-8 line bytes exactly. */ -export const parseGoModuleBytes = (bytes: Buffer): GoModuleMetadata => - parseModuleRecords(bytes); diff --git a/tests/boundary/go/goModuleText.test.ts b/tests/boundary/go/goModuleText.test.ts index 2cbb9a8b72..8d4e076574 100644 --- a/tests/boundary/go/goModuleText.test.ts +++ b/tests/boundary/go/goModuleText.test.ts @@ -1,13 +1,12 @@ import { describe, expect, it } from "vitest"; -import { - parseGoModuleBytes, - parseGoModuleText, -} from "../../../src/go/GoModuleText.js"; +import { parseGoModuleBytes } from "../../../src/go/GoModuleText.js"; describe("Go producer module text", () => { it("preserves local replacements, empty fields and ordered duplicate build settings", () => { - const parsed = parseGoModuleText( - "path\texample.invalid/app\nmod\texample.invalid/app\t(devel)\t\ndep\texample.invalid/lib\tv1.2.3\th1:bytes\n=>\t../local lib\t\t\nbuild\tGOOS=linux\nbuild\tGOOS=custom\nbuild\t__proto__=kept\n", + const parsed = parseGoModuleBytes( + Buffer.from( + "path\texample.invalid/app\nmod\texample.invalid/app\t(devel)\t\ndep\texample.invalid/lib\tv1.2.3\th1:bytes\n=>\t../local lib\t\t\nbuild\tGOOS=linux\nbuild\tGOOS=custom\nbuild\t__proto__=kept\n", + ), ); expect(parsed).toMatchObject({ path: "example.invalid/app", @@ -39,7 +38,7 @@ describe("Go producer module text", () => { ) + "\n" + "build\traw=`a\rb`\n"; - expect(parseGoModuleText(text)).toMatchObject({ + expect(parseGoModuleBytes(Buffer.from(text))).toMatchObject({ settings: [ { key: "key=quoted", value: "\x07\x0béé☺🚀" }, { key: "raw", value: "ab" }, @@ -51,7 +50,7 @@ describe("Go producer module text", () => { it("reports unparsed records and malformed escaped bytes while retaining observed fields", () => { const text = 'path\tapp\nmod\tapp\t(devel)\nbuild\tbad="\\xff"\nfuture\tdata\nbuild\tGOARCH=amd64'; - expect(parseGoModuleText(text)).toMatchObject({ + expect(parseGoModuleBytes(Buffer.from(text))).toMatchObject({ path: "app", main: { sum: null }, settings: [], @@ -65,8 +64,8 @@ describe("Go producer module text", () => { }); it("does not attach a replacement after a malformed module declaration", () => { - const parsed = parseGoModuleText( - "dep\tfirst\tv1\ndep\tmalformed\n=>\tlocal\t\t\n", + const parsed = parseGoModuleBytes( + Buffer.from("dep\tfirst\tv1\ndep\tmalformed\n=>\tlocal\t\t\n"), ); expect(parsed.dependencies).toEqual([ { path: "first", version: "v1", sum: null, replacement: null }, @@ -76,8 +75,10 @@ describe("Go producer module text", () => { it("preserves duplicate singleton declarations as unknown instead of silently overwriting", () => { expect( - parseGoModuleText( - "path\tfirst\npath\tsecond\nmod\tfirst\tv1\nmod\tsecond\tv2\n", + parseGoModuleBytes( + Buffer.from( + "path\tfirst\npath\tsecond\nmod\tfirst\tv1\nmod\tsecond\tv2\n", + ), ), ).toMatchObject({ path: "first",