Files
react/vendor/fbtransform/transforms/classes.js
T
Paul O’Shannessy 75897c2dcd Initial public release
2013-05-29 12:54:02 -07:00

493 lines
14 KiB
JavaScript

/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*global exports:true*/
"use strict";
/**
* Desugarizer for ES6 minimal class proposal. See
* http://wiki.ecmascript.org/doku.php?id=harmony:proposals
*
* Does not require any runtime. Preserves whitespace and comments.
* Supports a class declaration with methods, super calls and inheritance.
* Currently does not support for getters and setters, since there's a very
* low probability we're going to use them anytime soon.
*
* Additional features:
* - Any member with private name (the name with prefix _, such _name) inside
* the class's scope will be munged. This would will to eliminate the case
* of sub-class accidentally overriding the super-class's provate properties
* also discouage people from accessing private members that they should not
* access. However, quoted property names don't get munged.
*
* class SkinnedMesh extends require('THREE').Mesh {
*
* update(camera) {
* camera.code = 'iphone'
* super.update(camera);
* }
*
* /
* * @constructor
* /
* constructor(geometry, materials) {
* super(geometry, materials);
*
* super.update(1);
*
* this.identityMatrix = new THREE.Matrix4();
* this.bones = [];
* this.boneMatrices = [];
* this._name = 'foo';
* }
*
* /
* * some other code
* /
* readMore() {
*
* }
*
* _doSomething() {
*
* }
* }
*
* should be converted to
*
* var SkinnedMesh = (function() {
* var __super = require('parent').Mesh;
*
* /
* * @constructor
* /
* function SkinnedMesh(geometry, materials) {
* __super.call(this, geometry, materials);
*
* __super.prototype.update.call(this, 1);
*
* this.identityMatrix = new THREE.Matrix4();
* this.bones = [];
* this.boneMatrices = [];
* this.$SkinnedMesh_name = 'foo';
* }
* SkinnedMesh.prototype = Object.create(__super.prototype);
* SkinnedMesh.prototype.constructor = SkinnedMesh;
*
* /
* * @param camera
* /
* SkinnedMesh.prototype.update = function(camera) {
* camera.code = 'iphone'
* __super.prototype.update.call(this, camera);
* };
*
* SkinnedMesh.prototype.readMore = function() {
*
* };
*
* SkinnedMesh.prototype.$SkinnedMesh_doSomething = function() {
*
* };
*
* return SkinnedMesh;
* })();
*
*/
var Syntax = require('esprima').Syntax;
var base62 = require('base62');
var catchup = require('../lib/utils').catchup;
var append = require('../lib/utils').append;
var move = require('../lib/utils').move;
var indentBefore = require('../lib/utils').indentBefore;
var updateIndent = require('../lib/utils').updateIndent;
var updateState = require('../lib/utils').updateState;
function findConstructorIndex(object) {
var classElements = object.body && object.body.body || [];
for (var i = 0; i < classElements.length; i++) {
if (classElements[i].type === Syntax.MethodDefinition &&
classElements[i].key.name === 'constructor') {
return i;
}
}
return -1;
}
var _mungedSymbolMaps = {};
function getMungedName(scopeName, name, minify) {
if (minify) {
if (!_mungedSymbolMaps[scopeName]) {
_mungedSymbolMaps[scopeName] = {
symbolMap: {},
identifierUUIDCounter: 0
};
}
var symbolMap = _mungedSymbolMaps[scopeName].symbolMap;
if (!symbolMap[name]) {
symbolMap[name] =
base62.encode(_mungedSymbolMaps[scopeName].identifierUUIDCounter);
_mungedSymbolMaps[scopeName].identifierUUIDCounter++;
}
name = symbolMap[name];
}
return '$' + scopeName + name;
}
function shouldMungeName(scopeName, name, state) {
// only run when @preventMunge is not present in the docblock
if (state.g.preventMunge === undefined) {
var docblock = require('../lib/docblock');
state.g.preventMunge = docblock.parseAsObject(
docblock.extract(state.g.source)).preventMunge !== undefined;
}
// Starts with only a single underscore (i.e. don't count double-underscores)
return !state.g.preventMunge && scopeName ? /^_(?!_)/.test(name) : false;
}
function getProtoOfPrototypeVariableName(superVar) {
return superVar + 'ProtoOfPrototype';
}
function getSuperKeyName(superVar) {
return superVar + 'Key';
}
function getSuperProtoOfPrototypeVariable(superVariableName, indent) {
var string = (indent +
'var $proto = $superName && $superName.prototype ? ' +
'$superName.prototype : $superName;\n'
).replace(/\$proto/g, getProtoOfPrototypeVariableName(superVariableName))
.replace(/\$superName/g, superVariableName);
return string;
}
function getInheritanceSetup(superClassToken, className, indent, superName) {
var string = '';
if (superClassToken) {
string += getStaticMethodsOnConstructorSetup(className, indent, superName);
string += getPrototypeOnConstructorSetup(className, indent, superName);
string += getConstructorPropertySetup(className, indent);
}
return string;
}
function getStaticMethodsOnConstructorSetup(className, indent, superName) {
var string = ( indent +
'for (var $keyName in $superName) {\n' + indent +
' if ($superName.hasOwnProperty($keyName)) {\n' + indent +
' $className[$keyName] = $superName[$keyName];\n' + indent +
' }\n' + indent +
'}\n')
.replace(/\$className/g, className)
.replace(/\$keyName/g, getSuperKeyName(superName))
.replace(/\$superName/g, superName);
return string;
}
function getPrototypeOnConstructorSetup(className, indent, superName) {
var string = ( indent +
'$className.prototype = Object.create($protoPrototype);\n')
.replace(/\$protoPrototype/g, getProtoOfPrototypeVariableName(superName))
.replace(/\$className/g, className);
return string;
}
function getConstructorPropertySetup(className, indent) {
var string = ( indent +
'$className.prototype.constructor = $className;\n')
.replace(/\$className/g, className);
return string;
}
function getSuperConstructorSetup(superClassToken, indent, superName) {
if (!superClassToken) return '';
var string = ( '\n' + indent +
' if ($superName && $superName.prototype) {\n' + indent +
' $superName.apply(this, arguments);\n' + indent +
' }\n' + indent)
.replace(/\$superName/g, superName);
return string;
}
function getMemberFunctionCall(superVar, propertyName, superArgs) {
var string = (
'$superPrototype.$propertyName.call($superArguments)')
.replace(/\$superPrototype/g, getProtoOfPrototypeVariableName(superVar))
.replace(/\$propertyName/g, propertyName)
.replace(/\$superArguments/g, superArgs);
return string;
}
function getCallParams(classElement, state) {
var params = classElement.value.params;
if (!params.length) {
return '';
}
return state.g.source.substring(
params[0].range[0],
params[params.length - 1].range[1]);
}
function getSuperArguments(callExpression, state) {
var args = callExpression.arguments;
if (!args.length) {
return 'this';
}
return 'this, ' + state.g.source.substring(
args[0].range[0],
args[args.length - 1].range[1]);
}
// The seed is used to generate the name for an anonymous class,
// and this seed should be unique per browser's session.
// The value of the seed looks like this: 1229588505.2969012.
var classIDSeed = Date.now() % (60 * 60 * 1000) + Math.random();
/**
* Generates a name for an anonymous class. The generated value looks like
* this: "Classkc6pcn_mniza1yvi"
* @param {String} scopeName
* @return {string} the scope name for Anonymous Class
*/
function generateAnonymousClassName(scopeName) {
classIDSeed++;
return 'Class' +
(classIDSeed).toString(36).replace('.', '_') +
(scopeName || '');
}
function renderMethods(traverse, object, name, path, state) {
var classElements = object.body && object.body.body || [];
move(object.body.range[0] + 1, state);
for (var i = 0; i < classElements.length; i++) {
if (classElements[i].key.name !== 'constructor') {
catchup(classElements[i].range[0], state);
var memberName = classElements[i].key.name;
if (shouldMungeName(state.scopeName, memberName, state)) {
memberName = getMungedName(
state.scopeName,
memberName,
state.g.opts.minify
);
}
var prototypeOrStatic;
if (classElements[i]['static']) {
prototypeOrStatic = '';
} else {
prototypeOrStatic = 'prototype.';
}
append(name + '.' + prototypeOrStatic + memberName + ' = ', state);
renderMethod(traverse, classElements[i], null, path, state);
append(';', state);
}
move(classElements[i].range[1], state);
}
if (classElements.length) {
append('\n', state);
}
move(object.range[1], state);
}
function renderMethod(traverse, method, name, path, state) {
append(name ? 'function ' + name + '(' : 'function(', state);
append(getCallParams(method, state) + ') {', state);
move(method.value.body.range[0] + 1, state);
traverse(method.value.body, path, state);
catchup(method.value.body.range[1] - 1, state);
append('}', state);
}
function renderSuperClass(traverse, superClass, path, state) {
append('var ' + state.superVar + ' = ', state);
move(superClass.range[0], state);
traverse(superClass, path, state);
catchup(superClass.range[1], state);
append(';\n', state);
}
function renderConstructor(traverse, object, name, indent, path, state) {
var classElements = object.body && object.body.body || [];
var constructorIndex = findConstructorIndex(object);
var constructor = constructorIndex === -1 ?
null :
classElements[constructorIndex];
if (constructor) {
move(constructorIndex === 0 ?
object.body.range[0] + 1 :
classElements[constructorIndex - 1].range[1], state);
catchup(constructor.range[0], state);
renderMethod(traverse, constructor, name, path, state);
append('\n', state);
} else {
if (object.superClass) {
append('\n' + indent, state);
}
append('function ', state);
if (object.id) {
move(object.id.range[0], state);
}
append(name, state);
if (object.id) {
move(object.id.range[1], state);
}
append('(){ ', state);
if (object.body) {
move(object.body.range[0], state);
}
append(getSuperConstructorSetup(
object.superClass,
indent,
state.superVar), state);
append('}\n', state);
}
}
var superId = 0;
function renderClassBody(traverse, object, path, state) {
var name = object.id ? object.id.name : 'constructor';
var superClass = object.superClass;
var indent = updateIndent(
indentBefore(object.range[0], state) + ' ',
state);
state = updateState(
state,
{
scopeName: object.id ? object.id.name :
generateAnonymousClassName(state.scopeName),
superVar: superClass ? '__super' + superId++ : ''
});
// super class
if (superClass) {
append(indent, state);
renderSuperClass(traverse, superClass, path, state);
append(getSuperProtoOfPrototypeVariable(state.superVar, indent), state);
}
renderConstructor(traverse, object, name, indent, path, state);
append(getInheritanceSetup(superClass, name, indent, state.superVar), state);
renderMethods(traverse, object, name, path, state);
}
/**
* @public
*/
function visitClassExpression(traverse, object, path, state) {
var indent = updateIndent(
indentBefore(object.range[0], state) + ' ',
state);
var name = object.id ? object.id.name : 'constructor';
append('(function() {\n', state);
renderClassBody(traverse, object, path, state);
append(indent + 'return ' + name + ';\n', state);
append(indent.substring(0, indent.length - 2) + '})()', state);
return false
}
visitClassExpression.test = function(object, path, state) {
return object.type === Syntax.ClassExpression;
};
/**
* @public
*/
function visitClassDeclaration(traverse, object, path, state) {
state.g.indentBy--;
renderClassBody(traverse, object, path, state);
state.g.indentBy++;
return false;
}
visitClassDeclaration.test = function(object, path, state) {
return object.type === Syntax.ClassDeclaration;
};
/**
* @public
*/
function visitSuperCall(traverse, object, path, state) {
if (path[0].type === Syntax.CallExpression) {
append(state.superVar +
'.call(' + getSuperArguments(path[0], state) + ')', state);
move(path[0].range[1], state);
} else if (path[0].type === Syntax.MemberExpression) {
append(getMemberFunctionCall(
state.superVar,
path[0].property.name,
getSuperArguments(path[1], state)), state);
move(path[1].range[1], state);
}
return false;
}
visitSuperCall.test = function(object, path, state) {
return state.superVar && object.type === Syntax.Identifier &&
object.name === 'super';
};
/**
* @public
*/
function visitPrivateProperty(traverse, object, path, state) {
var type = path[0] ? path[0].type : null;
if (type !== Syntax.Property) {
if (type === Syntax.MemberExpression) {
type = path[0].object ? path[0].object.type : null;
if (type === Syntax.Identifier &&
path[0].object.range[0] === object.range[0]) {
// Identifier is a variable that appears "private".
return;
}
} else {
// Other syntax that are neither Property nor MemberExpression.
return;
}
}
var oldName = object.name;
var newName = getMungedName(
state.scopeName,
oldName,
state.g.opts.minify
);
append(newName, state);
move(object.range[1], state);
}
visitPrivateProperty.test = function(object, path, state) {
return object.type === Syntax.Identifier &&
shouldMungeName(state.scopeName, object.name, state);
};
exports.visitClassDeclaration = visitClassDeclaration;
exports.visitClassExpression = visitClassExpression;
exports.visitSuperCall = visitSuperCall;
exports.visitPrivateProperty = visitPrivateProperty;