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