Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add media-query-no-invalid specific problem messages #7462

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/healthy-donkeys-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint": patch
romainmenke marked this conversation as resolved.
Show resolved Hide resolved
---

Added: `media-query-no-invalid` specific problem messages
30 changes: 24 additions & 6 deletions lib/rules/media-query-no-invalid/__tests__/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -159,31 +159,43 @@ 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,
endColumn: 18,
},
{
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,
endColumn: 27,
},
{
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,
endColumn: 19,
},
{
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,
Expand Down Expand Up @@ -248,15 +260,21 @@ 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,
endColumn: 22,
},
{
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,
Expand Down
84 changes: 57 additions & 27 deletions lib/rules/media-query-no-invalid/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ 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, hint) => {
if (!hint) return `Unexpected invalid media query "${query}"`;

return `Unexpected invalid media query "${query}", ${hint}`;
},
});

const HAS_MIN_MAX_PREFIX = /^(?:min|max)-/i;
Expand All @@ -34,21 +38,20 @@ const rule = (primary) => {
}

root.walkAtRules(/^media$/i, (atRule) => {
/** @type {Array<{tokens(): Array<import('@csstools/css-tokenizer').CSSToken>}>} */
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;
}

mediaQuery.walk(({ node, parent }) => {
// All general enclosed nodes are invalid.
if (mediaQueryListParser.isGeneralEnclosed(node)) {
invalidNodes.push(node);
complain(atRule, atRuleParamIndexValue, node);

return;
}
Expand All @@ -59,7 +62,12 @@ const rule = (primary) => {

if (isCustomMediaQuery(name)) {
// In a plain context, custom media queries are invalid.
invalidNodes.push(parent);
complain(
atRule,
atRuleParamIndexValue,
parent,
'custom media queries can only be used in boolean queries',
);

return;
}
Expand All @@ -73,21 +81,36 @@ const rule = (primary) => {

if (isCustomMediaQuery(name)) {
// In a range context, custom media queries are invalid.
invalidNodes.push(parent);
complain(
atRule,
atRuleParamIndexValue,
parent,
'custom media queries can only be used in boolean queries',
);

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-" and "max-" prefixes are not needed when using range queries',
);

return;
}

if (!mediaFeatures.rangeTypeMediaFeatureNames.has(name)) {
// In a range context, non-range typed features are invalid.
invalidNodes.push(parent);
complain(
atRule,
atRuleParamIndexValue,
parent,
'discrete features can only be used in plain and boolean queries',
);

return;
}
Expand All @@ -101,30 +124,37 @@ const rule = (primary) => {

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-" and "max-" prefixes are not needed in boolean queries',
);
}
}
});
});
});

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<import('@csstools/css-tokenizer').CSSToken>}} node
* @param {string} messageHint
*/
function complain(atRule, index, node, messageHint = '') {
const [start, end] = cssParserAlgorithms.sourceIndices(node);

report({
message: messages.rejected,
messageArgs: [node.toString(), messageHint],
index: index + start,
endIndex: index + end + 1,
node: atRule,
ruleName,
result,
});
});
}
};
};

Expand Down
84 changes: 57 additions & 27 deletions lib/rules/media-query-no-invalid/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ 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, hint) => {
if (!hint) return `Unexpected invalid media query "${query}"`;

return `Unexpected invalid media query "${query}", ${hint}`;
},
});

const HAS_MIN_MAX_PREFIX = /^(?:min|max)-/i;
Expand All @@ -37,21 +41,20 @@ const rule = (primary) => {
}

root.walkAtRules(/^media$/i, (atRule) => {
/** @type {Array<{tokens(): Array<import('@csstools/css-tokenizer').CSSToken>}>} */
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;
}

mediaQuery.walk(({ node, parent }) => {
// All general enclosed nodes are invalid.
if (isGeneralEnclosed(node)) {
invalidNodes.push(node);
complain(atRule, atRuleParamIndexValue, node);

return;
}
Expand All @@ -62,7 +65,12 @@ const rule = (primary) => {

if (isCustomMediaQuery(name)) {
// In a plain context, custom media queries are invalid.
invalidNodes.push(parent);
complain(
ybiquitous marked this conversation as resolved.
Show resolved Hide resolved
atRule,
atRuleParamIndexValue,
parent,
'custom media queries can only be used in boolean queries',
);

return;
}
Expand All @@ -76,21 +84,36 @@ const rule = (primary) => {

if (isCustomMediaQuery(name)) {
// In a range context, custom media queries are invalid.
invalidNodes.push(parent);
complain(
atRule,
atRuleParamIndexValue,
parent,
'custom media queries can only be used in boolean queries',
);

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-" and "max-" prefixes are not needed when using range queries',
);

return;
}

if (!rangeTypeMediaFeatureNames.has(name)) {
// In a range context, non-range typed features are invalid.
invalidNodes.push(parent);
complain(
atRule,
atRuleParamIndexValue,
parent,
'discrete features can only be used in plain and boolean queries',
);

return;
}
Expand All @@ -104,30 +127,37 @@ const rule = (primary) => {

if (HAS_MIN_MAX_PREFIX.test(name)) {
// In a range context, min- and max- prefixed feature names are invalid
Mouvedia marked this conversation as resolved.
Show resolved Hide resolved
invalidNodes.push(parent);
complain(
atRule,
atRuleParamIndexValue,
parent,
'"min-" and "max-" prefixes are not needed in boolean queries',
);
}
}
});
});
});

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<import('@csstools/css-tokenizer').CSSToken>}} node
* @param {string} messageHint
*/
function complain(atRule, index, node, messageHint = '') {
const [start, end] = sourceIndices(node);

report({
message: messages.rejected,
messageArgs: [node.toString(), messageHint],
index: index + start,
endIndex: index + end + 1,
node: atRule,
ruleName,
result,
});
});
}
};
};

Expand Down