From 5bb517bee77bebe77e04dfc8a45ed2778067ac48 Mon Sep 17 00:00:00 2001 From: Romain Menke <11521496+romainmenke@users.noreply.github.com> Date: Mon, 15 Jan 2024 08:11:45 +0100 Subject: [PATCH] Add `media-query-no-invalid` specific problem messages (#7462) * Add `media-query-no-invalid` specific problem messages * Create healthy-donkeys-appear.md * apply suggestions * Update .changeset/healthy-donkeys-appear.md Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --------- Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --- .changeset/healthy-donkeys-appear.md | 5 ++ .../__tests__/index.mjs | 30 ++++++-- lib/rules/media-query-no-invalid/index.cjs | 70 +++++++++++-------- lib/rules/media-query-no-invalid/index.mjs | 70 +++++++++++-------- 4 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 .changeset/healthy-donkeys-appear.md diff --git a/.changeset/healthy-donkeys-appear.md b/.changeset/healthy-donkeys-appear.md new file mode 100644 index 0000000000..670ab0c69e --- /dev/null +++ b/.changeset/healthy-donkeys-appear.md @@ -0,0 +1,5 @@ +--- +"stylelint": minor +--- + +Added: `media-query-no-invalid` specific problem messages diff --git a/lib/rules/media-query-no-invalid/__tests__/index.mjs b/lib/rules/media-query-no-invalid/__tests__/index.mjs index 8350cb2d2f..5121b0e5bb 100644 --- a/lib/rules/media-query-no-invalid/__tests__/index.mjs +++ b/lib/rules/media-query-no-invalid/__tests__/index.mjs @@ -159,7 +159,10 @@ testRule({ }, { code: '@media (--foo: 2) {}', - message: messages.rejected('(--foo: 2)'), + message: messages.rejected( + '(--foo: 2)', + 'custom media queries can only be used in boolean queries', + ), line: 1, column: 8, endLine: 1, @@ -167,7 +170,10 @@ testRule({ }, { code: '@media (min-width < 500px) {}', - message: messages.rejected('(min-width < 500px)'), + message: messages.rejected( + '(min-width < 500px)', + '"min-" and "max-" prefixes are not needed when using range queries', + ), line: 1, column: 8, endLine: 1, @@ -175,7 +181,10 @@ testRule({ }, { code: '@media (min-width) {}', - message: messages.rejected('(min-width)'), + message: messages.rejected( + '(min-width)', + '"min-" and "max-" prefixes are not needed in boolean queries', + ), line: 1, column: 8, endLine: 1, @@ -183,7 +192,10 @@ testRule({ }, { code: '@media (grid < 0) {}', - message: messages.rejected('(grid < 0)'), + message: messages.rejected( + '(grid < 0)', + 'discrete features can only be used in plain and boolean queries', + ), line: 1, column: 8, endLine: 1, @@ -248,7 +260,10 @@ testRule({ }, { code: '@media (--foo: 300px) {}', - message: messages.rejected('(--foo: 300px)'), + message: messages.rejected( + '(--foo: 300px)', + 'custom media queries can only be used in boolean queries', + ), line: 1, column: 8, endLine: 1, @@ -256,7 +271,10 @@ testRule({ }, { code: '@media (--foo < 300px) {}', - message: messages.rejected('(--foo < 300px)'), + message: messages.rejected( + '(--foo < 300px)', + 'custom media queries can only be used in boolean queries', + ), line: 1, column: 8, endLine: 1, diff --git a/lib/rules/media-query-no-invalid/index.cjs b/lib/rules/media-query-no-invalid/index.cjs index 3b69b9884c..9a12ca15b0 100644 --- a/lib/rules/media-query-no-invalid/index.cjs +++ b/lib/rules/media-query-no-invalid/index.cjs @@ -15,9 +15,20 @@ const validateOptions = require('../../utils/validateOptions.cjs'); const ruleName = 'media-query-no-invalid'; const messages = ruleMessages(ruleName, { - rejected: (query) => `Unexpected invalid media query "${query}"`, + rejected: (query, reason) => { + if (!reason) return `Unexpected invalid media query "${query}"`; + + return `Unexpected invalid media query "${query}", ${reason}`; + }, }); +const reasons = { + custom: 'custom media queries can only be used in boolean queries', + min_max_in_range: '"min-" and "max-" prefixes are not needed when using range queries', + min_max_in_boolean: '"min-" and "max-" prefixes are not needed in boolean queries', + discrete: 'discrete features can only be used in plain and boolean queries', +}; + const HAS_MIN_MAX_PREFIX = /^(?:min|max)-/i; const meta = { @@ -34,13 +45,12 @@ const rule = (primary) => { } root.walkAtRules(/^media$/i, (atRule) => { - /** @type {Array<{tokens(): Array}>} */ - let invalidNodes = []; + const atRuleParamIndexValue = atRuleParamIndex(atRule); parseMediaQuery(atRule).forEach((mediaQuery) => { if (mediaQueryListParser.isMediaQueryInvalid(mediaQuery)) { // Queries that fail to parse are invalid. - invalidNodes.push(mediaQuery); + complain(atRule, atRuleParamIndexValue, mediaQuery); return; } @@ -48,7 +58,7 @@ const rule = (primary) => { mediaQuery.walk(({ node, parent }) => { // All general enclosed nodes are invalid. if (mediaQueryListParser.isGeneralEnclosed(node)) { - invalidNodes.push(node); + complain(atRule, atRuleParamIndexValue, node); return; } @@ -59,7 +69,7 @@ const rule = (primary) => { if (isCustomMediaQuery(name)) { // In a plain context, custom media queries are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'custom'); return; } @@ -73,21 +83,21 @@ const rule = (primary) => { if (isCustomMediaQuery(name)) { // In a range context, custom media queries are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'custom'); return; } if (HAS_MIN_MAX_PREFIX.test(name)) { // In a range context, min- and max- prefixed feature names are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'min_max_in_range'); return; } if (!mediaFeatures.rangeTypeMediaFeatureNames.has(name)) { // In a range context, non-range typed features are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'discrete'); return; } @@ -97,34 +107,36 @@ const rule = (primary) => { // Invalid boolean media features. if (mediaQueryListParser.isMediaFeatureBoolean(node)) { - const name = node.getName().toLowerCase(); + const name = node.getName(); if (HAS_MIN_MAX_PREFIX.test(name)) { - // In a range context, min- and max- prefixed feature names are invalid - invalidNodes.push(parent); + // In a boolean feature, min- and max- prefixed feature names are invalid + complain(atRule, atRuleParamIndexValue, parent, 'min_max_in_boolean'); } } }); }); + }); - if (invalidNodes.length === 0) return; - - const atRuleParamIndexValue = atRuleParamIndex(atRule); - - invalidNodes.forEach((invalidNode) => { - const [start, end] = cssParserAlgorithms.sourceIndices(invalidNode); - - report({ - message: messages.rejected, - messageArgs: [invalidNode.toString()], - index: atRuleParamIndexValue + start, - endIndex: atRuleParamIndexValue + end + 1, - node: atRule, - ruleName, - result, - }); + /** + * @param {import('postcss').AtRule} atRule + * @param {number} index + * @param {{tokens(): Array}} node + * @param {keyof reasons} [reason] + */ + function complain(atRule, index, node, reason) { + const [start, end] = cssParserAlgorithms.sourceIndices(node); + + report({ + message: messages.rejected, + messageArgs: [node.toString(), reason ? reasons[reason] : ''], + index: index + start, + endIndex: index + end + 1, + node: atRule, + ruleName, + result, }); - }); + } }; }; diff --git a/lib/rules/media-query-no-invalid/index.mjs b/lib/rules/media-query-no-invalid/index.mjs index 97c0c3cbf5..570db60745 100644 --- a/lib/rules/media-query-no-invalid/index.mjs +++ b/lib/rules/media-query-no-invalid/index.mjs @@ -18,9 +18,20 @@ import validateOptions from '../../utils/validateOptions.mjs'; const ruleName = 'media-query-no-invalid'; const messages = ruleMessages(ruleName, { - rejected: (query) => `Unexpected invalid media query "${query}"`, + rejected: (query, reason) => { + if (!reason) return `Unexpected invalid media query "${query}"`; + + return `Unexpected invalid media query "${query}", ${reason}`; + }, }); +const reasons = { + custom: 'custom media queries can only be used in boolean queries', + min_max_in_range: '"min-" and "max-" prefixes are not needed when using range queries', + min_max_in_boolean: '"min-" and "max-" prefixes are not needed in boolean queries', + discrete: 'discrete features can only be used in plain and boolean queries', +}; + const HAS_MIN_MAX_PREFIX = /^(?:min|max)-/i; const meta = { @@ -37,13 +48,12 @@ const rule = (primary) => { } root.walkAtRules(/^media$/i, (atRule) => { - /** @type {Array<{tokens(): Array}>} */ - let invalidNodes = []; + const atRuleParamIndexValue = atRuleParamIndex(atRule); parseMediaQuery(atRule).forEach((mediaQuery) => { if (isMediaQueryInvalid(mediaQuery)) { // Queries that fail to parse are invalid. - invalidNodes.push(mediaQuery); + complain(atRule, atRuleParamIndexValue, mediaQuery); return; } @@ -51,7 +61,7 @@ const rule = (primary) => { mediaQuery.walk(({ node, parent }) => { // All general enclosed nodes are invalid. if (isGeneralEnclosed(node)) { - invalidNodes.push(node); + complain(atRule, atRuleParamIndexValue, node); return; } @@ -62,7 +72,7 @@ const rule = (primary) => { if (isCustomMediaQuery(name)) { // In a plain context, custom media queries are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'custom'); return; } @@ -76,21 +86,21 @@ const rule = (primary) => { if (isCustomMediaQuery(name)) { // In a range context, custom media queries are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'custom'); return; } if (HAS_MIN_MAX_PREFIX.test(name)) { // In a range context, min- and max- prefixed feature names are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'min_max_in_range'); return; } if (!rangeTypeMediaFeatureNames.has(name)) { // In a range context, non-range typed features are invalid. - invalidNodes.push(parent); + complain(atRule, atRuleParamIndexValue, parent, 'discrete'); return; } @@ -100,34 +110,36 @@ const rule = (primary) => { // Invalid boolean media features. if (isMediaFeatureBoolean(node)) { - const name = node.getName().toLowerCase(); + const name = node.getName(); if (HAS_MIN_MAX_PREFIX.test(name)) { - // In a range context, min- and max- prefixed feature names are invalid - invalidNodes.push(parent); + // In a boolean feature, min- and max- prefixed feature names are invalid + complain(atRule, atRuleParamIndexValue, parent, 'min_max_in_boolean'); } } }); }); + }); - if (invalidNodes.length === 0) return; - - const atRuleParamIndexValue = atRuleParamIndex(atRule); - - invalidNodes.forEach((invalidNode) => { - const [start, end] = sourceIndices(invalidNode); - - report({ - message: messages.rejected, - messageArgs: [invalidNode.toString()], - index: atRuleParamIndexValue + start, - endIndex: atRuleParamIndexValue + end + 1, - node: atRule, - ruleName, - result, - }); + /** + * @param {import('postcss').AtRule} atRule + * @param {number} index + * @param {{tokens(): Array}} node + * @param {keyof reasons} [reason] + */ + function complain(atRule, index, node, reason) { + const [start, end] = sourceIndices(node); + + report({ + message: messages.rejected, + messageArgs: [node.toString(), reason ? reasons[reason] : ''], + index: index + start, + endIndex: index + end + 1, + node: atRule, + ruleName, + result, }); - }); + } }; };