mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[Flight] Copy the name field of a serialized function debug value (#34085)
This ensures that if the name is set manually after the declaration, then we get that name when we log the value. For example Node.js `Response` is declared as `_Response` and then later assigned a new name. We should probably really serialize all static enumerable properties but "name" is non-enumerable so it's still a special case.
This commit is contained in:
parent
738aebdbac
commit
3958d5d84b
86
packages/react-client/src/ReactFlightClient.js
vendored
86
packages/react-client/src/ReactFlightClient.js
vendored
|
|
@ -1969,6 +1969,44 @@ function createModel(response: Response, model: any): any {
|
|||
return model;
|
||||
}
|
||||
|
||||
const mightHaveStaticConstructor = /\bclass\b.*\bstatic\b/;
|
||||
|
||||
function getInferredFunctionApproximate(code: string): () => void {
|
||||
let slicedCode;
|
||||
if (code.startsWith('Object.defineProperty(')) {
|
||||
slicedCode = code.slice('Object.defineProperty('.length);
|
||||
} else if (code.startsWith('(')) {
|
||||
slicedCode = code.slice(1);
|
||||
} else {
|
||||
slicedCode = code;
|
||||
}
|
||||
if (slicedCode.startsWith('async function')) {
|
||||
const idx = slicedCode.indexOf('(', 14);
|
||||
if (idx !== -1) {
|
||||
const name = slicedCode.slice(14, idx).trim();
|
||||
// eslint-disable-next-line no-eval
|
||||
return (0, eval)('({' + JSON.stringify(name) + ':async function(){}})')[
|
||||
name
|
||||
];
|
||||
}
|
||||
} else if (slicedCode.startsWith('function')) {
|
||||
const idx = slicedCode.indexOf('(', 8);
|
||||
if (idx !== -1) {
|
||||
const name = slicedCode.slice(8, idx).trim();
|
||||
// eslint-disable-next-line no-eval
|
||||
return (0, eval)('({' + JSON.stringify(name) + ':function(){}})')[name];
|
||||
}
|
||||
} else if (slicedCode.startsWith('class')) {
|
||||
const idx = slicedCode.indexOf('{', 5);
|
||||
if (idx !== -1) {
|
||||
const name = slicedCode.slice(5, idx).trim();
|
||||
// eslint-disable-next-line no-eval
|
||||
return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[name];
|
||||
}
|
||||
}
|
||||
return function () {};
|
||||
}
|
||||
|
||||
function parseModelString(
|
||||
response: Response,
|
||||
parentObject: Object,
|
||||
|
|
@ -2158,41 +2196,37 @@ function parseModelString(
|
|||
// This should not compile to eval() because then it has local scope access.
|
||||
const code = value.slice(2);
|
||||
try {
|
||||
// If this might be a class constructor with a static initializer or
|
||||
// static constructor then don't eval it. It might cause unexpected
|
||||
// side-effects. Instead, fallback to parsing out the function type
|
||||
// and name.
|
||||
if (!mightHaveStaticConstructor.test(code)) {
|
||||
// eslint-disable-next-line no-eval
|
||||
return (0, eval)(code);
|
||||
}
|
||||
} catch (x) {
|
||||
// Fallthrough to fallback case.
|
||||
}
|
||||
// We currently use this to express functions so we fail parsing it,
|
||||
// let's just return a blank function as a place holder.
|
||||
if (code.startsWith('(async function')) {
|
||||
const idx = code.indexOf('(', 15);
|
||||
let fn;
|
||||
try {
|
||||
fn = getInferredFunctionApproximate(code);
|
||||
if (code.startsWith('Object.defineProperty(')) {
|
||||
const DESCRIPTOR = ',"name",{value:"';
|
||||
const idx = code.lastIndexOf(DESCRIPTOR);
|
||||
if (idx !== -1) {
|
||||
const name = code.slice(15, idx).trim();
|
||||
// eslint-disable-next-line no-eval
|
||||
return (0, eval)(
|
||||
'({' + JSON.stringify(name) + ':async function(){}})',
|
||||
)[name];
|
||||
}
|
||||
} else if (code.startsWith('(function')) {
|
||||
const idx = code.indexOf('(', 9);
|
||||
if (idx !== -1) {
|
||||
const name = code.slice(9, idx).trim();
|
||||
// eslint-disable-next-line no-eval
|
||||
return (0, eval)(
|
||||
'({' + JSON.stringify(name) + ':function(){}})',
|
||||
)[name];
|
||||
}
|
||||
} else if (code.startsWith('(class')) {
|
||||
const idx = code.indexOf('{', 6);
|
||||
if (idx !== -1) {
|
||||
const name = code.slice(6, idx).trim();
|
||||
// eslint-disable-next-line no-eval
|
||||
return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[
|
||||
name
|
||||
];
|
||||
const name = JSON.parse(
|
||||
code.slice(idx + DESCRIPTOR.length - 1, code.length - 2),
|
||||
);
|
||||
// $FlowFixMe[cannot-write]
|
||||
Object.defineProperty(fn, 'name', {value: name});
|
||||
}
|
||||
}
|
||||
return function () {};
|
||||
} catch (_) {
|
||||
fn = function () {};
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
// Fallthrough
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3239,6 +3239,8 @@ describe('ReactFlight', () => {
|
|||
}
|
||||
Object.defineProperty(MyClass.prototype, 'y', {enumerable: true});
|
||||
|
||||
Object.defineProperty(MyClass, 'name', {value: 'MyClassName'});
|
||||
|
||||
function ServerComponent() {
|
||||
console.log('hi', {
|
||||
prop: 123,
|
||||
|
|
@ -3341,6 +3343,7 @@ describe('ReactFlight', () => {
|
|||
const instance = mockConsoleLog.mock.calls[0][1].instance;
|
||||
expect(typeof Class).toBe('function');
|
||||
expect(Class.prototype.constructor).toBe(Class);
|
||||
expect(Class.name).toBe('MyClassName');
|
||||
expect(instance instanceof Class).toBe(true);
|
||||
expect(Object.getPrototypeOf(instance)).toBe(Class.prototype);
|
||||
expect(instance.x).toBe(1);
|
||||
|
|
|
|||
13
packages/react-server/src/ReactFlightServer.js
vendored
13
packages/react-server/src/ReactFlightServer.js
vendored
|
|
@ -4848,9 +4848,18 @@ function renderDebugModel(
|
|||
return existingReference;
|
||||
}
|
||||
|
||||
const serializedValue = serializeEval(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
'(' + Function.prototype.toString.call(value) + ')',
|
||||
const functionBody: string = Function.prototype.toString.call(value);
|
||||
|
||||
const name = value.name;
|
||||
const serializedValue = serializeEval(
|
||||
typeof name === 'string'
|
||||
? 'Object.defineProperty(' +
|
||||
functionBody +
|
||||
',"name",{value:' +
|
||||
JSON.stringify(name) +
|
||||
'})'
|
||||
: '(' + functionBody + ')',
|
||||
);
|
||||
request.pendingDebugChunks++;
|
||||
const id = request.nextChunkId++;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user