UNPKG

distinctiomagnam

Version:
620 lines (557 loc) 12 kB
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, }; }