Convert Flipper plugin "LeakCanary" to TypeScript
Summary: The Flipper codebase was recently converted to TypeScript. Plugins should be converted as well to maintain type safety. Reviewed By: passy Differential Revision: D18347730 fbshipit-source-id: be63e8bac677c13fa5c0fa4f964dda8e7ba6251d
This commit is contained in:
committed by
Facebook Github Bot
parent
96049d43a5
commit
e59dbb1315
@@ -7,6 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Panel,
|
Panel,
|
||||||
FlexRow,
|
FlexRow,
|
||||||
@@ -20,29 +21,30 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
styled,
|
styled,
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import type {ElementID, Element} from 'flipper';
|
import {Element} from 'flipper';
|
||||||
import {processLeaks} from './processLeakString';
|
import {processLeaks} from './processLeakString';
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
leaks: Leak[],
|
leaks: Leak[];
|
||||||
selectedIdx: ?number,
|
selectedIdx: number | null;
|
||||||
selectedEid: ?string,
|
selectedEid: string | null;
|
||||||
showFullClassPaths: boolean,
|
showFullClassPaths: boolean;
|
||||||
leaksCount: number,
|
leaksCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type LeakReport = {
|
type LeakReport = {
|
||||||
leaks: string[],
|
leaks: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Fields = {[key: string]: string};
|
||||||
export type Leak = {
|
export type Leak = {
|
||||||
title: string,
|
title: string;
|
||||||
root: string,
|
root: string;
|
||||||
elements: {[key: ElementID]: Element},
|
elements: {[key: string]: Element};
|
||||||
elementsSimple: {[key: ElementID]: Element},
|
elementsSimple: {[key: string]: Element};
|
||||||
instanceFields: {},
|
instanceFields: {[key: string]: Fields};
|
||||||
staticFields: {},
|
staticFields: {[key: string]: Fields};
|
||||||
retainedSize: string,
|
retainedSize: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Window = styled(FlexRow)({
|
const Window = styled(FlexRow)({
|
||||||
@@ -55,8 +57,12 @@ const ToolbarItem = styled(FlexRow)({
|
|||||||
marginLeft: '8px',
|
marginLeft: '8px',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class LeakCanary extends FlipperPlugin<State> {
|
export default class LeakCanary<PersistedState> extends FlipperPlugin<
|
||||||
state = {
|
State,
|
||||||
|
{type: 'LeakCanary'},
|
||||||
|
PersistedState
|
||||||
|
> {
|
||||||
|
state: State = {
|
||||||
leaks: [],
|
leaks: [],
|
||||||
selectedIdx: null,
|
selectedIdx: null,
|
||||||
selectedEid: null,
|
selectedEid: null,
|
||||||
@@ -101,13 +107,15 @@ export default class LeakCanary extends FlipperPlugin<State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_toggleElement = (leakIdx: number, eid: string) => {
|
_toggleElement = (leakIdx: number, eid: string) => {
|
||||||
const leaks = this.state.leaks;
|
const {leaks} = this.state;
|
||||||
const leak = leaks[leakIdx];
|
const leak = leaks[leakIdx];
|
||||||
|
|
||||||
const element = leak.elements[eid];
|
const element = leak.elements[eid];
|
||||||
element.expanded = !element.expanded;
|
|
||||||
|
|
||||||
const elementSimple = leak.elementsSimple[eid];
|
const elementSimple = leak.elementsSimple[eid];
|
||||||
|
if (!element || !elementSimple) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.expanded = !element.expanded;
|
||||||
elementSimple.expanded = !elementSimple.expanded;
|
elementSimple.expanded = !elementSimple.expanded;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -121,8 +129,8 @@ export default class LeakCanary extends FlipperPlugin<State> {
|
|||||||
*/
|
*/
|
||||||
_extractValue(
|
_extractValue(
|
||||||
value: any,
|
value: any,
|
||||||
depth: number,
|
_: number, // depth
|
||||||
): {|mutable: boolean, type: string, value: any|} {
|
): {mutable: boolean; type: string; value: any} {
|
||||||
if (!isNaN(value)) {
|
if (!isNaN(value)) {
|
||||||
return {mutable: false, type: 'number', value: value};
|
return {mutable: false, type: 'number', value: value};
|
||||||
} else if (value == 'true' || value == 'false') {
|
} else if (value == 'true' || value == 'false') {
|
||||||
@@ -189,7 +197,7 @@ export default class LeakCanary extends FlipperPlugin<State> {
|
|||||||
this._selectElement(idx, eid);
|
this._selectElement(idx, eid);
|
||||||
}}
|
}}
|
||||||
onElementHovered={() => {}}
|
onElementHovered={() => {}}
|
||||||
onElementExpanded={(eid, deep) => {
|
onElementExpanded={(eid /*, deep*/) => {
|
||||||
this._toggleElement(idx, eid);
|
this._toggleElement(idx, eid);
|
||||||
}}
|
}}
|
||||||
onValueChanged={() => {}}
|
onValueChanged={() => {}}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "LeakCanary",
|
"name": "LeakCanary",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.tsx",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": ["flipper-plugin"],
|
"keywords": [
|
||||||
"dependencies": {
|
"flipper-plugin"
|
||||||
"lodash": "^4.17.5"
|
],
|
||||||
},
|
"dependencies": {},
|
||||||
|
"devDependencies": {},
|
||||||
"icon": "bird",
|
"icon": "bird",
|
||||||
"title": "LeakCanary",
|
"title": "LeakCanary",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|||||||
@@ -8,8 +8,39 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {Leak} from './index.js';
|
import {Leak} from './index';
|
||||||
import type {Element} from 'flipper';
|
import {Element} from 'flipper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility Function to add a child element
|
||||||
|
* @param childElementId
|
||||||
|
* @param elementId
|
||||||
|
* @param elements
|
||||||
|
*/
|
||||||
|
function safeAddChildElementId(
|
||||||
|
childElementId: string,
|
||||||
|
elementId: string,
|
||||||
|
elements: Map<string, Element>,
|
||||||
|
) {
|
||||||
|
const element = elements.get(elementId);
|
||||||
|
if (element && element.children) {
|
||||||
|
element.children.push(childElementId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toObjectMap(
|
||||||
|
dict: Map<any, any>,
|
||||||
|
deep: boolean = false,
|
||||||
|
): {[key: string]: any} {
|
||||||
|
const result: {[key: string]: any} = {};
|
||||||
|
for (let [key, value] of dict.entries()) {
|
||||||
|
if (deep && value instanceof Map) {
|
||||||
|
value = toObjectMap(value, true);
|
||||||
|
}
|
||||||
|
result[String(key)] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Element (for ElementsInspector) representing a single Object in
|
* Creates an Element (for ElementsInspector) representing a single Object in
|
||||||
@@ -25,8 +56,8 @@ function getElementSimple(str: string, id: string): Element {
|
|||||||
name = match[5];
|
name = match[5];
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: id,
|
id,
|
||||||
name: name,
|
name,
|
||||||
expanded: true,
|
expanded: true,
|
||||||
children: [],
|
children: [],
|
||||||
attributes: [],
|
attributes: [],
|
||||||
@@ -54,17 +85,21 @@ const RETAINED_SIZE_INDICATOR = '* Retaining: ';
|
|||||||
function generateFieldsList(
|
function generateFieldsList(
|
||||||
lines: string[],
|
lines: string[],
|
||||||
i: number,
|
i: number,
|
||||||
): {|staticFields: {}, instanceFields: {}, packages: {}|} {
|
): {
|
||||||
const staticFields = {};
|
staticFields: Map<string, Map<string, string>>;
|
||||||
const instanceFields = {};
|
instanceFields: Map<string, Map<string, string>>;
|
||||||
|
packages: Map<string, string>;
|
||||||
|
} {
|
||||||
|
const staticFields = new Map<string, Map<string, string>>();
|
||||||
|
const instanceFields = new Map<string, Map<string, string>>();
|
||||||
|
|
||||||
let staticValues = {};
|
let staticValues = new Map<string, string>();
|
||||||
let instanceValues = {};
|
let instanceValues = new Map<string, string>();
|
||||||
|
|
||||||
let elementId = -1;
|
let elementId = -1;
|
||||||
let elementIdStr = String(-1);
|
let elementIdStr = '';
|
||||||
|
|
||||||
const packages = {};
|
const packages = new Map<string, any>();
|
||||||
|
|
||||||
// Process everything between Details and Excluded Refs
|
// Process everything between Details and Excluded Refs
|
||||||
while (
|
while (
|
||||||
@@ -74,10 +109,10 @@ function generateFieldsList(
|
|||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
if (line.startsWith('*')) {
|
if (line.startsWith('*')) {
|
||||||
if (elementId != -1) {
|
if (elementId != -1) {
|
||||||
staticFields[elementIdStr] = staticValues;
|
staticFields.set(elementIdStr, staticValues);
|
||||||
instanceFields[elementIdStr] = instanceValues;
|
instanceFields.set(elementIdStr, instanceValues);
|
||||||
staticValues = {};
|
staticValues = new Map<string, string>();
|
||||||
instanceValues = {};
|
instanceValues = new Map<string, string>();
|
||||||
}
|
}
|
||||||
elementId++;
|
elementId++;
|
||||||
elementIdStr = String(elementId);
|
elementIdStr = String(elementId);
|
||||||
@@ -88,7 +123,7 @@ function generateFieldsList(
|
|||||||
if (match) {
|
if (match) {
|
||||||
pkg = match[3];
|
pkg = match[3];
|
||||||
}
|
}
|
||||||
packages[elementIdStr] = pkg;
|
packages.set(elementIdStr, pkg);
|
||||||
} else {
|
} else {
|
||||||
// Field/value pairs represented in input lines as
|
// Field/value pairs represented in input lines as
|
||||||
// | fieldName = value
|
// | fieldName = value
|
||||||
@@ -99,22 +134,18 @@ function generateFieldsList(
|
|||||||
|
|
||||||
if (fieldName.startsWith(STATIC_PREFIX)) {
|
if (fieldName.startsWith(STATIC_PREFIX)) {
|
||||||
const strippedFieldName = fieldName.substr(7);
|
const strippedFieldName = fieldName.substr(7);
|
||||||
staticValues[strippedFieldName] = fieldValue;
|
staticValues.set(strippedFieldName, fieldValue);
|
||||||
} else {
|
} else {
|
||||||
instanceValues[fieldName] = fieldValue;
|
instanceValues.set(fieldName, fieldValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
staticFields[elementIdStr] = staticValues;
|
staticFields.set(elementIdStr, staticValues);
|
||||||
instanceFields[elementIdStr] = instanceValues;
|
instanceFields.set(elementIdStr, instanceValues);
|
||||||
|
|
||||||
return {
|
return {staticFields, instanceFields, packages};
|
||||||
staticFields: staticFields,
|
|
||||||
instanceFields: instanceFields,
|
|
||||||
packages: packages,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,8 +160,8 @@ function processLeak(output: Leak[], leakInfo: string): Leak[] {
|
|||||||
|
|
||||||
// Elements shows a Object's classname and package, wheras elementsSimple shows
|
// Elements shows a Object's classname and package, wheras elementsSimple shows
|
||||||
// just its classname
|
// just its classname
|
||||||
const elements = {};
|
const elements = new Map<string, Element>();
|
||||||
const elementsSimple = {};
|
const elementsSimple = new Map<string, Element>();
|
||||||
|
|
||||||
let rootElementId = '';
|
let rootElementId = '';
|
||||||
|
|
||||||
@@ -145,27 +176,29 @@ function processLeak(output: Leak[], leakInfo: string): Leak[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let elementId = 0;
|
let elementId = 0;
|
||||||
let elementIdStr = '';
|
let elementIdStr = String(elementId);
|
||||||
|
// Last element is leaked object
|
||||||
|
let leakedObjName = '';
|
||||||
while (i < lines.length && lines[i].startsWith('*')) {
|
while (i < lines.length && lines[i].startsWith('*')) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
|
|
||||||
elementIdStr = String(elementId);
|
const prevElementIdStr = String(elementId - 1);
|
||||||
const prevIdStr = String(elementId - 1);
|
|
||||||
if (elementId !== 0) {
|
if (elementId !== 0) {
|
||||||
// Add element to previous element's children
|
// Add element to previous element's children
|
||||||
elements[prevIdStr].children.push(elementIdStr);
|
safeAddChildElementId(elementIdStr, prevElementIdStr, elements);
|
||||||
elementsSimple[prevIdStr].children.push(elementIdStr);
|
safeAddChildElementId(elementIdStr, prevElementIdStr, elementsSimple);
|
||||||
} else {
|
} else {
|
||||||
rootElementId = elementIdStr;
|
rootElementId = elementIdStr;
|
||||||
}
|
}
|
||||||
elements[elementIdStr] = getElementSimple(line, elementIdStr);
|
const element = getElementSimple(line, elementIdStr);
|
||||||
elementsSimple[elementIdStr] = getElementSimple(line, elementIdStr);
|
leakedObjName = element.name;
|
||||||
|
elements.set(elementIdStr, element);
|
||||||
|
elementsSimple.set(elementIdStr, element);
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
elementId++;
|
elementId++;
|
||||||
|
elementIdStr = String(elementId);
|
||||||
}
|
}
|
||||||
// Last element is leaked object
|
|
||||||
const leakedObjName = elements[elementIdStr].name;
|
|
||||||
|
|
||||||
while (
|
while (
|
||||||
i < lines.length &&
|
i < lines.length &&
|
||||||
@@ -197,23 +230,25 @@ function processLeak(output: Leak[], leakInfo: string): Leak[] {
|
|||||||
|
|
||||||
// While elementsSimple remains as-is, elements has the package of each class
|
// While elementsSimple remains as-is, elements has the package of each class
|
||||||
// inserted, in order to enable 'Show full class path'
|
// inserted, in order to enable 'Show full class path'
|
||||||
Object.keys(packages).forEach(elementId => {
|
for (const [elementId, pkg] of packages.entries()) {
|
||||||
const pkg = packages[elementId];
|
const element = elements.get(elementId);
|
||||||
const simpleName = elements[elementId].name;
|
if (!element) {
|
||||||
// Gets everything before the field name, which is replaced by the package
|
continue;
|
||||||
const match = simpleName.match(/([^\. ]*)(.*)/);
|
|
||||||
if (match) {
|
|
||||||
elements[elementId].name = pkg + match[2];
|
|
||||||
}
|
}
|
||||||
});
|
// Gets everything before the field name, which is replaced by the package
|
||||||
|
const match = element.name.match(/([^\. ]*)(.*)/);
|
||||||
|
if (match && match.length === 3) {
|
||||||
|
element.name = pkg + match[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
output.push({
|
output.push({
|
||||||
root: rootElementId,
|
|
||||||
elements: elements,
|
|
||||||
elementsSimple: elementsSimple,
|
|
||||||
staticFields: staticFields,
|
|
||||||
instanceFields: instanceFields,
|
|
||||||
title: leakedObjName,
|
title: leakedObjName,
|
||||||
|
root: rootElementId,
|
||||||
|
elements: toObjectMap(elements),
|
||||||
|
elementsSimple: toObjectMap(elementsSimple),
|
||||||
|
staticFields: toObjectMap(staticFields, true),
|
||||||
|
instanceFields: toObjectMap(instanceFields, true),
|
||||||
retainedSize: retainedSize,
|
retainedSize: retainedSize,
|
||||||
});
|
});
|
||||||
return output;
|
return output;
|
||||||
@@ -2,6 +2,3 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
lodash@^4.17.5:
|
|
||||||
version "4.17.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user