You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

94 lines
2.7 KiB

  1. /**
  2. * @fileoverview Prevent extra closing tags for components without children
  3. * @author Yannick Croissant
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const jsxUtil = require('../util/jsx');
  8. // ------------------------------------------------------------------------------
  9. // Rule Definition
  10. // ------------------------------------------------------------------------------
  11. const optionDefaults = {component: true, html: true};
  12. module.exports = {
  13. meta: {
  14. docs: {
  15. description: 'Prevent extra closing tags for components without children',
  16. category: 'Stylistic Issues',
  17. recommended: false,
  18. url: docsUrl('self-closing-comp')
  19. },
  20. fixable: 'code',
  21. schema: [{
  22. type: 'object',
  23. properties: {
  24. component: {
  25. default: optionDefaults.component,
  26. type: 'boolean'
  27. },
  28. html: {
  29. default: optionDefaults.html,
  30. type: 'boolean'
  31. }
  32. },
  33. additionalProperties: false
  34. }]
  35. },
  36. create: function(context) {
  37. function isComponent(node) {
  38. return node.name && node.name.type === 'JSXIdentifier' && !jsxUtil.isDOMComponent(node);
  39. }
  40. function hasChildren(node) {
  41. const childrens = node.parent.children;
  42. if (
  43. !childrens.length ||
  44. (childrens.length === 1 && (childrens[0].type === 'Literal' || childrens[0].type === 'JSXText') && !childrens[0].value.replace(/(?!\xA0)\s/g, ''))
  45. ) {
  46. return false;
  47. }
  48. return true;
  49. }
  50. function isShouldBeSelfClosed(node) {
  51. const configuration = Object.assign({}, optionDefaults, context.options[0]);
  52. return (
  53. configuration.component && isComponent(node) ||
  54. configuration.html && jsxUtil.isDOMComponent(node)
  55. ) && !node.selfClosing && !hasChildren(node);
  56. }
  57. // --------------------------------------------------------------------------
  58. // Public
  59. // --------------------------------------------------------------------------
  60. return {
  61. JSXOpeningElement: function(node) {
  62. if (!isShouldBeSelfClosed(node)) {
  63. return;
  64. }
  65. context.report({
  66. node: node,
  67. message: 'Empty components are self-closing',
  68. fix: function(fixer) {
  69. // Represents the last character of the JSXOpeningElement, the '>' character
  70. const openingElementEnding = node.range[1] - 1;
  71. // Represents the last character of the JSXClosingElement, the '>' character
  72. const closingElementEnding = node.parent.closingElement.range[1];
  73. // Replace />.*<\/.*>/ with '/>'
  74. const range = [openingElementEnding, closingElementEnding];
  75. return fixer.replaceTextRange(range, ' />');
  76. }
  77. });
  78. }
  79. };
  80. }
  81. };