@double-great/stylelint-a11y 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +40 -12
- package/package.json +13 -2
- package/src/index.js +35 -1
- package/src/rules/content-property-no-static-value/README.md +20 -0
- package/src/rules/content-property-no-static-value/index.js +13 -7
- package/src/rules/font-size-is-readable/index.js +6 -0
- package/src/rules/index.js +1 -1
- package/src/rules/line-height-is-vertical-rhythmed/README.md +19 -0
- package/src/rules/line-height-is-vertical-rhythmed/index.js +15 -5
- package/src/rules/media-prefers-color-scheme/index.js +6 -0
- package/src/rules/media-prefers-reduced-motion/index.js +88 -19
- package/src/rules/no-display-none/index.js +7 -1
- package/src/rules/no-obsolete-attribute/index.js +9 -1
- package/src/rules/no-obsolete-element/index.js +9 -1
- package/src/rules/no-outline-none/index.js +7 -1
- package/src/rules/no-spread-text/README.md +19 -0
- package/src/rules/no-spread-text/index.js +12 -3
- package/src/rules/no-text-align-justify/index.js +7 -1
- package/src/rules/selector-pseudo-class-focus/index.js +6 -0
- package/test/e2e/README.md +37 -0
- package/test/e2e/cli.test.js +71 -0
- package/test/e2e/performance.test.js +98 -0
- package/test/e2e/projects/basic-css/.stylelintrc.json +17 -0
- package/test/e2e/projects/basic-css/styles.css +81 -0
- package/test/e2e/projects/large-codebase/.stylelintrc.json +17 -0
- package/test/e2e/projects/large-codebase/components.css +329 -0
- package/test/e2e/projects/large-codebase/layout.css +393 -0
- package/test/e2e/projects/scss-project/.stylelintrc.json +18 -0
- package/test/e2e/projects/scss-project/main.scss +163 -0
- package/test/e2e/projects.test.js +349 -0
- package/test/helpers/exec-stylelint.js +80 -0
- package/test/helpers/simple-test-utils.js +75 -0
- package/test/integration/integration.test.js +265 -0
- package/test/system/ci-integration.test.js +244 -0
- package/test/system/compatibility.test.js +249 -0
- package/test/system/performance-regression.test.js +277 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,68 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.3.0] - 2025-08-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Advanced testing infrastructure with comprehensive CI/CD workflows
|
|
13
|
+
- System-level performance regression testing suite
|
|
14
|
+
- Cross-platform compatibility testing for Node.js environments
|
|
15
|
+
- Integration tests for real-world stylelint plugin functionality
|
|
16
|
+
- Enhanced rule configuration options with customizable parameters
|
|
17
|
+
- Meta objects for all rules with documentation URLs and fixable status
|
|
18
|
+
- Comprehensive rule documentation with examples and configuration options
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Enhanced stylelint standards compliance with proper meta objects across all rules
|
|
23
|
+
- Standardized rule message formatting for consistent error reporting
|
|
24
|
+
- Improved Jest configuration with proper Node.js module support
|
|
25
|
+
- Updated all rule implementations to follow modern stylelint plugin patterns
|
|
26
|
+
- Enhanced rule functionality with configurable options for better customization
|
|
27
|
+
- Improved error messages for better developer experience
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Removed deprecated experimental VM modules flag from Jest scripts for Node.js compatibility
|
|
32
|
+
- Resolved CI performance test expectations for consistent build environments
|
|
33
|
+
- Cleaned up test infrastructure and removed redundant configuration files
|
|
34
|
+
- Fixed rule message clarity and consistency across all accessibility rules
|
|
35
|
+
|
|
36
|
+
## [3.2.0] - 2025-08-16
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- Comprehensive E2E testing infrastructure with real-world projects
|
|
41
|
+
- Integration testing suite for plugin functionality
|
|
42
|
+
- Performance benchmarking with regression detection
|
|
43
|
+
- Test helper utilities for simplified testing
|
|
44
|
+
- Support for SCSS/Sass project testing
|
|
45
|
+
- Large codebase performance testing
|
|
46
|
+
- CLI integration tests with full stylelint compatibility
|
|
47
|
+
- Development documentation in README
|
|
48
|
+
- Pull request template for consistent PR descriptions
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Standardized import patterns across all rules for better consistency
|
|
53
|
+
- Fixed rule order in exports object to maintain alphabetical sorting
|
|
54
|
+
- Improved Jest configuration for multiple test types
|
|
55
|
+
- Enhanced ESLint configuration for E2E tests
|
|
56
|
+
- Updated actions/checkout from v4 to v5 (#77)
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- Fixed `media-prefers-reduced-motion` rule incorrectly adding duplicate media queries when `prefers-reduced-motion` is already nested inside another media query (#66)
|
|
61
|
+
|
|
62
|
+
### Developer Experience
|
|
63
|
+
|
|
64
|
+
- Added npm scripts for granular test execution (`test:unit`, `test:integration`, `test:e2e`, `test:all`)
|
|
65
|
+
- Simplified test utilities for faster development
|
|
66
|
+
- Cross-platform compatibility testing
|
|
67
|
+
- Performance metrics and scaling tests
|
|
68
|
+
- Individual rule benchmarks included
|
|
69
|
+
|
|
8
70
|
## [3.1.0] - 2025-08-16
|
|
9
71
|
|
|
10
72
|
### Added
|
package/README.md
CHANGED
|
@@ -52,19 +52,47 @@ This shareable config contains the following:
|
|
|
52
52
|
|
|
53
53
|
Since it adds stylelint-a11y to `plugins`, you don't have to do this yourself when extending this config.
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## Rule Configuration
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Many rules support additional configuration options for customization. For example:
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"rules": {
|
|
62
|
+
"a11y/font-size-is-readable": [true, { "thresholdInPixels": 16 }],
|
|
63
|
+
"a11y/no-spread-text": [true, { "minWidth": 30, "maxWidth": 60 }],
|
|
64
|
+
"a11y/line-height-is-vertical-rhythmed": [true, { "baselineGrid": 20 }]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Refer to individual rule documentation for available options.
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
### Testing
|
|
74
|
+
|
|
75
|
+
Run tests with the following commands:
|
|
76
|
+
|
|
77
|
+
- `npm run test` - Run unit tests for all rules
|
|
78
|
+
- `npm run test:unit` - Run unit tests only
|
|
79
|
+
- `npm run test:integration` - Run integration tests
|
|
80
|
+
- `npm run test:e2e` - Run end-to-end tests with real projects
|
|
81
|
+
- `npm run test:performance` - Run performance benchmarks
|
|
82
|
+
- `npm run test:all` - Run complete test suite
|
|
83
|
+
|
|
84
|
+
### Testing Infrastructure
|
|
85
|
+
|
|
86
|
+
This project includes testing at a few levels:
|
|
87
|
+
|
|
88
|
+
- **Unit tests** - Individual rule functionality
|
|
89
|
+
- **Integration tests** - Plugin integration with stylelint
|
|
90
|
+
- **E2E tests** - Real-world project testing with intentional violations
|
|
91
|
+
- **Performance tests** - Benchmark testing for large codebases
|
|
67
92
|
|
|
68
|
-
|
|
93
|
+
### Other Commands
|
|
69
94
|
|
|
70
|
-
|
|
95
|
+
- `npm run lint` - Run ESLint
|
|
96
|
+
- `npm run format:check` - Check code formatting
|
|
97
|
+
- `npm run format:fix` - Fix code formatting
|
|
98
|
+
- `npm run coverage` - Run tests with coverage report
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@double-great/stylelint-a11y",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Plugin for stylelint with a11y rules",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,17 @@
|
|
|
29
29
|
"format:check": "prettier --check .",
|
|
30
30
|
"format:fix": "prettier --write .",
|
|
31
31
|
"test": "jest",
|
|
32
|
-
"
|
|
32
|
+
"test:unit": "jest src",
|
|
33
|
+
"test:integration": "jest test/integration",
|
|
34
|
+
"test:e2e": "jest test/e2e",
|
|
35
|
+
"test:performance": "jest test/e2e/performance.test.js",
|
|
36
|
+
"test:system": "jest test/system",
|
|
37
|
+
"test:regression": "jest test/system/performance-regression.test.js",
|
|
38
|
+
"test:compatibility": "jest test/system/compatibility.test.js",
|
|
39
|
+
"test:ci": "jest test/system/ci-integration.test.js",
|
|
40
|
+
"test:all": "jest",
|
|
41
|
+
"coverage": "jest --coverage",
|
|
42
|
+
"coverage:all": "jest --coverage"
|
|
33
43
|
},
|
|
34
44
|
"prettier": {
|
|
35
45
|
"printWidth": 100,
|
|
@@ -48,6 +58,7 @@
|
|
|
48
58
|
"jest": "^30.0.5",
|
|
49
59
|
"jest-light-runner": "^0.7.9",
|
|
50
60
|
"jest-preset-stylelint": "^8.0.0",
|
|
61
|
+
"postcss-scss": "^4.0.9",
|
|
51
62
|
"prettier": "^3.6.2",
|
|
52
63
|
"stylelint": "^16.23.1"
|
|
53
64
|
},
|
package/src/index.js
CHANGED
|
@@ -3,8 +3,42 @@ const { createPlugin } = stylelint;
|
|
|
3
3
|
|
|
4
4
|
import rules from './rules/index.js';
|
|
5
5
|
|
|
6
|
+
// Import meta objects
|
|
7
|
+
import { meta as contentPropertyMeta } from './rules/content-property-no-static-value/index.js';
|
|
8
|
+
import { meta as fontSizeReadableMeta } from './rules/font-size-is-readable/index.js';
|
|
9
|
+
import { meta as lineHeightRhythmedMeta } from './rules/line-height-is-vertical-rhythmed/index.js';
|
|
10
|
+
import { meta as mediaPrefersColorSchemeMeta } from './rules/media-prefers-color-scheme/index.js';
|
|
11
|
+
import { meta as mediaPrefersReducedMotionMeta } from './rules/media-prefers-reduced-motion/index.js';
|
|
12
|
+
import { meta as noDisplayNoneMeta } from './rules/no-display-none/index.js';
|
|
13
|
+
import { meta as noObsoleteAttributeMeta } from './rules/no-obsolete-attribute/index.js';
|
|
14
|
+
import { meta as noObsoleteElementMeta } from './rules/no-obsolete-element/index.js';
|
|
15
|
+
import { meta as noOutlineNoneMeta } from './rules/no-outline-none/index.js';
|
|
16
|
+
import { meta as noSpreadTextMeta } from './rules/no-spread-text/index.js';
|
|
17
|
+
import { meta as noTextAlignJustifyMeta } from './rules/no-text-align-justify/index.js';
|
|
18
|
+
import { meta as selectorPseudoClassFocusMeta } from './rules/selector-pseudo-class-focus/index.js';
|
|
19
|
+
|
|
20
|
+
const ruleMetaMap = {
|
|
21
|
+
'content-property-no-static-value': contentPropertyMeta,
|
|
22
|
+
'font-size-is-readable': fontSizeReadableMeta,
|
|
23
|
+
'line-height-is-vertical-rhythmed': lineHeightRhythmedMeta,
|
|
24
|
+
'media-prefers-color-scheme': mediaPrefersColorSchemeMeta,
|
|
25
|
+
'media-prefers-reduced-motion': mediaPrefersReducedMotionMeta,
|
|
26
|
+
'no-display-none': noDisplayNoneMeta,
|
|
27
|
+
'no-obsolete-attribute': noObsoleteAttributeMeta,
|
|
28
|
+
'no-obsolete-element': noObsoleteElementMeta,
|
|
29
|
+
'no-outline-none': noOutlineNoneMeta,
|
|
30
|
+
'no-spread-text': noSpreadTextMeta,
|
|
31
|
+
'no-text-align-justify': noTextAlignJustifyMeta,
|
|
32
|
+
'selector-pseudo-class-focus': selectorPseudoClassFocusMeta,
|
|
33
|
+
};
|
|
34
|
+
|
|
6
35
|
const rulesPlugins = Object.keys(rules).map((ruleName) => {
|
|
7
|
-
|
|
36
|
+
const plugin = createPlugin(`a11y/${ruleName}`, rules[ruleName]);
|
|
37
|
+
|
|
38
|
+
// Add meta object to the plugin
|
|
39
|
+
plugin.meta = ruleMetaMap[ruleName];
|
|
40
|
+
|
|
41
|
+
return plugin;
|
|
8
42
|
});
|
|
9
43
|
|
|
10
44
|
export default rulesPlugins;
|
|
@@ -11,6 +11,26 @@ Disallow CSS generated content except aria-label attribute content and empty str
|
|
|
11
11
|
|
|
12
12
|
### true
|
|
13
13
|
|
|
14
|
+
The rule is enabled with default allowed values: `['""', "''", 'attr(aria-label)']`.
|
|
15
|
+
|
|
16
|
+
### { allowedValues: string[] }
|
|
17
|
+
|
|
18
|
+
- `allowedValues` (default: `['""', "''", 'attr(aria-label)']`): Array of allowed content values
|
|
19
|
+
|
|
20
|
+
#### Example configuration
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
{
|
|
24
|
+
"a11y/content-property-no-static-value": [true, {
|
|
25
|
+
"allowedValues": ["''", '""', "attr(title)", "counter(section)"]
|
|
26
|
+
}]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Examples
|
|
31
|
+
|
|
32
|
+
#### ✓ Default configuration (`true`)
|
|
33
|
+
|
|
14
34
|
The following pattern is considered a violation:
|
|
15
35
|
|
|
16
36
|
```css
|
|
@@ -7,9 +7,15 @@ const {
|
|
|
7
7
|
export const ruleName = 'a11y/content-property-no-static-value';
|
|
8
8
|
|
|
9
9
|
export const messages = ruleMessages(ruleName, {
|
|
10
|
-
expected: (selector) => `
|
|
10
|
+
expected: (selector) => `Expected "content" property to not be used in ${selector}`,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
export const meta = {
|
|
14
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/content-property-no-static-value/README.md',
|
|
15
|
+
fixable: false,
|
|
16
|
+
deprecated: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
const isContentPropertyUsedCorrectly = (selectors) =>
|
|
14
20
|
selectors.every((selector) => {
|
|
15
21
|
return /:before|:after/.test(selector);
|
|
@@ -18,24 +24,24 @@ const isContentPropertyUsedCorrectly = (selectors) =>
|
|
|
18
24
|
const checkNodesForContentProperty = (node) =>
|
|
19
25
|
node.nodes.filter((n) => n.prop).some((n) => n.prop.toLowerCase() === 'content');
|
|
20
26
|
|
|
21
|
-
function check(node) {
|
|
27
|
+
function check(node, options = {}) {
|
|
22
28
|
if (node.type !== 'rule' || !checkNodesForContentProperty(node) || !node.first) {
|
|
23
29
|
return true;
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
const allowedValues = options.allowedValues || ["''", '""', 'attr(aria-label)'];
|
|
33
|
+
|
|
26
34
|
return node.nodes.some((o) => {
|
|
27
35
|
return (
|
|
28
36
|
o.type === 'decl' &&
|
|
29
37
|
o.prop.toLowerCase() === 'content' &&
|
|
30
38
|
isContentPropertyUsedCorrectly(o.parent.selectors) &&
|
|
31
|
-
(o.value.toLowerCase() ===
|
|
32
|
-
o.value.toLowerCase() === '""' ||
|
|
33
|
-
o.value.toLowerCase() === 'attr(aria-label)')
|
|
39
|
+
allowedValues.some((allowed) => o.value.toLowerCase() === allowed.toLowerCase())
|
|
34
40
|
);
|
|
35
41
|
});
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
export default function contentPropertyNoStaticValue(actual) {
|
|
44
|
+
export default function contentPropertyNoStaticValue(actual, options) {
|
|
39
45
|
return (root, result) => {
|
|
40
46
|
const validOptions = validateOptions(result, ruleName, { actual });
|
|
41
47
|
|
|
@@ -60,7 +66,7 @@ export default function contentPropertyNoStaticValue(actual) {
|
|
|
60
66
|
return;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
const isAccepted = check(node);
|
|
69
|
+
const isAccepted = check(node, options);
|
|
64
70
|
|
|
65
71
|
if (!isAccepted) {
|
|
66
72
|
report({
|
|
@@ -10,6 +10,12 @@ export const messages = ruleMessages(ruleName, {
|
|
|
10
10
|
expected: (selector) => `Expected a larger font-size in ${selector}`,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
export const meta = {
|
|
14
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/font-size-is-readable/README.md',
|
|
15
|
+
fixable: false,
|
|
16
|
+
deprecated: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
const pxToPt = (v) => 0.75 * v;
|
|
14
20
|
|
|
15
21
|
const checkInPx = (value, THRESHOLD_IN_PX) =>
|
package/src/rules/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export default {
|
|
|
15
15
|
'content-property-no-static-value': contentPropertyNoStaticValue,
|
|
16
16
|
'font-size-is-readable': fontSizeIsReadable,
|
|
17
17
|
'line-height-is-vertical-rhythmed': lineHeightIsVerticalRhythmed,
|
|
18
|
+
'media-prefers-color-scheme': mediaPrefersColorScheme,
|
|
18
19
|
'media-prefers-reduced-motion': mediaPrefersReducedMotion,
|
|
19
20
|
'no-display-none': noDisplayNone,
|
|
20
21
|
'no-obsolete-attribute': noObsoleteAttribute,
|
|
@@ -23,5 +24,4 @@ export default {
|
|
|
23
24
|
'no-spread-text': noSpreadText,
|
|
24
25
|
'no-text-align-justify': noTextAlignJustify,
|
|
25
26
|
'selector-pseudo-class-focus': selectorPseudoClassFocus,
|
|
26
|
-
'media-prefers-color-scheme': mediaPrefersColorScheme,
|
|
27
27
|
};
|
|
@@ -10,6 +10,25 @@ Disallow not vertical rhythmed line-height.
|
|
|
10
10
|
|
|
11
11
|
### true
|
|
12
12
|
|
|
13
|
+
The rule is enabled with default values (24px baseline grid, 1.5 minimum relative line-height).
|
|
14
|
+
|
|
15
|
+
### { baselineGrid: number, minRelativeLineHeight: number }
|
|
16
|
+
|
|
17
|
+
- `baselineGrid` (default: `24`): Baseline grid value in pixels for pixel-based line-heights
|
|
18
|
+
- `minRelativeLineHeight` (default: `1.5`): Minimum allowed relative line-height value
|
|
19
|
+
|
|
20
|
+
#### Example configuration
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
{
|
|
24
|
+
"a11y/line-height-is-vertical-rhythmed": [true, { "baselineGrid": 20, "minRelativeLineHeight": 1.3 }]
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Examples
|
|
29
|
+
|
|
30
|
+
#### ✓ Default configuration (`true`)
|
|
31
|
+
|
|
13
32
|
The following pattern is considered a violation:
|
|
14
33
|
|
|
15
34
|
```css
|
|
@@ -10,13 +10,23 @@ export const messages = ruleMessages(ruleName, {
|
|
|
10
10
|
expected: (selector) => `Expected a vertical rhythmed line-height in ${selector}`,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
export const meta = {
|
|
14
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/line-height-is-vertical-rhythmed/README.md',
|
|
15
|
+
fixable: false,
|
|
16
|
+
deprecated: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function check(node, options = {}) {
|
|
14
20
|
if (node.type !== 'rule') {
|
|
15
21
|
return true;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
24
|
+
const baselineGrid = options.baselineGrid || 24;
|
|
25
|
+
const minRelativeLineHeight = options.minRelativeLineHeight || 1.5;
|
|
26
|
+
|
|
27
|
+
const checkInPx = (o) =>
|
|
28
|
+
o.value.toLowerCase().endsWith('px') && parseInt(o.value) % baselineGrid !== 0;
|
|
29
|
+
const checkInRel = (o) => !isNaN(o.value) && parseFloat(o.value) < minRelativeLineHeight;
|
|
20
30
|
|
|
21
31
|
return !node.nodes.some(
|
|
22
32
|
(o) =>
|
|
@@ -24,7 +34,7 @@ function check(node) {
|
|
|
24
34
|
);
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
export default function lineHeightIsVerticalRhythmed(actual) {
|
|
37
|
+
export default function lineHeightIsVerticalRhythmed(actual, options) {
|
|
28
38
|
return (root, result) => {
|
|
29
39
|
const validOptions = validateOptions(result, ruleName, { actual });
|
|
30
40
|
|
|
@@ -49,7 +59,7 @@ export default function lineHeightIsVerticalRhythmed(actual) {
|
|
|
49
59
|
return;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
const isAccepted = check(node);
|
|
62
|
+
const isAccepted = check(node, options);
|
|
53
63
|
|
|
54
64
|
if (!isAccepted) {
|
|
55
65
|
report({
|
|
@@ -13,6 +13,12 @@ export const ruleName = 'a11y/media-prefers-color-scheme';
|
|
|
13
13
|
export const messages = ruleMessages(ruleName, {
|
|
14
14
|
expected: (selector) => `Expected ${selector} is used with @media (prefers-color-scheme)`,
|
|
15
15
|
});
|
|
16
|
+
|
|
17
|
+
export const meta = {
|
|
18
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/media-prefers-color-scheme/README.md',
|
|
19
|
+
fixable: false,
|
|
20
|
+
deprecated: false,
|
|
21
|
+
};
|
|
16
22
|
const targetProperties = ['background-color', 'color'];
|
|
17
23
|
|
|
18
24
|
function check(selector, node) {
|
|
@@ -2,7 +2,9 @@ import isCustomSelector from 'stylelint/lib/utils/isCustomSelector.mjs';
|
|
|
2
2
|
import isStandardSyntaxAtRule from 'stylelint/lib/utils/isStandardSyntaxAtRule.mjs';
|
|
3
3
|
import isStandardSyntaxRule from 'stylelint/lib/utils/isStandardSyntaxRule.mjs';
|
|
4
4
|
import isStandardSyntaxSelector from 'stylelint/lib/utils/isStandardSyntaxSelector.mjs';
|
|
5
|
+
|
|
5
6
|
import { parse } from 'postcss';
|
|
7
|
+
|
|
6
8
|
import stylelint from 'stylelint';
|
|
7
9
|
const {
|
|
8
10
|
utils: { report, ruleMessages, validateOptions },
|
|
@@ -13,6 +15,12 @@ export const ruleName = 'a11y/media-prefers-reduced-motion';
|
|
|
13
15
|
export const messages = ruleMessages(ruleName, {
|
|
14
16
|
expected: (selector) => `Expected ${selector} is used with @media (prefers-reduced-motion)`,
|
|
15
17
|
});
|
|
18
|
+
|
|
19
|
+
export const meta = {
|
|
20
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/media-prefers-reduced-motion/README.md',
|
|
21
|
+
fixable: true,
|
|
22
|
+
deprecated: false,
|
|
23
|
+
};
|
|
16
24
|
const targetProperties = ['transition', 'animation', 'animation-name'];
|
|
17
25
|
|
|
18
26
|
function checkChildrenNodes(childrenNodes, currentSelector, parentNode) {
|
|
@@ -62,6 +70,30 @@ function check(selector, node) {
|
|
|
62
70
|
|
|
63
71
|
if (!declarationsIsMatched) return true;
|
|
64
72
|
|
|
73
|
+
// Check if there's a nested media query with prefers-reduced-motion inside the current rule
|
|
74
|
+
const hasNestedPrefersReducedMotion = declarations.some((childNode) => {
|
|
75
|
+
if (childNode.type === 'atrule' && childNode.name === 'media') {
|
|
76
|
+
return childNode.params && childNode.params.indexOf('prefers-reduced-motion') >= 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (hasNestedPrefersReducedMotion) return true;
|
|
83
|
+
|
|
84
|
+
// Check if there's a sibling media query with prefers-reduced-motion at the same level
|
|
85
|
+
if (node.parent && node.parent.type === 'atrule' && node.parent.name === 'media') {
|
|
86
|
+
const siblingHasPrefersReducedMotion = node.parent.nodes.some((siblingNode) => {
|
|
87
|
+
if (siblingNode.type === 'atrule' && siblingNode.name === 'media') {
|
|
88
|
+
return siblingNode.params && siblingNode.params.indexOf('prefers-reduced-motion') >= 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (siblingHasPrefersReducedMotion) return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
65
97
|
if (declarationsIsMatched) {
|
|
66
98
|
const parentMatchedNode = parentNodes.some((parentNode) => {
|
|
67
99
|
if (!parentNode || !parentNode.nodes) return false;
|
|
@@ -143,25 +175,62 @@ export default function mediaPrefersReducedMotion(actual, _, context) {
|
|
|
143
175
|
media.nodes.forEach((o) => {
|
|
144
176
|
o.raws.after = '\n';
|
|
145
177
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
o.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
|
|
179
|
+
// Check if we're already inside a media query
|
|
180
|
+
if (node.parent && node.parent.type === 'atrule' && node.parent.name === 'media') {
|
|
181
|
+
// Create a clone with only the animation/transition properties set to none
|
|
182
|
+
const cloneRule = node.clone();
|
|
183
|
+
|
|
184
|
+
cloneRule.nodes = cloneRule.nodes.filter((o) => {
|
|
185
|
+
if (o.prop === 'animation-name') {
|
|
186
|
+
o.prop = 'animation';
|
|
187
|
+
o.value = 'none';
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (targetProperties.indexOf(o.prop) >= 0) {
|
|
193
|
+
o.value = 'none';
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return false;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
cloneRule.raws = {
|
|
202
|
+
...cloneRule.raws,
|
|
203
|
+
before: '\n',
|
|
204
|
+
after: '\n',
|
|
205
|
+
semicolon: true,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
media.first.append(cloneRule);
|
|
209
|
+
// Insert the nested media query inside the current rule
|
|
210
|
+
node.append(media.first);
|
|
211
|
+
} else {
|
|
212
|
+
// Original logic for non-nested case
|
|
213
|
+
const cloneRule = node.clone();
|
|
214
|
+
|
|
215
|
+
cloneRule.raws = {
|
|
216
|
+
...cloneRule.raws,
|
|
217
|
+
before: '\n',
|
|
218
|
+
after: '\n',
|
|
219
|
+
semicolon: true,
|
|
220
|
+
};
|
|
221
|
+
cloneRule.nodes.forEach((o) => {
|
|
222
|
+
if (o.prop === 'animation-name') {
|
|
223
|
+
o.prop = 'animation';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (targetProperties.indexOf(o.prop) >= 0) {
|
|
227
|
+
o.value = 'none';
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
media.first.append(cloneRule);
|
|
231
|
+
// Insert the media query before the current node
|
|
232
|
+
node.before(media);
|
|
233
|
+
}
|
|
165
234
|
|
|
166
235
|
return;
|
|
167
236
|
}
|
|
@@ -7,9 +7,15 @@ const {
|
|
|
7
7
|
export const ruleName = 'a11y/no-display-none';
|
|
8
8
|
|
|
9
9
|
export const messages = ruleMessages(ruleName, {
|
|
10
|
-
expected: (selector) => `
|
|
10
|
+
expected: (selector) => `Expected "display: none" to not be used in ${selector}`,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
export const meta = {
|
|
14
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/no-display-none/README.md',
|
|
15
|
+
fixable: false,
|
|
16
|
+
deprecated: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
function check(selector, node) {
|
|
14
20
|
if (node.type !== 'rule') {
|
|
15
21
|
return true;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import isStandardSyntaxRule from 'stylelint/lib/utils/isStandardSyntaxRule.mjs';
|
|
2
|
+
|
|
2
3
|
import { obsoleteAttributes } from './obsoleteAttributes.js';
|
|
4
|
+
|
|
3
5
|
import stylelint from 'stylelint';
|
|
4
6
|
const {
|
|
5
7
|
utils: { report, ruleMessages, validateOptions },
|
|
@@ -8,9 +10,15 @@ const {
|
|
|
8
10
|
export const ruleName = 'a11y/no-obsolete-attribute';
|
|
9
11
|
|
|
10
12
|
export const messages = ruleMessages(ruleName, {
|
|
11
|
-
expected: (selector) => `
|
|
13
|
+
expected: (selector) => `Expected obsolete attribute "${selector}" to not be used`,
|
|
12
14
|
});
|
|
13
15
|
|
|
16
|
+
export const meta = {
|
|
17
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/no-obsolete-attribute/README.md',
|
|
18
|
+
fixable: false,
|
|
19
|
+
deprecated: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
14
22
|
function check(selector, node) {
|
|
15
23
|
if (node.type !== 'rule') {
|
|
16
24
|
return true;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import isStandardSyntaxRule from 'stylelint/lib/utils/isStandardSyntaxRule.mjs';
|
|
2
|
+
|
|
2
3
|
import { obsoleteElements } from './obsoleteElements.js';
|
|
4
|
+
|
|
3
5
|
import stylelint from 'stylelint';
|
|
4
6
|
const {
|
|
5
7
|
utils: { report, ruleMessages, validateOptions },
|
|
@@ -8,9 +10,15 @@ const {
|
|
|
8
10
|
export const ruleName = 'a11y/no-obsolete-element';
|
|
9
11
|
|
|
10
12
|
export const messages = ruleMessages(ruleName, {
|
|
11
|
-
expected: (selector) => `
|
|
13
|
+
expected: (selector) => `Expected obsolete selector "${selector}" to not be used`,
|
|
12
14
|
});
|
|
13
15
|
|
|
16
|
+
export const meta = {
|
|
17
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/no-obsolete-element/README.md',
|
|
18
|
+
fixable: false,
|
|
19
|
+
deprecated: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
14
22
|
function check(selector, node) {
|
|
15
23
|
if (node.type !== 'rule') {
|
|
16
24
|
return true;
|
|
@@ -7,9 +7,15 @@ const {
|
|
|
7
7
|
export const ruleName = 'a11y/no-outline-none';
|
|
8
8
|
|
|
9
9
|
export const messages = ruleMessages(ruleName, {
|
|
10
|
-
expected: (selector) => `
|
|
10
|
+
expected: (selector) => `Expected "outline" to not be removed without alternative in ${selector}`,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
export const meta = {
|
|
14
|
+
url: 'https://github.com/double-great/stylelint-a11y/blob/main/src/rules/no-outline-none/README.md',
|
|
15
|
+
fixable: false,
|
|
16
|
+
deprecated: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
function check(selector, node) {
|
|
14
20
|
if (node.type !== 'rule') {
|
|
15
21
|
return true;
|
|
@@ -13,6 +13,25 @@ Require width of text greater than 45 characters and less than 80 characters.
|
|
|
13
13
|
|
|
14
14
|
### true
|
|
15
15
|
|
|
16
|
+
The rule is enabled with default values (45ch minimum, 80ch maximum).
|
|
17
|
+
|
|
18
|
+
### { minWidth: number, maxWidth: number }
|
|
19
|
+
|
|
20
|
+
- `minWidth` (default: `45`): Minimum allowed width in `ch` units
|
|
21
|
+
- `maxWidth` (default: `80`): Maximum allowed width in `ch` units
|
|
22
|
+
|
|
23
|
+
#### Example configuration
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
{
|
|
27
|
+
"a11y/no-spread-text": [true, { "minWidth": 30, "maxWidth": 60 }]
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Examples
|
|
32
|
+
|
|
33
|
+
#### ✓ Default configuration (`true`)
|
|
34
|
+
|
|
16
35
|
The following pattern is considered a violation:
|
|
17
36
|
|
|
18
37
|
```css
|