Intellicode/eslint-plugin-react-native

View on GitHub
lib/rules/split-platform-components.js

Summary

Maintainability
C
1 day
Test Coverage
/**
 * @fileoverview Android and IOS components should be
 * used in platform specific React Native components.
 * @author Tom Hastjarjanto
 */

'use strict';

function create(context) {
  let reactComponents = [];
  const androidMessage = 'Android components should be placed in android files';
  const iosMessage = 'IOS components should be placed in ios files';
  const conflictMessage = 'IOS and Android components can\'t be mixed';
  const iosPathRegex = context.options[0] && context.options[0].iosPathRegex
    ? new RegExp(context.options[0].iosPathRegex)
    : /\.ios\.[j|t]sx?$/;
  const androidPathRegex = context.options[0] && context.options[0].androidPathRegex
    ? new RegExp(context.options[0].androidPathRegex)
    : /\.android\.[j|t]sx?$/;

  function getName(node) {
    if (node.type === 'Property') {
      const key = node.key || node.argument;
      return key.type === 'Identifier' ? key.name : key.value;
    } if (node.type === 'Identifier') {
      return node.name;
    }
  }

  function hasNodeWithName(nodes, name) {
    return nodes.some((node) => {
      const nodeName = getName(node);
      return nodeName && nodeName.includes(name);
    });
  }

  function reportErrors(components, filename) {
    const containsAndroidAndIOS = (
      hasNodeWithName(components, 'IOS')
      && hasNodeWithName(components, 'Android')
    );

    components.forEach((node) => {
      const propName = getName(node);

      if (propName.includes('IOS') && !filename.match(iosPathRegex)) {
        context.report(node, containsAndroidAndIOS ? conflictMessage : iosMessage);
      }

      if (propName.includes('Android') && !filename.match(androidPathRegex)) {
        context.report(node, containsAndroidAndIOS ? conflictMessage : androidMessage);
      }
    });
  }

  return {
    VariableDeclarator: function (node) {
      const destructuring = node.init && node.id && node.id.type === 'ObjectPattern';
      const statelessDestructuring = destructuring && node.init.name === 'React';
      if (destructuring && statelessDestructuring) {
        reactComponents = reactComponents.concat(node.id.properties);
      }
    },
    ImportDeclaration: function (node) {
      if (node.source.value === 'react-native') {
        node.specifiers.forEach((importSpecifier) => {
          if (importSpecifier.type === 'ImportSpecifier') {
            reactComponents = reactComponents.concat(importSpecifier.imported);
          }
        });
      }
    },
    'Program:exit': function () {
      const filename = context.getFilename();
      reportErrors(reactComponents, filename);
    },
  };
}

module.exports = {
  meta: {
    fixable: 'code',
    schema: [{
      type: 'object',
      properties: {
        androidPathRegex: {
          type: 'string',
        },
        iosPathRegex: {
          type: 'string',
        },
      },
      additionalProperties: false,
    }],
  },
  create,
};