1"use strict"; 2/** 3 * Copyright (c) 650 Industries. 4 * Copyright (c) Meta Platforms, Inc. and affiliates. 5 * 6 * This source code is licensed under the MIT license found in the 7 * LICENSE file in the root directory of this source tree. 8 */ 9var __importDefault = (this && this.__importDefault) || function (mod) { 10 return (mod && mod.__esModule) ? mod : { "default": mod }; 11}; 12Object.defineProperty(exports, "__esModule", { value: true }); 13exports.parseLogBoxLog = exports.parseLogBoxException = exports.parseComponentStack = exports.parseInterpolation = void 0; 14const UTFSequence_1 = __importDefault(require("react-native/Libraries/UTFSequence")); 15const parseErrorStack_1 = __importDefault(require("../modules/parseErrorStack")); 16const stringifySafe_1 = __importDefault(require("../modules/stringifySafe")); 17const BABEL_TRANSFORM_ERROR_FORMAT = /^(?:TransformError )?(?:SyntaxError: |ReferenceError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/; 18const BABEL_CODE_FRAME_ERROR_FORMAT = /^(?:TransformError )?(?:.*):? (?:.*?)(\/.*): ([\s\S]+?)\n([ >]{2}[\d\s]+ \|[\s\S]+|\u{001b}[\s\S]+)/u; 19const METRO_ERROR_FORMAT = /^(?:InternalError Metro has encountered an error:) (.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/u; 20const SUBSTITUTION = UTFSequence_1.default.BOM + '%s'; 21function parseInterpolation(args) { 22 const categoryParts = []; 23 const contentParts = []; 24 const substitutionOffsets = []; 25 const remaining = [...args]; 26 if (typeof remaining[0] === 'string') { 27 const formatString = String(remaining.shift()); 28 const formatStringParts = formatString.split('%s'); 29 const substitutionCount = formatStringParts.length - 1; 30 const substitutions = remaining.splice(0, substitutionCount); 31 let categoryString = ''; 32 let contentString = ''; 33 let substitutionIndex = 0; 34 for (const formatStringPart of formatStringParts) { 35 categoryString += formatStringPart; 36 contentString += formatStringPart; 37 if (substitutionIndex < substitutionCount) { 38 if (substitutionIndex < substitutions.length) { 39 // Don't stringify a string type. 40 // It adds quotation mark wrappers around the string, 41 // which causes the LogBox to look odd. 42 const substitution = typeof substitutions[substitutionIndex] === 'string' 43 ? substitutions[substitutionIndex] 44 : (0, stringifySafe_1.default)(substitutions[substitutionIndex]); 45 substitutionOffsets.push({ 46 length: substitution.length, 47 offset: contentString.length, 48 }); 49 categoryString += SUBSTITUTION; 50 contentString += substitution; 51 } 52 else { 53 substitutionOffsets.push({ 54 length: 2, 55 offset: contentString.length, 56 }); 57 categoryString += '%s'; 58 contentString += '%s'; 59 } 60 substitutionIndex++; 61 } 62 } 63 categoryParts.push(categoryString); 64 contentParts.push(contentString); 65 } 66 const remainingArgs = remaining.map((arg) => { 67 // Don't stringify a string type. 68 // It adds quotation mark wrappers around the string, 69 // which causes the LogBox to look odd. 70 return typeof arg === 'string' ? arg : (0, stringifySafe_1.default)(arg); 71 }); 72 categoryParts.push(...remainingArgs); 73 contentParts.push(...remainingArgs); 74 return { 75 category: categoryParts.join(' '), 76 message: { 77 content: contentParts.join(' '), 78 substitutions: substitutionOffsets, 79 }, 80 }; 81} 82exports.parseInterpolation = parseInterpolation; 83function isComponentStack(consoleArgument) { 84 const isOldComponentStackFormat = / {4}in/.test(consoleArgument); 85 const isNewComponentStackFormat = / {4}at/.test(consoleArgument); 86 const isNewJSCComponentStackFormat = /@.*\n/.test(consoleArgument); 87 return isOldComponentStackFormat || isNewComponentStackFormat || isNewJSCComponentStackFormat; 88} 89function parseComponentStack(message) { 90 // In newer versions of React, the component stack is formatted as a call stack frame. 91 // First try to parse the component stack as a call stack frame, and if that doesn't 92 // work then we'll fallback to the old custom component stack format parsing. 93 const stack = (0, parseErrorStack_1.default)(message); 94 if (stack && stack.length > 0) { 95 return stack.map((frame) => ({ 96 content: frame.methodName, 97 collapse: frame.collapse || false, 98 fileName: frame.file == null ? 'unknown' : frame.file, 99 location: { 100 column: frame.column == null ? -1 : frame.column, 101 row: frame.lineNumber == null ? -1 : frame.lineNumber, 102 }, 103 })); 104 } 105 return message 106 .split(/\n {4}in /g) 107 .map((s) => { 108 if (!s) { 109 return null; 110 } 111 const match = s.match(/(.*) \(at (.*\.js):([\d]+)\)/); 112 if (!match) { 113 return null; 114 } 115 const [content, fileName, row] = match.slice(1); 116 return { 117 content, 118 fileName, 119 location: { column: -1, row: parseInt(row, 10) }, 120 }; 121 }) 122 .filter(Boolean); 123} 124exports.parseComponentStack = parseComponentStack; 125function parseLogBoxException(error) { 126 const message = error.originalMessage != null ? error.originalMessage : 'Unknown'; 127 const metroInternalError = message.match(METRO_ERROR_FORMAT); 128 if (metroInternalError) { 129 const [content, fileName, row, column, codeFrame] = metroInternalError.slice(1); 130 return { 131 level: 'fatal', 132 type: 'Metro Error', 133 stack: [], 134 isComponentError: false, 135 componentStack: [], 136 codeFrame: { 137 fileName, 138 location: { 139 row: parseInt(row, 10), 140 column: parseInt(column, 10), 141 }, 142 content: codeFrame, 143 }, 144 message: { 145 content, 146 substitutions: [], 147 }, 148 category: `${fileName}-${row}-${column}`, 149 }; 150 } 151 const babelTransformError = message.match(BABEL_TRANSFORM_ERROR_FORMAT); 152 if (babelTransformError) { 153 // Transform errors are thrown from inside the Babel transformer. 154 const [fileName, content, row, column, codeFrame] = babelTransformError.slice(1); 155 return { 156 level: 'syntax', 157 stack: [], 158 isComponentError: false, 159 componentStack: [], 160 codeFrame: { 161 fileName, 162 location: { 163 row: parseInt(row, 10), 164 column: parseInt(column, 10), 165 }, 166 content: codeFrame, 167 }, 168 message: { 169 content, 170 substitutions: [], 171 }, 172 category: `${fileName}-${row}-${column}`, 173 }; 174 } 175 const babelCodeFrameError = message.match(BABEL_CODE_FRAME_ERROR_FORMAT); 176 if (babelCodeFrameError) { 177 // Codeframe errors are thrown from any use of buildCodeFrameError. 178 const [fileName, content, codeFrame] = babelCodeFrameError.slice(1); 179 return { 180 level: 'syntax', 181 stack: [], 182 isComponentError: false, 183 componentStack: [], 184 codeFrame: { 185 fileName, 186 location: null, 187 content: codeFrame, 188 }, 189 message: { 190 content, 191 substitutions: [], 192 }, 193 category: `${fileName}-${1}-${1}`, 194 }; 195 } 196 if (message.match(/^TransformError /)) { 197 return { 198 level: 'syntax', 199 stack: error.stack, 200 isComponentError: error.isComponentError, 201 componentStack: [], 202 message: { 203 content: message, 204 substitutions: [], 205 }, 206 category: message, 207 }; 208 } 209 const componentStack = error.componentStack; 210 if (error.isFatal || error.isComponentError) { 211 return { 212 level: 'fatal', 213 stack: error.stack, 214 isComponentError: error.isComponentError, 215 componentStack: componentStack != null ? parseComponentStack(componentStack) : [], 216 ...parseInterpolation([message]), 217 }; 218 } 219 if (componentStack != null) { 220 // It is possible that console errors have a componentStack. 221 return { 222 level: 'error', 223 stack: error.stack, 224 isComponentError: error.isComponentError, 225 componentStack: parseComponentStack(componentStack), 226 ...parseInterpolation([message]), 227 }; 228 } 229 // Most `console.error` calls won't have a componentStack. We parse them like 230 // regular logs which have the component stack burried in the message. 231 return { 232 level: 'error', 233 stack: error.stack, 234 isComponentError: error.isComponentError, 235 ...parseLogBoxLog([message]), 236 }; 237} 238exports.parseLogBoxException = parseLogBoxException; 239function parseLogBoxLog(args) { 240 const message = args[0]; 241 let argsWithoutComponentStack = []; 242 let componentStack = []; 243 // Extract component stack from warnings like "Some warning%s". 244 if (typeof message === 'string' && message.slice(-2) === '%s' && args.length > 0) { 245 const lastArg = args[args.length - 1]; 246 if (typeof lastArg === 'string' && isComponentStack(lastArg)) { 247 argsWithoutComponentStack = args.slice(0, -1); 248 argsWithoutComponentStack[0] = message.slice(0, -2); 249 componentStack = parseComponentStack(lastArg); 250 } 251 } 252 if (componentStack.length === 0) { 253 // Try finding the component stack elsewhere. 254 for (const arg of args) { 255 if (typeof arg === 'string' && isComponentStack(arg)) { 256 // Strip out any messages before the component stack. 257 let messageEndIndex = arg.search(/\n {4}(in|at) /); 258 if (messageEndIndex < 0) { 259 // Handle JSC component stacks. 260 messageEndIndex = arg.search(/\n/); 261 } 262 if (messageEndIndex > 0) { 263 argsWithoutComponentStack.push(arg.slice(0, messageEndIndex)); 264 } 265 componentStack = parseComponentStack(arg); 266 } 267 else { 268 argsWithoutComponentStack.push(arg); 269 } 270 } 271 } 272 return { 273 ...parseInterpolation(argsWithoutComponentStack), 274 componentStack, 275 }; 276} 277exports.parseLogBoxLog = parseLogBoxLog; 278//# sourceMappingURL=parseLogBoxLog.js.map