blob: ccd2bb9e12ab6c0e370bde02573ff7cadcb60368 [file] [log] [blame]
Mathias Bynens79e2cf02020-05-29 16:46:17 +02001'use strict';
2
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +00003const optionsMatches = require('./utils/optionsMatches');
Tim van der Lippeefb716a2020-12-01 12:54:04 +00004const putIfAbsent = require('./utils/putIfAbsent');
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +00005const validateDisableSettings = require('./validateDisableSettings');
Mathias Bynens79e2cf02020-05-29 16:46:17 +02006
Tim van der Lippe16b82282021-11-08 13:50:26 +00007/** @typedef {import('postcss').Comment} PostcssComment */
Tim van der Lippeefb716a2020-12-01 12:54:04 +00008/** @typedef {import('stylelint').DisabledRange} DisabledRange */
Mathias Bynens79e2cf02020-05-29 16:46:17 +02009/** @typedef {import('stylelint').RangeType} RangeType */
Tim van der Lippeefb716a2020-12-01 12:54:04 +000010/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
Mathias Bynens79e2cf02020-05-29 16:46:17 +020011
12/**
Tim van der Lippe16b82282021-11-08 13:50:26 +000013 * @param {import('stylelint').LintResult[]} results
Mathias Bynens79e2cf02020-05-29 16:46:17 +020014 */
Tim van der Lippe16b82282021-11-08 13:50:26 +000015module.exports = function needlessDisables(results) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020016 results.forEach((result) => {
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000017 const settings = validateDisableSettings(result._postcssResult, 'reportNeedlessDisables');
Mathias Bynens79e2cf02020-05-29 16:46:17 +020018
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000019 if (!settings) return;
Tim van der Lippecc71b282021-02-12 15:51:14 +000020
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000021 const [enabled, options, stylelintResult] = settings;
Tim van der Lippecc71b282021-02-12 15:51:14 +000022
Tim van der Lippe16b82282021-11-08 13:50:26 +000023 const rangeData = stylelintResult.disabledRanges;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020024
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000025 if (!rangeData) return;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020026
Tim van der Lippecc71b282021-02-12 15:51:14 +000027 const disabledWarnings = stylelintResult.disabledWarnings || [];
Mathias Bynens79e2cf02020-05-29 16:46:17 +020028
Tim van der Lippeefb716a2020-12-01 12:54:04 +000029 // A map from `stylelint-disable` comments to the set of rules that
30 // are usefully disabled by each comment. We track this
31 // comment-by-comment rather than range-by-range because ranges that
32 // disable *all* rules are duplicated for each rule they apply to in
33 // practice.
34 /** @type {Map<PostcssComment, Set<string>>}} */
35 const usefulDisables = new Map();
Mathias Bynens79e2cf02020-05-29 16:46:17 +020036
Tim van der Lippeefb716a2020-12-01 12:54:04 +000037 for (const warning of disabledWarnings) {
38 const rule = warning.rule;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020039 const ruleRanges = rangeData[rule];
40
41 if (ruleRanges) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000042 for (const range of ruleRanges) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020043 if (isWarningInRange(warning, range)) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000044 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020045 }
46 }
47 }
48
Tim van der Lippeefb716a2020-12-01 12:54:04 +000049 for (const range of rangeData.all) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020050 if (isWarningInRange(warning, range)) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000051 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020052 }
53 }
Tim van der Lippeefb716a2020-12-01 12:54:04 +000054 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020055
Tim van der Lippe16b82282021-11-08 13:50:26 +000056 const allRangeComments = new Set(rangeData.all.map((range) => range.comment));
Tim van der Lippeefb716a2020-12-01 12:54:04 +000057
Tim van der Lippe16b82282021-11-08 13:50:26 +000058 for (const [rule, ranges] of Object.entries(rangeData)) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000059 for (const range of ranges) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000060 if (rule !== 'all' && allRangeComments.has(range.comment)) continue;
61
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000062 if (enabled === optionsMatches(options, 'except', rule)) continue;
63
Tim van der Lippeefb716a2020-12-01 12:54:04 +000064 const useful = usefulDisables.get(range.comment) || new Set();
65
66 // Only emit a warning if this range's comment isn't useful for this rule.
67 // For the special rule "all", only emit a warning if it's not useful for
Tim van der Lippecc71b282021-02-12 15:51:14 +000068 // *any* rules, because it covers all of them.
Tim van der Lippeefb716a2020-12-01 12:54:04 +000069 if (rule === 'all' ? useful.size !== 0 : useful.has(rule)) continue;
70
71 // If the comment doesn't have a location, we can't report a useful error.
72 // In practice we expect all comments to have locations, though.
73 if (!range.comment.source || !range.comment.source.start) continue;
74
75 result.warnings.push({
76 text: `Needless disable for "${rule}"`,
77 rule: '--report-needless-disables',
78 line: range.comment.source.start.line,
79 column: range.comment.source.start.column,
Tim van der Lippe0a9b84d2021-03-24 11:53:15 +000080 severity: options.severity,
Mathias Bynens79e2cf02020-05-29 16:46:17 +020081 });
Tim van der Lippeefb716a2020-12-01 12:54:04 +000082 }
83 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020084 });
Mathias Bynens79e2cf02020-05-29 16:46:17 +020085};
86
87/**
88 * @param {import('stylelint').DisabledWarning} warning
89 * @param {RangeType} range
90 * @return {boolean}
91 */
92function isWarningInRange(warning, range) {
93 const line = warning.line;
94
95 // Need to check if range.end exist, because line number type cannot be compared to undefined
96 return (
97 range.start <= line &&
98 ((range.end !== undefined && range.end >= line) || range.end === undefined)
99 );
100}