mirror of
https://github.com/facebook/react.git
synced 2026-02-26 18:58:05 +00:00
1. Fixed all reported Flow errors 2. Added a few missing package declarations 3. Deleted ReactDebugHooks fork in favor of react-debug-tools
191 lines
4.7 KiB
JavaScript
191 lines
4.7 KiB
JavaScript
// @flow
|
|
|
|
import escapeStringRegExp from 'escape-string-regexp';
|
|
import { meta } from '../../hydration';
|
|
|
|
import type { HooksTree } from 'react-debug-tools/src/ReactDebugHooks';
|
|
|
|
export function createRegExp(string: string): RegExp {
|
|
// Allow /regex/ syntax with optional last /
|
|
if (string[0] === '/') {
|
|
// Cut off first slash
|
|
string = string.substring(1);
|
|
// Cut off last slash, but only if it's there
|
|
if (string[string.length - 1] === '/') {
|
|
string = string.substring(0, string.length - 1);
|
|
}
|
|
try {
|
|
return new RegExp(string, 'i');
|
|
} catch (err) {
|
|
// Bad regex. Make it not match anything.
|
|
// TODO: maybe warn in console?
|
|
return new RegExp('.^');
|
|
}
|
|
}
|
|
|
|
function isLetter(char: string) {
|
|
return char.toLowerCase() !== char.toUpperCase();
|
|
}
|
|
|
|
function matchAnyCase(char: string) {
|
|
if (!isLetter(char)) {
|
|
// Don't mess with special characters like [.
|
|
return char;
|
|
}
|
|
return '[' + char.toLowerCase() + char.toUpperCase() + ']';
|
|
}
|
|
|
|
// 'item' should match 'Item' and 'ListItem', but not 'InviteMom'.
|
|
// To do this, we'll slice off 'tem' and check first letter separately.
|
|
const escaped = escapeStringRegExp(string);
|
|
const firstChar = escaped[0];
|
|
let restRegex = '';
|
|
// For 'item' input, restRegex becomes '[tT][eE][mM]'
|
|
// We can't simply make it case-insensitive because first letter case matters.
|
|
for (let i = 1; i < escaped.length; i++) {
|
|
restRegex += matchAnyCase(escaped[i]);
|
|
}
|
|
|
|
if (!isLetter(firstChar)) {
|
|
// We can't put a non-character like [ in a group
|
|
// so we fall back to the simple case.
|
|
return new RegExp(firstChar + restRegex);
|
|
}
|
|
|
|
// Construct a smarter regex.
|
|
return new RegExp(
|
|
// For example:
|
|
// (^[iI]|I)[tT][eE][mM]
|
|
// Matches:
|
|
// 'Item'
|
|
// 'ListItem'
|
|
// but not 'InviteMom'
|
|
'(^' +
|
|
matchAnyCase(firstChar) +
|
|
'|' +
|
|
firstChar.toUpperCase() +
|
|
')' +
|
|
restRegex
|
|
);
|
|
}
|
|
|
|
export function getMetaValueLabel(data: Object): string | null {
|
|
const name = data[meta.name];
|
|
const type = data[meta.type];
|
|
|
|
switch (type) {
|
|
case 'html_element':
|
|
return name ? `<${name.toLowerCase()} />` : '';
|
|
case 'react_element':
|
|
return `<${name} />`;
|
|
case 'function':
|
|
return `${name || 'fn'}()`;
|
|
case 'object':
|
|
return 'Object';
|
|
case 'date':
|
|
case 'symbol':
|
|
return name;
|
|
case 'iterator':
|
|
return `${name}(…)`;
|
|
case 'array_buffer':
|
|
case 'data_view':
|
|
case 'array':
|
|
case 'typed_array':
|
|
return `${name}[${data[meta.size]}]`;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function sanitize(data: Object): void {
|
|
for (const key in data) {
|
|
const value = data[key];
|
|
|
|
if (value && value[meta.type]) {
|
|
data[key] = getMetaValueLabel(value);
|
|
} else if (value != null) {
|
|
if (Array.isArray(value)) {
|
|
sanitize(value);
|
|
} else if (typeof value === 'object') {
|
|
sanitize(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function serializeDataForCopy(props: Object): string {
|
|
const cloned = Object.assign({}, props);
|
|
|
|
sanitize(cloned);
|
|
|
|
try {
|
|
return JSON.stringify(cloned, null, 2);
|
|
} catch (error) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
export function serializeHooksForCopy(hooks: HooksTree | null): string {
|
|
// $FlowFixMe "HooksTree is not an object"
|
|
const cloned = Object.assign([], hooks);
|
|
|
|
const queue = [...cloned];
|
|
|
|
while (queue.length > 0) {
|
|
const current = queue.pop();
|
|
|
|
// These aren't meaningful
|
|
delete current.id;
|
|
delete current.isStateEditable;
|
|
|
|
if (current.subHooks.length > 0) {
|
|
queue.push(...current.subHooks);
|
|
}
|
|
}
|
|
|
|
sanitize(cloned);
|
|
|
|
try {
|
|
return JSON.stringify(cloned, null, 2);
|
|
} catch (error) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// Keeping this in memory seems to be enough to enable the browser to download larger profiles.
|
|
// Without this, we would see a "Download failed: network error" failure.
|
|
let downloadUrl = null;
|
|
|
|
export function downloadFile(filename: string, text: string): void {
|
|
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
|
|
|
|
if (downloadUrl !== null) {
|
|
URL.revokeObjectURL(downloadUrl);
|
|
}
|
|
|
|
downloadUrl = URL.createObjectURL(blob);
|
|
|
|
const element = document.createElement('a');
|
|
element.setAttribute('href', downloadUrl);
|
|
element.setAttribute('download', filename);
|
|
element.style.display = 'none';
|
|
((document.body: any): HTMLBodyElement).appendChild(element);
|
|
|
|
element.click();
|
|
|
|
((document.body: any): HTMLBodyElement).removeChild(element);
|
|
}
|
|
|
|
export function truncateText(text: string, maxLength: number): string {
|
|
const { length } = text;
|
|
if (length > maxLength) {
|
|
return (
|
|
text.substr(0, Math.floor(maxLength / 2)) +
|
|
'…' +
|
|
text.substr(length - Math.ceil(maxLength / 2) - 1)
|
|
);
|
|
} else {
|
|
return text;
|
|
}
|
|
}
|