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

Commit bf159b5

Browse files
committed
feat: partial support stack track
1 parent b15694b commit bf159b5

File tree

9 files changed

+290
-28
lines changed

9 files changed

+290
-28
lines changed

src/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export const REQUIRE = "require";
55
export const UNDEFINED = "undefined";
66
export const ARGUMENTS = "arguments";
77
export const NEW = "new";
8+
export const ANONYMOUS = "anonymous";

src/evaluate.ts

Lines changed: 129 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,16 @@ import { EvaluateFunc, EvaluateMap, Kind, ScopeType } from "./type";
2323
// tslint:disable-next-line
2424
import { Signal } from "./signal";
2525
import { Scope } from "./scope";
26-
import { MODULE, THIS, REQUIRE, UNDEFINED, ARGUMENTS, NEW } from "./constant";
26+
import { Stack } from "./stack";
27+
import {
28+
MODULE,
29+
THIS,
30+
REQUIRE,
31+
UNDEFINED,
32+
ARGUMENTS,
33+
NEW,
34+
ANONYMOUS
35+
} from "./constant";
2736

2837
import {
2938
isArrayExpression,
@@ -42,11 +51,28 @@ import {
4251
isObjectProperty,
4352
isRestElement,
4453
isSpreadElement,
45-
isVariableDeclaration
54+
isVariableDeclaration,
55+
isStringLiteral
4656
} from "./packages/babel-types";
4757

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

60+
function overriteStack(err: Error, stack: Stack, node: types.Node): Error {
61+
stack.push({
62+
filename: ANONYMOUS,
63+
stack: stack.currentStackName,
64+
location: node.loc
65+
});
66+
const originStack = (err.stack || "").split("\n");
67+
originStack.shift(); // drop the error message
68+
err.stack =
69+
err.toString() +
70+
"\n" +
71+
stack.raw +
72+
(originStack ? "\n" + originStack.join("\n") : "");
73+
return err;
74+
}
75+
5076
const visitors: EvaluateMap = {
5177
File(path) {
5278
evaluate(path.createChild(path.node.program));
@@ -74,15 +100,15 @@ const visitors: EvaluateMap = {
74100
},
75101

76102
Identifier(path) {
77-
const { node, scope } = path;
103+
const { node, scope, stack } = path;
78104
if (node.name === UNDEFINED) {
79105
return undefined;
80106
}
81107
const $var = scope.hasBinding(node.name);
82108
if ($var) {
83109
return $var.value;
84110
} else {
85-
throw ErrNotDefined(node.name);
111+
throw overriteStack(ErrNotDefined(node.name), stack, node);
86112
}
87113
},
88114
RegExpLiteral(path) {
@@ -468,7 +494,7 @@ const visitors: EvaluateMap = {
468494
},
469495
// @es2015 for of
470496
ForOfStatement(path) {
471-
const { node, scope, ctx } = path;
497+
const { node, scope, ctx, stack } = path;
472498
const labelName: string | void = ctx.labelName;
473499
const entity = evaluate(path.createChild(node.right));
474500
const SymbolConst: any = (() => {
@@ -480,7 +506,11 @@ const visitors: EvaluateMap = {
480506
if (!entity || !entity[SymbolConst.iterator]) {
481507
// FIXME: how to get function name
482508
// for (let value of get()){}
483-
throw ErrInvalidIterable((node.right as types.Identifier).name);
509+
throw overriteStack(
510+
ErrInvalidIterable((node.right as types.Identifier).name),
511+
stack,
512+
node.right
513+
);
484514
}
485515
}
486516

@@ -657,7 +687,12 @@ const visitors: EvaluateMap = {
657687
}
658688
},
659689
ThrowStatement(path) {
660-
throw evaluate(path.createChild(path.node.argument));
690+
const r = evaluate(path.createChild(path.node.argument));
691+
if (r instanceof Error) {
692+
throw r;
693+
} else {
694+
throw overriteStack(r, path.stack, path.node.argument);
695+
}
661696
},
662697
CatchClause(path) {
663698
return evaluate(path.createChild(path.node.body));
@@ -729,14 +764,14 @@ const visitors: EvaluateMap = {
729764
}
730765
},
731766
UpdateExpression(path) {
732-
const { node, scope } = path;
767+
const { node, scope, stack } = path;
733768
const { prefix } = node;
734769
let $var: IVar;
735770
if (isIdentifier(node.argument)) {
736771
const { name } = node.argument;
737772
const $$var = scope.hasBinding(name);
738773
if (!$$var) {
739-
throw ErrNotDefined(name);
774+
throw overriteStack(ErrNotDefined(name), stack, node.argument);
740775
}
741776
$var = $$var;
742777
} else if (isMemberExpression(node.argument)) {
@@ -772,7 +807,7 @@ const visitors: EvaluateMap = {
772807
// use this in class constructor it it never call super();
773808
if (scope.type === ScopeType.Constructor) {
774809
if (!scope.hasOwnBinding(THIS)) {
775-
throw ErrNoSuper();
810+
throw overriteStack(ErrNoSuper(), path.stack, path.node);
776811
}
777812
}
778813
const thisVar = scope.hasBinding(THIS);
@@ -831,13 +866,14 @@ const visitors: EvaluateMap = {
831866
}
832867
},
833868
ObjectMethod(path) {
834-
const { node, scope } = path;
869+
const { node, scope, stack } = path;
835870
const methodName: string = !node.computed
836871
? isIdentifier(node.key)
837872
? node.key.name
838873
: evaluate(path.createChild(node.key))
839874
: evaluate(path.createChild(node.key));
840875
const method = function() {
876+
stack.enter("Object." + methodName);
841877
const args = [].slice.call(arguments);
842878
const newScope = scope.createChild(ScopeType.Function);
843879
newScope.const(THIS, this);
@@ -846,6 +882,7 @@ const visitors: EvaluateMap = {
846882
newScope.const((param as types.Identifier).name, args[i]);
847883
});
848884
const result = evaluate(path.createChild(node.body, newScope));
885+
stack.leave();
849886
if (Signal.isReturn(result)) {
850887
return result.value;
851888
}
@@ -873,8 +910,11 @@ const visitors: EvaluateMap = {
873910
}
874911
},
875912
FunctionExpression(path) {
876-
const { node, scope } = path;
913+
const { node, scope, stack } = path;
914+
915+
const functionName = node.id ? node.id.name : "";
877916
const func = function functionDeclaration(...args) {
917+
stack.enter(functionName); // enter the stack
878918
const funcScope = scope.createChild(ScopeType.Function);
879919
for (let i = 0; i < node.params.length; i++) {
880920
const param = node.params[i];
@@ -902,6 +942,7 @@ const visitors: EvaluateMap = {
902942
funcScope.isolated = false;
903943

904944
const result = evaluate(path.createChild(node.body, funcScope));
945+
stack.leave(); // leave stack
905946
if (result instanceof Signal) {
906947
return result.value;
907948
} else {
@@ -992,17 +1033,55 @@ const visitors: EvaluateMap = {
9921033
},
9931034

9941035
CallExpression(path) {
995-
const { node, scope } = path;
1036+
const { node, scope, stack } = path;
1037+
1038+
const functionName: string = isMemberExpression(node.callee)
1039+
? (() => {
1040+
if (isIdentifier(node.callee.property)) {
1041+
return (
1042+
(node.callee.object as any).name + "." + node.callee.property.name
1043+
);
1044+
} else if (isStringLiteral(node.callee.property)) {
1045+
return (
1046+
(node.callee.object as any).name +
1047+
"." +
1048+
node.callee.property.value
1049+
);
1050+
} else {
1051+
return "undefined";
1052+
}
1053+
})()
1054+
: (node.callee as types.Identifier).name;
1055+
9961056
const func = evaluate(path.createChild(node.callee));
9971057
const args = node.arguments.map(arg => evaluate(path.createChild(arg)));
9981058
const isValidFunction = isFunction(func) as boolean;
9991059

10001060
if (isMemberExpression(node.callee)) {
1061+
if (!isValidFunction) {
1062+
throw overriteStack(
1063+
ErrIsNotFunction(functionName),
1064+
stack,
1065+
node.callee.property
1066+
);
1067+
} else {
1068+
stack.push({
1069+
filename: ANONYMOUS,
1070+
stack: stack.currentStackName,
1071+
location: node.callee.property.loc
1072+
});
1073+
}
10011074
const object = evaluate(path.createChild(node.callee.object));
10021075
return func.apply(object, args);
10031076
} else {
10041077
if (!isValidFunction) {
1005-
throw ErrIsNotFunction((node.callee as types.Identifier).name);
1078+
throw overriteStack(ErrIsNotFunction(functionName), stack, node);
1079+
} else {
1080+
stack.push({
1081+
filename: ANONYMOUS,
1082+
stack: stack.currentStackName,
1083+
location: node.loc
1084+
});
10061085
}
10071086
const thisVar = scope.hasBinding(THIS);
10081087
return func.apply(thisVar ? thisVar.value : null, args);
@@ -1037,7 +1116,7 @@ const visitors: EvaluateMap = {
10371116
if (globalVar) {
10381117
$var = globalVar;
10391118
} else {
1040-
throw ErrNotDefined(name);
1119+
throw overriteStack(ErrNotDefined(name), path.stack, node.right);
10411120
}
10421121
} else {
10431122
$var = varOrNot as Var<any>;
@@ -1046,7 +1125,11 @@ const visitors: EvaluateMap = {
10461125
* test = 321 // it should throw an error
10471126
*/
10481127
if ($var.kind === Kind.Const) {
1049-
throw new TypeError("Assignment to constant variable.");
1128+
throw overriteStack(
1129+
new TypeError("Assignment to constant variable."),
1130+
path.stack,
1131+
node.left
1132+
);
10501133
}
10511134
}
10521135
} else if (isMemberExpression(node.left)) {
@@ -1141,12 +1224,24 @@ const visitors: EvaluateMap = {
11411224
: evaluate(path.createChild(path.node.alternate));
11421225
},
11431226
NewExpression(path) {
1144-
const { node } = path;
1227+
const { node, stack } = path;
11451228
const func = evaluate(path.createChild(node.callee));
11461229
const args: any[] = node.arguments.map(arg =>
11471230
evaluate(path.createChild(arg))
11481231
);
11491232
const entity = new func(...args);
1233+
1234+
// stack track for Error constructor
1235+
if (func === Error || entity instanceof Error) {
1236+
path.stack.push({
1237+
filename: ANONYMOUS,
1238+
stack: stack.currentStackName,
1239+
location: node.loc
1240+
});
1241+
entity.stack = `Error: ${entity.message}
1242+
${path.stack.raw}
1243+
`;
1244+
}
11501245
entity.prototype = entity.prototype || {};
11511246
entity.prototype.constructor = func;
11521247
return entity;
@@ -1215,7 +1310,7 @@ const visitors: EvaluateMap = {
12151310
path.scope.const(path.node.id.name, ClassConstructor);
12161311
},
12171312
ClassBody(path) {
1218-
const { node, scope } = path;
1313+
const { node, scope, stack } = path;
12191314
const constructor: types.ClassMethod | void = node.body.find(
12201315
n => isClassMethod(n) && n.kind === "constructor"
12211316
) as types.ClassMethod | void;
@@ -1234,6 +1329,7 @@ const visitors: EvaluateMap = {
12341329
}
12351330

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

@@ -1280,9 +1376,11 @@ const visitors: EvaluateMap = {
12801376
}
12811377

12821378
if (!classScope.hasOwnBinding(THIS)) {
1283-
throw ErrNoSuper();
1379+
throw overriteStack(ErrNoSuper(), path.stack, node);
12841380
}
12851381

1382+
stack.leave();
1383+
12861384
return this;
12871385
}
12881386

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

12961394
const classMethods = methods
12971395
.map((method: types.ClassMethod) => {
1396+
const methodName: string = method.id
1397+
? method.id.name
1398+
: method.computed
1399+
? evaluate(path.createChild(method.key))
1400+
: (method.key as types.Identifier).name;
12981401
const methodScope = scope.createChild(ScopeType.Function);
12991402
const func = function(...args) {
1403+
stack.enter(parentNode.id.name + "." + methodName);
13001404
methodScope.const(THIS, this);
13011405
methodScope.const(NEW, { target: undefined });
13021406

@@ -1316,21 +1420,22 @@ const visitors: EvaluateMap = {
13161420
})
13171421
);
13181422

1423+
stack.leave();
1424+
13191425
if (Signal.isReturn(result)) {
13201426
return result.value;
13211427
}
13221428
};
13231429

13241430
defineFunctionLength(func, method.params.length);
1325-
defineFunctionName(func, method.id ? method.id.name : "");
1431+
defineFunctionName(func, methodName);
13261432

13271433
return {
13281434
key: (method.key as any).name,
13291435
[method.kind === "method" ? "value" : method.kind]: func
13301436
};
13311437
})
13321438
.concat([{ key: "constructor", value: ClassConstructor }]);
1333-
13341439
// define class methods
13351440
_createClass(ClassConstructor, classMethods);
13361441

@@ -1397,7 +1502,7 @@ const visitors: EvaluateMap = {
13971502
Object.assign(object, evaluate(path.createChild(node.argument)));
13981503
},
13991504
ImportDeclaration(path) {
1400-
const { node, scope } = path;
1505+
const { node, scope, stack } = path;
14011506
let defaultImport: string = ""; // default import object
14021507
const otherImport: string[] = []; // import property
14031508
const moduleName: string = evaluate(path.createChild(node.source));
@@ -1414,13 +1519,13 @@ const visitors: EvaluateMap = {
14141519
const requireVar = scope.hasBinding(REQUIRE);
14151520

14161521
if (requireVar === undefined) {
1417-
throw ErrNotDefined(REQUIRE);
1522+
throw overriteStack(ErrNotDefined(REQUIRE), stack, node);
14181523
}
14191524

14201525
const requireFunc = requireVar.value;
14211526

14221527
if (!isFunction(requireFunc)) {
1423-
throw ErrIsNotFunction(REQUIRE);
1528+
throw overriteStack(ErrIsNotFunction(REQUIRE), stack, node);
14241529
}
14251530

14261531
const targetModule: any = requireFunc(moduleName) || {};

src/packages/babel-types/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ import {
1616
ObjectProperty,
1717
RestElement,
1818
SpreadElement,
19-
VariableDeclaration
19+
VariableDeclaration,
20+
StringLiteral
2021
} from "babel-types";
2122

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

27+
export function isStringLiteral(node: Node): node is StringLiteral {
28+
return is(node, "StringLiteral");
29+
}
30+
2631
export function isArrayExpression(node: Node): node is ArrayExpression {
2732
return is(node, "ArrayExpression");
2833
}

0 commit comments

Comments
 (0)