split-platform-components.js 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /**
  2. * @fileoverview Android and IOS components should be
  3. * used in platform specific React Native components.
  4. * @author Tom Hastjarjanto
  5. */
  6. 'use strict';
  7. function create(context) {
  8. let reactComponents = [];
  9. const androidMessage = 'Android components should be placed in android files';
  10. const iosMessage = 'IOS components should be placed in ios files';
  11. const conflictMessage = 'IOS and Android components can\'t be mixed';
  12. const iosPathRegex = context.options[0] && context.options[0].iosPathRegex
  13. ? new RegExp(context.options[0].iosPathRegex)
  14. : /\.ios\.[j|t]sx?$/;
  15. const androidPathRegex = context.options[0] && context.options[0].androidPathRegex
  16. ? new RegExp(context.options[0].androidPathRegex)
  17. : /\.android\.[j|t]sx?$/;
  18. function getName(node) {
  19. if (node.type === 'Property') {
  20. const key = node.key || node.argument;
  21. return key.type === 'Identifier' ? key.name : key.value;
  22. } if (node.type === 'Identifier') {
  23. return node.name;
  24. }
  25. }
  26. function hasNodeWithName(nodes, name) {
  27. return nodes.some((node) => {
  28. const nodeName = getName(node);
  29. return nodeName && nodeName.includes(name);
  30. });
  31. }
  32. function reportErrors(components, filename) {
  33. const containsAndroidAndIOS = (
  34. hasNodeWithName(components, 'IOS')
  35. && hasNodeWithName(components, 'Android')
  36. );
  37. components.forEach((node) => {
  38. const propName = getName(node);
  39. if (propName.includes('IOS') && !filename.match(iosPathRegex)) {
  40. context.report(node, containsAndroidAndIOS ? conflictMessage : iosMessage);
  41. }
  42. if (propName.includes('Android') && !filename.match(androidPathRegex)) {
  43. context.report(node, containsAndroidAndIOS ? conflictMessage : androidMessage);
  44. }
  45. });
  46. }
  47. return {
  48. VariableDeclarator: function (node) {
  49. const destructuring = node.init && node.id && node.id.type === 'ObjectPattern';
  50. const statelessDestructuring = destructuring && node.init.name === 'React';
  51. if (destructuring && statelessDestructuring) {
  52. reactComponents = reactComponents.concat(node.id.properties);
  53. }
  54. },
  55. ImportDeclaration: function (node) {
  56. if (node.source.value === 'react-native') {
  57. node.specifiers.forEach((importSpecifier) => {
  58. if (importSpecifier.type === 'ImportSpecifier') {
  59. reactComponents = reactComponents.concat(importSpecifier.imported);
  60. }
  61. });
  62. }
  63. },
  64. 'Program:exit': function () {
  65. const filename = context.getFilename();
  66. reportErrors(reactComponents, filename);
  67. },
  68. };
  69. }
  70. module.exports = {
  71. meta: {
  72. fixable: 'code',
  73. schema: [{
  74. type: 'object',
  75. properties: {
  76. androidPathRegex: {
  77. type: 'string',
  78. },
  79. iosPathRegex: {
  80. type: 'string',
  81. },
  82. },
  83. additionalProperties: false,
  84. }],
  85. },
  86. create,
  87. };