123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- /*
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- */
- 'use strict';
- const _ = require('lodash');
- const Collection = require('../Collection');
- const NodeCollection = require('./Node');
- const assert = require('assert');
- const recast = require('recast');
- const requiresModule = require('./VariableDeclarator').filters.requiresModule;
- const types = recast.types.namedTypes;
- const JSXElement = types.JSXElement;
- const JSXAttribute = types.JSXAttribute;
- const Literal = types.Literal;
- /**
- * Contains filter methods and mutation methods for processing JSXElements.
- * @mixin
- */
- const globalMethods = {
- /**
- * Finds all JSXElements optionally filtered by name
- *
- * @param {string} name
- * @return {Collection}
- */
- findJSXElements: function(name) {
- const nameFilter = name && {openingElement: {name: {name: name}}};
- return this.find(JSXElement, nameFilter);
- },
- /**
- * Finds all JSXElements by module name. Given
- *
- * var Bar = require('Foo');
- * <Bar />
- *
- * findJSXElementsByModuleName('Foo') will find <Bar />, without having to
- * know the variable name.
- */
- findJSXElementsByModuleName: function(moduleName) {
- assert.ok(
- moduleName && typeof moduleName === 'string',
- 'findJSXElementsByModuleName(...) needs a name to look for'
- );
- return this.find(types.VariableDeclarator)
- .filter(requiresModule(moduleName))
- .map(function(path) {
- const id = path.value.id.name;
- if (id) {
- return Collection.fromPaths([path])
- .closestScope()
- .findJSXElements(id)
- .paths();
- }
- });
- }
- };
- const filterMethods = {
- /**
- * Filter method for attributes.
- *
- * @param {Object} attributeFilter
- * @return {function}
- */
- hasAttributes: function(attributeFilter) {
- const attributeNames = Object.keys(attributeFilter);
- return function filter(path) {
- if (!JSXElement.check(path.value)) {
- return false;
- }
- const elementAttributes = Object.create(null);
- path.value.openingElement.attributes.forEach(function(attr) {
- if (!JSXAttribute.check(attr) ||
- !(attr.name.name in attributeFilter)) {
- return;
- }
- elementAttributes[attr.name.name] = attr;
- });
- return attributeNames.every(function(name) {
- if (!(name in elementAttributes) ){
- return false;
- }
- const value = elementAttributes[name].value;
- const expected = attributeFilter[name];
- const actual = Literal.check(value) ? value.value : value.expression;
- if (typeof expected === 'function') {
- return expected(actual);
- } else {
- // Literal attribute values are always strings
- return String(expected) === actual;
- }
- });
- };
- },
- /**
- * Filter elements which contain a specific child type
- *
- * @param {string} name
- * @return {function}
- */
- hasChildren: function(name) {
- return function filter(path) {
- return JSXElement.check(path.value) &&
- path.value.children.some(
- child => JSXElement.check(child) &&
- child.openingElement.name.name === name
- );
- };
- }
- };
- /**
- * @mixin
- */
- const traversalMethods = {
- /**
- * Returns all child nodes, including literals and expressions.
- *
- * @return {Collection}
- */
- childNodes: function() {
- const paths = [];
- this.forEach(function(path) {
- const children = path.get('children');
- const l = children.value.length;
- for (let i = 0; i < l; i++) {
- paths.push(children.get(i));
- }
- });
- return Collection.fromPaths(paths, this);
- },
- /**
- * Returns all children that are JSXElements.
- *
- * @return {JSXElementCollection}
- */
- childElements: function() {
- const paths = [];
- this.forEach(function(path) {
- const children = path.get('children');
- const l = children.value.length;
- for (let i = 0; i < l; i++) {
- if (types.JSXElement.check(children.value[i])) {
- paths.push(children.get(i));
- }
- }
- });
- return Collection.fromPaths(paths, this, JSXElement);
- },
- };
- const mappingMethods = {
- /**
- * Given a JSXElement, returns its "root" name. E.g. it would return "Foo" for
- * both <Foo /> and <Foo.Bar />.
- *
- * @param {NodePath} path
- * @return {string}
- */
- getRootName: function(path) {
- let name = path.value.openingElement.name;
- while (types.JSXMemberExpression.check(name)) {
- name = name.object;
- }
- return name && name.name || null;
- }
- };
- function register() {
- NodeCollection.register();
- Collection.registerMethods(globalMethods, types.Node);
- Collection.registerMethods(traversalMethods, JSXElement);
- }
- exports.register = _.once(register);
- exports.filters = filterMethods;
- exports.mappings = mappingMethods;
|