index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // @ts-nocheck
  2. 'use strict';
  3. const _ = require('lodash');
  4. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
  5. const parseSelector = require('../../utils/parseSelector');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const styleSearch = require('style-search');
  9. const validateOptions = require('../../utils/validateOptions');
  10. const ruleName = 'selector-attribute-brackets-space-inside';
  11. const messages = ruleMessages(ruleName, {
  12. expectedOpening: 'Expected single space after "["',
  13. rejectedOpening: 'Unexpected whitespace after "["',
  14. expectedClosing: 'Expected single space before "]"',
  15. rejectedClosing: 'Unexpected whitespace before "]"',
  16. });
  17. function rule(expectation, options, context) {
  18. return (root, result) => {
  19. const validOptions = validateOptions(result, ruleName, {
  20. actual: expectation,
  21. possible: ['always', 'never'],
  22. });
  23. if (!validOptions) {
  24. return;
  25. }
  26. root.walkRules((rule) => {
  27. if (!isStandardSyntaxRule(rule)) {
  28. return;
  29. }
  30. if (!rule.selector.includes('[')) {
  31. return;
  32. }
  33. const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector;
  34. let hasFixed;
  35. const fixedSelector = parseSelector(selector, result, rule, (selectorTree) => {
  36. selectorTree.walkAttributes((attributeNode) => {
  37. const attributeSelectorString = attributeNode.toString();
  38. styleSearch({ source: attributeSelectorString, target: '[' }, (match) => {
  39. const nextCharIsSpace = attributeSelectorString[match.startIndex + 1] === ' ';
  40. const index = attributeNode.sourceIndex + match.startIndex + 1;
  41. if (nextCharIsSpace && expectation === 'never') {
  42. if (context.fix) {
  43. hasFixed = true;
  44. fixBefore(attributeNode);
  45. return;
  46. }
  47. complain(messages.rejectedOpening, index);
  48. }
  49. if (!nextCharIsSpace && expectation === 'always') {
  50. if (context.fix) {
  51. hasFixed = true;
  52. fixBefore(attributeNode);
  53. return;
  54. }
  55. complain(messages.expectedOpening, index);
  56. }
  57. });
  58. styleSearch({ source: attributeSelectorString, target: ']' }, (match) => {
  59. const prevCharIsSpace = attributeSelectorString[match.startIndex - 1] === ' ';
  60. const index = attributeNode.sourceIndex + match.startIndex - 1;
  61. if (prevCharIsSpace && expectation === 'never') {
  62. if (context.fix) {
  63. hasFixed = true;
  64. fixAfter(attributeNode);
  65. return;
  66. }
  67. complain(messages.rejectedClosing, index);
  68. }
  69. if (!prevCharIsSpace && expectation === 'always') {
  70. if (context.fix) {
  71. hasFixed = true;
  72. fixAfter(attributeNode);
  73. return;
  74. }
  75. complain(messages.expectedClosing, index);
  76. }
  77. });
  78. });
  79. });
  80. if (hasFixed) {
  81. if (!rule.raws.selector) {
  82. rule.selector = fixedSelector;
  83. } else {
  84. rule.raws.selector.raw = fixedSelector;
  85. }
  86. }
  87. function complain(message, index) {
  88. report({
  89. message,
  90. index,
  91. result,
  92. ruleName,
  93. node: rule,
  94. });
  95. }
  96. });
  97. };
  98. function fixBefore(attributeNode) {
  99. const rawAttrBefore = _.get(attributeNode, 'raws.spaces.attribute.before');
  100. const { attrBefore, setAttrBefore } = rawAttrBefore
  101. ? {
  102. attrBefore: rawAttrBefore,
  103. setAttrBefore(fixed) {
  104. attributeNode.raws.spaces.attribute.before = fixed;
  105. },
  106. }
  107. : {
  108. attrBefore: _.get(attributeNode, 'spaces.attribute.before', ''),
  109. setAttrBefore(fixed) {
  110. _.set(attributeNode, 'spaces.attribute.before', fixed);
  111. },
  112. };
  113. if (expectation === 'always') {
  114. setAttrBefore(attrBefore.replace(/^\s*/, ' '));
  115. } else if (expectation === 'never') {
  116. setAttrBefore(attrBefore.replace(/^\s*/, ''));
  117. }
  118. }
  119. function fixAfter(attributeNode) {
  120. let key;
  121. if (attributeNode.operator) {
  122. if (attributeNode.insensitive) {
  123. key = 'insensitive';
  124. } else {
  125. key = 'value';
  126. }
  127. } else {
  128. key = 'attribute';
  129. }
  130. const rawAfter = _.get(attributeNode, `raws.spaces.${key}.after`);
  131. const { after, setAfter } = rawAfter
  132. ? {
  133. after: rawAfter,
  134. setAfter(fixed) {
  135. attributeNode.raws.spaces[key].after = fixed;
  136. },
  137. }
  138. : {
  139. after: _.get(attributeNode, `spaces.${key}.after`, ''),
  140. setAfter(fixed) {
  141. _.set(attributeNode, `spaces.${key}.after`, fixed);
  142. },
  143. };
  144. if (expectation === 'always') {
  145. setAfter(after.replace(/\s*$/, ' '));
  146. } else if (expectation === 'never') {
  147. setAfter(after.replace(/\s*$/, ''));
  148. }
  149. }
  150. }
  151. rule.ruleName = ruleName;
  152. rule.messages = messages;
  153. module.exports = rule;