validateOptions.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. const _ = require('lodash');
  2. module.exports = function validateOptions(options) {
  3. if (_.isUndefined(options) || _.isNull(options)) {
  4. return false;
  5. }
  6. if (!_.isPlainObject(options)) {
  7. return reportError('Options should be an object.');
  8. }
  9. if (!_.isUndefined(options.order) && !_.isNull(options.order)) {
  10. const validatedOrder = validateOrder(options.order);
  11. const { isValid, message } = validatedOrder;
  12. if (!isValid) {
  13. return reportInvalidOption('order', message);
  14. }
  15. }
  16. if (!_.isUndefined(options['properties-order']) && !_.isNull(options['properties-order'])) {
  17. const validatedPropertiesOrder = validatePropertiesOrder(options['properties-order']);
  18. const { isValid, message } = validatedPropertiesOrder;
  19. if (!isValid) {
  20. return reportInvalidOption('properties-order', message);
  21. }
  22. }
  23. if (
  24. !_.isUndefined(options['unspecified-properties-position']) &&
  25. !_.isNull(options['unspecified-properties-position'])
  26. ) {
  27. const validatedUnspecifiedPropertiesPosition = validateUnspecifiedPropertiesPosition(
  28. options['unspecified-properties-position']
  29. );
  30. const { isValid, message } = validatedUnspecifiedPropertiesPosition;
  31. if (!isValid) {
  32. return reportInvalidOption('unspecified-properties-position', message);
  33. }
  34. }
  35. return true;
  36. };
  37. function reportError(errorMessage) {
  38. return `postcss-sorting: ${errorMessage}`;
  39. }
  40. function reportInvalidOption(optionName, optionError = 'Invalid value') {
  41. return reportError(`${optionName}: ${optionError}`);
  42. }
  43. function keywordsList(keywords) {
  44. return keywords.reduce(function(accumulator, value, index) {
  45. const comma = index === 0 ? '' : ', ';
  46. return accumulator + comma + value;
  47. }, '');
  48. }
  49. function validateOrder(options) {
  50. // Otherwise, begin checking array options
  51. if (!Array.isArray(options)) {
  52. return {
  53. isValid: false,
  54. message: 'Should be an array',
  55. };
  56. }
  57. const keywords = [
  58. 'custom-properties',
  59. 'dollar-variables',
  60. 'at-variables',
  61. 'declarations',
  62. 'rules',
  63. 'at-rules',
  64. ];
  65. // Every item in the array must be a certain string or an object
  66. // with a "type" property
  67. if (
  68. !options.every(item => {
  69. if (_.isString(item)) {
  70. return _.includes(keywords, item);
  71. }
  72. return _.isPlainObject(item) && !_.isUndefined(item.type);
  73. })
  74. ) {
  75. return {
  76. isValid: false,
  77. message: `Every item in the array must be an object with a "type" property, or one of keywords: ${keywordsList(
  78. keywords
  79. )}.`,
  80. };
  81. }
  82. const objectItems = options.filter(_.isPlainObject);
  83. let wrongObjectItem;
  84. if (
  85. !objectItems.every(item => {
  86. let result = true;
  87. if (item.type !== 'at-rule' && item.type !== 'rule') {
  88. wrongObjectItem = `"type" could be 'at-rule' or 'rule' only`;
  89. return false;
  90. }
  91. if (item.type === 'at-rule') {
  92. // if parameter is specified, name should be specified also
  93. if (!_.isUndefined(item.parameter) && _.isUndefined(item.name)) {
  94. wrongObjectItem = `"at-rule" with "parameter" should also has a "name"`;
  95. return false;
  96. }
  97. if (!_.isUndefined(item.hasBlock)) {
  98. result = item.hasBlock === true || item.hasBlock === false;
  99. }
  100. if (!_.isUndefined(item.name)) {
  101. result = _.isString(item.name) && item.name.length;
  102. }
  103. if (!_.isUndefined(item.parameter)) {
  104. result =
  105. (_.isString(item.parameter) && item.parameter.length) || _.isRegExp(item.parameter);
  106. }
  107. }
  108. if (item.type === 'rule') {
  109. if (!_.isUndefined(item.selector)) {
  110. result = (_.isString(item.selector) && item.selector.length) || _.isRegExp(item.selector);
  111. }
  112. }
  113. if (!result) {
  114. wrongObjectItem = `Following option is incorrect: ${JSON.stringify(item)}`;
  115. }
  116. return result;
  117. })
  118. ) {
  119. return {
  120. isValid: false,
  121. message: wrongObjectItem,
  122. };
  123. }
  124. return {
  125. isValid: true,
  126. };
  127. }
  128. function validatePropertiesOrder(options) {
  129. // Return true early if alphabetical
  130. if (options === 'alphabetical') {
  131. return {
  132. isValid: true,
  133. };
  134. }
  135. // Otherwise, begin checking array options
  136. if (!Array.isArray(options)) {
  137. return {
  138. isValid: false,
  139. message: 'Should be an array',
  140. };
  141. }
  142. // Every item in the array must be a string
  143. if (!options.every(item => _.isString(item))) {
  144. return {
  145. isValid: false,
  146. message: 'Array should contain strings only',
  147. };
  148. }
  149. return {
  150. isValid: true,
  151. };
  152. }
  153. function validateUnspecifiedPropertiesPosition(options) {
  154. const keywords = ['top', 'bottom', 'bottomAlphabetical'];
  155. if (_.isString(options) && _.includes(keywords, options)) {
  156. return {
  157. isValid: true,
  158. };
  159. }
  160. return {
  161. isValid: false,
  162. message: `Option should be one of the following values: ${keywordsList(keywords)}.`,
  163. };
  164. }