es6-class-visitors.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /**
  2. * Copyright 2013 Facebook, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*jslint node:true*/
  17. /**
  18. * @typechecks
  19. */
  20. 'use strict';
  21. var base62 = require('base62');
  22. var Syntax = require('esprima-fb').Syntax;
  23. var utils = require('../src/utils');
  24. var SUPER_PROTO_IDENT_PREFIX = '____SuperProtoOf';
  25. var _anonClassUUIDCounter = 0;
  26. var _mungedSymbolMaps = {};
  27. /**
  28. * Used to generate a unique class for use with code-gens for anonymous class
  29. * expressions.
  30. *
  31. * @param {object} state
  32. * @return {string}
  33. */
  34. function _generateAnonymousClassName(state) {
  35. var mungeNamespace = state.mungeNamespace || '';
  36. return '____Class' + mungeNamespace + base62.encode(_anonClassUUIDCounter++);
  37. }
  38. /**
  39. * Given an identifier name, munge it using the current state's mungeNamespace.
  40. *
  41. * @param {string} identName
  42. * @param {object} state
  43. * @return {string}
  44. */
  45. function _getMungedName(identName, state) {
  46. var mungeNamespace = state.mungeNamespace;
  47. var shouldMinify = state.g.opts.minify;
  48. if (shouldMinify) {
  49. if (!_mungedSymbolMaps[mungeNamespace]) {
  50. _mungedSymbolMaps[mungeNamespace] = {
  51. symbolMap: {},
  52. identUUIDCounter: 0
  53. };
  54. }
  55. var symbolMap = _mungedSymbolMaps[mungeNamespace].symbolMap;
  56. if (!symbolMap[identName]) {
  57. symbolMap[identName] =
  58. base62.encode(_mungedSymbolMaps[mungeNamespace].identUUIDCounter++);
  59. }
  60. identName = symbolMap[identName];
  61. }
  62. return '$' + mungeNamespace + identName;
  63. }
  64. /**
  65. * Extracts super class information from a class node.
  66. *
  67. * Information includes name of the super class and/or the expression string
  68. * (if extending from an expression)
  69. *
  70. * @param {object} node
  71. * @param {object} state
  72. * @return {object}
  73. */
  74. function _getSuperClassInfo(node, state) {
  75. var ret = {
  76. name: null,
  77. expression: null
  78. };
  79. if (node.superClass) {
  80. if (node.superClass.type === Syntax.Identifier) {
  81. ret.name = node.superClass.name;
  82. } else {
  83. // Extension from an expression
  84. ret.name = _generateAnonymousClassName(state);
  85. ret.expression = state.g.source.substring(
  86. node.superClass.range[0],
  87. node.superClass.range[1]
  88. );
  89. }
  90. }
  91. return ret;
  92. }
  93. /**
  94. * Used with .filter() to find the constructor method in a list of
  95. * MethodDefinition nodes.
  96. *
  97. * @param {object} classElement
  98. * @return {boolean}
  99. */
  100. function _isConstructorMethod(classElement) {
  101. return classElement.type === Syntax.MethodDefinition &&
  102. classElement.key.type === Syntax.Identifier &&
  103. classElement.key.name === 'constructor';
  104. }
  105. /**
  106. * @param {object} node
  107. * @param {object} state
  108. * @return {boolean}
  109. */
  110. function _shouldMungeIdentifier(node, state) {
  111. return (
  112. !!state.methodFuncNode &&
  113. !utils.getDocblock(state).hasOwnProperty('preventMunge') &&
  114. /^_(?!_)/.test(node.name)
  115. );
  116. }
  117. /**
  118. * @param {function} traverse
  119. * @param {object} node
  120. * @param {array} path
  121. * @param {object} state
  122. */
  123. function visitClassMethod(traverse, node, path, state) {
  124. utils.catchup(node.range[0], state);
  125. path.unshift(node);
  126. traverse(node.value, path, state);
  127. path.shift();
  128. return false;
  129. }
  130. visitClassMethod.test = function(node, path, state) {
  131. return node.type === Syntax.MethodDefinition;
  132. };
  133. /**
  134. * @param {function} traverse
  135. * @param {object} node
  136. * @param {array} path
  137. * @param {object} state
  138. */
  139. function visitClassFunctionExpression(traverse, node, path, state) {
  140. var methodNode = path[0];
  141. state = utils.updateState(state, {
  142. methodFuncNode: node
  143. });
  144. if (methodNode.key.name === 'constructor') {
  145. utils.append('function ' + state.className, state);
  146. } else {
  147. var methodName = methodNode.key.name;
  148. if (_shouldMungeIdentifier(methodNode.key, state)) {
  149. methodName = _getMungedName(methodName, state);
  150. }
  151. var prototypeOrStatic = methodNode.static ? '' : 'prototype.';
  152. utils.append(
  153. state.className + '.' + prototypeOrStatic + methodName + '=function',
  154. state
  155. );
  156. }
  157. utils.move(methodNode.key.range[1], state);
  158. var params = node.params;
  159. var paramName;
  160. if (params.length > 0) {
  161. for (var i = 0; i < params.length; i++) {
  162. utils.catchup(node.params[i].range[0], state);
  163. paramName = params[i].name;
  164. if (_shouldMungeIdentifier(params[i], state)) {
  165. paramName = _getMungedName(params[i].name, state);
  166. }
  167. utils.append(paramName, state);
  168. utils.move(params[i].range[1], state);
  169. }
  170. } else {
  171. utils.append('(', state);
  172. }
  173. utils.append(')', state);
  174. utils.catchupWhiteSpace(node.body.range[0], state);
  175. utils.append('{', state);
  176. if (!state.scopeIsStrict) {
  177. utils.append('"use strict";', state);
  178. }
  179. utils.move(node.body.range[0] + '{'.length, state);
  180. path.unshift(node);
  181. traverse(node.body, path, state);
  182. path.shift();
  183. utils.catchup(node.body.range[1], state);
  184. if (methodNode.key.name !== 'constructor') {
  185. utils.append(';', state);
  186. }
  187. return false;
  188. }
  189. visitClassFunctionExpression.test = function(node, path, state) {
  190. return node.type === Syntax.FunctionExpression
  191. && path[0].type === Syntax.MethodDefinition;
  192. };
  193. /**
  194. * @param {function} traverse
  195. * @param {object} node
  196. * @param {array} path
  197. * @param {object} state
  198. */
  199. function _renderClassBody(traverse, node, path, state) {
  200. var className = state.className;
  201. var superClass = state.superClass;
  202. // Set up prototype of constructor on same line as `extends` for line-number
  203. // preservation. This relies on function-hoisting if a constructor function is
  204. // defined in the class body.
  205. if (superClass.name) {
  206. // If the super class is an expression, we need to memoize the output of the
  207. // expression into the generated class name variable and use that to refer
  208. // to the super class going forward. Example:
  209. //
  210. // class Foo extends mixin(Bar, Baz) {}
  211. // --transforms to--
  212. // function Foo() {} var ____Class0Blah = mixin(Bar, Baz);
  213. if (superClass.expression !== null) {
  214. utils.append(
  215. 'var ' + superClass.name + '=' + superClass.expression + ';',
  216. state
  217. );
  218. }
  219. var keyName = superClass.name + '____Key';
  220. var keyNameDeclarator = '';
  221. if (!utils.identWithinLexicalScope(keyName, state)) {
  222. keyNameDeclarator = 'var ';
  223. utils.declareIdentInLocalScope(keyName, state);
  224. }
  225. utils.append(
  226. 'for(' + keyNameDeclarator + keyName + ' in ' + superClass.name + '){' +
  227. 'if(' + superClass.name + '.hasOwnProperty(' + keyName + ')){' +
  228. className + '[' + keyName + ']=' +
  229. superClass.name + '[' + keyName + '];' +
  230. '}' +
  231. '}',
  232. state
  233. );
  234. var superProtoIdentStr = SUPER_PROTO_IDENT_PREFIX + superClass.name;
  235. if (!utils.identWithinLexicalScope(superProtoIdentStr, state)) {
  236. utils.append(
  237. 'var ' + superProtoIdentStr + '=' + superClass.name + '===null?' +
  238. 'null:' + superClass.name + '.prototype;',
  239. state
  240. );
  241. utils.declareIdentInLocalScope(superProtoIdentStr, state);
  242. }
  243. utils.append(
  244. className + '.prototype=Object.create(' + superProtoIdentStr + ');',
  245. state
  246. );
  247. utils.append(
  248. className + '.prototype.constructor=' + className + ';',
  249. state
  250. );
  251. utils.append(
  252. className + '.__superConstructor__=' + superClass.name + ';',
  253. state
  254. );
  255. }
  256. // If there's no constructor method specified in the class body, create an
  257. // empty constructor function at the top (same line as the class keyword)
  258. if (!node.body.body.filter(_isConstructorMethod).pop()) {
  259. utils.append('function ' + className + '(){', state);
  260. if (!state.scopeIsStrict) {
  261. utils.append('"use strict";', state);
  262. }
  263. if (superClass.name) {
  264. utils.append(
  265. 'if(' + superClass.name + '!==null){' +
  266. superClass.name + '.apply(this,arguments);}',
  267. state
  268. );
  269. }
  270. utils.append('}', state);
  271. }
  272. utils.move(node.body.range[0] + '{'.length, state);
  273. traverse(node.body, path, state);
  274. utils.catchupWhiteSpace(node.range[1], state);
  275. }
  276. /**
  277. * @param {function} traverse
  278. * @param {object} node
  279. * @param {array} path
  280. * @param {object} state
  281. */
  282. function visitClassDeclaration(traverse, node, path, state) {
  283. var className = node.id.name;
  284. var superClass = _getSuperClassInfo(node, state);
  285. state = utils.updateState(state, {
  286. mungeNamespace: className,
  287. className: className,
  288. superClass: superClass
  289. });
  290. _renderClassBody(traverse, node, path, state);
  291. return false;
  292. }
  293. visitClassDeclaration.test = function(node, path, state) {
  294. return node.type === Syntax.ClassDeclaration;
  295. };
  296. /**
  297. * @param {function} traverse
  298. * @param {object} node
  299. * @param {array} path
  300. * @param {object} state
  301. */
  302. function visitClassExpression(traverse, node, path, state) {
  303. var className = node.id && node.id.name || _generateAnonymousClassName(state);
  304. var superClass = _getSuperClassInfo(node, state);
  305. utils.append('(function(){', state);
  306. state = utils.updateState(state, {
  307. mungeNamespace: className,
  308. className: className,
  309. superClass: superClass
  310. });
  311. _renderClassBody(traverse, node, path, state);
  312. utils.append('return ' + className + ';})()', state);
  313. return false;
  314. }
  315. visitClassExpression.test = function(node, path, state) {
  316. return node.type === Syntax.ClassExpression;
  317. };
  318. /**
  319. * @param {function} traverse
  320. * @param {object} node
  321. * @param {array} path
  322. * @param {object} state
  323. */
  324. function visitPrivateIdentifier(traverse, node, path, state) {
  325. utils.append(_getMungedName(node.name, state), state);
  326. utils.move(node.range[1], state);
  327. }
  328. visitPrivateIdentifier.test = function(node, path, state) {
  329. if (node.type === Syntax.Identifier && _shouldMungeIdentifier(node, state)) {
  330. // Always munge non-computed properties of MemberExpressions
  331. // (a la preventing access of properties of unowned objects)
  332. if (path[0].type === Syntax.MemberExpression && path[0].object !== node
  333. && path[0].computed === false) {
  334. return true;
  335. }
  336. // Always munge identifiers that were declared within the method function
  337. // scope
  338. if (utils.identWithinLexicalScope(node.name, state, state.methodFuncNode)) {
  339. return true;
  340. }
  341. // Always munge private keys on object literals defined within a method's
  342. // scope.
  343. if (path[0].type === Syntax.Property
  344. && path[1].type === Syntax.ObjectExpression) {
  345. return true;
  346. }
  347. // Always munge function parameters
  348. if (path[0].type === Syntax.FunctionExpression
  349. || path[0].type === Syntax.FunctionDeclaration) {
  350. for (var i = 0; i < path[0].params.length; i++) {
  351. if (path[0].params[i] === node) {
  352. return true;
  353. }
  354. }
  355. }
  356. }
  357. return false;
  358. };
  359. /**
  360. * @param {function} traverse
  361. * @param {object} node
  362. * @param {array} path
  363. * @param {object} state
  364. */
  365. function visitSuperCallExpression(traverse, node, path, state) {
  366. var superClassName = state.superClass.name;
  367. if (node.callee.type === Syntax.Identifier) {
  368. utils.append(superClassName + '.call(', state);
  369. utils.move(node.callee.range[1], state);
  370. } else if (node.callee.type === Syntax.MemberExpression) {
  371. utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);
  372. utils.move(node.callee.object.range[1], state);
  373. if (node.callee.computed) {
  374. // ["a" + "b"]
  375. utils.catchup(node.callee.property.range[1] + ']'.length, state);
  376. } else {
  377. // .ab
  378. utils.append('.' + node.callee.property.name, state);
  379. }
  380. utils.append('.call(', state);
  381. utils.move(node.callee.range[1], state);
  382. }
  383. utils.append('this', state);
  384. if (node.arguments.length > 0) {
  385. utils.append(',', state);
  386. utils.catchupWhiteSpace(node.arguments[0].range[0], state);
  387. traverse(node.arguments, path, state);
  388. }
  389. utils.catchupWhiteSpace(node.range[1], state);
  390. utils.append(')', state);
  391. return false;
  392. }
  393. visitSuperCallExpression.test = function(node, path, state) {
  394. if (state.superClass && node.type === Syntax.CallExpression) {
  395. var callee = node.callee;
  396. if (callee.type === Syntax.Identifier && callee.name === 'super'
  397. || callee.type == Syntax.MemberExpression
  398. && callee.object.name === 'super') {
  399. return true;
  400. }
  401. }
  402. return false;
  403. };
  404. /**
  405. * @param {function} traverse
  406. * @param {object} node
  407. * @param {array} path
  408. * @param {object} state
  409. */
  410. function visitSuperMemberExpression(traverse, node, path, state) {
  411. var superClassName = state.superClass.name;
  412. utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);
  413. utils.move(node.object.range[1], state);
  414. }
  415. visitSuperMemberExpression.test = function(node, path, state) {
  416. return state.superClass
  417. && node.type === Syntax.MemberExpression
  418. && node.object.type === Syntax.Identifier
  419. && node.object.name === 'super';
  420. };
  421. exports.visitorList = [
  422. visitClassDeclaration,
  423. visitClassExpression,
  424. visitClassFunctionExpression,
  425. visitClassMethod,
  426. visitPrivateIdentifier,
  427. visitSuperCallExpression,
  428. visitSuperMemberExpression
  429. ];