From b7fc62bfd42523bffc98654cf6bcafef4b03203e Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 24 Sep 2020 17:45:25 -0700 Subject: [PATCH] [WIP] Report disables in the same manner as lints Closes #4896 --- lib/__tests__/needlessDisables.test.js | 145 ++++++++++++++++++++----- lib/__tests__/reportDisables.test.js | 69 ++++++++---- lib/assignDisabledRanges.js | 31 +++--- lib/descriptionlessDisables.js | 24 +--- lib/invalidScopeDisables.js | 20 +--- lib/needlessDisables.js | 87 +++++++-------- lib/prepareReturnValue.js | 21 ++-- lib/reportDisables.js | 25 +---- lib/utils/putIfAbsent.js | 19 ++++ scripts/visual.css | 1 + types/stylelint/index.d.ts | 3 +- 11 files changed, 270 insertions(+), 175 deletions(-) create mode 100644 lib/utils/putIfAbsent.js diff --git a/lib/__tests__/needlessDisables.test.js b/lib/__tests__/needlessDisables.test.js index 2a0b7380e4..50e34017c0 100644 --- a/lib/__tests__/needlessDisables.test.js +++ b/lib/__tests__/needlessDisables.test.js @@ -38,12 +38,58 @@ it('needlessDisables simple case', () => { code: css, reportNeedlessDisables: true, }).then((linted) => { - const report = linted.needlessDisables; + const results = linted.results; - expect(report).toHaveLength(1); - expect(report[0].ranges).toEqual([ - { start: 7, end: 9, rule: 'all', unusedRule: 'all' }, - { start: 11, end: 11, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, + expect(results).toHaveLength(1); + const warnings = results[0].warnings; + + expect(warnings).toEqual([ + { + line: 7, + column: 1, + text: 'Needless disable for "all"', + rule: '--report-needless-disables', + severity: 'error', + }, + { + line: 11, + column: 22, + text: 'Needless disable for "block-no-empty"', + rule: '--report-needless-disables', + severity: 'error', + }, + ]); + }); +}); + +it('needlessDisables with multiple rules', () => { + const config = { + rules: { 'block-no-empty': true, 'color-named': true }, + }; + + const css = stripIndent` + /* stylelint-disable-next-line block-no-empty, color-named */ + a {} + `; + + return standalone({ + config, + code: css, + reportNeedlessDisables: true, + }).then((linted) => { + const results = linted.results; + + expect(results).toHaveLength(1); + const warnings = results[0].warnings; + + expect(warnings).toEqual([ + { + line: 1, + column: 1, + text: 'Needless disable for "color-named"', + rule: '--report-needless-disables', + severity: 'error', + }, ]); }); }); @@ -66,24 +112,51 @@ it('needlessDisables complex case', () => { ], reportNeedlessDisables: true, }).then((linted) => { - expect(linted.needlessDisables).toEqual([ + const results = linted.results; + + expect(results).toHaveLength(3); + expect(needlessDisables(results[0].warnings)).toEqual([ { - source: source('disabled-ranges-1.css'), - ranges: [ - { start: 1, end: 3, rule: 'color-named', unusedRule: 'color-named' }, - { start: 5, end: 7, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, - { start: 10, end: 10, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, - ], + line: 1, + column: 1, + text: 'Needless disable for "color-named"', + rule: '--report-needless-disables', + severity: 'error', }, { - source: source('disabled-ranges-2.css'), - ranges: [ - { start: 5, end: 5, rule: 'color-named', unusedRule: 'color-named' }, - { start: 6, end: 6, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, - { start: 8, end: 10, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, - ], + line: 5, + column: 1, + text: 'Needless disable for "block-no-empty"', + rule: '--report-needless-disables', + severity: 'error', }, ]); + + expect(needlessDisables(results[1].warnings)).toEqual([ + { + line: 6, + column: 19, + text: 'Needless disable for "block-no-empty"', + rule: '--report-needless-disables', + severity: 'error', + }, + { + line: 8, + column: 1, + text: 'Needless disable for "block-no-empty"', + rule: '--report-needless-disables', + severity: 'error', + }, + { + line: 5, + column: 6, + text: 'Needless disable for "color-named"', + rule: '--report-needless-disables', + severity: 'error', + }, + ]); + + expect(needlessDisables(results[2].warnings)).toHaveLength(0); }); }); @@ -100,15 +173,37 @@ it('needlessDisables ignored case', () => { reportNeedlessDisables: true, ignorePath: fixture('.stylelintignore'), }).then((linted) => { - expect(linted.needlessDisables).toEqual([ + const results = linted.results; + + expect(results).toHaveLength(1); + const warnings = results[0].warnings; + + expect(needlessDisables(warnings)).toEqual([ + { + line: 10, + column: 19, + text: 'Needless disable for "all"', + rule: '--report-needless-disables', + severity: 'error', + }, + { + line: 1, + column: 1, + text: 'Needless disable for "color-named"', + rule: '--report-needless-disables', + severity: 'error', + }, { - source: source('disabled-ranges-1.css'), - ranges: [ - { start: 1, end: 3, rule: 'color-named', unusedRule: 'color-named' }, - { start: 5, end: 7, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, - { start: 10, end: 10, rule: 'all', unusedRule: 'all' }, - ], + line: 5, + column: 1, + text: 'Needless disable for "block-no-empty"', + rule: '--report-needless-disables', + severity: 'error', }, ]); }); }); + +function needlessDisables(warnings) { + return warnings.filter((warning) => warning.rule === '--report-needless-disables'); +} diff --git a/lib/__tests__/reportDisables.test.js b/lib/__tests__/reportDisables.test.js index c04d529c19..af682574c6 100644 --- a/lib/__tests__/reportDisables.test.js +++ b/lib/__tests__/reportDisables.test.js @@ -19,17 +19,27 @@ describe('reportDisables', () => { `; return standalone({ config, code: css }).then((linted) => { - const report = linted.reportedDisables; - - expect(report).toHaveLength(1); - expect(report[0].ranges).toEqual([ - { start: 1, end: 3, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, - { start: 5, end: 5, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, + const results = linted.results; + + expect(results).toHaveLength(1); + const warnings = results[0].warnings; + + expect(warnings).toEqual([ + { + line: 1, + column: 1, + text: 'Rule "block-no-empty" may not be disabled', + rule: 'reportDisables', + severity: 'error', + }, + { + line: 5, + column: 8, + text: 'Rule "block-no-empty" may not be disabled', + rule: 'reportDisables', + severity: 'error', + }, ]); - - // Although these disables are reported as issues, they're still in effect - // so the underlying lint issues are not reported. - expect(linted.results[0].warnings).toHaveLength(0); }); }); @@ -52,15 +62,27 @@ describe('reportDisables', () => { code: css, ignoreDisables: true, }).then((linted) => { - const report = linted.reportedDisables; - - expect(report).toHaveLength(1); - expect(report[0].ranges).toEqual([ - { start: 1, end: 3, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, - { start: 5, end: 5, rule: 'block-no-empty', unusedRule: 'block-no-empty' }, + const results = linted.results; + + expect(results).toHaveLength(1); + const warnings = results[0].warnings; + + expect(warnings.filter((warning) => warning.rule === 'reportDisables')).toEqual([ + { + line: 1, + column: 1, + text: 'Rule "block-no-empty" may not be disabled', + rule: 'reportDisables', + severity: 'error', + }, + { + line: 5, + column: 8, + text: 'Rule "block-no-empty" may not be disabled', + rule: 'reportDisables', + severity: 'error', + }, ]); - - expect(linted.results[0].warnings).toHaveLength(2); }); }); @@ -79,9 +101,10 @@ describe('reportDisables', () => { `; return standalone({ config, code: css }).then((linted) => { - const report = linted.reportedDisables; + const results = linted.results; - expect(report).toHaveLength(0); + expect(results).toHaveLength(1); + expect(results[0].warnings).toHaveLength(0); }); }); @@ -96,10 +119,10 @@ describe('reportDisables', () => { `; return standalone({ config, code: css }).then((linted) => { - const report = linted.reportedDisables; + const results = linted.results; - expect(report).toHaveLength(1); - expect(report[0].ranges).toHaveLength(0); + expect(results).toHaveLength(1); + expect(results[0].warnings).toHaveLength(0); }); }); }); diff --git a/lib/assignDisabledRanges.js b/lib/assignDisabledRanges.js index d57043e059..6c6db33b8d 100644 --- a/lib/assignDisabledRanges.js +++ b/lib/assignDisabledRanges.js @@ -16,6 +16,7 @@ const ALL_RULES = 'all'; /** @typedef {import('stylelint').DisabledRange} DisabledRange */ /** + * @param {PostcssComment} comment * @param {number} start * @param {boolean} strictStart * @param {string|undefined} description @@ -23,8 +24,9 @@ const ALL_RULES = 'all'; * @param {boolean} [strictEnd] * @returns {DisabledRange} */ -function createDisableRange(start, strictStart, description, end, strictEnd) { +function createDisableRange(comment, start, strictStart, description, end, strictEnd) { return { + comment, start, end: end || undefined, strictStart, @@ -117,7 +119,7 @@ module.exports = function (root, result) { const description = getDescription(comment.text); getCommandRules(disableLineCommand, comment.text).forEach((ruleName) => { - disableLine(line, ruleName, comment, description); + disableLine(comment, line, ruleName, description); }); } } @@ -131,18 +133,18 @@ module.exports = function (root, result) { const description = getDescription(comment.text); getCommandRules(disableNextLineCommand, comment.text).forEach((ruleName) => { - disableLine(line + 1, ruleName, comment, description); + disableLine(comment, line + 1, ruleName, description); }); } } /** + * @param {PostcssComment} comment * @param {number} line * @param {string} ruleName - * @param {PostcssComment} comment * @param {string|undefined} description */ - function disableLine(line, ruleName, comment, description) { + function disableLine(comment, line, ruleName, description) { if (ruleIsDisabled(ALL_RULES)) { throw comment.error('All rules have already been disabled', { plugin: 'stylelint', @@ -155,7 +157,7 @@ module.exports = function (root, result) { const strict = disabledRuleName === ALL_RULES; - startDisabledRange(line, disabledRuleName, strict, description); + startDisabledRange(comment, line, disabledRuleName, strict, description); endDisabledRange(line, disabledRuleName, strict); }); } else { @@ -165,7 +167,7 @@ module.exports = function (root, result) { }); } - startDisabledRange(line, ruleName, true, description); + startDisabledRange(comment, line, ruleName, true, description); endDisabledRange(line, ruleName, true); } } @@ -195,10 +197,10 @@ module.exports = function (root, result) { if (isAllRules) { Object.keys(disabledRanges).forEach((ruleName) => { - startDisabledRange(line, ruleName, ruleName === ALL_RULES, description); + startDisabledRange(comment, line, ruleName, ruleName === ALL_RULES, description); }); } else { - startDisabledRange(line, ruleToDisable, true, description); + startDisabledRange(comment, line, ruleToDisable, true, description); } } }); @@ -239,7 +241,7 @@ module.exports = function (root, result) { // Get a starting point from the where all rules were disabled if (!disabledRanges[ruleToEnable]) { disabledRanges[ruleToEnable] = disabledRanges.all.map(({ start, end, description }) => - createDisableRange(start, false, description, end, false), + createDisableRange(comment, start, false, description, end, false), ); } else { const range = _.last(disabledRanges[ALL_RULES]); @@ -323,13 +325,14 @@ module.exports = function (root, result) { } /** + * @param {PostcssComment} comment * @param {number} line * @param {string} ruleName * @param {boolean} strict * @param {string|undefined} description */ - function startDisabledRange(line, ruleName, strict, description) { - const rangeObj = createDisableRange(line, strict, description); + function startDisabledRange(comment, line, ruleName, strict, description) { + const rangeObj = createDisableRange(comment, line, strict, description); ensureRuleRanges(ruleName); disabledRanges[ruleName].push(rangeObj); @@ -357,8 +360,8 @@ module.exports = function (root, result) { */ function ensureRuleRanges(ruleName) { if (!disabledRanges[ruleName]) { - disabledRanges[ruleName] = disabledRanges.all.map(({ start, end, description }) => - createDisableRange(start, false, description, end, false), + disabledRanges[ruleName] = disabledRanges.all.map(({ comment, start, end, description }) => + createDisableRange(comment, start, false, description, end, false), ); } } diff --git a/lib/descriptionlessDisables.js b/lib/descriptionlessDisables.js index 6bfb634cce..450058bb6d 100644 --- a/lib/descriptionlessDisables.js +++ b/lib/descriptionlessDisables.js @@ -6,12 +6,8 @@ /** * @param {import('stylelint').StylelintResult[]} results - * @returns {StylelintDisableOptionsReport} */ module.exports = function (results) { - /** @type {StylelintDisableOptionsReport} */ - const report = []; - results.forEach((result) => { // File with `CssSyntaxError` have not `_postcssResult` if (!result._postcssResult) { @@ -20,9 +16,6 @@ module.exports = function (results) { const rangeData = result._postcssResult.stylelint.disabledRanges; - /** @type {import('stylelint').StylelintDisableReportEntry} */ - const entry = { source: result.source, ranges: [] }; - Object.keys(rangeData).forEach((rule) => { rangeData[rule].forEach((range) => { if (range.description) return; @@ -34,19 +27,14 @@ module.exports = function (results) { if (alreadyReported) return; - entry.ranges.push({ - rule, - start: range.start, - end: range.end, - unusedRule: rule, + result.warnings.push({ + text: `Disable for "${rule}" is missing a description`, + rule: '--report-descriptionless-disables', + line: range.comment.source.start.line, + column: range.comment.source.start.column, + severity: 'error', }); }); }); - - if (entry.ranges.length > 0) { - report.push(entry); - } }); - - return report; }; diff --git a/lib/invalidScopeDisables.js b/lib/invalidScopeDisables.js index 1249284cb0..5eb4d93366 100644 --- a/lib/invalidScopeDisables.js +++ b/lib/invalidScopeDisables.js @@ -1,16 +1,11 @@ 'use strict'; /** @typedef {import('stylelint').RangeType} RangeType */ -/** @typedef {import('stylelint').StylelintDisableOptionsReport} StylelintDisableOptionsReport */ /** * @param {import('stylelint').StylelintResult[]} results - * @returns {StylelintDisableOptionsReport} */ module.exports = function (results) { - /** @type {StylelintDisableOptionsReport} */ - const report = []; - results.forEach((result) => { // File with `CssSyntaxError` have not `_postcssResult` if (!result._postcssResult) { @@ -28,8 +23,6 @@ module.exports = function (results) { usedRules.add('all'); - /** @type {import('stylelint').StylelintDisableReportEntry} */ - const sourceReport = { source: result.source, ranges: [] }; const rangeData = result._postcssResult.stylelint.disabledRanges; const disabledRules = Object.keys(rangeData); @@ -43,11 +36,12 @@ module.exports = function (results) { return; } - sourceReport.ranges.push({ - rule, - start: range.start, - end: range.end, - unusedRule: rule, + result.warnings.push({ + text: `Rule "${rule}" isn't enabled`, + rule: '--report-invalid-scope-disables', + line: range.comment.source.start.line, + column: range.comment.source.start.column, + severity: 'error', }); }); }); @@ -56,6 +50,4 @@ module.exports = function (results) { report.push(sourceReport); } }); - - return report; }; diff --git a/lib/needlessDisables.js b/lib/needlessDisables.js index 70eae0f92a..e0ee33707e 100644 --- a/lib/needlessDisables.js +++ b/lib/needlessDisables.js @@ -1,29 +1,23 @@ 'use strict'; const _ = require('lodash'); +const putIfAbsent = require('./utils/putIfAbsent'); +/** @typedef {import('postcss/lib/comment')} PostcssComment */ /** @typedef {import('stylelint').RangeType} RangeType */ /** @typedef {import('stylelint').DisableReportRange} DisableReportRange */ -/** @typedef {import('stylelint').StylelintDisableOptionsReport} StylelintDisableOptionsReport */ /** * @param {import('stylelint').StylelintResult[]} results - * @returns {StylelintDisableOptionsReport} */ module.exports = function (results) { - /** @type {StylelintDisableOptionsReport} */ - const report = []; - results.forEach((result) => { // File with `CssSyntaxError` have not `_postcssResult` if (!result._postcssResult) { return; } - /** @type {{ranges: DisableReportRange[], source: string}} */ - const unused = { source: result.source || '', ranges: [] }; - - /** @type {{[ruleName: string]: Array}} */ + /** @type {{[ruleName: string]: Array}} */ const rangeData = _.cloneDeep(result._postcssResult.stylelint.disabledRanges); if (!rangeData) { @@ -32,64 +26,61 @@ module.exports = function (results) { const disabledWarnings = result._postcssResult.stylelint.disabledWarnings || []; + // A map from `stylelint-disable` comments to the set of rules that + // are usefully disabled by each comment. We track this + // comment-by-comment rather than range-by-range because ranges that + // disable *all* rules are duplicated for each rule they apply to in + // practice. + /** @type {Map>}} */ + const usefulDisables = new Map(); + disabledWarnings.forEach((warning) => { const rule = warning.rule; - const ruleRanges = rangeData[rule]; if (ruleRanges) { - // Back to front so we get the *last* range that applies to the warning - for (const range of ruleRanges.reverse()) { + for (const range of ruleRanges) { if (isWarningInRange(warning, range)) { - range.used = true; - - return; + putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule); } } } - for (const range of rangeData.all.reverse()) { + for (const range of rangeData.all) { if (isWarningInRange(warning, range)) { - range.used = true; - - return; + putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule); } } }); - Object.keys(rangeData).forEach((rule) => { - rangeData[rule].forEach((range) => { - // Is an equivalent range already marked as unused? - const alreadyMarkedUnused = unused.ranges.find((unusedRange) => { - return unusedRange.start === range.start && unusedRange.end === range.end; - }); - - // If this range is unused and no equivalent is marked, - // mark this range as unused - if (!range.used && !alreadyMarkedUnused) { - unused.ranges.push({ - rule, - start: range.start, - end: range.end, - unusedRule: rule, - }); - } + // Get rid of the duplicated ranges for each `all` rule. We only care + // if the entire `all` rule is useful as a whole or not. + for (const range of rangeData.all) { + for (const [rule, ranges] of Object.entries(rangeData)) { + if (rule === 'all') continue; + _.remove(ranges, (otherRange) => range.comment === otherRange.comment); + } + } - // If this range is used but an equivalent has been marked as unused, - // remove that equivalent. This can happen because of the duplication - // of ranges in rule-specific range sets and the "all" range set - if (range.used && alreadyMarkedUnused) { - _.remove(unused.ranges, alreadyMarkedUnused); - } + Object.entries(rangeData).forEach(([rule, ranges]) => { + ranges.forEach((range) => { + const useful = usefulDisables.get(range.comment) || new Set(); + + // Only emit a warning if this range's comment isn't useful for this rule. + // For the special rule "all", only emit a warning if it's not useful for + // *any* // rules, becuase it covers all of them. + if (rule === 'all' ? useful.size !== 0 : useful.has(rule)) return; + + result.warnings.push({ + text: `Needless disable for "${rule}"`, + rule: '--report-needless-disables', + line: range.comment.source.start.line, + column: range.comment.source.start.column, + severity: 'error', + }); }); }); - - unused.ranges = _.sortBy(unused.ranges, ['start', 'end']); - - report.push(unused); }); - - return report; }; /** diff --git a/lib/prepareReturnValue.js b/lib/prepareReturnValue.js index 9319433cb2..1d7f4c776f 100644 --- a/lib/prepareReturnValue.js +++ b/lib/prepareReturnValue.js @@ -25,6 +25,14 @@ function prepareReturnValue(stylelintResults, options, formatter) { maxWarnings, } = options; + reportDisables(stylelintResults); + + if (reportNeedlessDisables) needlessDisables(stylelintResults); + + if (reportInvalidScopeDisables) invalidScopeDisables(stylelintResults); + + if (reportDescriptionlessDisables) descriptionlessDisables(stylelintResults); + const errored = stylelintResults.some( (result) => result.errored || result.parseErrors.length > 0, ); @@ -34,21 +42,8 @@ function prepareReturnValue(stylelintResults, options, formatter) { errored, results: [], output: '', - reportedDisables: reportDisables(stylelintResults), }; - if (reportNeedlessDisables) { - returnValue.needlessDisables = needlessDisables(stylelintResults); - } - - if (reportInvalidScopeDisables) { - returnValue.invalidScopeDisables = invalidScopeDisables(stylelintResults); - } - - if (reportDescriptionlessDisables) { - returnValue.descriptionlessDisables = descriptionlessDisables(stylelintResults); - } - if (maxWarnings !== undefined) { const foundWarnings = stylelintResults.reduce((count, file) => { return count + file.warnings.length; diff --git a/lib/reportDisables.js b/lib/reportDisables.js index a528fe7640..99aeb2c114 100644 --- a/lib/reportDisables.js +++ b/lib/reportDisables.js @@ -4,28 +4,20 @@ const _ = require('lodash'); /** @typedef {import('stylelint').RangeType} RangeType */ /** @typedef {import('stylelint').DisableReportRange} DisabledRange */ -/** @typedef {import('stylelint').StylelintDisableOptionsReport} StylelintDisableOptionsReport */ /** * Returns a report describing which `results` (if any) contain disabled ranges * for rules that disallow disables via `reportDisables: true`. * * @param {import('stylelint').StylelintResult[]} results - * @returns {StylelintDisableOptionsReport} */ module.exports = function (results) { - /** @type {StylelintDisableOptionsReport} */ - const report = []; - results.forEach((result) => { // File with `CssSyntaxError` don't have `_postcssResult`s. if (!result._postcssResult) { return; } - /** @type {{ranges: DisabledRange[], source: string}} */ - const reported = { source: result.source || '', ranges: [] }; - /** @type {{[ruleName: string]: Array}} */ const rangeData = result._postcssResult.stylelint.disabledRanges; @@ -43,21 +35,16 @@ module.exports = function (results) { rangeData[rule].forEach((range) => { if (!reportDisablesForRule(_.get(config, ['rules', rule], []))) return; - reported.ranges.push({ - rule, - start: range.start, - end: range.end, - unusedRule: rule, + result.warnings.push({ + text: `Rule "${rule}" may not be disabled`, + rule: 'reportDisables', + line: range.comment.source.start.line, + column: range.comment.source.start.column, + severity: 'error', }); }); }); - - reported.ranges = _.sortBy(reported.ranges, ['start', 'end']); - - report.push(reported); }); - - return report; }; /** diff --git a/lib/utils/putIfAbsent.js b/lib/utils/putIfAbsent.js new file mode 100644 index 0000000000..2c5d49d867 --- /dev/null +++ b/lib/utils/putIfAbsent.js @@ -0,0 +1,19 @@ +'use strict'; + +/** + * If `map` already has the given `key`, returns its value. Otherwise, calls + * `callback`, adds the result to `map` at `key`, and then returns it. + * + * @template K + * @template V + * @param {Map} map + * @param {K} key + * @param {() => V} callback + * @returns {V} + */ +module.exports = function (map, key, callback) { + if (map.has(key)) return map.get(key); + const value = callback(); + map.set(key, value); + return value; +}; diff --git a/scripts/visual.css b/scripts/visual.css index d8e28debf6..3564d9623d 100644 --- a/scripts/visual.css +++ b/scripts/visual.css @@ -1,3 +1,4 @@ +/* stylelint-disable foo */ .baz { color: #4f; } diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index 800abaa67e..3c10155b93 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -1,5 +1,5 @@ declare module 'stylelint' { - import { Result, ResultMessage, Root, Syntax, WarningOptions, Warning } from 'postcss'; + import { Comment, Result, ResultMessage, Root, Syntax, WarningOptions, Warning } from 'postcss'; export type StylelintConfigExtends = string | Array; export type StylelintConfigPlugins = string | Array; @@ -31,6 +31,7 @@ declare module 'stylelint' { export type CosmiconfigResult = { config: StylelintConfig; filepath: string }; export type DisabledRange = { + comment: postcss.Comment; start: number; strictStart: boolean; end?: number;