react / wstein / node_modules / react / node_modules / envify / node_modules / jstransform / visitors / es6-class-visitors.js
80540 views/**1* Copyright 2013 Facebook, Inc.2*3* Licensed under the Apache License, Version 2.0 (the "License");4* you may not use this file except in compliance with the License.5* You may obtain a copy of the License at6*7* http://www.apache.org/licenses/LICENSE-2.08*9* Unless required by applicable law or agreed to in writing, software10* distributed under the License is distributed on an "AS IS" BASIS,11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12* See the License for the specific language governing permissions and13* limitations under the License.14*/1516/*jslint node:true*/1718/**19* @typechecks20*/21'use strict';2223var base62 = require('base62');24var Syntax = require('esprima-fb').Syntax;25var utils = require('../src/utils');26var reservedWordsHelper = require('./reserved-words-helper');2728var declareIdentInLocalScope = utils.declareIdentInLocalScope;29var initScopeMetadata = utils.initScopeMetadata;3031var SUPER_PROTO_IDENT_PREFIX = '____SuperProtoOf';3233var _anonClassUUIDCounter = 0;34var _mungedSymbolMaps = {};3536function resetSymbols() {37_anonClassUUIDCounter = 0;38_mungedSymbolMaps = {};39}4041/**42* Used to generate a unique class for use with code-gens for anonymous class43* expressions.44*45* @param {object} state46* @return {string}47*/48function _generateAnonymousClassName(state) {49var mungeNamespace = state.mungeNamespace || '';50return '____Class' + mungeNamespace + base62.encode(_anonClassUUIDCounter++);51}5253/**54* Given an identifier name, munge it using the current state's mungeNamespace.55*56* @param {string} identName57* @param {object} state58* @return {string}59*/60function _getMungedName(identName, state) {61var mungeNamespace = state.mungeNamespace;62var shouldMinify = state.g.opts.minify;6364if (shouldMinify) {65if (!_mungedSymbolMaps[mungeNamespace]) {66_mungedSymbolMaps[mungeNamespace] = {67symbolMap: {},68identUUIDCounter: 069};70}7172var symbolMap = _mungedSymbolMaps[mungeNamespace].symbolMap;73if (!symbolMap[identName]) {74symbolMap[identName] =75base62.encode(_mungedSymbolMaps[mungeNamespace].identUUIDCounter++);76}77identName = symbolMap[identName];78}79return '$' + mungeNamespace + identName;80}8182/**83* Extracts super class information from a class node.84*85* Information includes name of the super class and/or the expression string86* (if extending from an expression)87*88* @param {object} node89* @param {object} state90* @return {object}91*/92function _getSuperClassInfo(node, state) {93var ret = {94name: null,95expression: null96};97if (node.superClass) {98if (node.superClass.type === Syntax.Identifier) {99ret.name = node.superClass.name;100} else {101// Extension from an expression102ret.name = _generateAnonymousClassName(state);103ret.expression = state.g.source.substring(104node.superClass.range[0],105node.superClass.range[1]106);107}108}109return ret;110}111112/**113* Used with .filter() to find the constructor method in a list of114* MethodDefinition nodes.115*116* @param {object} classElement117* @return {boolean}118*/119function _isConstructorMethod(classElement) {120return classElement.type === Syntax.MethodDefinition &&121classElement.key.type === Syntax.Identifier &&122classElement.key.name === 'constructor';123}124125/**126* @param {object} node127* @param {object} state128* @return {boolean}129*/130function _shouldMungeIdentifier(node, state) {131return (132!!state.methodFuncNode &&133!utils.getDocblock(state).hasOwnProperty('preventMunge') &&134/^_(?!_)/.test(node.name)135);136}137138/**139* @param {function} traverse140* @param {object} node141* @param {array} path142* @param {object} state143*/144function visitClassMethod(traverse, node, path, state) {145if (!state.g.opts.es5 && (node.kind === 'get' || node.kind === 'set')) {146throw new Error(147'This transform does not support ' + node.kind + 'ter methods for ES6 ' +148'classes. (line: ' + node.loc.start.line + ', col: ' +149node.loc.start.column + ')'150);151}152state = utils.updateState(state, {153methodNode: node154});155utils.catchup(node.range[0], state);156path.unshift(node);157traverse(node.value, path, state);158path.shift();159return false;160}161visitClassMethod.test = function(node, path, state) {162return node.type === Syntax.MethodDefinition;163};164165/**166* @param {function} traverse167* @param {object} node168* @param {array} path169* @param {object} state170*/171function visitClassFunctionExpression(traverse, node, path, state) {172var methodNode = path[0];173var isGetter = methodNode.kind === 'get';174var isSetter = methodNode.kind === 'set';175176state = utils.updateState(state, {177methodFuncNode: node178});179180if (methodNode.key.name === 'constructor') {181utils.append('function ' + state.className, state);182} else {183var methodAccessorComputed = false;184var methodAccessor;185var prototypeOrStatic = methodNode.static ? '' : '.prototype';186var objectAccessor = state.className + prototypeOrStatic;187188if (methodNode.key.type === Syntax.Identifier) {189// foo() {}190methodAccessor = methodNode.key.name;191if (_shouldMungeIdentifier(methodNode.key, state)) {192methodAccessor = _getMungedName(methodAccessor, state);193}194if (isGetter || isSetter) {195methodAccessor = JSON.stringify(methodAccessor);196} else if (reservedWordsHelper.isReservedWord(methodAccessor)) {197methodAccessorComputed = true;198methodAccessor = JSON.stringify(methodAccessor);199}200} else if (methodNode.key.type === Syntax.Literal) {201// 'foo bar'() {} | get 'foo bar'() {} | set 'foo bar'() {}202methodAccessor = JSON.stringify(methodNode.key.value);203methodAccessorComputed = true;204}205206if (isSetter || isGetter) {207utils.append(208'Object.defineProperty(' +209objectAccessor + ',' +210methodAccessor + ',' +211'{configurable:true,' +212methodNode.kind + ':function',213state214);215} else {216if (state.g.opts.es3) {217if (methodAccessorComputed) {218methodAccessor = '[' + methodAccessor + ']';219} else {220methodAccessor = '.' + methodAccessor;221}222utils.append(223objectAccessor +224methodAccessor + '=function' + (node.generator ? '*' : ''),225state226);227} else {228if (!methodAccessorComputed) {229methodAccessor = JSON.stringify(methodAccessor);230}231utils.append(232'Object.defineProperty(' +233objectAccessor + ',' +234methodAccessor + ',' +235'{writable:true,configurable:true,' +236'value:function' + (node.generator ? '*' : ''),237state238);239}240}241}242utils.move(methodNode.key.range[1], state);243utils.append('(', state);244245var params = node.params;246if (params.length > 0) {247utils.catchupNewlines(params[0].range[0], state);248for (var i = 0; i < params.length; i++) {249utils.catchup(node.params[i].range[0], state);250path.unshift(node);251traverse(params[i], path, state);252path.shift();253}254}255256var closingParenPosition = utils.getNextSyntacticCharOffset(')', state);257utils.catchupWhiteSpace(closingParenPosition, state);258259var openingBracketPosition = utils.getNextSyntacticCharOffset('{', state);260utils.catchup(openingBracketPosition + 1, state);261262if (!state.scopeIsStrict) {263utils.append('"use strict";', state);264state = utils.updateState(state, {265scopeIsStrict: true266});267}268utils.move(node.body.range[0] + '{'.length, state);269270path.unshift(node);271traverse(node.body, path, state);272path.shift();273utils.catchup(node.body.range[1], state);274275if (methodNode.key.name !== 'constructor') {276if (isGetter || isSetter || !state.g.opts.es3) {277utils.append('})', state);278}279utils.append(';', state);280}281return false;282}283visitClassFunctionExpression.test = function(node, path, state) {284return node.type === Syntax.FunctionExpression285&& path[0].type === Syntax.MethodDefinition;286};287288function visitClassMethodParam(traverse, node, path, state) {289var paramName = node.name;290if (_shouldMungeIdentifier(node, state)) {291paramName = _getMungedName(node.name, state);292}293utils.append(paramName, state);294utils.move(node.range[1], state);295}296visitClassMethodParam.test = function(node, path, state) {297if (!path[0] || !path[1]) {298return;299}300301var parentFuncExpr = path[0];302var parentClassMethod = path[1];303304return parentFuncExpr.type === Syntax.FunctionExpression305&& parentClassMethod.type === Syntax.MethodDefinition306&& node.type === Syntax.Identifier;307};308309/**310* @param {function} traverse311* @param {object} node312* @param {array} path313* @param {object} state314*/315function _renderClassBody(traverse, node, path, state) {316var className = state.className;317var superClass = state.superClass;318319// Set up prototype of constructor on same line as `extends` for line-number320// preservation. This relies on function-hoisting if a constructor function is321// defined in the class body.322if (superClass.name) {323// If the super class is an expression, we need to memoize the output of the324// expression into the generated class name variable and use that to refer325// to the super class going forward. Example:326//327// class Foo extends mixin(Bar, Baz) {}328// --transforms to--329// function Foo() {} var ____Class0Blah = mixin(Bar, Baz);330if (superClass.expression !== null) {331utils.append(332'var ' + superClass.name + '=' + superClass.expression + ';',333state334);335}336337var keyName = superClass.name + '____Key';338var keyNameDeclarator = '';339if (!utils.identWithinLexicalScope(keyName, state)) {340keyNameDeclarator = 'var ';341declareIdentInLocalScope(keyName, initScopeMetadata(node), state);342}343utils.append(344'for(' + keyNameDeclarator + keyName + ' in ' + superClass.name + '){' +345'if(' + superClass.name + '.hasOwnProperty(' + keyName + ')){' +346className + '[' + keyName + ']=' +347superClass.name + '[' + keyName + '];' +348'}' +349'}',350state351);352353var superProtoIdentStr = SUPER_PROTO_IDENT_PREFIX + superClass.name;354if (!utils.identWithinLexicalScope(superProtoIdentStr, state)) {355utils.append(356'var ' + superProtoIdentStr + '=' + superClass.name + '===null?' +357'null:' + superClass.name + '.prototype;',358state359);360declareIdentInLocalScope(superProtoIdentStr, initScopeMetadata(node), state);361}362363utils.append(364className + '.prototype=Object.create(' + superProtoIdentStr + ');',365state366);367utils.append(368className + '.prototype.constructor=' + className + ';',369state370);371utils.append(372className + '.__superConstructor__=' + superClass.name + ';',373state374);375}376377// If there's no constructor method specified in the class body, create an378// empty constructor function at the top (same line as the class keyword)379if (!node.body.body.filter(_isConstructorMethod).pop()) {380utils.append('function ' + className + '(){', state);381if (!state.scopeIsStrict) {382utils.append('"use strict";', state);383}384if (superClass.name) {385utils.append(386'if(' + superClass.name + '!==null){' +387superClass.name + '.apply(this,arguments);}',388state389);390}391utils.append('}', state);392}393394utils.move(node.body.range[0] + '{'.length, state);395traverse(node.body, path, state);396utils.catchupWhiteSpace(node.range[1], state);397}398399/**400* @param {function} traverse401* @param {object} node402* @param {array} path403* @param {object} state404*/405function visitClassDeclaration(traverse, node, path, state) {406var className = node.id.name;407var superClass = _getSuperClassInfo(node, state);408409state = utils.updateState(state, {410mungeNamespace: className,411className: className,412superClass: superClass413});414415_renderClassBody(traverse, node, path, state);416417return false;418}419visitClassDeclaration.test = function(node, path, state) {420return node.type === Syntax.ClassDeclaration;421};422423/**424* @param {function} traverse425* @param {object} node426* @param {array} path427* @param {object} state428*/429function visitClassExpression(traverse, node, path, state) {430var className = node.id && node.id.name || _generateAnonymousClassName(state);431var superClass = _getSuperClassInfo(node, state);432433utils.append('(function(){', state);434435state = utils.updateState(state, {436mungeNamespace: className,437className: className,438superClass: superClass439});440441_renderClassBody(traverse, node, path, state);442443utils.append('return ' + className + ';})()', state);444return false;445}446visitClassExpression.test = function(node, path, state) {447return node.type === Syntax.ClassExpression;448};449450/**451* @param {function} traverse452* @param {object} node453* @param {array} path454* @param {object} state455*/456function visitPrivateIdentifier(traverse, node, path, state) {457utils.append(_getMungedName(node.name, state), state);458utils.move(node.range[1], state);459}460visitPrivateIdentifier.test = function(node, path, state) {461if (node.type === Syntax.Identifier && _shouldMungeIdentifier(node, state)) {462// Always munge non-computed properties of MemberExpressions463// (a la preventing access of properties of unowned objects)464if (path[0].type === Syntax.MemberExpression && path[0].object !== node465&& path[0].computed === false) {466return true;467}468469// Always munge identifiers that were declared within the method function470// scope471if (utils.identWithinLexicalScope(node.name, state, state.methodFuncNode)) {472return true;473}474475// Always munge private keys on object literals defined within a method's476// scope.477if (path[0].type === Syntax.Property478&& path[1].type === Syntax.ObjectExpression) {479return true;480}481482// Always munge function parameters483if (path[0].type === Syntax.FunctionExpression484|| path[0].type === Syntax.FunctionDeclaration485|| path[0].type === Syntax.ArrowFunctionExpression) {486for (var i = 0; i < path[0].params.length; i++) {487if (path[0].params[i] === node) {488return true;489}490}491}492}493return false;494};495496/**497* @param {function} traverse498* @param {object} node499* @param {array} path500* @param {object} state501*/502function visitSuperCallExpression(traverse, node, path, state) {503var superClassName = state.superClass.name;504505if (node.callee.type === Syntax.Identifier) {506if (_isConstructorMethod(state.methodNode)) {507utils.append(superClassName + '.call(', state);508} else {509var protoProp = SUPER_PROTO_IDENT_PREFIX + superClassName;510if (state.methodNode.key.type === Syntax.Identifier) {511protoProp += '.' + state.methodNode.key.name;512} else if (state.methodNode.key.type === Syntax.Literal) {513protoProp += '[' + JSON.stringify(state.methodNode.key.value) + ']';514}515utils.append(protoProp + ".call(", state);516}517utils.move(node.callee.range[1], state);518} else if (node.callee.type === Syntax.MemberExpression) {519utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);520utils.move(node.callee.object.range[1], state);521522if (node.callee.computed) {523// ["a" + "b"]524utils.catchup(node.callee.property.range[1] + ']'.length, state);525} else {526// .ab527utils.append('.' + node.callee.property.name, state);528}529530utils.append('.call(', state);531utils.move(node.callee.range[1], state);532}533534utils.append('this', state);535if (node.arguments.length > 0) {536utils.append(',', state);537utils.catchupWhiteSpace(node.arguments[0].range[0], state);538traverse(node.arguments, path, state);539}540541utils.catchupWhiteSpace(node.range[1], state);542utils.append(')', state);543return false;544}545visitSuperCallExpression.test = function(node, path, state) {546if (state.superClass && node.type === Syntax.CallExpression) {547var callee = node.callee;548if (callee.type === Syntax.Identifier && callee.name === 'super'549|| callee.type == Syntax.MemberExpression550&& callee.object.name === 'super') {551return true;552}553}554return false;555};556557/**558* @param {function} traverse559* @param {object} node560* @param {array} path561* @param {object} state562*/563function visitSuperMemberExpression(traverse, node, path, state) {564var superClassName = state.superClass.name;565566utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);567utils.move(node.object.range[1], state);568}569visitSuperMemberExpression.test = function(node, path, state) {570return state.superClass571&& node.type === Syntax.MemberExpression572&& node.object.type === Syntax.Identifier573&& node.object.name === 'super';574};575576exports.resetSymbols = resetSymbols;577578exports.visitorList = [579visitClassDeclaration,580visitClassExpression,581visitClassFunctionExpression,582visitClassMethod,583visitClassMethodParam,584visitPrivateIdentifier,585visitSuperCallExpression,586visitSuperMemberExpression587];588589590