jsx-child-element-spacing.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. 'use strict';
  2. const docsUrl = require('../util/docsUrl');
  3. // This list is taken from https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
  4. const INLINE_ELEMENTS = new Set([
  5. 'a',
  6. 'abbr',
  7. 'acronym',
  8. 'b',
  9. 'bdo',
  10. 'big',
  11. 'br',
  12. 'button',
  13. 'cite',
  14. 'code',
  15. 'dfn',
  16. 'em',
  17. 'i',
  18. 'img',
  19. 'input',
  20. 'kbd',
  21. 'label',
  22. 'map',
  23. 'object',
  24. 'q',
  25. 'samp',
  26. 'script',
  27. 'select',
  28. 'small',
  29. 'span',
  30. 'strong',
  31. 'sub',
  32. 'sup',
  33. 'textarea',
  34. 'tt',
  35. 'var'
  36. ]);
  37. module.exports = {
  38. meta: {
  39. docs: {
  40. description: 'Ensures inline tags are not rendered without spaces between them',
  41. category: 'Stylistic Issues',
  42. recommended: false,
  43. url: docsUrl('jsx-child-element-spacing')
  44. },
  45. fixable: false,
  46. schema: [
  47. {
  48. type: 'object',
  49. properties: {},
  50. default: {},
  51. additionalProperties: false
  52. }
  53. ]
  54. },
  55. create: function (context) {
  56. const elementName = node => (
  57. node.openingElement &&
  58. node.openingElement.name &&
  59. node.openingElement.name.type === 'JSXIdentifier' &&
  60. node.openingElement.name.name
  61. );
  62. const isInlineElement = node => (
  63. node.type === 'JSXElement' &&
  64. INLINE_ELEMENTS.has(elementName(node))
  65. );
  66. const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/;
  67. const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/;
  68. return {
  69. JSXElement: function(node) {
  70. let lastChild = null;
  71. let child = null;
  72. (node.children.concat([null])).forEach(nextChild => {
  73. if (
  74. (lastChild || nextChild) &&
  75. (!lastChild || isInlineElement(lastChild)) &&
  76. (child && (child.type === 'Literal' || child.type === 'JSXText')) &&
  77. (!nextChild || isInlineElement(nextChild)) &&
  78. true
  79. ) {
  80. if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
  81. context.report({
  82. node: lastChild,
  83. loc: lastChild.loc.end,
  84. message: `Ambiguous spacing after previous element ${elementName(lastChild)}`
  85. });
  86. } else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
  87. context.report({
  88. node: nextChild,
  89. loc: nextChild.loc.start,
  90. message: `Ambiguous spacing before next element ${elementName(nextChild)}`
  91. });
  92. }
  93. }
  94. lastChild = child;
  95. child = nextChild;
  96. });
  97. }
  98. };
  99. }
  100. };