module.exports = function()

in transforms/ReactNative-View-propTypes.js [53:218]


module.exports = function(file, api, options) {
  const j = api.jscodeshift;

  const printOptions = options.printOptions || { quote: 'single' };
  let root = j(file.source);

  let numMatchedPaths = 0;

  // Search for all View references
  const viewReferenceCount = root.find(j.Identifier).filter(isRootViewReference)
    .length;

  // Replace View.propTypes with ViewPropTypes
  root
    .find(j.Identifier)
    .filter(isViewPropTypes)
    .forEach(path => {
      numMatchedPaths++;

      j(path.parent).replaceWith(j.identifier('ViewPropTypes'));
    });

  // Add ViewPropTypes import/require()
  if (numMatchedPaths > 0) {
    const fileUsesImports =
      root.find(j.CallExpression, { callee: { name: 'require' } }).length === 0;

    // Determine which kind of import/require() we should create based on file contents
    let useHasteModules = false;
    if (fileUsesImports) {
      useHasteModules =
        root.find(j.ImportSpecifier).filter(isReactNativeImport).length === 0;
    } else {
      useHasteModules =
        root
          .find(j.CallExpression, { callee: { name: 'require' } })
          .filter(isReactNativeRequire).length === 0;
    }

    // Create a require statement or an import, based on file convention
    let importOrRequireStatement;
    if (fileUsesImports) {
      const identifier = j.identifier('ViewPropTypes');
      const variable =
        useHasteModules === true
          ? j.importDefaultSpecifier(identifier)
          : j.importSpecifier(identifier);
      const source =
        useHasteModules === true ? 'ViewPropTypes' : 'react-native';

      importOrRequireStatement = j.importDeclaration(
        [variable],
        j.literal(source)
      );
    } else {
      if (useHasteModules === true) {
        importOrRequireStatement = j.template.statement`
          const ViewPropTypes = require('ViewPropTypes');
        `;
      } else {
        importOrRequireStatement = j.template.statement`
          const { ViewPropTypes } = require('react-native');
        `;
      }
    }

    // If the only View reference left is the import/require(), replace it
    // Else insert our new import/require() after it
    // Add one to avoid counting the import/require() statement itself
    const replaceExistingImportOrRequireStatement =
      viewReferenceCount <= numMatchedPaths + 1;

    if (fileUsesImports) {
      root
        .find(j.ImportDeclaration)
        .filter(isViewImport)
        .forEach(path => {
          // Differentiate between destructured and default import
          if (path.node.specifiers.length > 1) {
            if (useHasteModules) {
              // Insert after before removing to avoid an error
              j(path).insertAfter(importOrRequireStatement);

              if (replaceExistingImportOrRequireStatement) {
                // If this is the last reference to a destructured import, remove it
                // We can't replace in this case b'c the target/source is different
                path.node.specifiers = path.node.specifiers.filter(
                  specifier => specifier.local.name !== 'View'
                );
              }
            } else {
              if (replaceExistingImportOrRequireStatement) {
                const viewImport = path.node.specifiers.find(
                  specifier => specifier.local.name === 'View'
                );

                viewImport.local.name = 'ViewPropTypes';
              } else {
                path.node.specifiers.push(
                  j.importSpecifier(
                    j.identifier('ViewPropTypes'),
                    j.identifier('ViewPropTypes')
                  )
                );
              }
            }
          } else {
            if (replaceExistingImportOrRequireStatement) {
              j(path).replaceWith(importOrRequireStatement);
            } else {
              j(path).insertAfter(importOrRequireStatement);
            }
          }
        });
    } else {
      root
        .find(j.CallExpression, { callee: { name: 'require' } })
        .filter(isViewRequire)
        .forEach(path => {
          // Differentiate between destructured and default require()
          if (path.parent.node.id.type === 'ObjectPattern') {
            if (useHasteModules) {
              // Insert after before removing to avoid an error
              j(path.parent.parent).insertAfter(importOrRequireStatement);

              if (replaceExistingImportOrRequireStatement) {
                // If this is the last reference to a destructured import, remove it
                // We can't replace in this case b'c the target/source is different
                const variableDeclarator =
                  path.parent.parent.value.declarations[0];
                variableDeclarator.id.properties = variableDeclarator.id.properties.filter(
                  property => property.value.name !== 'View'
                );
              }
            } else {
              const objectPattern = path.parent;
              if (replaceExistingImportOrRequireStatement) {
                const property = objectPattern.node.id.properties.find(
                  property => property.value.name === 'View'
                );

                property.key.name = 'ViewPropTypes';
              } else {
                const property = j.property(
                  'init',
                  j.identifier('ViewPropTypes'),
                  j.identifier('ViewPropTypes')
                );
                property.shorthand = true;

                objectPattern.node.id.properties.push(property);
              }
            }
          } else {
            if (replaceExistingImportOrRequireStatement) {
              j(path.parent.parent).replaceWith(importOrRequireStatement);
            } else {
              j(path.parent.parent).insertAfter(importOrRequireStatement);
            }
          }
        });
    }
  }

  return numMatchedPaths > 0 ? root.toSource(printOptions) : null;
};