needlessDisables.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. 'use strict';
  2. const _ = require('lodash');
  3. /** @typedef {import('stylelint').RangeType} RangeType */
  4. /** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
  5. /** @typedef {import('stylelint').StylelintDisableOptionsReport} StylelintDisableOptionsReport */
  6. /**
  7. * @param {import('stylelint').StylelintResult[]} results
  8. * @returns {StylelintDisableOptionsReport}
  9. */
  10. module.exports = function (results) {
  11. /** @type {StylelintDisableOptionsReport} */
  12. const report = [];
  13. results.forEach((result) => {
  14. // File with `CssSyntaxError` have not `_postcssResult`
  15. if (!result._postcssResult) {
  16. return;
  17. }
  18. /** @type {{ranges: DisableReportRange[], source: string}} */
  19. const unused = { source: result.source || '', ranges: [] };
  20. /** @type {{[ruleName: string]: Array<RangeType>}} */
  21. const rangeData = _.cloneDeep(result._postcssResult.stylelint.disabledRanges);
  22. if (!rangeData) {
  23. return;
  24. }
  25. const disabledWarnings = result._postcssResult.stylelint.disabledWarnings || [];
  26. disabledWarnings.forEach((warning) => {
  27. const rule = warning.rule;
  28. const ruleRanges = rangeData[rule];
  29. if (ruleRanges) {
  30. // Back to front so we get the *last* range that applies to the warning
  31. for (const range of ruleRanges.reverse()) {
  32. if (isWarningInRange(warning, range)) {
  33. range.used = true;
  34. return;
  35. }
  36. }
  37. }
  38. for (const range of rangeData.all.reverse()) {
  39. if (isWarningInRange(warning, range)) {
  40. range.used = true;
  41. return;
  42. }
  43. }
  44. });
  45. Object.keys(rangeData).forEach((rule) => {
  46. rangeData[rule].forEach((range) => {
  47. // Is an equivalent range already marked as unused?
  48. const alreadyMarkedUnused = unused.ranges.find((unusedRange) => {
  49. return unusedRange.start === range.start && unusedRange.end === range.end;
  50. });
  51. // If this range is unused and no equivalent is marked,
  52. // mark this range as unused
  53. if (!range.used && !alreadyMarkedUnused) {
  54. unused.ranges.push({
  55. rule,
  56. start: range.start,
  57. end: range.end,
  58. unusedRule: rule,
  59. });
  60. }
  61. // If this range is used but an equivalent has been marked as unused,
  62. // remove that equivalent. This can happen because of the duplication
  63. // of ranges in rule-specific range sets and the "all" range set
  64. if (range.used && alreadyMarkedUnused) {
  65. _.remove(unused.ranges, alreadyMarkedUnused);
  66. }
  67. });
  68. });
  69. unused.ranges = _.sortBy(unused.ranges, ['start', 'end']);
  70. report.push(unused);
  71. });
  72. return report;
  73. };
  74. /**
  75. * @param {import('stylelint').DisabledWarning} warning
  76. * @param {RangeType} range
  77. * @return {boolean}
  78. */
  79. function isWarningInRange(warning, range) {
  80. const line = warning.line;
  81. // Need to check if range.end exist, because line number type cannot be compared to undefined
  82. return (
  83. range.start <= line &&
  84. ((range.end !== undefined && range.end >= line) || range.end === undefined)
  85. );
  86. }