UNPKG

3.43 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const putIfAbsent = require('./utils/putIfAbsent');
5
6/** @typedef {import('postcss/lib/comment')} PostcssComment */
7/** @typedef {import('stylelint').DisabledRange} DisabledRange */
8/** @typedef {import('stylelint').RangeType} RangeType */
9/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
10
11/**
12 * @param {import('stylelint').StylelintResult[]} results
13 */
14module.exports = function (results) {
15 results.forEach((result) => {
16 // File with `CssSyntaxError` have not `_postcssResult`
17 if (!result._postcssResult) {
18 return;
19 }
20
21 /** @type {{[ruleName: string]: Array<DisabledRange>}} */
22 const rangeData = _.cloneDeep(result._postcssResult.stylelint.disabledRanges);
23
24 if (!rangeData) {
25 return;
26 }
27
28 const disabledWarnings = result._postcssResult.stylelint.disabledWarnings || [];
29
30 // A map from `stylelint-disable` comments to the set of rules that
31 // are usefully disabled by each comment. We track this
32 // comment-by-comment rather than range-by-range because ranges that
33 // disable *all* rules are duplicated for each rule they apply to in
34 // practice.
35 /** @type {Map<PostcssComment, Set<string>>}} */
36 const usefulDisables = new Map();
37
38 for (const warning of disabledWarnings) {
39 const rule = warning.rule;
40 const ruleRanges = rangeData[rule];
41
42 if (ruleRanges) {
43 for (const range of ruleRanges) {
44 if (isWarningInRange(warning, range)) {
45 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
46 }
47 }
48 }
49
50 for (const range of rangeData.all) {
51 if (isWarningInRange(warning, range)) {
52 putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
53 }
54 }
55 }
56
57 const rangeEntries = Object.entries(rangeData);
58
59 // Get rid of the duplicated ranges for each `all` rule. We only care
60 // if the entire `all` rule is useful as a whole or not.
61 for (const range of rangeData.all) {
62 for (const [rule, ranges] of rangeEntries) {
63 if (rule === 'all') continue;
64
65 _.remove(ranges, (otherRange) => range.comment === otherRange.comment);
66 }
67 }
68
69 for (const [rule, ranges] of rangeEntries) {
70 for (const range of ranges) {
71 const useful = usefulDisables.get(range.comment) || new Set();
72
73 // Only emit a warning if this range's comment isn't useful for this rule.
74 // For the special rule "all", only emit a warning if it's not useful for
75 // *any* // rules, becuase it covers all of them.
76 if (rule === 'all' ? useful.size !== 0 : useful.has(rule)) continue;
77
78 // If the comment doesn't have a location, we can't report a useful error.
79 // In practice we expect all comments to have locations, though.
80 if (!range.comment.source || !range.comment.source.start) continue;
81
82 result.warnings.push({
83 text: `Needless disable for "${rule}"`,
84 rule: '--report-needless-disables',
85 line: range.comment.source.start.line,
86 column: range.comment.source.start.column,
87 severity: 'error',
88 });
89 }
90 }
91 });
92};
93
94/**
95 * @param {import('stylelint').DisabledWarning} warning
96 * @param {RangeType} range
97 * @return {boolean}
98 */
99function isWarningInRange(warning, range) {
100 const line = warning.line;
101
102 // Need to check if range.end exist, because line number type cannot be compared to undefined
103 return (
104 range.start <= line &&
105 ((range.end !== undefined && range.end >= line) || range.end === undefined)
106 );
107}