Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing AST options #1

Closed
wants to merge 11 commits into from
167 changes: 167 additions & 0 deletions js/repl/ASTPanel.js
@@ -0,0 +1,167 @@
// @flow

import { css } from "emotion";
import { colors } from "./styles";
import React from "react";
import ReactJson from "react-json-view";
import {
flatten,
unflatten,
filterFlatten,
deleteFlatten,
mergeFlatten,
} from "./ASTUtils";

type Props = {
className?: string,
src: Object,
};

type State = {
src: Object,
flattenSrc: Object,
flattenType: Object,
astOption: {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

astOptions would be better name.

autofocus: boolean,
location: boolean,
empty: boolean,
type: boolean,
},
};

const OPTION_ORDER = ["autofocus", "location", "empty", "type"];

export default class ASTPanel extends React.Component<Props, State> {
state = {
src: {},
flattenSrc: {},
flattenType: {},
astOption: {
autofocus: true,
location: true,
empty: true,
type: true,
},
};

static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const flattenSrc = flatten(nextProps.src);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No harmful to be inside if.

if (nextProps.src !== prevState.src) {
return {
src: unflatten(flattenSrc),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could be just src instead of using unflatten.

flattenSrc: flattenSrc,
flattenType: filterFlatten(flattenSrc, "type"),
};
}
return null;
}

onOptionSettingCheck(option: string) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect they prefer underscored name for member method.

this.setState(prevState => ({
astOption: {
...prevState.astOption,
[option]: !prevState.astOption[option],
},
}));

this.onChangeJson(option);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always prefer reducing the count of setState calls as much as I can.
So, it would be good if you combine this method's return value and setState above.

}

onChangeJson(option: string) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect they prefer underscored name for member method.

const { src, astOption, flattenSrc, flattenType } = this.state;
// const OPTION_ORDER = ["autofocus", "location", "empty", "type"];

function triggerAstOutput(type) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think option is a better name than type.

const isShow = astOption[type];
let newSrc = {};
const types = {
autofocus: () => {},
empty: () => {},
type: () => {
if (isShow) {
newSrc = deleteFlatten(flattenSrc, flattenType);
} else {
newSrc = mergeFlatten(flattenSrc, flattenType);
}
return newSrc;
},
location: () => {
console.log(isShow, type);
},
default: () => {},
};
return (types[type] || types["default"])();
}

const result = triggerAstOutput(option);
this.setState({ flattenSrc: result, src: unflatten(result) });
}

render() {
const { src, astOption } = this.state;
const { className = "" } = this.props;

return (
<div className={`${styles.panel} ${className}`}>
<div>
{OPTION_ORDER.map(option => {
return (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can reduce line since it has only return.

<label>
<input
checked={astOption[option]}
type="checkbox"
onChange={() => this.onOptionSettingCheck(option)}
/>
{option}
</label>
);
})}
</div>
{src && (
<ReactJson
src={src}
style={{
overflowY: "scroll",
width: "100%",
}}
shouldCollapse={field => field.name !== "root"}
enableClipboard={false}
displayObjectSize={false}
displayDataTypes={false}
/>
)}
</div>
);
}
}

const styles = {
codeMirror: css({
display: "block",
height: "100%",
width: "100%",
overflow: "auto",
position: "relative",
}),
// error: css({
// order: 2,
// backgroundColor: colors.errorBackground,
// borderTop: `1px solid ${colors.errorBorder}`,
// color: colors.errorForeground,
// ...sharedBoxStyles,
// }),
// info: css({
// order: 1,
// backgroundColor: colors.infoBackground,
// borderTop: `1px solid ${colors.infoBorder}`,
// color: colors.infoForeground,
// ...sharedBoxStyles,
// }),
panel: css({
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "stretch",
overflow: "auto",
}),
};
136 changes: 136 additions & 0 deletions js/repl/ASTUtils.js
@@ -0,0 +1,136 @@
// https://github.com/hughsk/flat/blob/master/index.js

function isBuffer(obj) {
return (
obj != null &&
obj.constructor != null &&
typeof obj.constructor.isBuffer === "function" &&
obj.constructor.isBuffer(obj)
);
}

