diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2dc48b09..6ab07cb90f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ All notable changes to this project are documented in this file. - Fixed: inconsistent trailing newlines in CLI error output ([#4876](https://github.com/stylelint/stylelint/pull/4876)). - Fixed: `selector-max-*` (except `selector-max-type`) false negatives for `where`, `is`, `nth-child` and `nth-last-child` ([#4842](https://github.com/stylelint/stylelint/pull/4842)). - Fixed: `length-zero-no-unit` TypeError for custom properties fallback ([#4860](https://github.com/stylelint/stylelint/pull/4860)). +- Fixed: false negatives for `where`, `is`, `nth-child` and `nth-last-child` in `selector-max-*` rules (except selector-max-type) ([#4842](https://github.com/stylelint/stylelint/pull/4842)). +- Fixed: support for multi-line disable descriptions ([#4895](https://github.com/stylelint/stylelint/pull/4895)). ## 13.6.1 diff --git a/lib/__tests__/disableRanges.test.js b/lib/__tests__/disableRanges.test.js index fc93c29ebb..d31bea78e8 100644 --- a/lib/__tests__/disableRanges.test.js +++ b/lib/__tests__/disableRanges.test.js @@ -747,6 +747,32 @@ it('SCSS // line-disabling comment (with description)', () => { }); }); +it('SCSS // disable next-line comment (with multi-line description)', () => { + const scssSource = `a { + // stylelint-disable-next-line declaration-no-important + // -- + // Long-winded description + color: pink !important; + }`; + + return postcss() + .use(assignDisabledRanges) + .process(scssSource, { syntax: scss, from: undefined }) + .then((result) => { + expect(result.stylelint.disabledRanges).toEqual({ + all: [], + 'declaration-no-important': [ + { + start: 5, + end: 5, + strictStart: true, + strictEnd: true, + }, + ], + }); + }); +}); + it('Less // line-disabling comment (with description)', () => { const lessSource = `a { color: pink !important; // stylelint-disable-line declaration-no-important -- Description @@ -770,6 +796,32 @@ it('Less // line-disabling comment (with description)', () => { }); }); +it('Less // disable next-line comment (with multi-line description)', () => { + const lessSource = `a { + // stylelint-disable-next-line declaration-no-important + // -- + // Long-winded description + color: pink !important; + }`; + + return postcss() + .use(assignDisabledRanges) + .process(lessSource, { syntax: less, from: undefined }) + .then((result) => { + expect(result.stylelint.disabledRanges).toEqual({ + all: [], + 'declaration-no-important': [ + { + start: 5, + end: 5, + strictStart: true, + strictEnd: true, + }, + ], + }); + }); +}); + function testDisableRanges(source, cb) { return postcss().use(assignDisabledRanges).process(source, { from: undefined }).then(cb); } diff --git a/lib/assignDisabledRanges.js b/lib/assignDisabledRanges.js index 31406d74b4..0f1b4352ba 100644 --- a/lib/assignDisabledRanges.js +++ b/lib/assignDisabledRanges.js @@ -9,7 +9,7 @@ const disableLineCommand = `${COMMAND_PREFIX}disable-line`; const disableNextLineCommand = `${COMMAND_PREFIX}disable-next-line`; const ALL_RULES = 'all'; -/** @typedef {import('postcss').Comment} PostcssComment */ +/** @typedef {import('postcss/lib/comment')} PostcssComment */ /** @typedef {import('postcss').Root} PostcssRoot */ /** @typedef {import('stylelint').PostcssResult} PostcssResult */ /** @typedef {import('stylelint').DisabledRangeObject} DisabledRangeObject */ @@ -53,10 +53,53 @@ module.exports = function (root, result) { }; result.stylelint.disabledRanges = disabledRanges; - root.walkComments(checkComment); + + // Work around postcss/postcss-scss#109 by merging adjacent `//` comments + // into a single node before passing to `checkComment`. + + /** @type {PostcssComment?} */ + let inlineEnd; + + root.walkComments((/** @type {PostcssComment} */ comment) => { + if (inlineEnd) { + // Ignore comments already processed by grouping with a previous one. + if (inlineEnd === comment) inlineEnd = null; + } else if (isInlineComment(comment)) { + const fullComment = comment.clone(); + let next = comment.next(); + + while (next && next.type === 'comment') { + /** @type {PostcssComment} */ + const current = next; + + if (!isInlineComment(current)) break; + + fullComment.text += `\n${current.text}`; + + if (fullComment.source && current.source) { + fullComment.source.end = current.source.end; + } + + inlineEnd = current; + next = current.next(); + } + checkComment(fullComment); + } else { + checkComment(comment); + } + }); return result; + /** + * @param {PostcssComment} comment + */ + function isInlineComment(comment) { + // We check both here because the Sass parser uses `raws.inline` to indicate + // inline comments, while the Less parser uses `inline`. + return comment.inline || comment.raws.inline; + } + /** * @param {PostcssComment} comment */ @@ -74,8 +117,8 @@ module.exports = function (root, result) { * @param {PostcssComment} comment */ function processDisableNextLineCommand(comment) { - if (comment.source && comment.source.start) { - const line = comment.source.start.line; + if (comment.source && comment.source.end) { + const line = comment.source.end.line; getCommandRules(disableNextLineCommand, comment.text).forEach((ruleName) => { disableLine(line + 1, ruleName, comment); diff --git a/types/postcss/index.d.ts b/types/postcss/index.d.ts index 17912361e6..93e6a97875 100644 --- a/types/postcss/index.d.ts +++ b/types/postcss/index.d.ts @@ -1,3 +1,20 @@ +declare module 'postcss/lib/comment' { + import { Comment, NodeRaws } from 'postcss'; + + interface NodeRawsExt extends NodeRaws { + // Used by the SCSS parser to indicate `//` comments. + inline?: boolean; + } + + interface CommentExt extends Comment { + // Used by the Less parser to indicate `//` comments. + inline?: boolean; + raws: NodeRawsExt; + } + + export = CommentExt; +} + declare module 'postcss/lib/lazy-result' { import { LazyResult,