Skip to content

Commit

Permalink
Add media-query-no-invalid (#6963)
Browse files Browse the repository at this point in the history
  • Loading branch information
romainmenke committed Jun 25, 2023
1 parent e409557 commit 5abe468
Show file tree
Hide file tree
Showing 10 changed files with 519 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-jobs-hunt.md
@@ -0,0 +1,5 @@
---
"stylelint": minor
---

Added: `media-query-no-invalid`
1 change: 1 addition & 0 deletions docs/user-guide/rules.md
Expand Up @@ -58,6 +58,7 @@ Disallow invalid syntax with these (sometimes implicit) `no-invalid` rules.
| [`color-no-invalid-hex`](../../lib/rules/color-no-invalid-hex/README.md)<br/>Disallow invalid hex colors. || |
| [`function-calc-no-unspaced-operator`](../../lib/rules/function-calc-no-unspaced-operator/README.md)<br/>Disallow invalid unspaced operator within `calc` functions. || 🔧 |
| [`keyframe-declaration-no-important`](../../lib/rules/keyframe-declaration-no-important/README.md)<br/>Disallow invalid `!important` within keyframe declarations. || |
| [`media-query-no-invalid`](../../lib/rules/media-query-no-invalid/README.md)<br/>Disallow invalid media queries. | | |
| [`named-grid-areas-no-invalid`](../../lib/rules/named-grid-areas-no-invalid/README.md)<br/>Disallow invalid named grid areas. || |
| [`no-invalid-double-slash-comments`](../../lib/rules/no-invalid-double-slash-comments/README.md)<br/>Disallow invalid double-slash comments. || |
| [`no-invalid-position-at-import-rule`](../../lib/rules/no-invalid-position-at-import-rule/README.md)<br/>Disallow invalid position `@import` rules. || |
Expand Down
1 change: 1 addition & 0 deletions lib/rules/index.js
Expand Up @@ -225,6 +225,7 @@ const rules = {
'media-query-list-comma-space-before': importLazy(() =>
require('./media-query-list-comma-space-before'),
)(),
'media-query-no-invalid': importLazy(() => require('./media-query-no-invalid'))(),
'named-grid-areas-no-invalid': importLazy(() => require('./named-grid-areas-no-invalid'))(),
'no-descending-specificity': importLazy(() => require('./no-descending-specificity'))(),
'no-duplicate-at-import-rules': importLazy(() => require('./no-duplicate-at-import-rules'))(),
Expand Down
11 changes: 11 additions & 0 deletions lib/rules/media-feature-name-no-unknown/__tests__/index.js
Expand Up @@ -62,6 +62,9 @@ testRule({
{
code: '@media () { }',
},
{
code: '@media (grid: 1) {}',
},
],

reject: [
Expand Down Expand Up @@ -153,6 +156,14 @@ testRule({
endLine: 1,
endColumn: 49,
},
{
code: '@media (min-grid: 1) { }',
message: messages.rejected('min-grid'),
line: 1,
column: 9,
endLine: 1,
endColumn: 17,
},
],
});

Expand Down
3 changes: 3 additions & 0 deletions lib/rules/media-feature-name-value-no-unknown/index.js
Expand Up @@ -6,6 +6,7 @@ const {
isMediaFeature,
isMediaFeatureValue,
matchesRatioExactly,
isMediaQueryInvalid,
} = require('@csstools/media-query-list-parser');

const atRuleParamIndex = require('../../utils/atRuleParamIndex');
Expand Down Expand Up @@ -239,6 +240,8 @@ const rule = (primary) => {
};

parseMediaQuery(atRule).forEach((mediaQuery) => {
if (isMediaQueryInvalid(mediaQuery)) return;

mediaQuery.walk((entry) => {
if (!entry.state) return;

Expand Down
64 changes: 64 additions & 0 deletions lib/rules/media-query-no-invalid/README.md
@@ -0,0 +1,64 @@
# media-query-no-invalid

Disallow invalid media queries.

<!-- prettier-ignore -->
```css
@media not(min-width: 300px) {}
/** ↑
* This media query */
```

Media queries must be grammatically valid according to the [Media Queries Level 5](https://www.w3.org/TR/mediaqueries-5/) specification.

This rule is only appropriate for CSS. You should not turn it on for CSS-like languages, such as Sass or Less, as they have their own syntaxes.

It works well with other rules that validate feature names and values:

- [`media-feature-name-no-unknown`](../media-feature-name-no-unknown/README.md)
- [`media-feature-name-value-no-unknown`](../media-feature-name-value-no-unknown/README.md)

The [`message` secondary option](../../../docs/user-guide/configure.md#message) can accept the arguments of this rule.

## Options

### `true`

The following patterns are considered problems:

<!-- prettier-ignore -->
```css
@media not(min-width: 300px) {}
```

<!-- prettier-ignore -->
```css
@media (width == 100px) {}
```

<!-- prettier-ignore -->
```css
@media (color) and (hover) or (width) {}
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
@media not (min-width: 300px) {}
```

<!-- prettier-ignore -->
```css
@media (width = 100px) {}
```

<!-- prettier-ignore -->
```css
@media ((color) and (hover)) or (width) {}
```

<!-- prettier-ignore -->
```css
@media (color) and ((hover) or (width)) {}
```
267 changes: 267 additions & 0 deletions lib/rules/media-query-no-invalid/__tests__/index.js
@@ -0,0 +1,267 @@
'use strict';

const { messages, ruleName } = require('..');

testRule({
ruleName,
config: [true],

accept: [
{
code: '@media screen {}',
},
{
code: '@media print {}',
},
{
code: '@media all {}',
},
{
code: '@media {}',
},
{
code: '@media screen and (color) {}',
},
{
code: '@media only screen and (color) {}',
},
{
code: '@media speech and (device-aspect-ratio: 16/9) {}',
},
{
code: '@media (width >= 600px) {}',
},
{
code: '@media (width: 600px) {}',
},
{
code: '@media not (width <= -100px) {}',
},
{
code: '@media not (resolution: -300dpi) {}',
},
{
code: '@media (min-width: 320.01px) {}',
},
{
code: '@media not (color) {}',
},
{
code: '@media (width < 600px) and (height < 600px) {}',
},
{
code: '@media (width < 600px) and (height < 600px) {}',
},
{
code: '@media (not (color)) or (hover) {}',
},
{
code: '@media (not (color)) and (not (hover)) {}',
},
{
code: '@media (not (color)) and (not (hover)) {}',
},
{
code: '@media screen and (max-weight: 3kg) and (color), (color) {}',
},
{
code: '@media not unknown {}',
},
],

reject: [
{
code: '@media @foo {}',
message: messages.rejected('@foo'),
line: 1,
column: 8,
endLine: 1,
endColumn: 12,
},
{
code: '@media screen or (min-width > 500px) {}',
message: messages.rejected('screen or (min-width > 500px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 37,
},
{
code: '@media ((min-width: 300px) and (hover: hover) or (aspect-ratio: 1 / 1)) {}',
message: messages.rejected(
'((min-width: 300px) and (hover: hover) or (aspect-ratio: 1 / 1))',
),
line: 1,
column: 8,
endLine: 1,
endColumn: 72,
},
{
code: '@media (min-width: var(--foo)) {}',
message: messages.rejected('(min-width: var(--foo))'),
line: 1,
column: 8,
endLine: 1,
endColumn: 31,
},
{
code: '@media (min-width: 50%) {}',
message: messages.rejected('(min-width: 50%)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 24,
},
{
code: '@media ((color: 2) and (min-width: 50%)) {}',
message: messages.rejected('(min-width: 50%)'),
line: 1,
column: 24,
endLine: 1,
endColumn: 40,
},
{
code: '@media ((color: foo(--bar)) and (min-width: 50%)) {}',
warnings: [
{
message: messages.rejected('(color: foo(--bar))'),
line: 1,
column: 9,
endLine: 1,
endColumn: 28,
},
{
message: messages.rejected('(min-width: 50%)'),
line: 1,
column: 33,
endLine: 1,
endColumn: 49,
},
],
},
{
code: '@media (color: foo(--bar)), (min-width: 50%) {}',
warnings: [
{
message: messages.rejected('(color: foo(--bar))'),
line: 1,
column: 8,
endLine: 1,
endColumn: 27,
},
{
message: messages.rejected('(min-width: 50%)'),
line: 1,
column: 29,
endLine: 1,
endColumn: 45,
},
],
},
{
code: '@media (--foo: 2) {}',
message: messages.rejected('(--foo: 2)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 18,
},
{
code: '@media (min-width < 500px) {}',
message: messages.rejected('(min-width < 500px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 27,
},
{
code: '@media (min-width) {}',
message: messages.rejected('(min-width)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 19,
},
{
code: '@media (grid < 0) {}',
message: messages.rejected('(grid < 0)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 18,
},
{
code: '@media (not (color) and (hover)) {}',
message: messages.rejected('(not (color) and (hover))'),
line: 1,
column: 8,
endLine: 1,
endColumn: 33,
},

{
code: '@media (color) and (hover) or (width) {}',
message: messages.rejected('(color) and (hover) or (width)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 38,
},
{
code: '@media (width => 100px) {}',
message: messages.rejected('(width => 100px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 24,
},
{
code: '@media (width == 100px) {}',
message: messages.rejected('(width == 100px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 24,
},
{
code: '@media not(min-width: 100px) {}',
message: messages.rejected('not(min-width: 100px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 29,
},
{
code: '@media foo screen {}',
message: messages.rejected('foo screen'),
line: 1,
column: 8,
endLine: 1,
endColumn: 18,
},
{
code: '@media not screen foo (min-width: 300px) {}',
message: messages.rejected('not screen foo (min-width: 300px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 41,
},
{
code: '@media (--foo: 300px) {}',
message: messages.rejected('(--foo: 300px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 22,
},
{
code: '@media (--foo < 300px) {}',
message: messages.rejected('(--foo < 300px)'),
line: 1,
column: 8,
endLine: 1,
endColumn: 23,
},
],
});

0 comments on commit 5abe468

Please sign in to comment.