Skip to content
This repository has been archived by the owner on Sep 10, 2023. It is now read-only.

Commit

Permalink
feat: partial support stack track
Browse files Browse the repository at this point in the history
  • Loading branch information
axetroy committed Apr 30, 2018
1 parent b15694b commit bf159b5
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 28 deletions.
1 change: 1 addition & 0 deletions src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export const REQUIRE = "require";
export const UNDEFINED = "undefined";
export const ARGUMENTS = "arguments";
export const NEW = "new";
export const ANONYMOUS = "anonymous";
153 changes: 129 additions & 24 deletions src/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ import { EvaluateFunc, EvaluateMap, Kind, ScopeType } from "./type";
// tslint:disable-next-line
import { Signal } from "./signal";
import { Scope } from "./scope";
import { MODULE, THIS, REQUIRE, UNDEFINED, ARGUMENTS, NEW } from "./constant";
import { Stack } from "./stack";
import {
MODULE,
THIS,
REQUIRE,
UNDEFINED,
ARGUMENTS,
NEW,
ANONYMOUS
} from "./constant";

import {
isArrayExpression,
Expand All @@ -42,11 +51,28 @@ import {
isObjectProperty,
isRestElement,
isSpreadElement,
isVariableDeclaration
isVariableDeclaration,
isStringLiteral
} from "./packages/babel-types";

import { defineFunctionLength, defineFunctionName } from "./utils";

function overriteStack(err: Error, stack: Stack, node: types.Node): Error {
stack.push({
filename: ANONYMOUS,
stack: stack.currentStackName,
location: node.loc
});
const originStack = (err.stack || "").split("\n");
originStack.shift(); // drop the error message
err.stack =
err.toString() +
"\n" +
stack.raw +
(originStack ? "\n" + originStack.join("\n") : "");
return err;
}

const visitors: EvaluateMap = {
File(path) {
evaluate(path.createChild(path.node.program));
Expand Down Expand Up @@ -74,15 +100,15 @@ const visitors: EvaluateMap = {
},

Identifier(path) {
const { node, scope } = path;
const { node, scope, stack } = path;
if (node.name === UNDEFINED) {
return undefined;
}
const $var = scope.hasBinding(node.name);
if ($var) {
return $var.value;
} else {
throw ErrNotDefined(node.name);
throw overriteStack(ErrNotDefined(node.name), stack, node);
}
},
RegExpLiteral(path) {
Expand Down Expand Up @@ -468,7 +494,7 @@ const visitors: EvaluateMap = {
},
// @es2015 for of
ForOfStatement(path) {
const { node, scope, ctx } = path;
const { node, scope, ctx, stack } = path;
const labelName: string | void = ctx.labelName;
const entity = evaluate(path.createChild(node.right));
const SymbolConst: any = (() => {
Expand All @@ -480,7 +506,11 @@ const visitors: EvaluateMap = {
if (!entity || !entity[SymbolConst.iterator]) {
// FIXME: how to get function name
// for (let value of get()){}
throw ErrInvalidIterable((node.right as types.Identifier).name);
throw overriteStack(
ErrInvalidIterable((node.right as types.Identifier).name),
stack,
node.right
);
}
}

Expand Down Expand Up @@ -657,7 +687,12 @@ const visitors: EvaluateMap = {
}
},
ThrowStatement(path) {
throw evaluate(path.createChild(path.node.argument));
const r = evaluate(path.createChild(path.node.argument));
if (r instanceof Error) {
throw r;
} else {
throw overriteStack(r, path.stack, path.node.argument);
}
},
CatchClause(path) {
return evaluate(path.createChild(path.node.body));
Expand Down Expand Up @@ -729,14 +764,14 @@ const visitors: EvaluateMap = {
}
},
UpdateExpression(path) {
const { node, scope } = path;
const { node, scope, stack } = path;
const { prefix } = node;
let $var: IVar;
if (isIdentifier(node.argument)) {
const { name } = node.argument;
const $$var = scope.hasBinding(name);
if (!$$var) {
throw ErrNotDefined(name);
throw overriteStack(ErrNotDefined(name), stack, node.argument);
}
$var = $$var;
} else if (isMemberExpression(node.argument)) {
Expand Down Expand Up @@ -772,7 +807,7 @@ const visitors: EvaluateMap = {
// use this in class constructor it it never call super();
if (scope.type === ScopeType.Constructor) {
if (!scope.hasOwnBinding(THIS)) {
throw ErrNoSuper();
throw overriteStack(ErrNoSuper(), path.stack, path.node);
}
}
const thisVar = scope.hasBinding(THIS);
Expand Down Expand Up @@ -831,13 +866,14 @@ const visitors: EvaluateMap = {
}
},
ObjectMethod(path) {
const { node, scope } = path;
const { node, scope, stack } = path;
const methodName: string = !node.computed
? isIdentifier(node.key)
? node.key.name
: evaluate(path.createChild(node.key))
: evaluate(path.createChild(node.key));
const method = function() {
stack.enter("Object." + methodName);
const args = [].slice.call(arguments);
const newScope = scope.createChild(ScopeType.Function);
newScope.const(THIS, this);
Expand All @@ -846,6 +882,7 @@ const visitors: EvaluateMap = {
newScope.const((param as types.Identifier).name, args[i]);
});
const result = evaluate(path.createChild(node.body, newScope));
stack.leave();
if (Signal.isReturn(result)) {
return result.value;
}
Expand Down Expand Up @@ -873,8 +910,11 @@ const visitors: EvaluateMap = {
}
},
FunctionExpression(path) {
const { node, scope } = path;
const { node, scope, stack } = path;

const functionName = node.id ? node.id.name : "";
const func = function functionDeclaration(...args) {
stack.enter(functionName); // enter the stack
const funcScope = scope.createChild(ScopeType.Function);
for (let i = 0; i < node.params.length; i++) {
const param = node.params[i];
Expand Down Expand Up @@ -902,6 +942,7 @@ const visitors: EvaluateMap = {
funcScope.isolated = false;

const result = evaluate(path.createChild(node.body, funcScope));
stack.leave(); // leave stack
if (result instanceof Signal) {
return result.value;
} else {
Expand Down Expand Up @@ -992,17 +1033,55 @@ const visitors: EvaluateMap = {
},

CallExpression(path) {
const { node, scope } = path;
const { node, scope, stack } = path;

const functionName: string = isMemberExpression(node.callee)
? (() => {
if (isIdentifier(node.callee.property)) {
return (
(node.callee.object as any).name + "." + node.callee.property.name
);
} else if (isStringLiteral(node.callee.property)) {
return (
(node.callee.object as any).name +
"." +
node.callee.property.value
);
} else {
return "undefined";
}
})()
: (node.callee as types.Identifier).name;

const func = evaluate(path.createChild(node.callee));
const args = node.arguments.map(arg => evaluate(path.createChild(arg)));
const isValidFunction = isFunction(func) as boolean;

if (isMemberExpression(node.callee)) {
if (!isValidFunction) {
throw overriteStack(
ErrIsNotFunction(functionName),
stack,
node.callee.property
);
} else {
stack.push({
filename: ANONYMOUS,
stack: stack.currentStackName,
location: node.callee.property.loc
});
}
const object = evaluate(path.createChild(node.callee.object));
return func.apply(object, args);
} else {
if (!isValidFunction) {
throw ErrIsNotFunction((node.callee as types.Identifier).name);
throw overriteStack(ErrIsNotFunction(functionName), stack, node);
} else {
stack.push({
filename: ANONYMOUS,
stack: stack.currentStackName,
location: node.loc
});
}
const thisVar = scope.hasBinding(THIS);
return func.apply(thisVar ? thisVar.value : null, args);
Expand Down Expand Up @@ -1037,7 +1116,7 @@ const visitors: EvaluateMap = {
if (globalVar) {
$var = globalVar;
} else {
throw ErrNotDefined(name);
throw overriteStack(ErrNotDefined(name), path.stack, node.right);
}
} else {
$var = varOrNot as Var<any>;
Expand All @@ -1046,7 +1125,11 @@ const visitors: EvaluateMap = {
* test = 321 // it should throw an error
*/
if ($var.kind === Kind.Const) {
throw new TypeError("Assignment to constant variable.");
throw overriteStack(
new TypeError("Assignment to constant variable."),
path.stack,
node.left
);
}
}
} else if (isMemberExpression(node.left)) {
Expand Down Expand Up @@ -1141,12 +1224,24 @@ const visitors: EvaluateMap = {
: evaluate(path.createChild(path.node.alternate));
},
NewExpression(path) {
const { node } = path;
const { node, stack } = path;
const func = evaluate(path.createChild(node.callee));
const args: any[] = node.arguments.map(arg =>
evaluate(path.createChild(arg))
);
const entity = new func(...args);

// stack track for Error constructor
if (func === Error || entity instanceof Error) {
path.stack.push({
filename: ANONYMOUS,
stack: stack.currentStackName,
location: node.loc
});
entity.stack = `Error: ${entity.message}
${path.stack.raw}
`;
}
entity.prototype = entity.prototype || {};
entity.prototype.constructor = func;
return entity;
Expand Down Expand Up @@ -1215,7 +1310,7 @@ const visitors: EvaluateMap = {
path.scope.const(path.node.id.name, ClassConstructor);
},
ClassBody(path) {
const { node, scope } = path;
const { node, scope, stack } = path;
const constructor: types.ClassMethod | void = node.body.find(
n => isClassMethod(n) && n.kind === "constructor"
) as types.ClassMethod | void;
Expand All @@ -1234,6 +1329,7 @@ const visitors: EvaluateMap = {
}

function ClassConstructor(...args) {
stack.enter(parentNode.id.name + ".constructor");
_classCallCheck(this, ClassConstructor);
const classScope = scope.createChild(ScopeType.Constructor);

Expand Down Expand Up @@ -1280,9 +1376,11 @@ const visitors: EvaluateMap = {
}

if (!classScope.hasOwnBinding(THIS)) {
throw ErrNoSuper();
throw overriteStack(ErrNoSuper(), path.stack, node);
}

stack.leave();

return this;
}

Expand All @@ -1295,8 +1393,14 @@ const visitors: EvaluateMap = {

const classMethods = methods
.map((method: types.ClassMethod) => {
const methodName: string = method.id
? method.id.name
: method.computed
? evaluate(path.createChild(method.key))
: (method.key as types.Identifier).name;
const methodScope = scope.createChild(ScopeType.Function);
const func = function(...args) {
stack.enter(parentNode.id.name + "." + methodName);
methodScope.const(THIS, this);
methodScope.const(NEW, { target: undefined });

Expand All @@ -1316,21 +1420,22 @@ const visitors: EvaluateMap = {
})
);

stack.leave();

if (Signal.isReturn(result)) {
return result.value;
}
};

defineFunctionLength(func, method.params.length);
defineFunctionName(func, method.id ? method.id.name : "");
defineFunctionName(func, methodName);

return {
key: (method.key as any).name,
[method.kind === "method" ? "value" : method.kind]: func
};
})
.concat([{ key: "constructor", value: ClassConstructor }]);

// define class methods
_createClass(ClassConstructor, classMethods);

Expand Down Expand Up @@ -1397,7 +1502,7 @@ const visitors: EvaluateMap = {
Object.assign(object, evaluate(path.createChild(node.argument)));
},
ImportDeclaration(path) {
const { node, scope } = path;
const { node, scope, stack } = path;
let defaultImport: string = ""; // default import object
const otherImport: string[] = []; // import property
const moduleName: string = evaluate(path.createChild(node.source));
Expand All @@ -1414,13 +1519,13 @@ const visitors: EvaluateMap = {
const requireVar = scope.hasBinding(REQUIRE);

if (requireVar === undefined) {
throw ErrNotDefined(REQUIRE);
throw overriteStack(ErrNotDefined(REQUIRE), stack, node);
}

const requireFunc = requireVar.value;

if (!isFunction(requireFunc)) {
throw ErrIsNotFunction(REQUIRE);
throw overriteStack(ErrIsNotFunction(REQUIRE), stack, node);
}

const targetModule: any = requireFunc(moduleName) || {};
Expand Down
7 changes: 6 additions & 1 deletion src/packages/babel-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ import {
ObjectProperty,
RestElement,
SpreadElement,
VariableDeclaration
VariableDeclaration,
StringLiteral
} from "babel-types";

function is(node: Node, type: string): boolean {
return node.type === type;
}

export function isStringLiteral(node: Node): node is StringLiteral {
return is(node, "StringLiteral");
}

export function isArrayExpression(node: Node): node is ArrayExpression {
return is(node, "ArrayExpression");
}
Expand Down
Loading

0 comments on commit bf159b5

Please sign in to comment.