UNPKG

babel-plugin-transform-private-to-hash

Version:
198 lines (197 loc) 11.2 kB
"use strict"; const helper_plugin_utils_1 = require("@babel/helper-plugin-utils"); const slash = require("slash"); const murmurhash = require("murmurhash"); module.exports = helper_plugin_utils_1.declare((api, options = {}) => { api.assertVersion(7); const t = api.types; const { salt = '', enumerable = false, hashLength = 8 } = options; return { name: 'transform-private-to-hash', pre(file) { this.classCounter = 0; this.classHashStore = new WeakMap(); }, visitor: { Class(classPath, state) { const filePath = state.filename || ''; const cwd = process.cwd(); const relativePath = slash(filePath.startsWith(cwd) ? filePath.slice(cwd.length) : filePath); const classIndex = state.classCounter++; const privateHashes = new Map(); // 处理不可枚举的情况 const staticFields = new Map(); const instanceFields = new Map(); // 收集所有私有属性和方法的哈希 classPath.node.body.body.forEach(prop => { if (t.isClassPrivateProperty(prop) || t.isClassPrivateMethod(prop)) { const privateName = prop.key.id.name; const hashInput = `${salt}_${relativePath}_${classIndex}_${privateName}`; const fullHash = murmurhash.v3(hashInput).toString(16); const hash = fullHash.slice(0, hashLength); privateHashes.set(privateName, `__${hash}`); } }); const bodyBodyPaths = classPath.get('body.body'); bodyBodyPaths.forEach(propPath => { let prop = propPath.node; if (t.isClassPrivateProperty(prop) || t.isClassPrivateMethod(prop)) { const privateName = prop.key.id.name; const hashInput = `${salt}_${relativePath}_${classIndex}_${privateName}`; const fullHash = murmurhash.v3(hashInput).toString(16); const hash = fullHash.slice(0, hashLength); privateHashes.set(privateName, `__${hash}`); if (t.isClassPrivateProperty(prop)) { if (prop.static) { staticFields.set(prop.key.id.name, prop.value); } else { instanceFields.set(prop.key.id.name, prop.value); } propPath.remove(); } } }); state.classHashStore.set(classPath.node, privateHashes); // 处理实例字段 if (instanceFields.size > 0) { let constructorPath = bodyBodyPaths.find(p => p.isClassMethod() && p.node.kind === 'constructor'); if (!constructorPath) { let constructorNode = t.classMethod('constructor', t.identifier('constructor'), [], t.blockStatement(classPath.node.superClass ? [t.expressionStatement(t.callExpression(t.super(), [t.spreadElement(t.identifier('arguments'))]))] : [])); const bodyPath = classPath.get('body'); constructorPath = bodyPath.unshiftContainer('body', constructorNode)[0]; } let statements = []; instanceFields.forEach((express, name) => { let statement = t.expressionStatement(enumerable ? t.assignmentExpression("=", t.memberExpression(t.thisExpression(), t.identifier(privateHashes.get(name))), express) : t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('defineProperty')), [ t.thisExpression(), t.stringLiteral(privateHashes.get(name)), t.objectExpression([ t.objectProperty(t.identifier('value'), express), t.objectProperty(t.identifier('enumerable'), t.booleanLiteral(false)), t.objectProperty(t.identifier('configurable'), t.booleanLiteral(true)), t.objectProperty(t.identifier('writable'), t.booleanLiteral(true)), ]) ])); statements.push(statement); }); let superPath = constructorPath.get('body.body').find(p => { if (p.isExpressionStatement()) { let callee = p.get('expression.callee'); if (callee && callee.isSuper()) { return true; } } return false; }); if (superPath) { superPath.insertAfter(statements); } else { constructorPath.get('body').unshiftContainer('body', statements); } } // 处理静态字段 if (staticFields.size > 0) { const staticBlockBody = []; staticFields.forEach((express, name) => { if (enumerable) { staticBlockBody.push(t.expressionStatement(t.assignmentExpression("=", t.memberExpression(t.thisExpression(), t.identifier(privateHashes.get(name))), express))); } else { staticBlockBody.push(t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('defineProperty')), [ t.thisExpression(), t.stringLiteral(privateHashes.get(name)), t.objectExpression([ t.objectProperty(t.identifier('value'), express), t.objectProperty(t.identifier('enumerable'), t.booleanLiteral(false)), t.objectProperty(t.identifier('configurable'), t.booleanLiteral(true)), t.objectProperty(t.identifier('writable'), t.booleanLiteral(true)), ]) ]))); } }); classPath.get('body').pushContainer('body', t.staticBlock(staticBlockBody)); } classPath.traverse({ Class(classPath) { classPath.skip(); }, ClassPrivateProperty(propertyPath) { const classPath = propertyPath.findParent(p => p.isClass()); const privateNode = propertyPath.node; const hashes = state.classHashStore.get(classPath.node); if (hashes) { const privateName = privateNode.key.id.name; const hash = hashes.get(privateName); if (hash) { if (enumerable) { propertyPath.replaceWith(t.classProperty(t.identifier(hash), privateNode.value, null, null, false, privateNode.static)); } else { propertyPath.remove(); } } } }, ClassPrivateMethod(methodPath) { const classPath = methodPath.findParent(p => p.isClass()); const privateNode = methodPath.node; const hashes = state.classHashStore.get(classPath.node); if (hashes) { const privateName = privateNode.key.id.name; const hash = hashes.get(privateName); if (hash) { methodPath.replaceWith(t.classMethod(privateNode.kind, t.identifier(hash), privateNode.params, privateNode.body, privateNode.computed, privateNode.static, privateNode.generator, privateNode.async)); } } }, MemberExpression(memberExpPath) { const propertyPath = memberExpPath.get('property'); const propertyNode = propertyPath.node; if (!t.isPrivateName(propertyNode)) return; memberExpPath.findParent(parentPath => { if (parentPath.isClass) { const hashes = state.classHashStore.get(parentPath.node); if (hashes) { const privateName = propertyNode.id.name; const hash = hashes.get(privateName); if (hash) { propertyPath.replaceWith(t.identifier(hash)); return true; } } } return false; }); }, BinaryExpression(binaryPath) { const leftPath = binaryPath.get('left'); const leftNode = leftPath.node; if (!t.isPrivateName(leftNode)) return; binaryPath.findParent(parentPath => { if (parentPath.isClass()) { const hashes = state.classHashStore.get(parentPath.node); if (hashes) { const privateName = leftNode.id.name; const hash = hashes.get(privateName); if (hash) { leftPath.replaceWith(t.stringLiteral(hash)); return true; } } } return false; }); } }); }, } }; });