index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // @ts-nocheck
  2. 'use strict';
  3. const _ = require('lodash');
  4. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  5. const declarationValueIndex = require('../../utils/declarationValueIndex');
  6. const isWhitespace = require('../../utils/isWhitespace');
  7. const report = require('../../utils/report');
  8. const ruleMessages = require('../../utils/ruleMessages');
  9. const styleSearch = require('style-search');
  10. const validateOptions = require('../../utils/validateOptions');
  11. const ruleName = 'function-whitespace-after';
  12. const messages = ruleMessages(ruleName, {
  13. expected: 'Expected whitespace after ")"',
  14. rejected: 'Unexpected whitespace after ")"',
  15. });
  16. const ACCEPTABLE_AFTER_CLOSING_PAREN = new Set([')', ',', '}', ':', '/', undefined]);
  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. function check(node, value, getIndex, fix) {
  27. styleSearch(
  28. {
  29. source: value,
  30. target: ')',
  31. functionArguments: 'only',
  32. },
  33. (match) => {
  34. checkClosingParen(value, match.startIndex + 1, node, getIndex, fix);
  35. },
  36. );
  37. }
  38. function checkClosingParen(source, index, node, getIndex, fix) {
  39. const nextChar = source[index];
  40. if (expectation === 'always') {
  41. // Allow for the next character to be a single empty space,
  42. // another closing parenthesis, a comma, or the end of the value
  43. if (nextChar === ' ') {
  44. return;
  45. }
  46. if (nextChar === '\n') {
  47. return;
  48. }
  49. if (source.substr(index, 2) === '\r\n') {
  50. return;
  51. }
  52. if (ACCEPTABLE_AFTER_CLOSING_PAREN.has(nextChar)) {
  53. return;
  54. }
  55. if (fix) {
  56. fix(index);
  57. return;
  58. }
  59. report({
  60. message: messages.expected,
  61. node,
  62. index: getIndex(node) + index,
  63. result,
  64. ruleName,
  65. });
  66. } else if (expectation === 'never') {
  67. if (isWhitespace(nextChar)) {
  68. if (fix) {
  69. fix(index);
  70. return;
  71. }
  72. report({
  73. message: messages.rejected,
  74. node,
  75. index: getIndex(node) + index,
  76. result,
  77. ruleName,
  78. });
  79. }
  80. }
  81. }
  82. function createFixer(value) {
  83. let fixed = '';
  84. let lastIndex = 0;
  85. let applyFix;
  86. if (expectation === 'always') {
  87. applyFix = (index) => {
  88. // eslint-disable-next-line prefer-template
  89. fixed += value.slice(lastIndex, index) + ' ';
  90. lastIndex = index;
  91. };
  92. } else if (expectation === 'never') {
  93. applyFix = (index) => {
  94. let whitespaceEndIndex = index + 1;
  95. while (whitespaceEndIndex < value.length && isWhitespace(value[whitespaceEndIndex])) {
  96. whitespaceEndIndex++;
  97. }
  98. fixed += value.slice(lastIndex, index);
  99. lastIndex = whitespaceEndIndex;
  100. };
  101. }
  102. return {
  103. applyFix,
  104. get hasFixed() {
  105. return Boolean(lastIndex);
  106. },
  107. get fixed() {
  108. return fixed + value.slice(lastIndex);
  109. },
  110. };
  111. }
  112. root.walkAtRules(/^import$/i, (atRule) => {
  113. const param = _.get(atRule, 'raws.params.raw', atRule.params);
  114. const fixer = context.fix && createFixer(param);
  115. check(atRule, param, atRuleParamIndex, fixer && fixer.applyFix);
  116. if (fixer && fixer.hasFixed) {
  117. if (atRule.raws.params) {
  118. atRule.raws.params.raw = fixer.fixed;
  119. } else {
  120. atRule.params = fixer.fixed;
  121. }
  122. }
  123. });
  124. root.walkDecls((decl) => {
  125. const value = _.get(decl, 'raws.value.raw', decl.value);
  126. const fixer = context.fix && createFixer(value);
  127. check(decl, value, declarationValueIndex, fixer && fixer.applyFix);
  128. if (fixer && fixer.hasFixed) {
  129. if (decl.raws.value) {
  130. decl.raws.value.raw = fixer.fixed;
  131. } else {
  132. decl.value = fixer.fixed;
  133. }
  134. }
  135. });
  136. };
  137. }
  138. rule.ruleName = ruleName;
  139. rule.messages = messages;
  140. module.exports = rule;