react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / jstransform / visitors / es6-class-visitors.js
81147 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 methodAccessor;184var prototypeOrStatic = methodNode.static ? '' : '.prototype';185var objectAccessor = state.className + prototypeOrStatic;186187if (methodNode.key.type === Syntax.Identifier) {188// foo() {}189methodAccessor = methodNode.key.name;190if (_shouldMungeIdentifier(methodNode.key, state)) {191methodAccessor = _getMungedName(methodAccessor, state);192}193if (isGetter || isSetter) {194methodAccessor = JSON.stringify(methodAccessor);195} else if (reservedWordsHelper.isReservedWord(methodAccessor)) {196methodAccessor = '[' + JSON.stringify(methodAccessor) + ']';197} else {198methodAccessor = '.' + 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);203if (!(isGetter || isSetter)) {204methodAccessor = '[' + methodAccessor + ']';205}206}207208if (isSetter || isGetter) {209utils.append(210'Object.defineProperty(' +211objectAccessor + ',' +212methodAccessor + ',' +213'{enumerable:true,configurable:true,' +214methodNode.kind + ':function',215state216);217} else {218utils.append(219objectAccessor +220methodAccessor + '=function' + (node.generator ? '*' : ''),221state222);223}224}225utils.move(methodNode.key.range[1], state);226utils.append('(', state);227228var params = node.params;229if (params.length > 0) {230utils.catchupNewlines(params[0].range[0], state);231for (var i = 0; i < params.length; i++) {232utils.catchup(node.params[i].range[0], state);233path.unshift(node);234traverse(params[i], path, state);235path.shift();236}237}238utils.append(')', state);239utils.catchupWhiteSpace(node.body.range[0], state);240utils.append('{', state);241if (!state.scopeIsStrict) {242utils.append('"use strict";', state);243state = utils.updateState(state, {244scopeIsStrict: true245});246}247utils.move(node.body.range[0] + '{'.length, state);248249path.unshift(node);250traverse(node.body, path, state);251path.shift();252utils.catchup(node.body.range[1], state);253254if (methodNode.key.name !== 'constructor') {255if (isGetter || isSetter) {256utils.append('})', state);257}258utils.append(';', state);259}260return false;261}262visitClassFunctionExpression.test = function(node, path, state) {263return node.type === Syntax.FunctionExpression264&& path[0].type === Syntax.MethodDefinition;265};266267function visitClassMethodParam(traverse, node, path, state) {268var paramName = node.name;269if (_shouldMungeIdentifier(node, state)) {270paramName = _getMungedName(node.name, state);271}272utils.append(paramName, state);273utils.move(node.range[1], state);274}275visitClassMethodParam.test = function(node, path, state) {276if (!path[0] || !path[1]) {277return;278}279280var parentFuncExpr = path[0];281var parentClassMethod = path[1];282283return parentFuncExpr.type === Syntax.FunctionExpression284&& parentClassMethod.type === Syntax.MethodDefinition285&& node.type === Syntax.Identifier;286};287288/**289* @param {function} traverse290* @param {object} node291* @param {array} path292* @param {object} state293*/294function _renderClassBody(traverse, node, path, state) {295var className = state.className;296var superClass = state.superClass;297298// Set up prototype of constructor on same line as `extends` for line-number299// preservation. This relies on function-hoisting if a constructor function is300// defined in the class body.301if (superClass.name) {302// If the super class is an expression, we need to memoize the output of the303// expression into the generated class name variable and use that to refer304// to the super class going forward. Example:305//306// class Foo extends mixin(Bar, Baz) {}307// --transforms to--308// function Foo() {} var ____Class0Blah = mixin(Bar, Baz);309if (superClass.expression !== null) {310utils.append(311'var ' + superClass.name + '=' + superClass.expression + ';',312state313);314}315316var keyName = superClass.name + '____Key';317var keyNameDeclarator = '';318if (!utils.identWithinLexicalScope(keyName, state)) {319keyNameDeclarator = 'var ';320declareIdentInLocalScope(keyName, initScopeMetadata(node), state);321}322utils.append(323'for(' + keyNameDeclarator + keyName + ' in ' + superClass.name + '){' +324'if(' + superClass.name + '.hasOwnProperty(' + keyName + ')){' +325className + '[' + keyName + ']=' +326superClass.name + '[' + keyName + '];' +327'}' +328'}',329state330);331332var superProtoIdentStr = SUPER_PROTO_IDENT_PREFIX + superClass.name;333if (!utils.identWithinLexicalScope(superProtoIdentStr, state)) {334utils.append(335'var ' + superProtoIdentStr + '=' + superClass.name + '===null?' +336'null:' + superClass.name + '.prototype;',337state338);339declareIdentInLocalScope(superProtoIdentStr, initScopeMetadata(node), state);340}341342utils.append(343className + '.prototype=Object.create(' + superProtoIdentStr + ');',344state345);346utils.append(347className + '.prototype.constructor=' + className + ';',348state349);350utils.append(351className + '.__superConstructor__=' + superClass.name + ';',352state353);354}355356// If there's no constructor method specified in the class body, create an357// empty constructor function at the top (same line as the class keyword)358if (!node.body.body.filter(_isConstructorMethod).pop()) {359utils.append('function ' + className + '(){', state);360if (!state.scopeIsStrict) {361utils.append('"use strict";', state);362}363if (superClass.name) {364utils.append(365'if(' + superClass.name + '!==null){' +366superClass.name + '.apply(this,arguments);}',367state368);369}370utils.append('}', state);371}372373utils.move(node.body.range[0] + '{'.length, state);374traverse(node.body, path, state);375utils.catchupWhiteSpace(node.range[1], state);376}377378/**379* @param {function} traverse380* @param {object} node381* @param {array} path382* @param {object} state383*/384function visitClassDeclaration(traverse, node, path, state) {385var className = node.id.name;386var superClass = _getSuperClassInfo(node, state);387388state = utils.updateState(state, {389mungeNamespace: className,390className: className,391superClass: superClass392});393394_renderClassBody(traverse, node, path, state);395396return false;397}398visitClassDeclaration.test = function(node, path, state) {399return node.type === Syntax.ClassDeclaration;400};401402/**403* @param {function} traverse404* @param {object} node405* @param {array} path406* @param {object} state407*/408function visitClassExpression(traverse, node, path, state) {409var className = node.id && node.id.name || _generateAnonymousClassName(state);410var superClass = _getSuperClassInfo(node, state);411412utils.append('(function(){', state);413414state = utils.updateState(state, {415mungeNamespace: className,416className: className,417superClass: superClass418});419420_renderClassBody(traverse, node, path, state);421422utils.append('return ' + className + ';})()', state);423return false;424}425visitClassExpression.test = function(node, path, state) {426return node.type === Syntax.ClassExpression;427};428429/**430* @param {function} traverse431* @param {object} node432* @param {array} path433* @param {object} state434*/435function visitPrivateIdentifier(traverse, node, path, state) {436utils.append(_getMungedName(node.name, state), state);437utils.move(node.range[1], state);438}439visitPrivateIdentifier.test = function(node, path, state) {440if (node.type === Syntax.Identifier && _shouldMungeIdentifier(node, state)) {441// Always munge non-computed properties of MemberExpressions442// (a la preventing access of properties of unowned objects)443if (path[0].type === Syntax.MemberExpression && path[0].object !== node444&& path[0].computed === false) {445return true;446}447448// Always munge identifiers that were declared within the method function449// scope450if (utils.identWithinLexicalScope(node.name, state, state.methodFuncNode)) {451return true;452}453454// Always munge private keys on object literals defined within a method's455// scope.456if (path[0].type === Syntax.Property457&& path[1].type === Syntax.ObjectExpression) {458return true;459}460461// Always munge function parameters462if (path[0].type === Syntax.FunctionExpression463|| path[0].type === Syntax.FunctionDeclaration464|| path[0].type === Syntax.ArrowFunctionExpression) {465for (var i = 0; i < path[0].params.length; i++) {466if (path[0].params[i] === node) {467return true;468}469}470}471}472return false;473};474475/**476* @param {function} traverse477* @param {object} node478* @param {array} path479* @param {object} state480*/481function visitSuperCallExpression(traverse, node, path, state) {482var superClassName = state.superClass.name;483484if (node.callee.type === Syntax.Identifier) {485if (_isConstructorMethod(state.methodNode)) {486utils.append(superClassName + '.call(', state);487} else {488var protoProp = SUPER_PROTO_IDENT_PREFIX + superClassName;489if (state.methodNode.key.type === Syntax.Identifier) {490protoProp += '.' + state.methodNode.key.name;491} else if (state.methodNode.key.type === Syntax.Literal) {492protoProp += '[' + JSON.stringify(state.methodNode.key.value) + ']';493}494utils.append(protoProp + ".call(", state);495}496utils.move(node.callee.range[1], state);497} else if (node.callee.type === Syntax.MemberExpression) {498utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);499utils.move(node.callee.object.range[1], state);500501if (node.callee.computed) {502// ["a" + "b"]503utils.catchup(node.callee.property.range[1] + ']'.length, state);504} else {505// .ab506utils.append('.' + node.callee.property.name, state);507}508509utils.append('.call(', state);510utils.move(node.callee.range[1], state);511}512513utils.append('this', state);514if (node.arguments.length > 0) {515utils.append(',', state);516utils.catchupWhiteSpace(node.arguments[0].range[0], state);517traverse(node.arguments, path, state);518}519520utils.catchupWhiteSpace(node.range[1], state);521utils.append(')', state);522return false;523}524visitSuperCallExpression.test = function(node, path, state) {525if (state.superClass && node.type === Syntax.CallExpression) {526var callee = node.callee;527if (callee.type === Syntax.Identifier && callee.name === 'super'528|| callee.type == Syntax.MemberExpression529&& callee.object.name === 'super') {530return true;531}532}533return false;534};535536/**537* @param {function} traverse538* @param {object} node539* @param {array} path540* @param {object} state541*/542function visitSuperMemberExpression(traverse, node, path, state) {543var superClassName = state.superClass.name;544545utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);546utils.move(node.object.range[1], state);547}548visitSuperMemberExpression.test = function(node, path, state) {549return state.superClass550&& node.type === Syntax.MemberExpression551&& node.object.type === Syntax.Identifier552&& node.object.name === 'super';553};554555exports.resetSymbols = resetSymbols;556557exports.visitorList = [558visitClassDeclaration,559visitClassExpression,560visitClassFunctionExpression,561visitClassMethod,562visitClassMethodParam,563visitPrivateIdentifier,564visitSuperCallExpression,565visitSuperMemberExpression566];567568569