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