refactor(native): retain one plist JSON parse owner

Keep the JSON parse attempt and exact numeric source literals together through optional XML classification. Replace the probing parse and raw-string parser with readPlistJson, preserving duplicate-key behavior, signed zero, ambiguous number types, prototype omissions and failure precedence.

Project fresh results from private facts and preserve native command controls, captures and provenance. Verify parser reuse and real provider-boundary conversion choices, plus compiled CLI and MCP behavior with real plutil.
This commit is contained in:
morluto committed 2026-10-24 08:13:10 +08:00
1 parent 76500f12b5
commit bf6e49a8f4
5 files changed
+508 -134

No files matched your search

+42 -38
View File
@@ -112,10 +112,9 @@ import { parseCodeSignature } from "./parsers/codesign.js";
import { parseDemangledSymbols } from "./parsers/demangle.js";
import { parseLipoCapture } from "./parsers/lipo.js";
import {
parsePlistJson,
readPlistJson,
parsePlistBinary,
parsePlistXml,
plistJsonNeedsNumberTypes,
type ParsedPlist,
} from "./parsers/plist.js";
import {
@@ -1081,49 +1080,54 @@ class NativeMacOSClient implements AnalysisClient {
// such plists, so decode its XML conversion instead. JSON also prints an
// integral <real> like an <integer>, so XML identifies unsafe number types.
const jsonDecoded = json.value.exitCode === 0;
const xml =
jsonDecoded && !plistJsonNeedsNumberTypes(json.value.stdout)
? undefined
: await this.#run(
"inspect_plist",
"plutil",
["-convert", "xml1", "-o", "-", "--", path],
{ signal, acceptNonZero: !jsonDecoded, timeoutMs },
);
if (xml !== undefined && !xml.ok) return xml;
if (xml !== undefined && xml.value.exitCode !== 0) {
const capture = failureCapture(xml.value, false);
return err(
unreadablePlist(
fileInput.selection,
`plutil could not decode it as a property list (${plutilDiagnostic(capture, path)})`,
{
capturedOutput: {
stdout: capture.stdout,
stderr: capture.stderr,
truncated: capture.truncated,
stdout_bytes: capture.stdoutBytes,
stderr_bytes: capture.stderrBytes,
exit_code: capture.exitCode,
signal: capture.signal,
},
},
),
const jsonAttempt = jsonDecoded
? readPlistJson(json.value.stdout)
: undefined;
let xml: NativeCommandCapture | undefined;
let parsed: Result<ParsedPlist, AnalysisOutputError>;
if (jsonAttempt === undefined || jsonAttempt.needsNumberTypes) {
const converted = await this.#run(
"inspect_plist",
"plutil",
["-convert", "xml1", "-o", "-", "--", path],
{ signal, acceptNonZero: !jsonDecoded, timeoutMs },
);
if (!converted.ok) return converted;
xml = converted.value;
if (xml.exitCode !== 0) {
const capture = failureCapture(xml, false);
return err(
unreadablePlist(
fileInput.selection,
`plutil could not decode it as a property list (${plutilDiagnostic(capture, path)})`,
{
capturedOutput: {
stdout: capture.stdout,
stderr: capture.stderr,
truncated: capture.truncated,
stdout_bytes: capture.stdoutBytes,
stderr_bytes: capture.stderrBytes,
exit_code: capture.exitCode,
signal: capture.signal,
},
},
),
);
}
parsed =
jsonAttempt === undefined
? parsePlistXml(xml.stdout)
: jsonAttempt.project(xml.stdout);
} else {
parsed = jsonAttempt.project();
}
const parsed =
xml === undefined
? parsePlistJson(json.value.stdout)
: jsonDecoded
? parsePlistJson(json.value.stdout, xml.value.stdout)
: parsePlistXml(xml.value.stdout);
if (!parsed.ok) return parsed;
const provenance = [
classified,
json.value,
...(xml === undefined ? [] : [xml.value]),
...(xml === undefined ? [] : [xml]),
].map((item) => invocation(item, path, "$PLIST"));
const converted = xml?.value ?? json.value;
const converted = xml ?? json.value;
const format = await plistSourceFormat(classified.stdout, {
...fileInput,
capturedOutput: {