distinctiomagnam
Version:
JavaScript Obfuscation Tool.
620 lines (557 loc) • 12 kB
text/typescript
import { ok } from "assert";
import { isValidIdentifier } from "./compare";
export type Type =
| "Identifier"
| "Literal"
| "VariableDeclaration"
| "VariableDeclarator"
| "IfStatement"
| "SwitchStatement"
| "SwitchCase"
| "FunctionDeclaration"
| "ClassDeclaration"
| "ClassExpression"
| "ClassBody"
| "BlockStatement"
| "Program"
| "ThisExpression"
| "Super"
| "ForInStatement"
| "ForOfStatement"
| "WhileStatement"
| "DoWhileStatement"
| "UnaryExpression"
| "ExpressionStatement"
| "AssignmentExpression"
| "NewExpression"
| "CallExpression"
| "ArrayPattern"
| "LogicalExpression"
| "BinaryExpression"
| "UpdateExpression"
| "ThrowStatement"
| "MethodDefinition"
| "LabeledStatement";
export type Node = { type: string; [key: string]: any };
/**
* 0. First index is the Node.
* 1. Second index is the parents as an array.
*/
export type Location = [Node, Node[]];
/**
* Eval Callbacks are called once all transformations are done.
*
* - Called with object, and parents.
*/
export type EvalCallback = {
$eval: (object: Node, parents: Node[]) => void;
[key: string]: any;
};
/**
* - 0. First index is the Node.
* - ...1 Parent nodes.
*/
export type Chain = Node[];
export function Literal(value: string | number | boolean) {
if (typeof value === "undefined") {
throw new Error("value is undefined");
}
if (typeof value == "number" && value < 0) {
return UnaryExpression("-", Literal(Math.abs(value)));
}
ok(value === value, "NaN value is disallowed");
return {
type: "Literal",
value: value,
};
}
export function RegexLiteral(pattern: string, flags: string) {
return {
type: "Literal",
regex: {
pattern,
flags,
},
};
}
export function Identifier(name: string) {
if (!name) {
throw new Error("name is null/empty");
}
if (name == "this") {
throw new Error("Use ThisExpression");
}
if (name == "super") {
throw new Error("Use Super");
}
return {
type: "Identifier",
name: name.toString(),
};
}
export function BlockStatement(body: Node[]) {
if (!Array.isArray(body)) {
throw new Error("not array");
}
return {
type: "BlockStatement",
body: body,
};
}
export function LogicalExpression(operator: string, left: Node, right: Node) {
return {
type: "LogicalExpression",
operator,
left,
right,
};
}
export function BinaryExpression(operator: string, left: Node, right: Node) {
if (operator == "||" || operator == "&&") {
throw new Error("invalid operator, use LogicalExpression");
}
return {
type: "BinaryExpression",
operator,
left,
right,
};
}
export function ThisExpression() {
return { type: "ThisExpression" };
}
export function SwitchCase(test: any, consequent: Node[]) {
ok(test === null || test);
ok(Array.isArray(consequent));
return {
type: "SwitchCase",
test,
consequent,
};
}
export function SwitchDefaultCase(consequent: Node[]) {
return SwitchCase(null, consequent);
}
export function LabeledStatement(label: string, body: Node) {
return {
type: "LabeledStatement",
label: Identifier(label),
body: body,
};
}
export function SwitchStatement(discriminant: any, cases: Node[]) {
return {
type: "SwitchStatement",
discriminant: discriminant,
cases: cases,
};
}
export function BreakStatement(label?: string) {
return {
type: "BreakStatement",
label: label ? Identifier(label) : null,
};
}
export function Property(key: Node, value: Node, computed = false) {
if (!key) {
throw new Error("key is undefined");
}
if (!value) {
throw new Error("value is undefined");
}
return {
type: "Property",
key: key,
computed: computed,
value: value,
kind: "init",
method: false,
shorthand: false,
};
}
export function ObjectExpression(properties: Node[]) {
if (!properties) {
throw new Error("properties is null");
}
return {
type: "ObjectExpression",
properties: properties,
};
}
export function VariableDeclarator(id: string | Node, init: Node = null) {
if (typeof id === "string") {
id = Identifier(id);
}
return {
type: "VariableDeclarator",
id,
init,
};
}
export function VariableDeclaration(
declarations: Node | Node[],
kind: "var" | "const" | "let" = "var"
) {
if (!Array.isArray(declarations)) {
declarations = [declarations];
}
ok(Array.isArray(declarations));
ok(declarations.length);
ok(!declarations.find((x) => x.type == "ExpressionStatement"));
return {
type: "VariableDeclaration",
declarations: declarations,
kind: kind,
};
}
export function ForStatement(
variableDeclaration: any,
test: any,
update: any,
body: any[]
) {
ok(variableDeclaration);
ok(test);
ok(update);
return {
type: "ForStatement",
init: variableDeclaration,
test: test,
update: update,
body: BlockStatement(body),
};
}
export function WhileStatement(test: any, body: Node[]) {
ok(test);
return {
type: "WhileStatement",
test,
body: BlockStatement(body),
};
}
export function IfStatement(
test: Node,
consequent: Node[],
alternate: Node[] | null = null
): Node {
if (!test) {
throw new Error("test is undefined");
}
if (!consequent) {
throw new Error("consequent undefined, use empty array instead");
}
if (!Array.isArray(consequent)) {
throw new Error(
"consequent needs to be array, found " + (consequent as any).type
);
}
if (alternate && !Array.isArray(alternate)) {
throw new Error(
"alternate needs to be array, found " + (alternate as any).type
);
}
return {
type: "IfStatement",
test: test,
consequent: BlockStatement(consequent),
alternate: alternate ? BlockStatement(alternate) : null,
};
}
export function FunctionExpression(params: Node[], body: any[]) {
ok(Array.isArray(params), "params should be an array");
return {
type: "FunctionExpression",
id: null,
params: params,
body: BlockStatement(body),
generator: false,
expression: false,
async: false,
};
}
/**
* ```js
* function name(p[0], p[1], p[2], ...p[4]){
* body[0];
* body[1]...
* }
* ```
* @param name
* @param params
* @param body
*/
export function FunctionDeclaration(
name: string,
params: Node[],
body: Node[]
) {
if (!body) {
throw new Error("undefined body");
}
if (body && Array.isArray(body[0])) {
throw new Error("nested array");
}
ok(Array.isArray(params), "params should be an array");
return {
type: "FunctionDeclaration",
id: Identifier(name),
params: params,
body: BlockStatement(body),
generator: false,
expression: false,
async: false,
};
}
export function DebuggerStatement() {
return {
type: "DebuggerStatement",
};
}
export function ReturnStatement(argument: Node = null) {
if (argument) {
ok(argument.type, "Argument should be a node");
}
return {
type: "ReturnStatement",
argument: argument,
};
}
export function AwaitExpression(argument: Node) {
ok(argument.type, "Argument should be a node");
return {
type: "AwaitExpression",
argument,
};
}
export function ConditionalExpression(
test: Node,
consequent: Node,
alternate: Node
) {
ok(test);
ok(consequent);
ok(alternate);
return {
type: "ConditionalExpression",
test,
consequent,
alternate,
};
}
export function ExpressionStatement(expression: Node) {
ok(expression.type);
return {
type: "ExpressionStatement",
expression: expression,
} as Node;
}
export function UnaryExpression(operator: string, argument: Node) {
ok(typeof operator === "string");
ok(argument.type);
return {
type: "UnaryExpression",
operator,
argument,
} as Node;
}
export function UpdateExpression(
operator: string,
argument: Node,
prefix = false
) {
return {
type: "UpdateExpression",
operator,
argument,
prefix,
} as Node;
}
export function SequenceExpression(expressions: Node[]) {
if (!expressions) {
throw new Error("expressions undefined");
}
if (!expressions.length) {
throw new Error("expressions length = 0");
}
return {
type: "SequenceExpression",
expressions: expressions,
};
}
export function MemberExpression(
object: Node,
property: Node,
computed = true
) {
if (!object) {
throw new Error("object undefined");
}
if (!property) {
throw new Error("property undefined");
}
if (!computed && property.type == "Literal") {
throw new Error("literal must be computed property");
}
if (object.name == "new" && property.name == "target") {
throw new Error("new.target is a MetaProperty");
}
return {
type: "MemberExpression",
computed: computed,
object: object,
property: property,
};
}
export function CallExpression(callee: Node, args: Node[]) {
ok(Array.isArray(args), "args should be an array");
return {
type: "CallExpression",
callee: callee,
arguments: args,
};
}
export function NewExpression(callee: Node, args: Node[]) {
return {
type: "NewExpression",
callee,
arguments: args,
};
}
export function AssignmentExpression(
operator: string,
left: Node,
right: Node
) {
return {
type: "AssignmentExpression",
operator: operator,
left: left,
right: right,
};
}
export function ArrayPattern(elements: Node[]) {
ok(Array.isArray(elements));
return {
type: "ArrayPattern",
elements: elements,
};
}
export function ArrayExpression(elements: Node[]) {
ok(Array.isArray(elements));
return {
type: "ArrayExpression",
elements,
};
}
export function AssignmentPattern(left: Node, right: Node) {
ok(left);
ok(right);
return {
type: "AssignmentPattern",
left: left,
right: right,
};
}
export function AddComment(node: Node, text: string) {
if ((node as any).leadingComments) {
(node as any).leadingComments.push({
type: "Block",
value: text,
});
} else {
Object.assign(node, {
leadingComments: [
{
type: "Block",
value: text,
},
],
});
}
return node;
}
export function MethodDefinition(
identifier: Node,
functionExpression: Node,
kind: "method" | "constructor" | "get" | "set",
isStatic = true,
computed = false
) {
return {
type: "MethodDefinition",
key: identifier,
computed: computed,
value: functionExpression,
kind: kind,
static: isStatic,
} as Node;
}
export function ClassDeclaration(
id: Node,
superClass: Node = null,
body: Node[] = []
) {
return {
type: "ClassDeclaration",
id: id,
superClass: superClass,
body: {
type: "ClassBody",
body: body,
},
} as Node;
}
export function ThrowStatement(argument: Node) {
return {
type: "ThrowStatement",
argument: argument,
} as Node;
}
export function WithStatement(object: Node, body: Node[]) {
ok(object, "object");
ok(object.type, "object should be node");
return {
type: "WithStatement",
object,
body: BlockStatement(body),
};
}
/**
* `fn(...args)`
* @param argument
* @returns
*/
export function SpreadElement(argument: Node) {
return {
type: "SpreadElement",
argument,
};
}
/**
* `function fn(...params){}`
* @param argument
* @returns
*/
export function RestElement(argument: Node) {
return {
type: "RestElement",
argument,
};
}
export function CatchClause(param: Node = null, body) {
return {
type: "CatchClause",
param: param,
body: BlockStatement(body),
};
}
export function TryStatement(body: Node[], handler: Node, finallyBody: Node[]) {
ok(handler);
ok(handler.type == "CatchClause");
return {
type: "TryStatement",
block: BlockStatement(body),
handler: handler,
finalizer: finallyBody ? BlockStatement(finallyBody) : null,
};
}