lintPostcssResult.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. 'use strict';
  2. const assignDisabledRanges = require('./assignDisabledRanges');
  3. const getOsEol = require('./utils/getOsEol');
  4. const reportUnknownRuleNames = require('./reportUnknownRuleNames');
  5. const rules = require('./rules');
  6. const getStylelintRule = require('./utils/getStylelintRule');
  7. const { DEFAULT_CONFIGURATION_COMMENT } = require('./utils/configurationComment');
  8. /** @typedef {import('stylelint').LinterOptions} LinterOptions */
  9. /** @typedef {import('stylelint').PostcssResult} PostcssResult */
  10. /** @typedef {import('stylelint').Config} StylelintConfig */
  11. /**
  12. * @param {LinterOptions} stylelintOptions
  13. * @param {PostcssResult} postcssResult
  14. * @param {StylelintConfig} config
  15. * @returns {Promise<any>}
  16. */
  17. module.exports = function lintPostcssResult(stylelintOptions, postcssResult, config) {
  18. postcssResult.stylelint.ruleSeverities = {};
  19. postcssResult.stylelint.customMessages = {};
  20. postcssResult.stylelint.ruleMetadata = {};
  21. postcssResult.stylelint.stylelintError = false;
  22. postcssResult.stylelint.stylelintWarning = false;
  23. postcssResult.stylelint.quiet = config.quiet;
  24. postcssResult.stylelint.config = config;
  25. /** @type {string | undefined} */
  26. let newline;
  27. const postcssDoc = postcssResult.root;
  28. if (postcssDoc) {
  29. if (!('type' in postcssDoc)) {
  30. throw new Error('Unexpected Postcss root object!');
  31. }
  32. const newlineMatch = postcssDoc.source && postcssDoc.source.input.css.match(/\r?\n/);
  33. newline = newlineMatch ? newlineMatch[0] : getOsEol();
  34. assignDisabledRanges(postcssDoc, postcssResult);
  35. }
  36. const isFileFixCompatible = isFixCompatible(postcssResult);
  37. if (!isFileFixCompatible) {
  38. postcssResult.stylelint.disableWritingFix = true;
  39. }
  40. const postcssRoots = /** @type {import('postcss').Root[]} */ (
  41. postcssDoc && postcssDoc.constructor.name === 'Document' ? postcssDoc.nodes : [postcssDoc]
  42. );
  43. // Promises for the rules. Although the rule code runs synchronously now,
  44. // the use of Promises makes it compatible with the possibility of async
  45. // rules down the line.
  46. /** @type {Array<Promise<any>>} */
  47. const performRules = [];
  48. const rulesOrder = Object.keys(rules);
  49. const ruleNames = config.rules
  50. ? Object.keys(config.rules).sort((a, b) => rulesOrder.indexOf(a) - rulesOrder.indexOf(b))
  51. : [];
  52. for (const ruleName of ruleNames) {
  53. const ruleFunction = getStylelintRule(ruleName, config);
  54. if (ruleFunction === undefined) {
  55. performRules.push(
  56. Promise.all(
  57. postcssRoots.map((postcssRoot) =>
  58. reportUnknownRuleNames(ruleName, postcssRoot, postcssResult),
  59. ),
  60. ),
  61. );
  62. continue;
  63. }
  64. const ruleSettings = config.rules && config.rules[ruleName];
  65. if (ruleSettings === null || ruleSettings[0] === null) {
  66. continue;
  67. }
  68. if (
  69. ruleFunction.meta &&
  70. ruleFunction.meta.deprecated &&
  71. !stylelintOptions.quietDeprecationWarnings
  72. ) {
  73. warnDeprecatedRule(postcssResult, ruleName);
  74. }
  75. const primaryOption = ruleSettings[0];
  76. const secondaryOptions = ruleSettings[1];
  77. // Log the rule's severity in the PostCSS result
  78. const defaultSeverity = config.defaultSeverity || 'error';
  79. // disableFix in secondary option
  80. const disableFix = (secondaryOptions && secondaryOptions.disableFix === true) || false;
  81. postcssResult.stylelint.ruleSeverities[ruleName] =
  82. (secondaryOptions && secondaryOptions.severity) || defaultSeverity;
  83. postcssResult.stylelint.customMessages[ruleName] = secondaryOptions && secondaryOptions.message;
  84. postcssResult.stylelint.ruleMetadata[ruleName] = ruleFunction.meta || {};
  85. performRules.push(
  86. Promise.all(
  87. postcssRoots.map((postcssRoot) =>
  88. ruleFunction(primaryOption, secondaryOptions, {
  89. configurationComment: config.configurationComment || DEFAULT_CONFIGURATION_COMMENT,
  90. fix:
  91. !disableFix &&
  92. stylelintOptions.fix &&
  93. // Next two conditionals are temporary measures until #2643 is resolved
  94. isFileFixCompatible &&
  95. !postcssResult.stylelint.disabledRanges[ruleName],
  96. newline,
  97. })(postcssRoot, postcssResult),
  98. ),
  99. ),
  100. );
  101. }
  102. return Promise.all(performRules);
  103. };
  104. /**
  105. * There are currently some bugs in the autofixer of Stylelint.
  106. * The autofixer does not yet adhere to stylelint-disable comments, so if there are disabled
  107. * ranges we can not autofix this document. More info in issue #2643.
  108. *
  109. * @param {PostcssResult} postcssResult
  110. * @returns {boolean}
  111. */
  112. function isFixCompatible({ stylelint }) {
  113. // Check for issue #2643
  114. if (stylelint.disabledRanges.all && stylelint.disabledRanges.all.length) return false;
  115. return true;
  116. }
  117. /**
  118. * @param {PostcssResult} result
  119. * @param {string} ruleName
  120. * @returns {void}
  121. */
  122. function warnDeprecatedRule(result, ruleName) {
  123. result.warn(`The "${ruleName}" rule is deprecated.`, { stylelintType: 'deprecation' });
  124. }