-
Notifications
You must be signed in to change notification settings - Fork 21
feat: add @supports condition awareness via ignoreWithinAtSupports #303
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
| import stylelint from 'stylelint'; | ||||||
| import doiuse from 'doiuse'; | ||||||
| import { Result } from 'postcss'; | ||||||
| import { SupportsParser } from 'css-supports-parser'; | ||||||
|
|
||||||
| /** | ||||||
| * Plugin settings | ||||||
|
|
@@ -11,6 +12,11 @@ const messages = stylelint.utils.ruleMessages(ruleName, { | |||||
| rejected: (details) => `Unexpected browser feature ${details}`, | ||||||
| }); | ||||||
|
|
||||||
| /** | ||||||
| * @type {Map<string, SupportsParser>} | ||||||
| */ | ||||||
| const supportsMap = new Map(); | ||||||
|
|
||||||
| /** | ||||||
| * Options | ||||||
| */ | ||||||
|
|
@@ -27,6 +33,7 @@ const optionsSchema = { | |||||
| browsers: [isString], | ||||||
| ignore: [isString], | ||||||
| ignorePartialSupport: isBoolean, | ||||||
| ignoreWithinAtSupports: isBoolean, | ||||||
| }; | ||||||
|
|
||||||
| /** | ||||||
|
|
@@ -62,6 +69,41 @@ function cleanWarningText(warningText, ignorePartialSupport) { | |||||
| return cleanedWarningText; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Check if node is inside @supports and feature is in positive condition | ||||||
| */ | ||||||
| function isFeatureInPositiveSupportsCondition(node) { | ||||||
| // Skip if feature is as @supports condition | ||||||
| if (node.type === 'atrule' && node.name === 'supports') { | ||||||
| return true; | ||||||
| } | ||||||
|
|
||||||
| let result = false; | ||||||
| let parent = node.parent; | ||||||
|
|
||||||
| while (parent) { | ||||||
| if (parent.type === 'atrule' && parent.name === 'supports') { | ||||||
| if (!supportsMap.has(parent.params)) { | ||||||
| supportsMap.set(parent.params, new SupportsParser(parent.params, true)); | ||||||
| } | ||||||
|
|
||||||
| const parser = supportsMap.get(parent.params); | ||||||
|
|
||||||
| switch (parser.checkProperty(node.toString())) { | ||||||
|
||||||
| switch (parser.checkProperty(node.toString())) { | |
| switch (parser.checkProperty(node.prop)) { |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -261,3 +261,184 @@ testRule({ | |||||||||
| }, | ||||||||||
| ], | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * ignoreWithinAtSupports option | ||||||||||
| */ | ||||||||||
|
|
||||||||||
| // Basic @supports acceptance | ||||||||||
| testRule({ | ||||||||||
| plugins: ['./lib'], | ||||||||||
| ruleName, | ||||||||||
| config: [ | ||||||||||
| true, | ||||||||||
| { | ||||||||||
| browsers: ['IE 11'], | ||||||||||
| ignore: ['css-featurequeries'], | ||||||||||
| ignoreWithinAtSupports: true, | ||||||||||
| }, | ||||||||||
| ], | ||||||||||
|
|
||||||||||
| accept: [ | ||||||||||
| { | ||||||||||
| code: '@supports (display: flex) { div { display: flex; } }', | ||||||||||
| description: 'should ignore unsupported features inside matching @supports', | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| code: '@supports (not (display: grid)) { div { display: table; } }', | ||||||||||
| description: 'should ignore when inside negated @supports that matches', | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| code: ` | ||||||||||
| @supports (display: table) { | ||||||||||
| @supports (display: flex) { | ||||||||||
| div { display: flex; } | ||||||||||
| } | ||||||||||
| }`, | ||||||||||
| description: 'should handle nested @supports blocks', | ||||||||||
| }, | ||||||||||
| ], | ||||||||||
|
|
||||||||||
| reject: [ | ||||||||||
| { | ||||||||||
| code: '@supports (not (display: flex)) { div { display: flex; } }', | ||||||||||
| description: 'should still reject unsupported features outside @supports', | ||||||||||
| message: `Unexpected browser feature "flexbox" is only partially supported by IE 11 (plugin/no-unsupported-browser-features)`, | ||||||||||
| line: 1, | ||||||||||
| column: 41, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| code: '@supports (display: grid) { div { display: flex; } }', | ||||||||||
| description: 'should reject when @supports condition doesnt match', | ||||||||||
| message: `Unexpected browser feature "flexbox" is only partially supported by IE 11 (plugin/no-unsupported-browser-features)`, | ||||||||||
| line: 1, | ||||||||||
| column: 35, | ||||||||||
| }, | ||||||||||
| ], | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| // Complex logical operators | ||||||||||
| testRule({ | ||||||||||
| plugins: ['./lib'], | ||||||||||
| ruleName, | ||||||||||
| config: [ | ||||||||||
| true, | ||||||||||
| { | ||||||||||
| browsers: ['IE 7'], | ||||||||||
| ignore: ['css-featurequeries'], | ||||||||||
| ignoreWithinAtSupports: true, | ||||||||||
| }, | ||||||||||
| ], | ||||||||||
|
|
||||||||||
| reject: [ | ||||||||||
| { | ||||||||||
| code: '@supports (display: flex) or (display: grid) { div { display: flex; } }', | ||||||||||
| description: 'should reject with OR conditions (uncertain support)', | ||||||||||
| message: `Unexpected browser feature "flexbox" is not supported by IE 7 (plugin/no-unsupported-browser-features)`, | ||||||||||
| line: 1, | ||||||||||
| column: 54, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| code: '@supports (display: flex) and (display: grid) { div { display: table; } }', | ||||||||||
| description: 'should reject when one condition in AND is unsupported', | ||||||||||
| message: `Unexpected browser feature "css-table" is not supported by IE 7 (plugin/no-unsupported-browser-features)`, | ||||||||||
| line: 1, | ||||||||||
| column: 55, | ||||||||||
| }, | ||||||||||
| ], | ||||||||||
|
|
||||||||||
| accept: [ | ||||||||||
| { | ||||||||||
| code: '@supports (display: table) and (not (display: flex)) { div { display: table; } }', | ||||||||||
| description: 'should accept when all AND conditions are satisfied', | ||||||||||
| }, | ||||||||||
| ], | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| // Edge cases with partial support | ||||||||||
| testRule({ | ||||||||||
| plugins: ['./lib'], | ||||||||||
| ruleName, | ||||||||||
| config: [ | ||||||||||
| true, | ||||||||||
| { | ||||||||||
| browsers: ['IE 9'], | ||||||||||
| ignore: ['css-featurequeries'], | ||||||||||
| ignoreWithinAtSupports: true, | ||||||||||
| }, | ||||||||||
| ], | ||||||||||
|
|
||||||||||
| accept: [ | ||||||||||
| { | ||||||||||
| code: '@supports (width: 1rem) { div { width: 1rem; } }', | ||||||||||
| description: | ||||||||||
| 'should acceot partial support when ignorePartialSupport=false while declared as condition', | ||||||||||
|
||||||||||
| 'should acceot partial support when ignorePartialSupport=false while declared as condition', | |
| 'should accept partial support when ignorePartialSupport=false while declared as condition', |
Copilot
AI
May 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update description to reference ignoreWithinAtSupports rather than ignorePartialSupport to match the test's intent.
| 'should acceot partial support when ignorePartialSupport=false while declared as condition', | |
| 'should accept partial support when ignoreWithinAtSupports=false while declared as condition', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The global
supportsMapis never cleared and could grow without bound across lint runs; consider scoping or clearing it to prevent a memory leak.