needlessDisables.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. 'use strict';
  2. const optionsMatches = require('./utils/optionsMatches');
  3. const putIfAbsent = require('./utils/putIfAbsent');
  4. const validateDisableSettings = require('./validateDisableSettings');
  5. /** @typedef {import('postcss').Comment} PostcssComment */
  6. /** @typedef {import('stylelint').DisabledRange} DisabledRange */
  7. /** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
  8. /**
  9. * @param {import('stylelint').LintResult[]} results
  10. */
  11. module.exports = function needlessDisables(results) {
  12. for (const result of results) {
  13. const settings = validateDisableSettings(result._postcssResult, 'reportNeedlessDisables');
  14. if (!settings) continue;
  15. const [enabled, options, stylelintResult] = settings;
  16. const rangeData = stylelintResult.disabledRanges;
  17. if (!rangeData) continue;
  18. const disabledWarnings = stylelintResult.disabledWarnings || [];
  19. // A map from `stylelint-disable` comments to the set of rules that
  20. // are usefully disabled by each comment. We track this
  21. // comment-by-comment rather than range-by-range because ranges that
  22. // disable *all* rules are duplicated for each rule they apply to in
  23. // practice.
  24. /** @type {Map<PostcssComment, Set<string>>}} */
  25. const usefulDisables = new Map();
  26. for (const warning of disabledWarnings) {
  27. const rule = warning.rule;
  28. const ruleRanges = rangeData[rule];
  29. if (ruleRanges) {
  30. for (const range of ruleRanges) {
  31. if (isWarningInRange(warning, range)) {
  32. putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
  33. }
  34. }
  35. }
  36. for (const range of rangeData.all || []) {
  37. if (isWarningInRange(warning, range)) {
  38. putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
  39. }
  40. }
  41. }
  42. const allRangeComments = new Set((rangeData.all || []).map((range) => range.comment));
  43. for (const [rule, ranges] of Object.entries(rangeData)) {
  44. for (const range of ranges) {
  45. if (rule !== 'all' && allRangeComments.has(range.comment)) continue;
  46. if (enabled === optionsMatches(options, 'except', rule)) continue;
  47. const useful = usefulDisables.get(range.comment) || new Set();
  48. // Only emit a warning if this range's comment isn't useful for this rule.
  49. // For the special rule "all", only emit a warning if it's not useful for
  50. // *any* rules, because it covers all of them.
  51. if (rule === 'all' ? useful.size !== 0 : useful.has(rule)) continue;
  52. // If the comment doesn't have a location, we can't report a useful error.
  53. // In practice we expect all comments to have locations, though.
  54. if (!range.comment.source || !range.comment.source.start) continue;
  55. result.warnings.push({
  56. text: `Needless disable for "${rule}"`,
  57. rule: '--report-needless-disables',
  58. line: range.comment.source.start.line,
  59. column: range.comment.source.start.column,
  60. endLine: range.comment.source.end && range.comment.source.end.line,
  61. endColumn: range.comment.source.end && range.comment.source.end.column,
  62. severity: options.severity,
  63. });
  64. }
  65. }
  66. }
  67. };
  68. /**
  69. * @param {import('stylelint').DisabledWarning} warning
  70. * @param {DisabledRange} range
  71. * @return {boolean}
  72. */
  73. function isWarningInRange(warning, range) {
  74. const line = warning.line;
  75. // Need to check if range.end exist, because line number type cannot be compared to undefined
  76. return (
  77. range.start <= line &&
  78. ((range.end !== undefined && range.end >= line) || range.end === undefined)
  79. );
  80. }