@@ -294,6 +294,88 @@ class OutputParser {
294294 return seenTestNames. contains ( normalizedTestName)
295295 }
296296
297+ /// Checks if a line looks like JSON output (e.g., from the tool's own output or other JSON sources)
298+ /// This prevents false positives when parsing build output that contains JSON
299+ private func isJSONLikeLine( _ line: String ) -> Bool {
300+ let trimmed = line. trimmingCharacters ( in: . whitespaces)
301+
302+ // Check for JSON-like patterns:
303+ // 1. Lines that start with quotes and contain colon (JSON key-value pairs)
304+ // 2. Lines containing JSON structure like "key" : "value"
305+ // 3. Lines with escaped quotes and backslashes typical of JSON
306+ // 4. Lines that are indented and contain JSON-like structures (common in formatted JSON)
307+
308+ // Pattern: "key" : "value" or "key" : value
309+ let jsonKeyValuePattern = Regex {
310+ Optionally ( OneOrMore ( . whitespace) )
311+ " \" "
312+ OneOrMore ( . any, . reluctant)
313+ " \" "
314+ Optionally ( OneOrMore ( . whitespace) )
315+ " : "
316+ Optionally ( OneOrMore ( . whitespace) )
317+ }
318+
319+ if trimmed. firstMatch ( of: jsonKeyValuePattern) != nil {
320+ return true
321+ }
322+
323+ // Check for JSON array/object markers at start
324+ if trimmed. hasPrefix ( " { " ) || trimmed. hasPrefix ( " [ " ) || trimmed. hasPrefix ( " } " ) || trimmed. hasPrefix ( " ] " ) {
325+ return true
326+ }
327+
328+ // Check for lines with multiple escaped characters (common in JSON)
329+ // Pattern like "\\(message)\"" suggests JSON escaping
330+ if line. contains ( " \\ \" " ) && line. contains ( " \" " ) && line. contains ( " : " ) {
331+ return true
332+ }
333+
334+ // Check for indented lines that look like JSON (common in formatted JSON output)
335+ // Lines starting with spaces/tabs followed by quotes are likely JSON
336+ if line. hasPrefix ( " " ) || line. hasPrefix ( " \t " ) {
337+ // If it's indented and contains quoted strings with colons, it's likely JSON
338+ if trimmed. firstMatch ( of: jsonKeyValuePattern) != nil {
339+ return true
340+ }
341+ // Check for JSON array/object markers in indented lines
342+ if trimmed. hasPrefix ( " { " ) || trimmed. hasPrefix ( " } " ) || trimmed. hasPrefix ( " [ " ) || trimmed. hasPrefix ( " ] " ) {
343+ return true
344+ }
345+ }
346+
347+ // Check for lines that contain "error:" but are clearly JSON (e.g., error messages in JSON)
348+ // Pattern: lines with quotes, colons, and escaped characters that contain "error:"
349+ if line. contains ( " error: " ) {
350+ let trimmed = line. trimmingCharacters ( in: . whitespaces)
351+
352+ // If line starts with "error:" (even if indented), it's likely a real error, not JSON
353+ // UNLESS it's clearly JSON structure like "error" : "value"
354+ if trimmed. hasPrefix ( " \" " ) && trimmed. contains ( " \" " ) && trimmed. contains ( " : " ) {
355+ // This looks like JSON: "error" : "value" or "errors" : [...]
356+ return true
357+ }
358+
359+ // If it's indented and has JSON-like structure (quoted keys), it's probably JSON
360+ if ( line. hasPrefix ( " " ) || line. hasPrefix ( " \t " ) ) && trimmed. hasPrefix ( " \" " ) {
361+ return true
362+ }
363+
364+ // If it has escaped quotes and looks like JSON structure, but NOT if it starts with "error:"
365+ // (lines starting with "error:" are likely real errors, not JSON)
366+ if !trimmed. hasPrefix ( " error: " ) {
367+ let hasQuotedStrings = line. contains ( " \" " ) && line. contains ( " : " )
368+ let hasEscapedContent = line. contains ( " \\ " ) && line. contains ( " \" " )
369+ // If it has escaped quotes and looks like JSON structure (but not a file path)
370+ if hasEscapedContent && hasQuotedStrings && !line. contains ( " file: " ) && !line. contains ( " .swift: " ) && !line. contains ( " .m: " ) && !line. contains ( " .h: " ) {
371+ return true
372+ }
373+ }
374+ }
375+
376+ return false
377+ }
378+
297379 private func recordPassedTest( named testName: String ) {
298380 let normalizedTestName = normalizeTestName ( testName)
299381 guard seenPassedTestNames. insert ( normalizedTestName) . inserted else {
@@ -303,6 +385,11 @@ class OutputParser {
303385 }
304386
305387 private func parseError( _ line: String ) -> BuildError ? {
388+ // Skip JSON-like lines (e.g., " \"message\" : \"\\\\(message)\\\"\"")
389+ if isJSONLikeLine ( line) {
390+ return nil
391+ }
392+
306393 // Skip visual error lines (e.g., " | `- error: message")
307394 if line. hasPrefix ( " " ) && ( line. contains ( " | " ) || line. contains ( " ` " ) ) {
308395 return nil
@@ -417,6 +504,11 @@ class OutputParser {
417504 }
418505
419506 private func parseWarning( _ line: String ) -> BuildWarning ? {
507+ // Skip JSON-like lines (e.g., " \"message\" : \"\\\\(message)\\\"\"")
508+ if isJSONLikeLine ( line) {
509+ return nil
510+ }
511+
420512 // Skip visual warning lines (e.g., " | `- warning: message")
421513 if line. hasPrefix ( " " ) && ( line. contains ( " | " ) || line. contains ( " ` " ) ) {
422514 return nil
0 commit comments