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-feature-name-value-no-unknown #6906

Merged
Show file tree
Hide file tree
Changes from 13 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/real-dingos-boil.md
@@ -0,0 +1,5 @@
---
"stylelint": patch
romainmenke marked this conversation as resolved.
Show resolved Hide resolved
---

Added: `media-feature-name-value-no-unknown`
89 changes: 65 additions & 24 deletions lib/reference/mediaFeatures.js
Expand Up @@ -6,25 +6,27 @@ const deprecatedMediaFeatureNames = new Set([
'device-aspect-ratio',
'device-height',
'device-width',
'max-device-aspect-ratio',
'max-device-height',
'max-device-width',
'min-device-aspect-ratio',
'min-device-height',
'min-device-width',
]);

const rangeTypeMediaFeatureNames = new Set([
const rangeTypeMediaFeatureNames = uniteSets(deprecatedMediaFeatureNames, [
'aspect-ratio',
'color-index',
'color',
'color-index',
'height',
'horizontal-viewport-segments',
'monochrome',
'resolution',
'vertical-viewport-segments',
'width',
]);

const mediaFeatureNames = uniteSets(deprecatedMediaFeatureNames, rangeTypeMediaFeatureNames, [
const rangeTypeMediaFeatureNamesWithMinMaxPrefix = new Set(
[...rangeTypeMediaFeatureNames].flatMap((name) => {
return [`min-${name}`, `max-${name}`];
}),
);
Mouvedia marked this conversation as resolved.
Show resolved Hide resolved

const discreteTypeMediaFeatureNames = new Set([
'any-hover',
'any-pointer',
'color-gamut',
Expand All @@ -35,20 +37,6 @@ const mediaFeatureNames = uniteSets(deprecatedMediaFeatureNames, rangeTypeMediaF
'hover',
'inverted-colors',
'light-level',
'max-aspect-ratio',
'max-color',
'max-color-index',
'max-height',
'max-monochrome',
'max-resolution',
'max-width',
'min-aspect-ratio',
'min-color',
'min-color-index',
'min-height',
'min-monochrome',
'min-resolution',
'min-width',
'orientation',
'overflow-block',
'overflow-inline',
Expand All @@ -63,7 +51,60 @@ const mediaFeatureNames = uniteSets(deprecatedMediaFeatureNames, rangeTypeMediaF
'video-dynamic-range',
]);

module.exports = {
const mediaFeatureNames = uniteSets(
deprecatedMediaFeatureNames,
rangeTypeMediaFeatureNames,
rangeTypeMediaFeatureNamesWithMinMaxPrefix,
discreteTypeMediaFeatureNames,
);

const mediaFeatureNameAllowedValueKeywords = new Map([
['any-hover', new Set(['none', 'hover'])],
['any-pointer', new Set(['none', 'coarse', 'fine'])],
['color-gamut', new Set(['srgb', 'p3', 'rec2020'])],
['display-mode', new Set(['fullscreen', 'standalone', 'minimal-ui', 'browser'])],
['dynamic-range', new Set(['standard', 'high'])],
['environment-blending', new Set(['opaque', 'additive', 'subtractive'])],
['forced-colors', new Set(['none', 'active'])],
['hover', new Set(['none', 'hover'])],
['inverted-colors', new Set(['none', 'inverted'])],
['nav-controls', new Set(['none', 'back'])],
['orientation', new Set(['portrait', 'landscape'])],
['overflow-block', new Set(['none', 'scroll', 'paged'])],
['overflow-inline', new Set(['none', 'scroll'])],
['pointer', new Set(['none', 'coarse', 'fine'])],
['prefers-color-scheme', new Set(['light', 'dark'])],
['prefers-contrast', new Set(['no-preference', 'less', 'more', 'custom'])],
['prefers-reduced-data', new Set(['no-preference', 'reduce'])],
['prefers-reduced-motion', new Set(['no-preference', 'reduce'])],
['prefers-reduced-transparency', new Set(['no-preference', 'reduce'])],
['resolution', new Set(['infinite'])],
['scan', new Set(['interlace', 'progressive'])],
['scripting', new Set(['none', 'initial-only', 'enabled'])],
['update', new Set(['none', 'slow', 'fast'])],
['video-color-gamut', new Set(['srgb', 'p3', 'rec2020'])],
['video-dynamic-range', new Set(['standard', 'high'])],
]);

const mediaFeatureNameAllowedValueTypes = new Map([
['aspect-ratio', new Set(['ratio'])],
['color', new Set(['integer'])],
['color-index', new Set(['integer'])],
['device-aspect-ratio', new Set(['ratio'])],
['device-height', new Set(['length'])],
['device-width', new Set(['length'])],
['height', new Set(['length'])],
['horizontal-viewport-segments', new Set(['integer'])],
['monochrome', new Set(['integer'])],
['resolution', new Set(['resolution'])],
['vertical-viewport-segments', new Set(['integer'])],
['width', new Set(['length'])],
]);

module.exports = {
mediaFeatureNameAllowedValueKeywords,
mediaFeatureNameAllowedValueTypes,
mediaFeatureNames,
rangeTypeMediaFeatureNames,
rangeTypeMediaFeatureNamesWithMinMaxPrefix,
};
3 changes: 3 additions & 0 deletions lib/rules/index.js
Expand Up @@ -200,6 +200,9 @@ const rules = {
'media-feature-name-value-allowed-list': importLazy(() =>
require('./media-feature-name-value-allowed-list'),
)(),
'media-feature-name-value-no-unknown': importLazy(() =>
require('./media-feature-name-value-no-unknown'),
)(),
'media-feature-parentheses-space-inside': importLazy(() =>
require('./media-feature-parentheses-space-inside'),
)(),
Expand Down
57 changes: 57 additions & 0 deletions lib/rules/media-feature-name-value-no-unknown/README.md
@@ -0,0 +1,57 @@
# media-feature-name-value-no-unknown

Disallow unknown values for media features.

<!-- prettier-ignore -->
```css
@media (color: red) {}
/** ↑ ↑
* feature and value pairs like these */
```

This rule considers values for media features defined within the CSS specifications to be known.

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.

This rule is experimental with some false negatives that we'll patch in minor releases.

It sometimes overlaps with:

- [`unit-no-unknown`](../unit-no-unknown/README.md)

If duplicate problems are flagged, you can turn off the corresponding rule.

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 (color: red) { top: 1px; }
```

<!-- prettier-ignore -->
```css
@media (width: 10) { top: 1px; }
```

<!-- prettier-ignore -->
```css
@media (width: auto) { top: 1px; }
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
@media (color: 8) { top: 1px; }
```

<!-- prettier-ignore -->
```css
@media (width: 10px) { top: 1px; }
```
196 changes: 196 additions & 0 deletions lib/rules/media-feature-name-value-no-unknown/__tests__/index.js
@@ -0,0 +1,196 @@
'use strict';

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

testRule({
ruleName,
config: [true],

accept: [
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

screen-spanning is an unknown media feature and those are ignored by this rule (tested with (foo: ...)).

media-feature-name-no-unknown already exists to cover those issues.

{
code: '@media screen and (min-width: 768px) {}',
description: 'Basic media feature',
},
{
code: '@media screen and (min-width: 0) {}',
description: 'Length of zero, without a unit',
},
{
code: '@media screen and ( min-resolution : 2dpcm ) {}',
description: 'A resolution',
},
{
code: '@media screen and ( -moz-min-resolution : 2dpcm ) {}',
description: 'A resolution with a vendor prefix',
},
{
code: '@media screen and (resolution: 10.1dpcm) {}',
description: 'A resolution with a floating point value',
},
{
code: '@media screen and (min-width: $sm) {}',
description: 'Non-standard syntax is not checked by this rule',
},
{
code: '@media (color) {}',
description: 'Boolean context',
},
{
code: '@media (color : 1) {}',
description: 'Integer value',
},
{
code: '@media (aspect-ratio : 1 / 1) {}',
description: 'Aspect ratio value',
},
{
code: '@media (aspect-ratio : 1.2) {}',
Mouvedia marked this conversation as resolved.
Show resolved Hide resolved
description: 'Float value for aspect ratio',
},
{
code: '@media (aspect-ratio : 1 / calc(Pi)) {}',
description: 'Math expression in aspect ratio',
},
{
code: '@media (width: max(sin(90deg) * 10px, 100px)) {}',
description: 'Math expression in length',
},
{
code: '@media screen and (min-width <= 768px) {}',
description: 'Range context, media feature in allowed list',
},
{
code: '@media (hover: #foo) {}',
description: 'General enclosed because hash tokens are invalid syntax',
},
{
code: '@media (hover: 10%) {}',
description: 'General enclosed because percentage tokens are invalid syntax',
},
{
code: '@media screen and (min-width: env(some-width-variable)) and (hover: env(some-width-variable)) and (aspect-ratio < env(some-number-variable) / 100) {}',
description: 'Environment variables',
},
{
code: '@media (-webkit-hover: -webkit-hover) {}',
description: 'Double vendor prefixes when valid',
},
{
code: '@media (foo: 100px) {}',
description: 'Unknown media feature',
},
],
romainmenke marked this conversation as resolved.
Show resolved Hide resolved

reject: [
{
code: '@media screen and (min-width: 1000khz) {}',
description: 'Frequency value when only lengths are allowed',
message: messages.rejected('min-width', '1000khz'),
line: 1,
column: 31,
endLine: 1,
endColumn: 37,
ybiquitous marked this conversation as resolved.
Show resolved Hide resolved
},
{
code: '@media screen and (width <= 1000khz) {}',
description: 'Frequency value when only lengths are allowed in a range context',
message: messages.rejected('width', '1000khz'),
line: 1,
column: 29,
endLine: 1,
endColumn: 35,
},
{
code: '@media screen and (-webkit-width = 1000khz) {}',
description: 'Validates vendor-prefixed media features',
message: messages.rejected('-webkit-width', '1000khz'),
line: 1,
column: 36,
endLine: 1,
endColumn: 42,
},
{
code: '@media (-webkit-hover: -webkit-pointer) {}',
description: 'Double vendor prefixes when invalid',
message: messages.rejected('-webkit-hover', '-webkit-pointer'),
line: 1,
column: 24,
endLine: 1,
endColumn: 38,
},
{
code: '@media (color: 1.1) {}',
description: 'Float value when only integers are allowed',
message: messages.rejected('color', '1.1'),
line: 1,
column: 16,
endLine: 1,
endColumn: 18,
},
{
code: '@media (color: 1 / 1) {}',
description: 'Aspect ratio value when only integers are allowed',
message: messages.rejected('color', '1 / 1'),
line: 1,
column: 16,
endLine: 1,
endColumn: 20,
},
{
code: '@media (hover: 15) {}',
description: 'Integer when only keywords are allowed',
message: messages.rejected('hover', '15'),
line: 1,
column: 16,
endLine: 1,
endColumn: 17,
},
{
code: '@media (width: 15) {}',
description: 'Integer when only lengths are allowed',
message: messages.rejected('width', '15'),
line: 1,
column: 16,
endLine: 1,
endColumn: 17,
},
{
code: '@media (hover: 10px) {}',
description: 'Dimension when only keywords are allowed',
message: messages.rejected('hover', '10px'),
line: 1,
column: 16,
endLine: 1,
endColumn: 19,
},
{
code: '@media (hover: sin(90deg)) {}',
description: 'Math expressions when only keywords are allowed',
message: messages.rejected('hover', 'sin(90deg)'),
line: 1,
column: 16,
endLine: 1,
endColumn: 25,
},
{
code: '@media (color: purple) and (hover: pointer) {}',
description: 'Incorrect keywords',
warnings: [
{
message: messages.rejected('color', 'purple'),
line: 1,
column: 16,
endLine: 1,
endColumn: 21,
},
{
message: messages.rejected('hover', 'pointer'),
line: 1,
column: 36,
endLine: 1,
endColumn: 42,
},
],
},
],
});