9
9
10
10
import type { ReactStackTrace } from 'shared/ReactTypes' ;
11
11
12
- import DefaultPrepareStackTrace from 'shared/DefaultPrepareStackTrace' ;
12
+ let framesToSkip : number = 0 ;
13
+ let collectedStackTrace : null | ReactStackTrace = null ;
13
14
14
- function getStack ( error : Error ) : string {
15
- // We override Error.prepareStackTrace with our own version that normalizes
16
- // the stack to V8 formatting even if the server uses other formatting.
17
- // It also ensures that source maps are NOT applied to this since that can
18
- // be slow we're better off doing that lazily from the client instead of
19
- // eagerly on the server. If the stack has already been read, then we might
20
- // not get a normalized stack and it might still have been source mapped.
21
- const previousPrepare = Error . prepareStackTrace ;
22
- Error . prepareStackTrace = DefaultPrepareStackTrace ;
23
- try {
24
- // eslint-disable-next-line react-internal/safe-string-coercion
25
- return String ( error . stack ) ;
26
- } finally {
27
- Error . prepareStackTrace = previousPrepare ;
15
+ const identifierRegExp = / ^ [ a - z A - Z _ $ ] [ 0 - 9 a - z A - Z _ $ ] * $ / ;
16
+
17
+ function getMethodCallName ( callSite : CallSite ) : string {
18
+ const typeName = callSite . getTypeName ( ) ;
19
+ const methodName = callSite . getMethodName ( ) ;
20
+ const functionName = callSite . getFunctionName ( ) ;
21
+ let result = '' ;
22
+ if ( functionName ) {
23
+ if (
24
+ typeName &&
25
+ identifierRegExp . test ( functionName ) &&
26
+ functionName !== typeName
27
+ ) {
28
+ result += typeName + '.' ;
29
+ }
30
+ result += functionName ;
31
+ if (
32
+ methodName &&
33
+ functionName !== methodName &&
34
+ ! functionName . endsWith ( '.' + methodName ) &&
35
+ ! functionName . endsWith ( ' ' + methodName )
36
+ ) {
37
+ result += ' [as ' + methodName + ']' ;
38
+ }
39
+ } else {
40
+ if ( typeName ) {
41
+ result += typeName + '.' ;
42
+ }
43
+ if ( methodName ) {
44
+ result += methodName ;
45
+ } else {
46
+ result += '<anonymous>' ;
47
+ }
48
+ }
49
+ return result ;
50
+ }
51
+
52
+ function collectStackTrace (
53
+ error : Error ,
54
+ structuredStackTrace : CallSite [ ] ,
55
+ ) : string {
56
+ const result : ReactStackTrace = [ ] ;
57
+ // Collect structured stack traces from the callsites.
58
+ // We mirror how V8 serializes stack frames and how we later parse them.
59
+ for ( let i = framesToSkip ; i < structuredStackTrace . length ; i ++ ) {
60
+ const callSite = structuredStackTrace [ i ] ;
61
+ let name = callSite . getFunctionName ( ) || '<anonymous>' ;
62
+ if ( name === 'react-stack-bottom-frame' ) {
63
+ // Skip everything after the bottom frame since it'll be internals.
64
+ break ;
65
+ } else if ( callSite . isNative ( ) ) {
66
+ result . push ( [ name , '' , 0 , 0 ] ) ;
67
+ } else {
68
+ // We encode complex function calls as if they're part of the function
69
+ // name since we cannot simulate the complex ones and they look the same
70
+ // as function names in UIs on the client as well as stacks.
71
+ if ( callSite . isConstructor ( ) ) {
72
+ name = 'new ' + name ;
73
+ } else if ( ! callSite . isToplevel ( ) ) {
74
+ name = getMethodCallName ( callSite ) ;
75
+ }
76
+ if ( name === '<anonymous>' ) {
77
+ name = '' ;
78
+ }
79
+ let filename = callSite . getScriptNameOrSourceURL ( ) || '<anonymous>' ;
80
+ if ( filename === '<anonymous>' ) {
81
+ filename = '' ;
82
+ }
83
+ if ( callSite . isEval ( ) && ! filename ) {
84
+ const origin = callSite . getEvalOrigin ( ) ;
85
+ if ( origin ) {
86
+ filename = origin . toString ( ) + ', <anonymous>' ;
87
+ }
88
+ }
89
+ const line = callSite . getLineNumber ( ) || 0 ;
90
+ const col = callSite . getColumnNumber ( ) || 0 ;
91
+ result . push ( [ name , filename , line , col ] ) ;
92
+ }
28
93
}
94
+ // At the same time we generate a string stack trace just in case someone
95
+ // else reads it. Ideally, we'd call the previous prepareStackTrace to
96
+ // ensure it's in the expected format but it's common for that to be
97
+ // source mapped and since we do a lot of eager parsing of errors, it
98
+ // would be slow in those environments. We could maybe just rely on those
99
+ // environments having to disable source mapping globally to speed things up.
100
+ // For now, we just generate a default V8 formatted stack trace without
101
+ // source mapping as a fallback.
102
+ const name = error . name || 'Error' ;
103
+ const message = error . message || '' ;
104
+ let stack = name + ': ' + message ;
105
+ for ( let i = 0 ; i < structuredStackTrace . length ; i ++ ) {
106
+ stack += '\n at ' + structuredStackTrace [ i ] . toString ( ) ;
107
+ }
108
+ collectedStackTrace = result ;
109
+ return stack ;
29
110
}
30
111
31
112
// This matches either of these V8 formats.
@@ -39,7 +120,32 @@ export function parseStackTrace(
39
120
error : Error ,
40
121
skipFrames : number ,
41
122
) : ReactStackTrace {
42
- let stack = getStack ( error ) ;
123
+ // We override Error.prepareStackTrace with our own version that collects
124
+ // the structured data. We need more information than the raw stack gives us
125
+ // and we need to ensure that we don't get the source mapped version.
126
+ collectedStackTrace = null ;
127
+ framesToSkip = skipFrames ;
128
+ const previousPrepare = Error . prepareStackTrace ;
129
+ Error . prepareStackTrace = collectStackTrace ;
130
+ let stack ;
131
+ try {
132
+ // eslint-disable-next-line react-internal/safe-string-coercion
133
+ stack = String ( error . stack ) ;
134
+ } finally {
135
+ Error . prepareStackTrace = previousPrepare ;
136
+ }
137
+
138
+ if ( collectedStackTrace !== null ) {
139
+ const result = collectedStackTrace ;
140
+ collectedStackTrace = null ;
141
+ return result ;
142
+ }
143
+
144
+ // If the stack has already been read, or this is not actually a V8 compatible
145
+ // engine then we might not get a normalized stack and it might still have been
146
+ // source mapped. Regardless we try our best to parse it. This works best if the
147
+ // environment just uses default V8 formatting and no source mapping.
148
+
43
149
if ( stack . startsWith ( 'Error: react-stack-top-frame\n' ) ) {
44
150
// V8's default formatting prefixes with the error message which we
45
151
// don't want/need.
0 commit comments