function flatten(target, opts) {
opts = opts || {};

const delimiter = opts.delimiter || ".";
const maxDepth = opts.maxDepth;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a default value.

const output = {};

function step(object, prev, currentDepth) {
currentDepth = currentDepth || 1;
Object.keys(object).forEach(function(key) {
const value = object[key];
const isarray = opts.safe && Array.isArray(value);
const type = Object.prototype.toString.call(value);
const isbuffer = isBuffer(value);
const isobject = type === "[object Object]" || type === "[object Array]";
const newKey = prev ? prev + delimiter + key : key;
if (
!isarray &&
!isbuffer &&
isobject &&
Object.keys(value).length &&
(!opts.maxDepth || currentDepth < maxDepth)
) {
return step(value, newKey, currentDepth + 1);
}
output[newKey] = value;
});
}

step(target);

return output;
}

function unflatten(target, opts) {
opts = opts || {};

const delimiter = opts.delimiter || ".";
const overwrite = opts.overwrite || false;
const result = {};
const isbuffer = isBuffer(target);
if (
isbuffer ||
Object.prototype.toString.call(target) !== "[object Object]"
) {
return target;
}

// safely ensure that the key is
// an integer.
function getkey(key) {
const parsedKey = Number(key);

return isNaN(parsedKey) || key.indexOf(".") !== -1 || opts.object
? key
: parsedKey;
}

const sortedKeys = Object.keys(target).sort(function(keyA, keyB) {
return keyA.length - keyB.length;
});

sortedKeys.forEach(function(key) {
const split = key.split(delimiter);
let key1 = getkey(split.shift());
let key2 = getkey(split[0]);
let recipient = result;

while (key2 !== undefined) {
const type = Object.prototype.toString.call(recipient[key1]);
const isobject = type === "[object Object]" || type === "[object Array]";

// do not write over falsey, non-undefined values if overwrite is false
if (!overwrite && !isobject && typeof recipient[key1] !== "undefined") {
return;
}

if ((overwrite && !isobject) || (!overwrite && recipient[key1] == null)) {
recipient[key1] = typeof key2 === "number" && !opts.object ? [] : {};
}

recipient = recipient[key1];
if (split.length > 0) {
key1 = getkey(split.shift());
key2 = getkey(split[0]);
}
}

// unflatten again for 'messy objects'
recipient[key1] = unflatten(target[key], opts);
});

return result;
}

function filterFlatten(flattenSrc, type) {
const result = Object.keys(flattenSrc)
.filter(key => {
const keys = key.split(".");
return keys.includes(type);
})
.reduce((object, key) => {
object[key] = flattenSrc[key];
return object;
}, {});
return result;
}

function deleteFlatten(currentSrc, deletedSrc) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, I would not use deleteFlatten and mergeFlatten.
Instead, just filtering flattened src by options state at getDerivedStateFromProps.
Is there a reason using delete and merge? Is it cheaper?

const deletedKeys = Object.keys(deletedSrc);
const result = Object.keys(currentSrc).reduce((object, key) => {
!deletedKeys.includes(key) ? (object[key] = currentSrc[key]) : null;
return object;
}, {});
return result;
}

function mergeFlatten(currentSrc, nextSrc) {
return {
...currentSrc,
...nextSrc,
};
}

export { flatten, unflatten, filterFlatten, deleteFlatten, mergeFlatten };
14 changes: 2 additions & 12 deletions js/repl/Repl.js
Expand Up @@ -35,7 +35,7 @@ import {
import WorkerApi from "./WorkerApi";
import scopedEval from "./scopedEval";
import { colors, media } from "./styles";
import ReactJson from "react-json-view";
import ASTPanel from "./ASTPanel";

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At compile method in Repl.js, their second parameter has astContext key, which should be added on Flow type as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't mean about above code example.
I wrote there because it was the only place I can leave a comment on Repl.js.

import type {
BabelPresets,
Expand Down Expand Up @@ -248,17 +248,7 @@ class Repl extends React.Component<Props, State> {
placeholder="Write code here"
/>
{state.ast ? (
<ReactJson
src={state.astContext}
style={{
overflowY: "scroll",
width: "100%",
}}
shouldCollapse={field => field.name !== "root"}
enableClipboard={false}
displayObjectSize={false}
displayDataTypes={false}
/>
<ASTPanel src={state.astContext} />
) : (
<CodeMirrorPanel
className={styles.codeMirrorPanel}
Expand Down