UNPKG

3.51 kBJavaScriptView Raw
1'use strict';
2
3const optionsMatches = require('./utils/optionsMatches');
4const putIfAbsent = require('./utils/putIfAbsent');
5const validateDisableSettings = require('./validateDisableSettings');
6
7/** @typedef {import('postcss').Comment} PostcssComment */
8/** @typedef {import('stylelint').DisabledRange} DisabledRange */
9/** @typedef {import('stylelint').RangeType} RangeType */
10/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
11
12/**
13 * @param {import('stylelint').LintResult[]} results
14 */
15module.exports = function needlessDisables(results) {
16 for (const result of results) {
17 const settings = validateDisableSettings(result._postcssResult, 'reportNeedlessDisables');
18
19 if (!settings) continue;
20
21 const [enabled, options, stylelintResult] = settings;
22
23 const rangeData = stylelintResult.disabledRanges;
24
25 if (!rangeData) continue;
26
27 const disabledWarnings = stylelintResult.disabledWarnings || [];
28
29 // 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();
36
37 for (const warning of disabledWarnings) {
38 const rule = warning.rule;
39 const ruleRanges = rangeData[rule];
40
41 if (ruleRanges) {
42 for (const range of ruleRanges) {
43 if (isWarningInRange(warning, range)) {
44 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
45 }
46 }
47 }
48
49 for (const range of rangeData.all || []) {
50 if (isWarningInRange(warning, range)) {
51 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
52 }
53 }
54 }
55
56 const allRangeComments = new Set((rangeData.all || []).map((range) => range.comment));
57
58 for (const [rule, ranges] of Object.entries(rangeData)) {
59 for (const range of ranges) {
60 if (rule !== 'all' && allRangeComments.has(range.comment)) continue;
61
62 if (enabled === optionsMatches(options, 'except', rule)) continue;
63
64 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
68 // *any* rules, because it covers all of them.
69 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,
80 endLine: range.comment.source.end && range.comment.source.end.line,
81 endColumn: range.comment.source.end && range.comment.source.end.column,
82 severity: options.severity,
83 });
84 }
85 }
86 }
87};
88
89/**
90 * @param {import('stylelint').DisabledWarning} warning
91 * @param {RangeType} range
92 * @return {boolean}
93 */
94function isWarningInRange(warning, range) {
95 const line = warning.line;
96
97 // Need to check if range.end exist, because line number type cannot be compared to undefined
98 return (
99 range.start <= line &&
100 ((range.end !== undefined && range.end >= line) || range.end === undefined)
101 );
102}