You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
539 lines
15 KiB
JavaScript
539 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
const React = require("react");
|
|
const createReactClass = require("create-react-class");
|
|
|
|
/* FIXME: Replace all inline simpleness determinations with calls to .isSimple */
|
|
|
|
function stringifyNode(node) {
|
|
if (node.type === "Identifier") {
|
|
return node.name;
|
|
} else if (node.type === "AssignmentPattern") {
|
|
return `${stringifyNode(node.left)} = ${stringifyNode(node.right)}`;
|
|
} else if (node.type === "Literal") {
|
|
return node.raw;
|
|
} else if (node.type === "MemberExpression") {
|
|
/* FIXME: Handle dynamic properties. */
|
|
if (node.computed === true) {
|
|
return `${stringifyNode(node.object)}[${stringifyNode(node.property)}]`;
|
|
} else {
|
|
return `${stringifyNode(node.object)}.${stringifyNode(node.property)}`;
|
|
}
|
|
} else if (node.type === "ThisExpression") {
|
|
return "this";
|
|
} else if (node.type === "ContinueStatement") {
|
|
return "continue";
|
|
} else if (node.type === "LogicalExpression" || node.type === "BinaryExpression") {
|
|
return `${stringifyNode(node.left)} ${node.operator} ${stringifyNode(node.right)}`;
|
|
} else if (node.type === "UnaryExpression" || node.type === "UpdateExpression") {
|
|
/* FIXME: Add spacing for typeof etc. but not for symbol operators like ++? */
|
|
if (node.prefix === true) {
|
|
return `${node.operator} ${stringifyNode(node.argument)}`;
|
|
} else {
|
|
return `${stringifyNode(node.argument)} ${node.operator}`;
|
|
}
|
|
} else if (node.type === "CallExpression") {
|
|
return `${stringifyNode(node.callee)}(${node.arguments.map((arg) => stringifyNode(arg)).join(", ")})`;
|
|
} else if (node.type === "ObjectExpression") {
|
|
if (node.properties.length > 0) {
|
|
throw new Error("Can only stringify zero-property object literals");
|
|
} else {
|
|
return "{}";
|
|
}
|
|
} else if (node.type === "ArrayExpression") {
|
|
if (node.elements.length > 0) {
|
|
throw new Error("Can only stringify zero-element array literals");
|
|
} else {
|
|
return "[]";
|
|
}
|
|
} else if (node.type === "ConditionalExpression") {
|
|
return `(${stringifyNode(node.test)} ? ${stringifyNode(node.consequent)} : ${stringifyNode(node.alternate)})`;
|
|
} else if (node.type === "AssignmentExpression") {
|
|
/* FIXME: Other assignment operators - does that include things like += ? */
|
|
return `${stringifyNode(node.left)} = ${stringifyNode(node.right)}`;
|
|
} else {
|
|
throw new Error(`Unrecognized node type: ${node.type}`);
|
|
}
|
|
}
|
|
|
|
function collectNestedExpressions(root, type, attribute) {
|
|
let collected = [ root ];
|
|
let last = root;
|
|
|
|
while (last[attribute] != null && last[attribute].type === type) {
|
|
collected.push(last[attribute]);
|
|
last = last[attribute];
|
|
}
|
|
|
|
return [collected, last];
|
|
}
|
|
|
|
module.exports = function ({dependencies}) {
|
|
let FunctionDeclarationItem = createReactClass({
|
|
displayName: "FunctionDeclarationItem",
|
|
render: function () {
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
if (this.props.data.id == null || this.props.data.id.type !== "Identifier") {
|
|
return (<GenericAstItem {...this.props} />);
|
|
} else {
|
|
let functionName = this.props.data.id.name;
|
|
|
|
let stringifiedParameters = this.props.data.params.map((param) => {
|
|
return stringifyNode(param);
|
|
});
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
hasFunctionScope: true, // TODO: Should this be hardcoded like this?
|
|
hasBlockScope: false,
|
|
type: this.props.data.type,
|
|
preview: `function ${functionName} (${stringifiedParameters.join(", ")})`,
|
|
scopeId: this.props.data._scopeId,
|
|
scopeType: "function",
|
|
children: {
|
|
body: this.props.data.body
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
let CallExpressionItem = createReactClass({
|
|
displayName: "CallExpressionItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
let showArguments = false;
|
|
|
|
let stringifiedArguments = this.props.data.arguments.map((arg) => {
|
|
try {
|
|
return stringifyNode(arg);
|
|
} catch (err) {
|
|
/* FIXME: Either catch specific error, or use a .isSimpleNode method on AST nodes to determine whether we can stringify this call expression */
|
|
showArguments = true;
|
|
return `<${arg.type}>`;
|
|
}
|
|
});
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: `${stringifyNode(this.props.data.callee)}(${stringifiedArguments.join(", ")})`,
|
|
childSequences: {
|
|
arguments: (showArguments === true ? this.props.data.arguments : null)
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
});
|
|
|
|
let MemberExpressionItem = createReactClass({
|
|
displayName: "MemberExpressionItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
|
|
/* The below is for handling recursive MemberExpressions, eg. for foo.bar.baz */
|
|
/* FIXME: How to handle the 'computed' property? */
|
|
// let [collected, last] = collectNestedExpressions(this.props.data, "MemberExpression", "object");
|
|
|
|
let hasNonLiteralProperty = (this.props.data.property.type !== "Identifier");
|
|
let hasNonLiteralObject = (this.props.data.object.type !== "Identifier");
|
|
|
|
if (hasNonLiteralObject || hasNonLiteralProperty) {
|
|
return (<GenericAstItem {...this.props} />);
|
|
} else {
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: stringifyNode(this.props.data)
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
let LiteralItem = createReactClass({
|
|
displayName: "LiteralItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: stringifyNode(this.props.data)
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
});
|
|
|
|
let ObjectPropertyItem = createReactClass({
|
|
displayName: "ObjectPropertyItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
|
|
if (this.props.data.computed === true || this.props.data.method === true || this.props.data.shorthand === true) {
|
|
return (<GenericAstItem {...this.props} />);
|
|
} else {
|
|
let preview, complexValue;
|
|
|
|
if (this.props.data.value.type === "Literal") {
|
|
preview = `${stringifyNode(this.props.data.key)}: ${this.props.data.value.raw}`;
|
|
complexValue = null;
|
|
} else {
|
|
preview = `${stringifyNode(this.props.data.key)}: <${this.props.data.value.type}>`;
|
|
complexValue = this.props.data.value;
|
|
}
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: preview,
|
|
children: {
|
|
value: complexValue
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
let VariableDeclaratorItem = createReactClass({
|
|
displayName: "VariableDeclaratorItem",
|
|
render: function () {
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
if (this.props.data.id.type !== "Identifier") {
|
|
return (<GenericAstItem {...this.props} />);
|
|
} else {
|
|
let preview;
|
|
let simpleValue = false;
|
|
|
|
if (this.props.data.init != null) {
|
|
try {
|
|
let stringifiedValue = stringifyNode(this.props.data.init);
|
|
simpleValue = true;
|
|
|
|
preview = `${this.props.data._kind} ${this.props.data.id.name} = ${stringifiedValue}`;
|
|
} catch (err) {
|
|
/* FIXME: Error type */
|
|
preview = `${this.props.data._kind} ${this.props.data.id.name} = <${this.props.data.init.type}>`;
|
|
}
|
|
} else {
|
|
preview = `${this.props.data._kind} ${this.props.data.id.name}`;
|
|
}
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: preview,
|
|
children: {
|
|
init: (simpleValue === false) ? this.props.data.init : null
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
let VariableDeclarationItem = createReactClass({
|
|
displayName: "VariableDeclarationItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
// preview: `(${this.props.data.kind})`,
|
|
childSequences: {
|
|
declarations: this.props.data.declarations
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
});
|
|
|
|
let AssignmentExpressionItem = createReactClass({
|
|
displayName: "AssignmentExpressionItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
let preview, simpleValue, leftHand, rightHand, collectedAssignments;
|
|
|
|
if (this.props.data.right.type === "AssignmentExpression") {
|
|
/* We want to collapse multiple nested AssignmentExpressions into one (eg. `foo = bar = baz`) */
|
|
let [collected, last] = collectNestedExpressions(this.props.data, "AssignmentExpression", "right");
|
|
|
|
leftHand = collected.map((node) => {
|
|
return stringifyNode(node.left);
|
|
}).join(" = ");
|
|
|
|
rightHand = last.right;
|
|
collectedAssignments = collected;
|
|
} else {
|
|
leftHand = stringifyNode(this.props.data.left);
|
|
rightHand = this.props.data.right;
|
|
}
|
|
|
|
if (rightHand.isSimple()) {
|
|
preview = `${leftHand} = ${stringifyNode(rightHand)}`;
|
|
simpleValue = true;
|
|
} else {
|
|
preview = `${leftHand} = <${rightHand.type}>`;
|
|
simpleValue = false;
|
|
}
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: "AssignmentExpression (nested)",
|
|
preview: preview,
|
|
children: {
|
|
value: (simpleValue === false ? this.props.data.right : null)
|
|
},
|
|
childSequences: {
|
|
assignments: (collectedAssignments != null ? collectedAssignments.map((node) => node.left) : null)
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
});
|
|
|
|
let ObjectExpressionItem = createReactClass({
|
|
displayName: "ObjectExpressionItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
let stringifiedProperties = this.props.data.properties.map((property) => {
|
|
if (property.computed === true) {
|
|
/* FIXME */
|
|
return "[computed]";
|
|
} else if (property.value.isSimple()) {
|
|
return `${stringifyNode(property.key)}: ${property.value.raw}`;
|
|
} else {
|
|
return stringifyNode(property.key);
|
|
}
|
|
});
|
|
|
|
let propertyPreviewLimit = 3;
|
|
let shortenedStringifiedProperties;
|
|
|
|
if (stringifiedProperties.length > propertyPreviewLimit) {
|
|
shortenedStringifiedProperties = stringifiedProperties.slice(0, propertyPreviewLimit).concat([`(... ${stringifiedProperties.length - propertyPreviewLimit} more)`]);
|
|
} else {
|
|
shortenedStringifiedProperties = stringifiedProperties;
|
|
}
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: `{ ${shortenedStringifiedProperties.join(", ")} }`,
|
|
childSequences: {
|
|
properties: this.props.data.properties
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
});
|
|
|
|
let FixedKeywordItem = createReactClass({
|
|
displayName: "FixedKeywordItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: stringifyNode(this.props.data)
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
});
|
|
|
|
let OperatorExpressionItem = createReactClass({
|
|
displayName: "OperatorExpressionItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
|
|
try {
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: stringifyNode(this.props.data)
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
} catch (err) {
|
|
/* FIXME: Error type */
|
|
return (<GenericAstItem {...this.props} />);
|
|
}
|
|
}
|
|
});
|
|
|
|
let IfStatementItem = createReactClass({
|
|
displayName: "IfStatementItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
|
|
if (this.props.data.test.isSimple()) {
|
|
let stringifiedTest = stringifyNode(this.props.data.test);
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: `if (${stringifiedTest}) ...`,
|
|
children: {
|
|
consequent: this.props.data.consequent,
|
|
/* FIXME: else-if chains */
|
|
alternate: this.props.data.alternate
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
} else {
|
|
return (<GenericAstItem {...this.props} />);
|
|
}
|
|
}
|
|
});
|
|
|
|
let ReturnStatementItem = createReactClass({
|
|
displayName: "ReturnStatementItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
|
|
if (this.props.data.isSimple()) {
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: `return ${stringifyNode(this.props.data.argument)}`
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
} else {
|
|
return (<GenericAstItem {...this.props} />);
|
|
}
|
|
}
|
|
});
|
|
|
|
let ExpressionStatementItem = createReactClass({
|
|
displayName: "ExpressionStatementItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
|
|
if (this.props.data.isSimple()) {
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: stringifyNode(this.props.data.expression)
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
} else {
|
|
return (<GenericAstItem {...this.props} />);
|
|
}
|
|
}
|
|
});
|
|
|
|
let IdentifierItem = createReactClass({
|
|
displayName: "IdentifierItem",
|
|
render: function () {
|
|
let AstItem = dependencies.get("AstItem");
|
|
|
|
let itemProps = {
|
|
abbreviated: true,
|
|
type: this.props.data.type,
|
|
preview: this.props.data.name
|
|
};
|
|
|
|
return (
|
|
<AstItem {...itemProps} data={this.props.data} />
|
|
);
|
|
}
|
|
});
|
|
|
|
let abbreviatedTypeMap = {
|
|
FunctionDeclaration: FunctionDeclarationItem,
|
|
FunctionExpression: FunctionDeclarationItem,
|
|
MemberExpression: MemberExpressionItem,
|
|
Identifier: IdentifierItem,
|
|
Literal: LiteralItem,
|
|
Property: ObjectPropertyItem,
|
|
CallExpression: CallExpressionItem,
|
|
VariableDeclarator: VariableDeclaratorItem,
|
|
VariableDeclaration: VariableDeclarationItem,
|
|
AssignmentExpression: AssignmentExpressionItem,
|
|
ObjectExpression: ObjectExpressionItem,
|
|
ThisExpression: FixedKeywordItem,
|
|
ContinueStatement: FixedKeywordItem,
|
|
IfStatement: IfStatementItem,
|
|
ConditionalExpression: IfStatementItem,
|
|
UpdateExpression: OperatorExpressionItem,
|
|
UnaryExpression: OperatorExpressionItem,
|
|
BinaryExpression: OperatorExpressionItem,
|
|
LogicalExpression: OperatorExpressionItem,
|
|
ReturnStatement: ReturnStatementItem,
|
|
ExpressionStatement: ExpressionStatementItem
|
|
};
|
|
|
|
dependencies.set("AbbreviatedAstItem", createReactClass({
|
|
displayName: "AbbreviatedAstItem",
|
|
render: function () {
|
|
let GenericAstItem = dependencies.get("GenericAstItem");
|
|
|
|
if (abbreviatedTypeMap[this.props.data.type] != null) {
|
|
let AbbreviatedItemType = abbreviatedTypeMap[this.props.data.type];
|
|
|
|
return (<AbbreviatedItemType {...this.props} />);
|
|
} else {
|
|
return (<GenericAstItem {...this.props} />);
|
|
}
|
|
}
|
|
}));
|
|
};
|