123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
- /*jslint node:true*/
- /**
- * @typechecks
- */
- 'use strict';
- var base62 = require('base62');
- var Syntax = require('esprima-fb').Syntax;
- var utils = require('../src/utils');
- var reservedWordsHelper = require('./reserved-words-helper');
- var declareIdentInLocalScope = utils.declareIdentInLocalScope;
- var initScopeMetadata = utils.initScopeMetadata;
- var SUPER_PROTO_IDENT_PREFIX = '____SuperProtoOf';
- var _anonClassUUIDCounter = 0;
- var _mungedSymbolMaps = {};
- function resetSymbols() {
- _anonClassUUIDCounter = 0;
- _mungedSymbolMaps = {};
- }
- /**
- * Used to generate a unique class for use with code-gens for anonymous class
- * expressions.
- *
- * @param {object} state
- * @return {string}
- */
- function _generateAnonymousClassName(state) {
- var mungeNamespace = state.mungeNamespace || '';
- return '____Class' + mungeNamespace + base62.encode(_anonClassUUIDCounter++);
- }
- /**
- * Given an identifier name, munge it using the current state's mungeNamespace.
- *
- * @param {string} identName
- * @param {object} state
- * @return {string}
- */
- function _getMungedName(identName, state) {
- var mungeNamespace = state.mungeNamespace;
- var shouldMinify = state.g.opts.minify;
- if (shouldMinify) {
- if (!_mungedSymbolMaps[mungeNamespace]) {
- _mungedSymbolMaps[mungeNamespace] = {
- symbolMap: {},
- identUUIDCounter: 0
- };
- }
- var symbolMap = _mungedSymbolMaps[mungeNamespace].symbolMap;
- if (!symbolMap[identName]) {
- symbolMap[identName] =
- base62.encode(_mungedSymbolMaps[mungeNamespace].identUUIDCounter++);
- }
- identName = symbolMap[identName];
- }
- return '$' + mungeNamespace + identName;
- }
- /**
- * Extracts super class information from a class node.
- *
- * Information includes name of the super class and/or the expression string
- * (if extending from an expression)
- *
- * @param {object} node
- * @param {object} state
- * @return {object}
- */
- function _getSuperClassInfo(node, state) {
- var ret = {
- name: null,
- expression: null
- };
- if (node.superClass) {
- if (node.superClass.type === Syntax.Identifier) {
- ret.name = node.superClass.name;
- } else {
- // Extension from an expression
- ret.name = _generateAnonymousClassName(state);
- ret.expression = state.g.source.substring(
- node.superClass.range[0],
- node.superClass.range[1]
- );
- }
- }
- return ret;
- }
- /**
- * Used with .filter() to find the constructor method in a list of
- * MethodDefinition nodes.
- *
- * @param {object} classElement
- * @return {boolean}
- */
- function _isConstructorMethod(classElement) {
- return classElement.type === Syntax.MethodDefinition &&
- classElement.key.type === Syntax.Identifier &&
- classElement.key.name === 'constructor';
- }
- /**
- * @param {object} node
- * @param {object} state
- * @return {boolean}
- */
- function _shouldMungeIdentifier(node, state) {
- return (
- !!state.methodFuncNode &&
- !utils.getDocblock(state).hasOwnProperty('preventMunge') &&
- /^_(?!_)/.test(node.name)
- );
- }
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function visitClassMethod(traverse, node, path, state) {
- if (!state.g.opts.es5 && (node.kind === 'get' || node.kind === 'set')) {
- throw new Error(
- 'This transform does not support ' + node.kind + 'ter methods for ES6 ' +
- 'classes. (line: ' + node.loc.start.line + ', col: ' +
- node.loc.start.column + ')'
- );
- }
- state = utils.updateState(state, {
- methodNode: node
- });
- utils.catchup(node.range[0], state);
- path.unshift(node);
- traverse(node.value, path, state);
- path.shift();
- return false;
- }
- visitClassMethod.test = function(node, path, state) {
- return node.type === Syntax.MethodDefinition;
- };
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function visitClassFunctionExpression(traverse, node, path, state) {
- var methodNode = path[0];
- var isGetter = methodNode.kind === 'get';
- var isSetter = methodNode.kind === 'set';
- state = utils.updateState(state, {
- methodFuncNode: node
- });
- if (methodNode.key.name === 'constructor') {
- utils.append('function ' + state.className, state);
- } else {
- var methodAccessorComputed = methodNode.computed;
- var methodAccessor;
- var prototypeOrStatic = methodNode.static ? '' : '.prototype';
- var objectAccessor = state.className + prototypeOrStatic;
- if (methodNode.key.type === Syntax.Identifier) {
- // foo() {}
- methodAccessor = methodNode.key.name;
- if (_shouldMungeIdentifier(methodNode.key, state)) {
- methodAccessor = _getMungedName(methodAccessor, state);
- }
- if (isGetter || isSetter) {
- methodAccessor = JSON.stringify(methodAccessor);
- } else if (reservedWordsHelper.isReservedWord(methodAccessor)) {
- methodAccessorComputed = true;
- methodAccessor = JSON.stringify(methodAccessor);
- }
- } else if (methodNode.key.type === Syntax.Literal) {
- // 'foo bar'() {} | get 'foo bar'() {} | set 'foo bar'() {}
- methodAccessor = JSON.stringify(methodNode.key.value);
- methodAccessorComputed = true;
- }
- if (isSetter || isGetter) {
- utils.append(
- 'Object.defineProperty(' +
- objectAccessor + ',' +
- methodAccessor + ',' +
- '{configurable:true,' +
- methodNode.kind + ':function',
- state
- );
- } else {
- if (state.g.opts.es3) {
- if (methodAccessorComputed) {
- methodAccessor = '[' + methodAccessor + ']';
- } else {
- methodAccessor = '.' + methodAccessor;
- }
- utils.append(
- objectAccessor +
- methodAccessor + '=function' + (node.generator ? '*' : ''),
- state
- );
- } else {
- if (!methodAccessorComputed) {
- methodAccessor = JSON.stringify(methodAccessor);
- }
- utils.append(
- 'Object.defineProperty(' +
- objectAccessor + ',' +
- methodAccessor + ',' +
- '{writable:true,configurable:true,' +
- 'value:function' + (node.generator ? '*' : ''),
- state
- );
- }
- }
- }
- utils.move(methodNode.key.range[1], state);
- utils.append('(', state);
- var params = node.params;
- if (params.length > 0) {
- utils.catchupNewlines(params[0].range[0], state);
- for (var i = 0; i < params.length; i++) {
- utils.catchup(node.params[i].range[0], state);
- path.unshift(node);
- traverse(params[i], path, state);
- path.shift();
- }
- }
- var closingParenPosition = utils.getNextSyntacticCharOffset(')', state);
- utils.catchupWhiteSpace(closingParenPosition, state);
- var openingBracketPosition = utils.getNextSyntacticCharOffset('{', state);
- utils.catchup(openingBracketPosition + 1, state);
- if (!state.scopeIsStrict) {
- utils.append('"use strict";', state);
- state = utils.updateState(state, {
- scopeIsStrict: true
- });
- }
- utils.move(node.body.range[0] + '{'.length, state);
- path.unshift(node);
- traverse(node.body, path, state);
- path.shift();
- utils.catchup(node.body.range[1], state);
- if (methodNode.key.name !== 'constructor') {
- if (isGetter || isSetter || !state.g.opts.es3) {
- utils.append('})', state);
- }
- utils.append(';', state);
- }
- return false;
- }
- visitClassFunctionExpression.test = function(node, path, state) {
- return node.type === Syntax.FunctionExpression
- && path[0].type === Syntax.MethodDefinition;
- };
- function visitClassMethodParam(traverse, node, path, state) {
- var paramName = node.name;
- if (_shouldMungeIdentifier(node, state)) {
- paramName = _getMungedName(node.name, state);
- }
- utils.append(paramName, state);
- utils.move(node.range[1], state);
- }
- visitClassMethodParam.test = function(node, path, state) {
- if (!path[0] || !path[1]) {
- return;
- }
- var parentFuncExpr = path[0];
- var parentClassMethod = path[1];
- return parentFuncExpr.type === Syntax.FunctionExpression
- && parentClassMethod.type === Syntax.MethodDefinition
- && node.type === Syntax.Identifier;
- };
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function _renderClassBody(traverse, node, path, state) {
- var className = state.className;
- var superClass = state.superClass;
- // Set up prototype of constructor on same line as `extends` for line-number
- // preservation. This relies on function-hoisting if a constructor function is
- // defined in the class body.
- if (superClass.name) {
- // If the super class is an expression, we need to memoize the output of the
- // expression into the generated class name variable and use that to refer
- // to the super class going forward. Example:
- //
- // class Foo extends mixin(Bar, Baz) {}
- // --transforms to--
- // function Foo() {} var ____Class0Blah = mixin(Bar, Baz);
- if (superClass.expression !== null) {
- utils.append(
- 'var ' + superClass.name + '=' + superClass.expression + ';',
- state
- );
- }
- var keyName = superClass.name + '____Key';
- var keyNameDeclarator = '';
- if (!utils.identWithinLexicalScope(keyName, state)) {
- keyNameDeclarator = 'var ';
- declareIdentInLocalScope(keyName, initScopeMetadata(node), state);
- }
- utils.append(
- 'for(' + keyNameDeclarator + keyName + ' in ' + superClass.name + '){' +
- 'if(' + superClass.name + '.hasOwnProperty(' + keyName + ')){' +
- className + '[' + keyName + ']=' +
- superClass.name + '[' + keyName + '];' +
- '}' +
- '}',
- state
- );
- var superProtoIdentStr = SUPER_PROTO_IDENT_PREFIX + superClass.name;
- if (!utils.identWithinLexicalScope(superProtoIdentStr, state)) {
- utils.append(
- 'var ' + superProtoIdentStr + '=' + superClass.name + '===null?' +
- 'null:' + superClass.name + '.prototype;',
- state
- );
- declareIdentInLocalScope(superProtoIdentStr, initScopeMetadata(node), state);
- }
- utils.append(
- className + '.prototype=Object.create(' + superProtoIdentStr + ');',
- state
- );
- utils.append(
- className + '.prototype.constructor=' + className + ';',
- state
- );
- utils.append(
- className + '.__superConstructor__=' + superClass.name + ';',
- state
- );
- }
- // If there's no constructor method specified in the class body, create an
- // empty constructor function at the top (same line as the class keyword)
- if (!node.body.body.filter(_isConstructorMethod).pop()) {
- utils.append('function ' + className + '(){', state);
- if (!state.scopeIsStrict) {
- utils.append('"use strict";', state);
- }
- if (superClass.name) {
- utils.append(
- 'if(' + superClass.name + '!==null){' +
- superClass.name + '.apply(this,arguments);}',
- state
- );
- }
- utils.append('}', state);
- }
- utils.move(node.body.range[0] + '{'.length, state);
- traverse(node.body, path, state);
- utils.catchupWhiteSpace(node.range[1], state);
- }
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function visitClassDeclaration(traverse, node, path, state) {
- var className = node.id.name;
- var superClass = _getSuperClassInfo(node, state);
- state = utils.updateState(state, {
- mungeNamespace: className,
- className: className,
- superClass: superClass
- });
- _renderClassBody(traverse, node, path, state);
- return false;
- }
- visitClassDeclaration.test = function(node, path, state) {
- return node.type === Syntax.ClassDeclaration;
- };
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function visitClassExpression(traverse, node, path, state) {
- var className = node.id && node.id.name || _generateAnonymousClassName(state);
- var superClass = _getSuperClassInfo(node, state);
- utils.append('(function(){', state);
- state = utils.updateState(state, {
- mungeNamespace: className,
- className: className,
- superClass: superClass
- });
- _renderClassBody(traverse, node, path, state);
- utils.append('return ' + className + ';})()', state);
- return false;
- }
- visitClassExpression.test = function(node, path, state) {
- return node.type === Syntax.ClassExpression;
- };
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function visitPrivateIdentifier(traverse, node, path, state) {
- utils.append(_getMungedName(node.name, state), state);
- utils.move(node.range[1], state);
- }
- visitPrivateIdentifier.test = function(node, path, state) {
- if (node.type === Syntax.Identifier && _shouldMungeIdentifier(node, state)) {
- // Always munge non-computed properties of MemberExpressions
- // (a la preventing access of properties of unowned objects)
- if (path[0].type === Syntax.MemberExpression && path[0].object !== node
- && path[0].computed === false) {
- return true;
- }
- // Always munge identifiers that were declared within the method function
- // scope
- if (utils.identWithinLexicalScope(node.name, state, state.methodFuncNode)) {
- return true;
- }
- // Always munge private keys on object literals defined within a method's
- // scope.
- if (path[0].type === Syntax.Property
- && path[1].type === Syntax.ObjectExpression) {
- return true;
- }
- // Always munge function parameters
- if (path[0].type === Syntax.FunctionExpression
- || path[0].type === Syntax.FunctionDeclaration
- || path[0].type === Syntax.ArrowFunctionExpression) {
- for (var i = 0; i < path[0].params.length; i++) {
- if (path[0].params[i] === node) {
- return true;
- }
- }
- }
- }
- return false;
- };
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function visitSuperCallExpression(traverse, node, path, state) {
- var superClassName = state.superClass.name;
- if (node.callee.type === Syntax.Identifier) {
- if (_isConstructorMethod(state.methodNode)) {
- utils.append(superClassName + '.call(', state);
- } else {
- var protoProp = SUPER_PROTO_IDENT_PREFIX + superClassName;
- if (state.methodNode.key.type === Syntax.Identifier) {
- protoProp += '.' + state.methodNode.key.name;
- } else if (state.methodNode.key.type === Syntax.Literal) {
- protoProp += '[' + JSON.stringify(state.methodNode.key.value) + ']';
- }
- utils.append(protoProp + ".call(", state);
- }
- utils.move(node.callee.range[1], state);
- } else if (node.callee.type === Syntax.MemberExpression) {
- utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);
- utils.move(node.callee.object.range[1], state);
- if (node.callee.computed) {
- // ["a" + "b"]
- utils.catchup(node.callee.property.range[1] + ']'.length, state);
- } else {
- // .ab
- utils.append('.' + node.callee.property.name, state);
- }
- utils.append('.call(', state);
- utils.move(node.callee.range[1], state);
- }
- utils.append('this', state);
- if (node.arguments.length > 0) {
- utils.append(',', state);
- utils.catchupWhiteSpace(node.arguments[0].range[0], state);
- traverse(node.arguments, path, state);
- }
- utils.catchupWhiteSpace(node.range[1], state);
- utils.append(')', state);
- return false;
- }
- visitSuperCallExpression.test = function(node, path, state) {
- if (state.superClass && node.type === Syntax.CallExpression) {
- var callee = node.callee;
- if (callee.type === Syntax.Identifier && callee.name === 'super'
- || callee.type == Syntax.MemberExpression
- && callee.object.name === 'super') {
- return true;
- }
- }
- return false;
- };
- /**
- * @param {function} traverse
- * @param {object} node
- * @param {array} path
- * @param {object} state
- */
- function visitSuperMemberExpression(traverse, node, path, state) {
- var superClassName = state.superClass.name;
- utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);
- utils.move(node.object.range[1], state);
- }
- visitSuperMemberExpression.test = function(node, path, state) {
- return state.superClass
- && node.type === Syntax.MemberExpression
- && node.object.type === Syntax.Identifier
- && node.object.name === 'super';
- };
- exports.resetSymbols = resetSymbols;
- exports.visitorList = [
- visitClassDeclaration,
- visitClassExpression,
- visitClassFunctionExpression,
- visitClassMethod,
- visitClassMethodParam,
- visitPrivateIdentifier,
- visitSuperCallExpression,
- visitSuperMemberExpression
- ];
|