您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Compiles a string containing Javascript to an ESTree object and/or executes an ESTree object in Javascript
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/506614/1441157/ESTreeProcessor.js
/* eslint-disable no-bitwise */ // @name ESTreeProcessor // @namespace https://github.com/mapomatic // @version 1.0.0 // @description Converts AST (ESTree format) into JS // @author MapOMatic // @license GNU GPLv3 // ==/UserScript== /* test * The esprima-next library is required to use this. * The older esprima library might also work but it lacks some functions. */ // TODO: Not working: // - Classes // - Probably a lot more I haven't discovered yet. /* eslint-disable eqeqeq */ /* eslint-disable max-classes-per-file */ /* eslint-disable no-multi-assign */ /* global esprima */ // eslint-disable-next-line func-names, no-unused-vars const ESTreeProcessor = (function() { 'use strict'; const ScopeType = { Global: 'Global', Function: 'Function', Block: 'Block' }; const VariableType = { Let: 'Let', Const: 'Const', Var: 'Var' }; class Scope { /** @type {Scope} */ parent; variables = {}; /** @type {string} */ type; constructor(type, parent) { this.type = type; this.parent = parent; this.variables = {}; } declare(type, name, value = undefined) { this.checkAlreadyDeclared(name, type); switch (type) { case VariableType.Let: this.variables[name] = { type, value }; break; case VariableType.Const: this.variables[name] = { type, value }; break; case VariableType.Var: { let scope = this; while (scope) { if (scope.type === ScopeType.Function || scope.type === ScopeType.Global) { scope.variables[name] = { type, value }; scope = undefined; } else { scope = scope.parent; } } break; } default: throw new SyntaxError(`Unexpected variable declaration type: ${type}`); } } checkAlreadyDeclared(name, type) { let scope = this; while (scope) { if (scope.variables.hasOwnProperty(name)) { if (type === VariableType.Const || type === VariableType.Let || scope.variables[name].type !== VariableType.Var) { throw new SyntaxError(`Identifier '${name}' has already been declared`); } } if (scope.type === ScopeType.Function || scope.type === ScopeType.Global) { scope = undefined; } else { scope = scope.parent; } } } setValue(name, value) { const reference = this.getReference(name, true); if (!reference) { throw new ReferenceError(`${name} is not defined.`); } if (reference.type === VariableType.Const) { throw new TypeError(`Assignment to constant variable: ${name}`); } return (reference.value = value); } getValue(name) { const reference = this.getReference(name); return reference?.value; } getReference(name) { let scope = this; while (scope) { if (scope.variables.hasOwnProperty(name)) { return scope.variables[name]; } if (scope.type === ScopeType.Global) { scope = undefined; } else { scope = scope.parent; } } return undefined; } createChildScope(type) { return new Scope(type, this); } } class FlowStopper {} class Break extends FlowStopper {} class Continue extends FlowStopper {} class Return extends FlowStopper { value; constructor(value) { super(); this.value = value; } } class Debugger {} class ChainIsNullish {} // eslint-disable-next-line no-shadow class ESTreeProcessor { process(esTree, globalVariables) { this.globalVariables = { ...globalVariables }; return { output: this.processNode(esTree, new Scope(ScopeType.Global)), variables: this.globalVariables }; } processNode(node, scope) { // if (!scope) debugger; // if (!node) debugger; let returnValue; switch (node.type) { case esprima.Syntax.Program: returnValue = this.processProgram(node, scope); break; case esprima.Syntax.ExpressionStatement: returnValue = this.processNode(node.expression, scope); break; case esprima.Syntax.Literal: returnValue = ESTreeProcessor.processLiteral(node, scope); break; case esprima.Syntax.MemberExpression: returnValue = this.processMemberExpression(node, scope); break; case esprima.Syntax.BinaryExpression: returnValue = this.processBinaryExpression(node, scope); break; case esprima.Syntax.VariableDeclaration: returnValue = this.processVariableDeclaration(node, scope); break; case esprima.Syntax.VariableDeclarator: returnValue = this.processVariableDeclarator(node, scope); break; case esprima.Syntax.CallExpression: returnValue = this.processCallExpression(node, scope); break; case esprima.Syntax.AssignmentExpression: returnValue = this.processAssigmentExpression(node, scope); break; case esprima.Syntax.Identifier: returnValue = ESTreeProcessor.processIdentifier(node, scope); break; case esprima.Syntax.IfStatement: returnValue = this.processIfStatement(node, scope); break; case esprima.Syntax.BlockStatement: returnValue = this.processBlockStatement(node, scope); break; case esprima.Syntax.EmptyStatement: // do nothing; break; case esprima.Syntax.TemplateLiteral: returnValue = this.processTemplateLiteral(node, scope); break; case esprima.Syntax.NewExpression: returnValue = this.processNewExpression(node, scope); break; case esprima.Syntax.FunctionDeclaration: returnValue = this.processFunctionDeclaration(node, scope); break; case esprima.Syntax.ReturnStatement: returnValue = this.processReturnStatement(node, scope); break; case esprima.Syntax.LogicalExpression: returnValue = this.processLogicalExpression(node, scope); break; case esprima.Syntax.UnaryExpression: returnValue = this.processUnaryExpression(node, scope); break; case esprima.Syntax.ArrayExpression: returnValue = this.processArrayExpression(node, scope); break; case esprima.Syntax.ConditionalExpression: returnValue = this.processConditionalExpression(node, scope); break; case esprima.Syntax.ObjectExpression: returnValue = this.processObjectExpression(node, scope); break; case esprima.Syntax.FunctionExpression: returnValue = this.processFunctionExpression(node, scope); break; case esprima.Syntax.ArrowFunctionExpression: returnValue = this.processArrowFunctionExpression(node, scope); break; case esprima.Syntax.ChainExpression: returnValue = this.processChainExpression(node, scope); break; case esprima.Syntax.UpdateExpression: returnValue = this.processUpdateExpression(node, scope); break; case esprima.Syntax.ForStatement: returnValue = this.processForStatement(node, scope); break; case esprima.Syntax.TryStatement: returnValue = this.processTryStatement(node, scope); break; case esprima.Syntax.BreakStatement: returnValue = ESTreeProcessor.processBreakStatement(); break; case esprima.Syntax.ContinueStatement: returnValue = ESTreeProcessor.processContinueStatement(); break; case esprima.Syntax.DebuggerStatement: returnValue = ESTreeProcessor.processDebuggerStatement(); break; case esprima.Syntax.SwitchStatement: returnValue = this.processSwitchStatement(node, scope); break; default: throw new SyntaxError(`Unexpected node type: ${node.type}`); } return returnValue; } processSwitchStatement(node, scope) { const discriminant = this.processNode(node.discriminant, scope); let matchIndex = -1; let defaultIndex = -1; for (let i = 0; i < node.cases.length && matchIndex === -1; i++) { const switchCase = node.cases[i]; if (!switchCase.test) { defaultIndex = i; } else if (this.processNode(switchCase.test, scope) === discriminant) { matchIndex = i; } } if (matchIndex === -1) matchIndex = defaultIndex; let returnValue; if (matchIndex > -1) { const switchScope = scope.createChildScope(ScopeType.Block); for (let i = matchIndex; i < node.cases.length && !(returnValue instanceof FlowStopper); i++) { let result; const switchCase = node.cases[i]; for (let j = 0; j < switchCase.consequent.length && !(result instanceof FlowStopper); j++) { result = this.processNode(switchCase.consequent[j], switchScope); if (!(result instanceof Break || result instanceof Continue)) { returnValue = result; } } } } return returnValue; } static processDebuggerStatement() { // eslint-disable-next-line no-debugger debugger; return new Debugger(); } processTryStatement(node, scope) { let returnValue; try { returnValue = this.processNode(node.block, scope); } catch (e) { // Based on research, I think a Function scope is appropriate here but not certain. const catchScope = scope.createChildScope(ScopeType.Function); node.handler.param.returnName = true; catchScope.declare(VariableType.Var, this.processNode(node.handler.param, catchScope), e); this.processNode(node.handler.body, catchScope); } finally { if (node.finalizer) { this.processNode(node.finalizer, scope); } } return returnValue; } processObjectExpression(node, scope) { const returnValue = {}; node.properties.forEach(property => { property.key.returnName = !node.computed; const key = this.processNode(property.key, scope); returnValue[key] = this.processNode(property.value, scope); }); return returnValue; } processConditionalExpression(node, scope) { const returnValue = this.processNode(node.test, scope) ? this.processNode(node.consequent, scope) : this.processNode(node.alternate, scope); return returnValue; } processArrayExpression(node, scope) { const returnValue = node.elements.map(element => this.processNode(element, scope)); return returnValue; } setScopeValue(targetNode, value, scope) { switch (targetNode.type) { case esprima.Syntax.MemberExpression: { const object = this.processNode(targetNode.object, scope); targetNode.property.returnName = true; const propertyName = this.processNode(targetNode.property, scope); object[propertyName] = value; break; } case esprima.Syntax.Identifier: scope.setValue(targetNode.name, value); break; default: throw new SyntaxError(`Unexpected target node type in setValue(): ${targetNode.type}`); } return value; } processUpdateExpression(node, scope) { const oldValue = this.processNode(node.argument, scope); let newValue; switch (node.operator) { case '++': newValue = this.setScopeValue(node.argument, oldValue + 1, scope); break; case '--': newValue = this.setScopeValue(node.argument, oldValue - 1, scope); break; default: throw new SyntaxError(`Unexpected update expression operator: ${node.operator}`); } return node.prefix ? newValue : oldValue; } processForStatement(node, scope) { let returnValue; this.processNode(node.init, scope); while (this.processNode(node.test, scope)) { returnValue = this.processNode(node.body, scope); if (returnValue instanceof FlowStopper) { if (returnValue instanceof Return) { returnValue = returnValue.value; } break; } this.processNode(node.update, scope); } return returnValue; } processUnaryExpression(node, scope) { let returnValue; switch (node.operator) { case '!': returnValue = !this.processNode(node.argument, scope); break; case '~': returnValue = ~this.processNode(node.argument, scope); break; case '-': returnValue = -this.processNode(node.argument, scope); break; case 'typeof': returnValue = typeof this.processNode(node.argument, scope); break; default: throw new SyntaxError(`Unexpected unary expression operator: ${node.operator}`); } return returnValue; } processLogicalExpression(node, scope) { let returnValue; switch (node.operator) { case '&&': returnValue = this.processNode(node.left, scope) && this.processNode(node.right, scope); break; case '||': returnValue = this.processNode(node.left, scope) || this.processNode(node.right, scope); break; case '??': returnValue = this.processNode(node.left, scope) ?? this.processNode(node.right, scope); break; default: throw new SyntaxError(`Unexpected logical expression operator: ${node.operator}`); } return returnValue; } processReturnStatement(node, scope) { const returnValue = this.processNode(node.argument, scope); return new Return(returnValue); } static processContinueStatement() { return new Continue(); } static processBreakStatement() { return new Break(); } /** * * @param {*} node * @param {Scope} scope * @returns */ processArrowFunctionExpression(node, scope) { const func = (...args) => { const esTreeArgs = args.map(arg => { const argNode = { type: esprima.Syntax.Literal, value: arg }; return argNode; }); const funcScope = scope.createChildScope(ScopeType.Function); for (let i = 0; i < Math.min(esTreeArgs.length, node.params.length); i++) { const param = node.params[i]; param.returnName = true; funcScope.declare( VariableType.Var, this.processNode(param, funcScope), this.processNode(esTreeArgs[i], funcScope) ); } let returnValue = this.processNode(node.body, funcScope); if (returnValue instanceof Return) { returnValue = returnValue.value; } return returnValue; }; return func; } processChainExpression(node, scope) { let returnValue; switch (node.expression.type) { case esprima.Syntax.CallExpression: case esprima.Syntax.MemberExpression: { returnValue = this.processNode(node.expression, scope); break; } default: throw new SyntaxError(`Unexpected chain expression type: ${node.expression.type}`); } if (returnValue instanceof ChainIsNullish) returnValue = undefined; return returnValue; } processFunctionDeclaration(node, scope) { const func = (...args) => { const esTreeArgs = args.map(arg => { const argNode = { type: esprima.Syntax.Literal, value: arg }; return argNode; }); const funcScope = scope.createChildScope(ScopeType.Function); for (let i = 0; i < Math.min(esTreeArgs.length, node.params.length); i++) { const param = node.params[i]; param.returnName = true; funcScope.declare( VariableType.Var, this.processNode(param, funcScope), this.processNode(esTreeArgs[i], funcScope) ); } let returnValue = this.processNode(node.body, funcScope); if (returnValue instanceof Return) { returnValue = returnValue.value; } return returnValue; }; if (node.id) { node.id.returnName = true; const funcName = this.processNode(node.id, scope); scope.declare(VariableType.Var, funcName, func); } return func; } processFunctionExpression(node, scope) { const func = (...args) => { const esTreeArgs = args.map(arg => { const argNode = { type: esprima.Syntax.Literal, value: arg }; return argNode; }); const funcScope = scope.createChildScope(ScopeType.Function); for (let i = 0; i < Math.min(esTreeArgs.length, node.params.length); i++) { const param = node.params[i]; param.returnName = true; funcScope.declare( VariableType.Var, this.processNode(param, funcScope), this.processNode(esTreeArgs[i], funcScope) ); } let returnValue = this.processNode(node.body, funcScope); if (returnValue instanceof Return) { returnValue = returnValue.value; } return returnValue; }; return func; } processProgram(node, scope) { let returnValue; // Add global variables Object.keys(this.globalVariables).forEach(key => { scope.declare(VariableType.Let, key, this.globalVariables[key]); }); node.body.forEach(childNode => { returnValue = this.processNode(childNode, scope); }); // this._scopeContainer.removeCurrentScope(); return returnValue; } processNewExpression(node, scope) { const args = node.arguments.map(argument => this.processNode(argument, scope)); return Reflect.construct(this.processNode(node.callee, scope), args); } processTemplateLiteral(node, scope) { let returnValue = ''; for (let i = 0; i < node.quasis.length; i++) { returnValue += node.quasis[i].value.cooked; if (!node.quasis[i].tail) { returnValue += this.processNode(node.expressions[i], scope); } } return returnValue; } processBlockStatement(node, scope) { let returnValue; // TODO: I don't think we need to ignore the block scope in a function declaration. Test it. // switch (context.type) { // case esprima.Syntax.FunctionDeclaration: // break; // default: const blockScope = scope.createChildScope(ScopeType.Block); // } for (let i = 0; i < node.body.length; i++) { const childNode = node.body[i]; const result = this.processNode(childNode, blockScope); if (result instanceof FlowStopper) { if (result instanceof Return) { returnValue = result; } break; } else if (result instanceof Debugger) { // do nothing } else { returnValue = result; } } return returnValue; } processIfStatement(node, scope) { let returnValue; const test = this.processNode(node.test, scope); if (test) { returnValue = this.processNode(node.consequent, scope); } else if (node.alternate) { returnValue = this.processNode(node.alternate, scope); } return returnValue; } static processLiteral(node) { return node.value; } static processIdentifier(node, scope) { let returnValue; if (node.returnName) { returnValue = node.name; } else { returnValue = scope.getValue(node.name); } return returnValue; } /** * * @param {*} expression * @param {Scope} scope * @returns */ getMemberExpressionObject(expression, scope) { let returnValue; switch (expression.object.type) { case esprima.Syntax.Identifier: { returnValue = scope.getValue(expression.object.name); break; } case esprima.Syntax.CallExpression: case esprima.Syntax.MemberExpression: { const object = this.processNode(expression.object, scope); returnValue = object; break; } default: throw new SyntaxError(`Unexpected member expression object type: ${expression.object.type}`); } return returnValue; } processMemberExpression(node, scope) { let returnValue; const object = this.getMemberExpressionObject(node, scope); if (object == null && node.optional) { returnValue = new ChainIsNullish(); } else if (object != null || (object == null && !node.optional)) { node.property.returnName = !node.computed; const propertyName = this.processNode(node.property, scope); // if (object[propertyName] == null) { // console.warn(`Object does not contain this property: ${propertyName}`); // } returnValue = object[propertyName]; } return returnValue; } doBinaryExpression(node, func, scope) { return func(this.processNode(node.left, scope), this.processNode(node.right, scope)); } static add(left, right) { return left + right; } static subtract(left, right) { return left - right; } static multiply(left, right) { return left * right; } static divide(left, right) { return left / right; } static mod(left, right) { return left % right; } static power(left, right) { return left ** right; } processBinaryExpression(node, scope) { let returnValue; const process = operation => this.doBinaryExpression(node, operation, scope); switch (node.operator) { case '+': returnValue = process(ESTreeProcessor.add); break; case '-': returnValue = process(ESTreeProcessor.subtract); break; case '*': returnValue = process(ESTreeProcessor.multiply); break; case '/': returnValue = process(ESTreeProcessor.divide); break; case '%': returnValue = process(ESTreeProcessor.mod); break; case '**': returnValue = process(ESTreeProcessor.power); break; case '>': returnValue = process((left, right) => left > right); break; case '<': returnValue = process((left, right) => left < right); break; case '==': returnValue = process((left, right) => left == right); break; case '===': returnValue = process((left, right) => left === right); break; case '>=': returnValue = process((left, right) => left >= right); break; case '<=': returnValue = process((left, right) => left <= right); break; case '!=': returnValue = process((left, right) => left != right); break; case '!==': returnValue = process((left, right) => left !== right); break; case '^': returnValue = process(ESTreeProcessor.bitwiseXOR); break; case '|': returnValue = process(ESTreeProcessor.bitwiseOR); break; case '&': returnValue = process(ESTreeProcessor.bitwiseAND); break; case '>>': returnValue = process(ESTreeProcessor.bitwiseRightShift); break; case '<<': returnValue = process(ESTreeProcessor.bitwiseLeftShift); break; case '>>>': returnValue = process(ESTreeProcessor.bitwiseUnsignedRightShift); break; default: throw new SyntaxError(`Unexpected binary expression operator: ${node.operator}`); } return returnValue; } processVariableDeclaration(node, scope) { switch (node.kind) { case 'let': case 'const': case 'var': node.declarations.forEach(declaration => { declaration.kind = node.kind; this.processNode(declaration, scope); }); break; default: throw new SyntaxError(`Unexpected variable declaration kind: ${node.type}`); } // I believe declarations always return undefined } /** * * @param {*} node * @param {Scope} scope */ processVariableDeclarator(node, scope) { switch (node.id.type) { case esprima.Syntax.Identifier: { node.id.returnName = true; const name = this.processNode(node.id, scope); const value = node.init ? this.processNode(node.init, scope) : undefined; let declarationType; switch (node.kind) { case 'let': declarationType = VariableType.Let; break; case 'const': declarationType = VariableType.Const; break; case 'var': declarationType = VariableType.Var; break; default: throw new TypeError(`Unexpected variable declaration kind: ${node.kind}`); } scope.declare(declarationType, name, value); break; } case esprima.Syntax.ObjectPattern: { const object = this.processNode(node.init, scope); node.id.properties.forEach(property => { property.key.returnName = true; const name = this.processNode(property.key, scope); property.value.returnName = true; const valueName = this.processNode(property.value, scope); const value = object[valueName]; let declarationType; switch (node.kind) { case 'let': declarationType = VariableType.Let; break; case 'const': declarationType = VariableType.Const; break; case 'var': declarationType = VariableType.Var; break; default: throw new TypeError(`Unexpected variable declaration kind: ${node.kind}`); } scope.declare(declarationType, name, value); }); break; } default: throw new SyntaxError(`Unexpected variable declarator id type: ${node.id.type}`); } } /** * * @param {*} node * @param {Scope} scope * @returns */ processCallExpression(node, scope) { // TODO: This code doesn't pass the sniff test. Need to work on it. const { callee } = node; let methodToCall; let calleeObject; let returnValue; switch (callee.type) { case esprima.Syntax.MemberExpression: { calleeObject = this.processNode(callee.object, scope); if (calleeObject == null) { if (callee.optional) { returnValue = new ChainIsNullish(); } else { throw new SyntaxError('Callee object not found in call expression.'); } } else if (calleeObject instanceof ChainIsNullish) { returnValue = new ChainIsNullish(); } else { methodToCall = calleeObject[callee.property.name]; } break; } case esprima.Syntax.Identifier: methodToCall = scope.getValue(callee.name); break; default: throw new SyntaxError(`Unexpected callee type in call expression: ${callee.type}`); } if (!(returnValue instanceof ChainIsNullish)) { if (methodToCall == null) { if (node.optional) { returnValue = new ChainIsNullish(); } else { let methodName; switch (callee.type) { case esprima.Syntax.Identifier: methodName = callee.name; break; case esprima.Syntax.MemberExpression: callee.property.returnName = true; methodName = this.processNode(callee.property); break; default: } throw new SyntaxError(`Method not found: ${methodName}`); } } else { const args = node.arguments.map(arg => this.processNode(arg, scope)); // if (!methodToCall) debugger; returnValue = methodToCall.call(calleeObject, ...args); } } return returnValue; } doAssignmentExpression(node, func, scope) { let returnValue; let leftValue; switch (node.left.type) { case esprima.Syntax.Identifier: { leftValue = scope.getValue(node.left.name); returnValue = this.setScopeValue(node.left, func(leftValue, this.processNode(node.right, scope)), scope); break; } case esprima.Syntax.MemberExpression: { const object = this.getMemberExpressionObject(node.left, scope); const propertyName = this.processNode(node.left.property, scope); leftValue = object[propertyName]; returnValue = (object[propertyName] = func(leftValue, this.processNode(node.right, scope))); break; } default: throw new SyntaxError(`Unexpected type on left side of assignment expression: ${node.left.type}`); } return returnValue; } static nullishCoalesce(left, right) { return left ?? right; } static or(left, right) { return left || right; } static and(left, right) { return left && right; } static bitwiseAND(left, right) { return left & right; } static bitwiseOR(left, right) { return left | right; } static bitwiseXOR(left, right) { return left ^ right; } static bitwiseLeftShift(left, right) { return left << right; } static bitwiseRightShift(left, right) { return left >> right; } static bitwiseUnsignedRightShift(left, right) { return left >>> right; } processAssigmentExpression(node, scope) { let returnValue; const assign = operation => this.doAssignmentExpression(node, operation, scope); switch (node.operator) { case '=': returnValue = this.setScopeValue(node.left, this.processNode(node.right, scope), scope); break; case '+=': returnValue = assign(ESTreeProcessor.add); break; case '-=': returnValue = assign(ESTreeProcessor.subtract); break; case '*=': returnValue = assign(ESTreeProcessor.multiply); break; case '/=': returnValue = assign(ESTreeProcessor.divide); break; case '%=': returnValue = assign(ESTreeProcessor.mod); break; case '**=': returnValue = assign(ESTreeProcessor.power); break; case '??=': returnValue = assign(ESTreeProcessor.nullishCoalesce); break; case '||=': returnValue = assign(ESTreeProcessor.or); break; case '&&=': returnValue = assign(ESTreeProcessor.and); break; case '<<=': returnValue = assign(ESTreeProcessor.bitwiseLeftShift); break; case '>>=': returnValue = assign(ESTreeProcessor.bitwiseRightShift); break; case '>>>=': returnValue = assign(ESTreeProcessor.bitwiseUnsignedRightShift); break; case '&=': returnValue = assign(ESTreeProcessor.bitwiseAND); break; case '|=': returnValue = assign(ESTreeProcessor.bitwiseOR); break; case '^=': returnValue = assign(ESTreeProcessor.bitwiseXOR); break; default: throw new SyntaxError(`Unexpected assigment expression operator: ${node.operator}`); } return returnValue; } } return ESTreeProcessor; })();