import type {ReactComponentInfo} from 'shared/ReactTypes';
import type {DevToolsHook, ReactRenderer, RendererInterface} from '../types';
import {getOwnerStackByComponentInfoInDev} from './DevToolsComponentInfoStack';
import {formatOwnerStack} from '../shared/DevToolsOwnerStack';
import {componentInfoToComponentLogsMap} from '../shared/DevToolsServerComponentLogs';
import {formatConsoleArgumentsToSingleString} from 'react-devtools-shared/src/backend/utils';
function supportsConsoleTasks(componentInfo: ReactComponentInfo): boolean {
return !!componentInfo.debugTask;
}
export function attach(
hook: DevToolsHook,
rendererID: number,
renderer: ReactRenderer,
global: Object,
): RendererInterface {
const {getCurrentComponentInfo} = renderer;
function getComponentStack(
topFrame: Error,
): null | {enableOwnerStacks: boolean, componentStack: string} {
if (getCurrentComponentInfo === undefined) {
return null;
}
const current = getCurrentComponentInfo();
if (current === null) {
return null;
}
if (supportsConsoleTasks(current)) {
return null;
}
const enableOwnerStacks = current.debugStack != null;
let componentStack = '';
if (enableOwnerStacks) {
const topStackFrames = formatOwnerStack(topFrame);
if (topStackFrames) {
componentStack += '\n' + topStackFrames;
}
componentStack += getOwnerStackByComponentInfoInDev(current);
}
return {enableOwnerStacks, componentStack};
}
function onErrorOrWarning(
type: 'error' | 'warn',
args: $ReadOnlyArray<any>,
): void {
if (getCurrentComponentInfo === undefined) {
return;
}
const componentInfo = getCurrentComponentInfo();
if (componentInfo === null) {
return;
}
if (
args.length > 3 &&
typeof args[0] === 'string' &&
args[0].startsWith('%c%s%c ') &&
typeof args[1] === 'string' &&
typeof args[2] === 'string' &&
typeof args[3] === 'string'
) {
const format = args[0].slice(7);
const env = args[2].trim();
args = args.slice(4);
if (env !== componentInfo.env) {
args.unshift('[' + env + '] ' + format);
} else {
args.unshift(format);
}
}
const message = formatConsoleArgumentsToSingleString(...args);
let componentLogsEntry = componentInfoToComponentLogsMap.get(componentInfo);
if (componentLogsEntry === undefined) {
componentLogsEntry = {
errors: new Map(),
errorsCount: 0,
warnings: new Map(),
warningsCount: 0,
};
componentInfoToComponentLogsMap.set(componentInfo, componentLogsEntry);
}
const messageMap =
type === 'error'
? componentLogsEntry.errors
: componentLogsEntry.warnings;
const count = messageMap.get(message) || 0;
messageMap.set(message, count + 1);
if (type === 'error') {
componentLogsEntry.errorsCount++;
} else {
componentLogsEntry.warningsCount++;
}
}
return {
cleanup() {},
clearErrorsAndWarnings() {},
clearErrorsForElementID() {},
clearWarningsForElementID() {},
getSerializedElementValueByPath() {},
deletePath() {},
findHostInstancesForElementID() {
return null;
},
flushInitialOperations() {},
getBestMatchForTrackedPath() {
return null;
},
getComponentStack,
getDisplayNameForElementID() {
return null;
},
getNearestMountedDOMNode() {
return null;
},
getElementIDForHostInstance() {
return null;
},
getInstanceAndStyle() {
return {
instance: null,
style: null,
};
},
getOwnersList() {
return null;
},
getPathForElement() {
return null;
},
getProfilingData() {
throw new Error('getProfilingData not supported by this renderer');
},
handleCommitFiberRoot() {},
handleCommitFiberUnmount() {},
handlePostCommitFiberRoot() {},
hasElementWithId() {
return false;
},
inspectElement(
requestID: number,
id: number,
path: Array<string | number> | null,
) {
return {
id,
responseID: requestID,
type: 'not-found',
};
},
logElementToConsole() {},
getElementAttributeByPath() {},
getElementSourceFunctionById() {},
onErrorOrWarning,
overrideError() {},
overrideSuspense() {},
overrideValueAtPath() {},
renamePath() {},
renderer,
setTraceUpdatesEnabled() {},
setTrackedPath() {},
startProfiling() {},
stopProfiling() {},
storeAsGlobal() {},
updateComponentFilters() {},
getEnvironmentNames() {
return [];
},
};
}