blob: f0d2fe036e0cfafdf445761c8ea1aaa6af08ad0d [file] [log] [blame]
Mathias Bynens79e2cf02020-05-29 16:46:17 +02001'use strict';
2
3const _ = require('lodash');
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +00004const optionsMatches = require('./utils/optionsMatches');
Tim van der Lippeefb716a2020-12-01 12:54:04 +00005const putIfAbsent = require('./utils/putIfAbsent');
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +00006const validateDisableSettings = require('./validateDisableSettings');
Mathias Bynens79e2cf02020-05-29 16:46:17 +02007
Tim van der Lippeefb716a2020-12-01 12:54:04 +00008/** @typedef {import('postcss/lib/comment')} PostcssComment */
9/** @typedef {import('stylelint').DisabledRange} DisabledRange */
Mathias Bynens79e2cf02020-05-29 16:46:17 +020010/** @typedef {import('stylelint').RangeType} RangeType */
Tim van der Lippeefb716a2020-12-01 12:54:04 +000011/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
Mathias Bynens79e2cf02020-05-29 16:46:17 +020012
13/**
14 * @param {import('stylelint').StylelintResult[]} results
Mathias Bynens79e2cf02020-05-29 16:46:17 +020015 */
16module.exports = function (results) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020017 results.forEach((result) => {
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000018 const settings = validateDisableSettings(result._postcssResult, 'reportNeedlessDisables');
Mathias Bynens79e2cf02020-05-29 16:46:17 +020019
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000020 if (!settings) return;
Tim van der Lippecc71b282021-02-12 15:51:14 +000021
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000022 const [enabled, options, stylelintResult] = settings;
Tim van der Lippecc71b282021-02-12 15:51:14 +000023
Tim van der Lippeefb716a2020-12-01 12:54:04 +000024 /** @type {{[ruleName: string]: Array<DisabledRange>}} */
Tim van der Lippecc71b282021-02-12 15:51:14 +000025 const rangeData = _.cloneDeep(stylelintResult.disabledRanges);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020026
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000027 if (!rangeData) return;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020028
Tim van der Lippecc71b282021-02-12 15:51:14 +000029 const disabledWarnings = stylelintResult.disabledWarnings || [];
Mathias Bynens79e2cf02020-05-29 16:46:17 +020030
Tim van der Lippeefb716a2020-12-01 12:54:04 +000031 // A map from `stylelint-disable` comments to the set of rules that
32 // are usefully disabled by each comment. We track this
33 // comment-by-comment rather than range-by-range because ranges that
34 // disable *all* rules are duplicated for each rule they apply to in
35 // practice.
36 /** @type {Map<PostcssComment, Set<string>>}} */
37 const usefulDisables = new Map();
Mathias Bynens79e2cf02020-05-29 16:46:17 +020038
Tim van der Lippeefb716a2020-12-01 12:54:04 +000039 for (const warning of disabledWarnings) {
40 const rule = warning.rule;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020041 const ruleRanges = rangeData[rule];
42
43 if (ruleRanges) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000044 for (const range of ruleRanges) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020045 if (isWarningInRange(warning, range)) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000046 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020047 }
48 }
49 }
50
Tim van der Lippeefb716a2020-12-01 12:54:04 +000051 for (const range of rangeData.all) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020052 if (isWarningInRange(warning, range)) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000053 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020054 }
55 }
Tim van der Lippeefb716a2020-12-01 12:54:04 +000056 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020057
Tim van der Lippeefb716a2020-12-01 12:54:04 +000058 const rangeEntries = Object.entries(rangeData);
59
60 // Get rid of the duplicated ranges for each `all` rule. We only care
61 // if the entire `all` rule is useful as a whole or not.
62 for (const range of rangeData.all) {
63 for (const [rule, ranges] of rangeEntries) {
64 if (rule === 'all') continue;
65
66 _.remove(ranges, (otherRange) => range.comment === otherRange.comment);
67 }
68 }
69
70 for (const [rule, ranges] of rangeEntries) {
71 for (const range of ranges) {
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000072 if (enabled === optionsMatches(options, 'except', rule)) continue;
73
Tim van der Lippeefb716a2020-12-01 12:54:04 +000074 const useful = usefulDisables.get(range.comment) || new Set();
75
76 // Only emit a warning if this range's comment isn't useful for this rule.
77 // For the special rule "all", only emit a warning if it's not useful for
Tim van der Lippecc71b282021-02-12 15:51:14 +000078 // *any* rules, because it covers all of them.
Tim van der Lippeefb716a2020-12-01 12:54:04 +000079 if (rule === 'all' ? useful.size !== 0 : useful.has(rule)) continue;
80
81 // If the comment doesn't have a location, we can't report a useful error.
82 // In practice we expect all comments to have locations, though.
83 if (!range.comment.source || !range.comment.source.start) continue;
84
85 result.warnings.push({
86 text: `Needless disable for "${rule}"`,
87 rule: '--report-needless-disables',
88 line: range.comment.source.start.line,
89 column: range.comment.source.start.column,
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000090 severity: options.severity,
Mathias Bynens79e2cf02020-05-29 16:46:17 +020091 });
Tim van der Lippeefb716a2020-12-01 12:54:04 +000092 }
93 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020094 });
Mathias Bynens79e2cf02020-05-29 16:46:17 +020095};
96
97/**
98 * @param {import('stylelint').DisabledWarning} warning
99 * @param {RangeType} range
100 * @return {boolean}
101 */
102function isWarningInRange(warning, range) {
103 const line = warning.line;
104
105 // Need to check if range.end exist, because line number type cannot be compared to undefined
106 return (
107 range.start <= line &&
108 ((range.end !== undefined && range.end >= line) || range.end === undefined)
109 );
110}