index.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. module.exports = transform;
  2. var pathMod = require('path')
  3. function transform (babel) {
  4. return {
  5. visitor: {
  6. ClassDeclaration: function (path, state) {
  7. if (classHasRenderMethod(path)) {
  8. setDisplayNameAfter(path, path.node.id, babel.types)
  9. }
  10. },
  11. FunctionDeclaration: function (path, state) {
  12. if (doesReturnJSX(path.node.body) || (path.node.id && path.node.id.name &&
  13. isKnownComponent(path.node.id.name, state.opts.knownComponents))) {
  14. var displayName
  15. if (path.parentPath.node.type === 'ExportDefaultDeclaration') {
  16. if (path.node.id == null) {
  17. // An anonymous function declaration in export default declaration.
  18. // Transform `export default function () { ... }`
  19. // to `var _uid1 = function () { .. }; export default __uid;`
  20. // then add displayName to _uid1
  21. var extension = pathMod.extname(state.file.opts.filename)
  22. var name = pathMod.basename(state.file.opts.filename, extension)
  23. var id = path.scope.generateUidIdentifier("uid");
  24. path.node.id = id
  25. displayName = name
  26. }
  27. setDisplayNameAfter(path, path.node.id, babel.types, displayName)
  28. }else if(path.parentPath.node.type === 'Program' || path.parentPath.node.type == 'ExportNamedDeclaration') {
  29. setDisplayNameAfter(path, path.node.id, babel.types, displayName)
  30. }
  31. }
  32. },
  33. FunctionExpression: function (path, state) {
  34. if(shouldSetDisplayNameForFuncExpr(path, state.opts.knownComponents)) {
  35. var id = findCandidateNameForExpression(path)
  36. if (id) {
  37. setDisplayNameAfter(path, id, babel.types)
  38. }
  39. }
  40. },
  41. ArrowFunctionExpression: function (path, state) {
  42. if(shouldSetDisplayNameForFuncExpr(path, state.opts.knownComponents)) {
  43. var id = findCandidateNameForExpression(path)
  44. if (id) {
  45. setDisplayNameAfter(path, id, babel.types)
  46. }
  47. }
  48. }
  49. }
  50. }
  51. }
  52. function isKnownComponent(name, knownComponents) {
  53. return (name && knownComponents && knownComponents.indexOf(name) > -1)
  54. }
  55. function componentNameFromFilename(filename) {
  56. var extension = pathMod.extname(filename);
  57. var name = pathMod.basename(filename, extension)
  58. return name
  59. }
  60. function shouldSetDisplayNameForFuncExpr(path, knownComponents) {
  61. // Parent must be either 'AssignmentExpression' or 'VariableDeclarator' or 'CallExpression' with a parent of 'VariableDeclarator'
  62. var id
  63. if (path.parentPath.node.type === 'AssignmentExpression' &&
  64. path.parentPath.node.left.type !== 'MemberExpression' && // skip static members
  65. path.parentPath.parentPath.node.type == 'ExpressionStatement' &&
  66. path.parentPath.parentPath.parentPath.node.type == 'Program') {
  67. id = path.parentPath.node.left
  68. }else{
  69. // if parent is a call expression, we have something like (function () { .. })()
  70. // move up, past the call expression and run the rest of the checks as usual
  71. if(path.parentPath.node.type === 'CallExpression') {
  72. path = path.parentPath
  73. }
  74. if(path.parentPath.node.type === 'VariableDeclarator') {
  75. if (path.parentPath.parentPath.parentPath.node.type === 'ExportNamedDeclaration' ||
  76. path.parentPath.parentPath.parentPath.node.type === 'Program') {
  77. id = path.parentPath.node.id
  78. }
  79. }
  80. }
  81. if (id) {
  82. if (id.name && isKnownComponent(id.name, knownComponents)) {
  83. return true
  84. }
  85. return doesReturnJSX(path.node.body)
  86. }
  87. return false
  88. }
  89. function classHasRenderMethod(path) {
  90. if(!path.node.body) {
  91. return false
  92. }
  93. var members = path.node.body.body
  94. for(var i = 0; i < members.length; i++) {
  95. if (members[i].type == 'ClassMethod' && members[i].key.name == 'render') {
  96. return true
  97. }
  98. }
  99. return false
  100. }
  101. // https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-react-display-name/src/index.js#L62-L77
  102. // crawl up the ancestry looking for possible candidates for displayName inference
  103. function findCandidateNameForExpression(path) {
  104. var id
  105. path.find(function (path) {
  106. if (path.isAssignmentExpression()) {
  107. id = path.node.left;
  108. // } else if (path.isObjectProperty()) {
  109. // id = path.node.key;
  110. } else if (path.isVariableDeclarator()) {
  111. id = path.node.id;
  112. } else if (path.isStatement()) {
  113. // we've hit a statement, we should stop crawling up
  114. return true;
  115. }
  116. // we've got an id! no need to continue
  117. if (id) return true;
  118. });
  119. return id
  120. }
  121. function doesReturnJSX (body) {
  122. if (!body) return false
  123. if (body.type === 'JSXElement') {
  124. return true
  125. }
  126. var block = body.body
  127. if (block && block.length) {
  128. var lastBlock = block.slice(0).pop()
  129. if (lastBlock.type === 'ReturnStatement') {
  130. return lastBlock.argument !== null && lastBlock.argument.type === 'JSXElement'
  131. }
  132. }
  133. return false
  134. }
  135. function setDisplayNameAfter(path, nameNodeId, t, displayName) {
  136. if (!displayName) {
  137. displayName = nameNodeId.name
  138. }
  139. var blockLevelStmnt
  140. path.find(function (path) {
  141. if (path.parentPath.isBlock()) {
  142. blockLevelStmnt = path
  143. return true
  144. }
  145. })
  146. if (blockLevelStmnt) {
  147. var trailingComments = blockLevelStmnt.node.trailingComments
  148. delete blockLevelStmnt.node.trailingComments
  149. var setDisplayNameStmn = t.expressionStatement(t.assignmentExpression(
  150. '=',
  151. t.memberExpression(nameNodeId, t.identifier('displayName')),
  152. t.stringLiteral(displayName)
  153. ))
  154. blockLevelStmnt.insertAfter(setDisplayNameStmn)
  155. }
  156. }