blob: d2d838b29b28b7966667abeecad2b0fbe086f8ba [file] [log] [blame]
Mathias Bynens79e2cf02020-05-29 16:46:17 +02001'use strict';
2
3const _ = require('lodash');
Tim van der Lippeefb716a2020-12-01 12:54:04 +00004const putIfAbsent = require('./utils/putIfAbsent');
Mathias Bynens79e2cf02020-05-29 16:46:17 +02005
Tim van der Lippeefb716a2020-12-01 12:54:04 +00006/** @typedef {import('postcss/lib/comment')} PostcssComment */
7/** @typedef {import('stylelint').DisabledRange} DisabledRange */
Mathias Bynens79e2cf02020-05-29 16:46:17 +02008/** @typedef {import('stylelint').RangeType} RangeType */
Tim van der Lippeefb716a2020-12-01 12:54:04 +00009/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
Mathias Bynens79e2cf02020-05-29 16:46:17 +020010
11/**
12 * @param {import('stylelint').StylelintResult[]} results
Mathias Bynens79e2cf02020-05-29 16:46:17 +020013 */
14module.exports = function (results) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020015 results.forEach((result) => {
16 // File with `CssSyntaxError` have not `_postcssResult`
17 if (!result._postcssResult) {
18 return;
19 }
20
Tim van der Lippecc71b282021-02-12 15:51:14 +000021 const stylelintResult = result._postcssResult.stylelint;
22
23 if (!stylelintResult.config) return; // Linting error
24
25 if (!stylelintResult.config.reportNeedlessDisables) return;
26
Tim van der Lippeefb716a2020-12-01 12:54:04 +000027 /** @type {{[ruleName: string]: Array<DisabledRange>}} */
Tim van der Lippecc71b282021-02-12 15:51:14 +000028 const rangeData = _.cloneDeep(stylelintResult.disabledRanges);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020029
30 if (!rangeData) {
31 return;
32 }
33
Tim van der Lippecc71b282021-02-12 15:51:14 +000034 const disabledWarnings = stylelintResult.disabledWarnings || [];
Mathias Bynens79e2cf02020-05-29 16:46:17 +020035
Tim van der Lippeefb716a2020-12-01 12:54:04 +000036 // A map from `stylelint-disable` comments to the set of rules that
37 // are usefully disabled by each comment. We track this
38 // comment-by-comment rather than range-by-range because ranges that
39 // disable *all* rules are duplicated for each rule they apply to in
40 // practice.
41 /** @type {Map<PostcssComment, Set<string>>}} */
42 const usefulDisables = new Map();
Mathias Bynens79e2cf02020-05-29 16:46:17 +020043
Tim van der Lippeefb716a2020-12-01 12:54:04 +000044 for (const warning of disabledWarnings) {
45 const rule = warning.rule;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020046 const ruleRanges = rangeData[rule];
47
48 if (ruleRanges) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000049 for (const range of ruleRanges) {
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 }
54 }
55
Tim van der Lippeefb716a2020-12-01 12:54:04 +000056 for (const range of rangeData.all) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020057 if (isWarningInRange(warning, range)) {
Tim van der Lippeefb716a2020-12-01 12:54:04 +000058 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020059 }
60 }
Tim van der Lippeefb716a2020-12-01 12:54:04 +000061 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020062
Tim van der Lippeefb716a2020-12-01 12:54:04 +000063 const rangeEntries = Object.entries(rangeData);
64
65 // Get rid of the duplicated ranges for each `all` rule. We only care
66 // if the entire `all` rule is useful as a whole or not.
67 for (const range of rangeData.all) {
68 for (const [rule, ranges] of rangeEntries) {
69 if (rule === 'all') continue;
70
71 _.remove(ranges, (otherRange) => range.comment === otherRange.comment);
72 }
73 }
74
75 for (const [rule, ranges] of rangeEntries) {
76 for (const range of ranges) {
77 const useful = usefulDisables.get(range.comment) || new Set();
78
79 // Only emit a warning if this range's comment isn't useful for this rule.
80 // For the special rule "all", only emit a warning if it's not useful for
Tim van der Lippecc71b282021-02-12 15:51:14 +000081 // *any* rules, because it covers all of them.
Tim van der Lippeefb716a2020-12-01 12:54:04 +000082 if (rule === 'all' ? useful.size !== 0 : useful.has(rule)) continue;
83
84 // If the comment doesn't have a location, we can't report a useful error.
85 // In practice we expect all comments to have locations, though.
86 if (!range.comment.source || !range.comment.source.start) continue;
87
88 result.warnings.push({
89 text: `Needless disable for "${rule}"`,
90 rule: '--report-needless-disables',
91 line: range.comment.source.start.line,
92 column: range.comment.source.start.column,
93 severity: 'error',
Mathias Bynens79e2cf02020-05-29 16:46:17 +020094 });
Tim van der Lippeefb716a2020-12-01 12:54:04 +000095 }
96 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020097 });
Mathias Bynens79e2cf02020-05-29 16:46:17 +020098};
99
100/**
101 * @param {import('stylelint').DisabledWarning} warning
102 * @param {RangeType} range
103 * @return {boolean}
104 */
105function isWarningInRange(warning, range) {
106 const line = warning.line;
107
108 // Need to check if range.end exist, because line number type cannot be compared to undefined
109 return (
110 range.start <= line &&
111 ((range.end !== undefined && range.end >= line) || range.end === undefined)
112 );
113}