jsx-props-no-multi-spaces.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /**
  2. * @fileoverview Disallow multiple spaces between inline JSX props
  3. * @author Adrian Moennich
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const eslintUtil = require('../util/eslint');
  8. const report = require('../util/report');
  9. const propsUtil = require('../util/props');
  10. const getSourceCode = eslintUtil.getSourceCode;
  11. const getText = eslintUtil.getText;
  12. // ------------------------------------------------------------------------------
  13. // Rule Definition
  14. // ------------------------------------------------------------------------------
  15. const messages = {
  16. noLineGap: 'Expected no line gap between “{{prop1}}” and “{{prop2}}”',
  17. onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”',
  18. };
  19. /** @type {import('eslint').Rule.RuleModule} */
  20. module.exports = {
  21. meta: {
  22. docs: {
  23. description: 'Disallow multiple spaces between inline JSX props',
  24. category: 'Stylistic Issues',
  25. recommended: false,
  26. url: docsUrl('jsx-props-no-multi-spaces'),
  27. },
  28. fixable: 'code',
  29. messages,
  30. schema: [],
  31. },
  32. create(context) {
  33. const sourceCode = getSourceCode(context);
  34. function getPropName(propNode) {
  35. switch (propNode.type) {
  36. case 'JSXSpreadAttribute':
  37. return getText(context, propNode.argument);
  38. case 'JSXIdentifier':
  39. return propNode.name;
  40. case 'JSXMemberExpression':
  41. return `${getPropName(propNode.object)}.${propNode.property.name}`;
  42. default:
  43. return propNode.name
  44. ? propNode.name.name
  45. : `${getText(context, propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser
  46. }
  47. }
  48. // First and second must be adjacent nodes
  49. function hasEmptyLines(first, second) {
  50. const comments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(second) : [];
  51. const nodes = [].concat(first, comments, second);
  52. for (let i = 1; i < nodes.length; i += 1) {
  53. const prev = nodes[i - 1];
  54. const curr = nodes[i];
  55. if (curr.loc.start.line - prev.loc.end.line >= 2) {
  56. return true;
  57. }
  58. }
  59. return false;
  60. }
  61. function checkSpacing(prev, node) {
  62. if (hasEmptyLines(prev, node)) {
  63. report(context, messages.noLineGap, 'noLineGap', {
  64. node,
  65. data: {
  66. prop1: getPropName(prev),
  67. prop2: getPropName(node),
  68. },
  69. });
  70. }
  71. if (prev.loc.end.line !== node.loc.end.line) {
  72. return;
  73. }
  74. const between = getSourceCode(context).text.slice(prev.range[1], node.range[0]);
  75. if (between !== ' ') {
  76. report(context, messages.onlyOneSpace, 'onlyOneSpace', {
  77. node,
  78. data: {
  79. prop1: getPropName(prev),
  80. prop2: getPropName(node),
  81. },
  82. fix(fixer) {
  83. return fixer.replaceTextRange([prev.range[1], node.range[0]], ' ');
  84. },
  85. });
  86. }
  87. }
  88. function containsGenericType(node) {
  89. const nodeTypeArguments = propsUtil.getTypeArguments(node);
  90. if (typeof nodeTypeArguments === 'undefined') {
  91. return false;
  92. }
  93. return nodeTypeArguments.type === 'TSTypeParameterInstantiation';
  94. }
  95. function getGenericNode(node) {
  96. const name = node.name;
  97. if (containsGenericType(node)) {
  98. const nodeTypeArguments = propsUtil.getTypeArguments(node);
  99. return Object.assign(
  100. {},
  101. node,
  102. {
  103. range: [
  104. name.range[0],
  105. nodeTypeArguments.range[1],
  106. ],
  107. }
  108. );
  109. }
  110. return name;
  111. }
  112. return {
  113. JSXOpeningElement(node) {
  114. node.attributes.reduce((prev, prop) => {
  115. checkSpacing(prev, prop);
  116. return prop;
  117. }, getGenericNode(node));
  118. },
  119. };
  120. },
  121. };