JSXElement.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /*
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. */
  10. 'use strict';
  11. const _ = require('lodash');
  12. const Collection = require('../Collection');
  13. const NodeCollection = require('./Node');
  14. const assert = require('assert');
  15. const recast = require('recast');
  16. const requiresModule = require('./VariableDeclarator').filters.requiresModule;
  17. const types = recast.types.namedTypes;
  18. const JSXElement = types.JSXElement;
  19. const JSXAttribute = types.JSXAttribute;
  20. const Literal = types.Literal;
  21. /**
  22. * Contains filter methods and mutation methods for processing JSXElements.
  23. * @mixin
  24. */
  25. const globalMethods = {
  26. /**
  27. * Finds all JSXElements optionally filtered by name
  28. *
  29. * @param {string} name
  30. * @return {Collection}
  31. */
  32. findJSXElements: function(name) {
  33. const nameFilter = name && {openingElement: {name: {name: name}}};
  34. return this.find(JSXElement, nameFilter);
  35. },
  36. /**
  37. * Finds all JSXElements by module name. Given
  38. *
  39. * var Bar = require('Foo');
  40. * <Bar />
  41. *
  42. * findJSXElementsByModuleName('Foo') will find <Bar />, without having to
  43. * know the variable name.
  44. */
  45. findJSXElementsByModuleName: function(moduleName) {
  46. assert.ok(
  47. moduleName && typeof moduleName === 'string',
  48. 'findJSXElementsByModuleName(...) needs a name to look for'
  49. );
  50. return this.find(types.VariableDeclarator)
  51. .filter(requiresModule(moduleName))
  52. .map(function(path) {
  53. const id = path.value.id.name;
  54. if (id) {
  55. return Collection.fromPaths([path])
  56. .closestScope()
  57. .findJSXElements(id)
  58. .paths();
  59. }
  60. });
  61. }
  62. };
  63. const filterMethods = {
  64. /**
  65. * Filter method for attributes.
  66. *
  67. * @param {Object} attributeFilter
  68. * @return {function}
  69. */
  70. hasAttributes: function(attributeFilter) {
  71. const attributeNames = Object.keys(attributeFilter);
  72. return function filter(path) {
  73. if (!JSXElement.check(path.value)) {
  74. return false;
  75. }
  76. const elementAttributes = Object.create(null);
  77. path.value.openingElement.attributes.forEach(function(attr) {
  78. if (!JSXAttribute.check(attr) ||
  79. !(attr.name.name in attributeFilter)) {
  80. return;
  81. }
  82. elementAttributes[attr.name.name] = attr;
  83. });
  84. return attributeNames.every(function(name) {
  85. if (!(name in elementAttributes) ){
  86. return false;
  87. }
  88. const value = elementAttributes[name].value;
  89. const expected = attributeFilter[name];
  90. const actual = Literal.check(value) ? value.value : value.expression;
  91. if (typeof expected === 'function') {
  92. return expected(actual);
  93. } else {
  94. // Literal attribute values are always strings
  95. return String(expected) === actual;
  96. }
  97. });
  98. };
  99. },
  100. /**
  101. * Filter elements which contain a specific child type
  102. *
  103. * @param {string} name
  104. * @return {function}
  105. */
  106. hasChildren: function(name) {
  107. return function filter(path) {
  108. return JSXElement.check(path.value) &&
  109. path.value.children.some(
  110. child => JSXElement.check(child) &&
  111. child.openingElement.name.name === name
  112. );
  113. };
  114. }
  115. };
  116. /**
  117. * @mixin
  118. */
  119. const traversalMethods = {
  120. /**
  121. * Returns all child nodes, including literals and expressions.
  122. *
  123. * @return {Collection}
  124. */
  125. childNodes: function() {
  126. const paths = [];
  127. this.forEach(function(path) {
  128. const children = path.get('children');
  129. const l = children.value.length;
  130. for (let i = 0; i < l; i++) {
  131. paths.push(children.get(i));
  132. }
  133. });
  134. return Collection.fromPaths(paths, this);
  135. },
  136. /**
  137. * Returns all children that are JSXElements.
  138. *
  139. * @return {JSXElementCollection}
  140. */
  141. childElements: function() {
  142. const paths = [];
  143. this.forEach(function(path) {
  144. const children = path.get('children');
  145. const l = children.value.length;
  146. for (let i = 0; i < l; i++) {
  147. if (types.JSXElement.check(children.value[i])) {
  148. paths.push(children.get(i));
  149. }
  150. }
  151. });
  152. return Collection.fromPaths(paths, this, JSXElement);
  153. },
  154. };
  155. const mappingMethods = {
  156. /**
  157. * Given a JSXElement, returns its "root" name. E.g. it would return "Foo" for
  158. * both <Foo /> and <Foo.Bar />.
  159. *
  160. * @param {NodePath} path
  161. * @return {string}
  162. */
  163. getRootName: function(path) {
  164. let name = path.value.openingElement.name;
  165. while (types.JSXMemberExpression.check(name)) {
  166. name = name.object;
  167. }
  168. return name && name.name || null;
  169. }
  170. };
  171. function register() {
  172. NodeCollection.register();
  173. Collection.registerMethods(globalMethods, types.Node);
  174. Collection.registerMethods(traversalMethods, JSXElement);
  175. }
  176. exports.register = _.once(register);
  177. exports.filters = filterMethods;
  178. exports.mappings = mappingMethods;