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

Fix no-duplicate-at-import-rules false negatives for imports with supports and layer conditions #7001

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/nasty-shoes-bathe.md
@@ -0,0 +1,5 @@
---
"stylelint": patch
---

Fixed: `no-duplicate-at-import-rules` false negatives for imports with `supports` and `layer` conditions
70 changes: 70 additions & 0 deletions lib/rules/no-duplicate-at-import-rules/__tests__/index.js
Expand Up @@ -31,6 +31,12 @@ testRule({
{
code: '@IMPORT "a.css"; @ImPoRt "b.css";',
},
{
code: '@import "a.css" supports(display: flex) tv; @import "a.css" layer tv;',
},
{
code: '@import "a.css" supports(display: flex) tv; @import "a.css" supports(display: grid) tv;',
},
],

reject: [
Expand Down Expand Up @@ -114,5 +120,69 @@ testRule({
line: 1,
column: 18,
},
{
code: '@import url("a.css") layer; @import url("a.css") layer;',
message: messages.rejected(`a.css`),
line: 1,
column: 29,
endLine: 1,
endColumn: 55,
},
{
code: '@import url("a.css") layer(base); @import url("a.css") layer(base);',
message: messages.rejected(`a.css`),
line: 1,
column: 35,
endLine: 1,
endColumn: 67,
},
{
code: '@import url("a.css") layer(base) supports(display: grid); @import url("a.css") layer(base) supports(display: grid);',
message: messages.rejected(`a.css`),
line: 1,
column: 59,
endLine: 1,
endColumn: 115,
},
{
code: '@import url("a.css") supports(display: grid); @import url("a.css") supports(display: grid);',
message: messages.rejected(`a.css`),
line: 1,
column: 47,
endLine: 1,
endColumn: 91,
},
{
code: '@import url("a.css") layer tv; @import url("a.css") layer tv;',
message: messages.rejected(`a.css`),
line: 1,
column: 32,
endLine: 1,
endColumn: 61,
},
{
code: '@import url("a.css") layer(base) tv; @import url("a.css") layer(base) tv;',
message: messages.rejected(`a.css`),
line: 1,
column: 38,
endLine: 1,
endColumn: 73,
},
{
code: '@import url("a.css") layer(base) supports(display: grid) tv; @import url("a.css") layer(base) supports(display: grid) tv;',
message: messages.rejected(`a.css`),
line: 1,
column: 62,
endLine: 1,
endColumn: 121,
},
{
code: '@import url("a.css") layer(base) supports(display: grid) screen, tv; @import url("a.css") layer(base) supports(display: grid) tv;',
message: messages.rejected(`a.css`),
line: 1,
column: 70,
endLine: 1,
endColumn: 129,
},
],
});
23 changes: 21 additions & 2 deletions lib/rules/no-duplicate-at-import-rules/index.js
Expand Up @@ -80,7 +80,9 @@ const rule = (primary) => {
function listImportConditions(params) {
if (!params.length) return [];

/** @type {Array<String>} */
/** @type {Array<string>} */
let sharedConditions = [];
/** @type {Array<string>} */
let media = [];
/** @type {Array<Node>} */
let lastMediaQuery = [];
Expand All @@ -91,6 +93,21 @@ function listImportConditions(params) {
continue;
}

// layer and supports conditions must precede media query conditions
if (!media.length) {
// @import url(...) layer(base) supports(display: flex)
if (param.type === 'function' && (param.value === 'supports' || param.value === 'layer')) {
Mouvedia marked this conversation as resolved.
Show resolved Hide resolved
sharedConditions.push(valueParser.stringify(param));
continue;
}

// @import url(...) layer
if (param.type === 'word' && param.value === 'layer') {
sharedConditions.push(valueParser.stringify(param));
continue;
}
}

if (param.type === 'div' && param.value === ',') {
media.push(valueParser.stringify(lastMediaQuery));
lastMediaQuery = [];
Expand All @@ -104,8 +121,10 @@ function listImportConditions(params) {
media.push(valueParser.stringify(lastMediaQuery));
}

const sharedConditionsString = sharedConditions.length ? `${sharedConditions.join(' ')} ` : '';

// remove remaining whitespace to get a more consistent key
return media.map((m) => m.replace(/\s/g, ''));
return media.map((m) => sharedConditionsString + m.replace(/\s/g, ''));
}

rule.ruleName = ruleName;
Expand Down