index.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. // @ts-nocheck
  2. 'use strict';
  3. const _ = require('lodash');
  4. const declarationValueIndex = require('../../utils/declarationValueIndex');
  5. const isSingleLineString = require('../../utils/isSingleLineString');
  6. const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
  7. const report = require('../../utils/report');
  8. const ruleMessages = require('../../utils/ruleMessages');
  9. const validateOptions = require('../../utils/validateOptions');
  10. const valueParser = require('postcss-value-parser');
  11. const ruleName = 'function-parentheses-newline-inside';
  12. const messages = ruleMessages(ruleName, {
  13. expectedOpening: 'Expected newline after "("',
  14. expectedClosing: 'Expected newline before ")"',
  15. expectedOpeningMultiLine: 'Expected newline after "(" in a multi-line function',
  16. rejectedOpeningMultiLine: 'Unexpected whitespace after "(" in a multi-line function',
  17. expectedClosingMultiLine: 'Expected newline before ")" in a multi-line function',
  18. rejectedClosingMultiLine: 'Unexpected whitespace before ")" in a multi-line function',
  19. });
  20. function rule(expectation, options, context) {
  21. return (root, result) => {
  22. const validOptions = validateOptions(result, ruleName, {
  23. actual: expectation,
  24. possible: ['always', 'always-multi-line', 'never-multi-line'],
  25. });
  26. if (!validOptions) {
  27. return;
  28. }
  29. root.walkDecls((decl) => {
  30. if (!decl.value.includes('(')) {
  31. return;
  32. }
  33. let hasFixed = false;
  34. const declValue = _.get(decl, 'raws.value.raw', decl.value);
  35. const parsedValue = valueParser(declValue);
  36. parsedValue.walk((valueNode) => {
  37. if (valueNode.type !== 'function') {
  38. return;
  39. }
  40. if (!isStandardSyntaxFunction(valueNode)) {
  41. return;
  42. }
  43. const functionString = valueParser.stringify(valueNode);
  44. const isMultiLine = !isSingleLineString(functionString);
  45. function containsNewline(str) {
  46. return str.includes('\n');
  47. }
  48. // Check opening ...
  49. const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1;
  50. const checkBefore = getCheckBefore(valueNode);
  51. if (expectation === 'always' && !containsNewline(checkBefore)) {
  52. if (context.fix) {
  53. hasFixed = true;
  54. fixBeforeForAlways(valueNode, context.newline);
  55. } else {
  56. complain(messages.expectedOpening, openingIndex);
  57. }
  58. }
  59. if (isMultiLine && expectation === 'always-multi-line' && !containsNewline(checkBefore)) {
  60. if (context.fix) {
  61. hasFixed = true;
  62. fixBeforeForAlways(valueNode, context.newline);
  63. } else {
  64. complain(messages.expectedOpeningMultiLine, openingIndex);
  65. }
  66. }
  67. if (isMultiLine && expectation === 'never-multi-line' && checkBefore !== '') {
  68. if (context.fix) {
  69. hasFixed = true;
  70. fixBeforeForNever(valueNode);
  71. } else {
  72. complain(messages.rejectedOpeningMultiLine, openingIndex);
  73. }
  74. }
  75. // Check closing ...
  76. const closingIndex = valueNode.sourceIndex + functionString.length - 2;
  77. const checkAfter = getCheckAfter(valueNode);
  78. if (expectation === 'always' && !containsNewline(checkAfter)) {
  79. if (context.fix) {
  80. hasFixed = true;
  81. fixAfterForAlways(valueNode, context.newline);
  82. } else {
  83. complain(messages.expectedClosing, closingIndex);
  84. }
  85. }
  86. if (isMultiLine && expectation === 'always-multi-line' && !containsNewline(checkAfter)) {
  87. if (context.fix) {
  88. hasFixed = true;
  89. fixAfterForAlways(valueNode, context.newline);
  90. } else {
  91. complain(messages.expectedClosingMultiLine, closingIndex);
  92. }
  93. }
  94. if (isMultiLine && expectation === 'never-multi-line' && checkAfter !== '') {
  95. if (context.fix) {
  96. hasFixed = true;
  97. fixAfterForNever(valueNode);
  98. } else {
  99. complain(messages.rejectedClosingMultiLine, closingIndex);
  100. }
  101. }
  102. });
  103. if (hasFixed) {
  104. if (!decl.raws.value) {
  105. decl.value = parsedValue.toString();
  106. } else {
  107. decl.raws.value.raw = parsedValue.toString();
  108. }
  109. }
  110. function complain(message, offset) {
  111. report({
  112. ruleName,
  113. result,
  114. message,
  115. node: decl,
  116. index: declarationValueIndex(decl) + offset,
  117. });
  118. }
  119. });
  120. };
  121. }
  122. function getCheckBefore(valueNode) {
  123. let before = valueNode.before;
  124. for (const node of valueNode.nodes) {
  125. if (node.type === 'comment') {
  126. continue;
  127. }
  128. if (node.type === 'space') {
  129. before += node.value;
  130. continue;
  131. }
  132. break;
  133. }
  134. return before;
  135. }
  136. function getCheckAfter(valueNode) {
  137. let after = '';
  138. for (const node of valueNode.nodes.slice().reverse()) {
  139. if (node.type === 'comment') {
  140. continue;
  141. }
  142. if (node.type === 'space') {
  143. after = node.value + after;
  144. continue;
  145. }
  146. break;
  147. }
  148. after += valueNode.after;
  149. return after;
  150. }
  151. function fixBeforeForAlways(valueNode, newline) {
  152. let target;
  153. for (const node of valueNode.nodes) {
  154. if (node.type === 'comment') {
  155. continue;
  156. }
  157. if (node.type === 'space') {
  158. target = node;
  159. continue;
  160. }
  161. break;
  162. }
  163. if (target) {
  164. target.value = newline + target.value;
  165. } else {
  166. valueNode.before = newline + valueNode.before;
  167. }
  168. }
  169. function fixBeforeForNever(valueNode) {
  170. valueNode.before = '';
  171. for (const node of valueNode.nodes) {
  172. if (node.type === 'comment') {
  173. continue;
  174. }
  175. if (node.type === 'space') {
  176. node.value = '';
  177. continue;
  178. }
  179. break;
  180. }
  181. }
  182. function fixAfterForAlways(valueNode, newline) {
  183. valueNode.after = newline + valueNode.after;
  184. }
  185. function fixAfterForNever(valueNode) {
  186. valueNode.after = '';
  187. for (const node of valueNode.nodes.slice().reverse()) {
  188. if (node.type === 'comment') {
  189. continue;
  190. }
  191. if (node.type === 'space') {
  192. node.value = '';
  193. continue;
  194. }
  195. break;
  196. }
  197. }
  198. rule.ruleName = ruleName;
  199. rule.messages = messages;
  200. module.exports = rule;