Skip to content

Commit

Permalink
Fix no-duplicate-at-import-rules false negatives for imports with `…
Browse files Browse the repository at this point in the history
…supports` and `layer` conditions (#7001)

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

* Create nasty-shoes-bathe.md

* fixes
  • Loading branch information
romainmenke committed Jun 30, 2023
1 parent 26f9351 commit 643576c
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 7 deletions.
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
89 changes: 89 additions & 0 deletions lib/rules/no-duplicate-at-import-rules/__tests__/index.js
Expand Up @@ -31,6 +31,15 @@ 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); @import "a.css" layer;',
},
{
code: '@import "a.css" supports(display: flex) tv; @import "a.css" supports(display: grid) tv;',
},
],

reject: [
Expand Down Expand Up @@ -114,5 +123,85 @@ 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,
},
{
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") supports(display: grid); @import url("a.css") supports(display: grid);',
message: messages.rejected(`a.css`),
line: 1,
column: 47,
endLine: 1,
endColumn: 91,
},
],
});
55 changes: 48 additions & 7 deletions lib/rules/no-duplicate-at-import-rules/index.js
Expand Up @@ -70,18 +70,31 @@ const rule = (primary) => {
};
};

/** @typedef { import('postcss-value-parser').Node } Node */

/**
* @param {Node | Array<Node>} node
* @returns {string}
*/
function stringifyCondition(node) {
// remove whitespace to get a more consistent key
return valueParser.stringify(node).replace(/\s/g, '');
}

/**
* List the import conditions found in the prelude of an `@import` rule
*
* @param {Node[]} params
* @typedef {import('postcss-value-parser').Node} Node
* @returns {Array<string>}
*/
function listImportConditions(params) {
if (!params.length) return [];

/** @type {Array<String>} */
let media = [];
const separator = ' ';
/** @type {Array<string>} */
const sharedConditions = [];
/** @type {Array<string>} */
const media = [];
/** @type {Array<Node>} */
let lastMediaQuery = [];

Expand All @@ -91,8 +104,23 @@ 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')) {
sharedConditions.push(stringifyCondition(param));
continue;
}

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

if (param.type === 'div' && param.value === ',') {
media.push(valueParser.stringify(lastMediaQuery));
media.push(stringifyCondition(lastMediaQuery));
lastMediaQuery = [];
continue;
}
Expand All @@ -101,11 +129,24 @@ function listImportConditions(params) {
}

if (lastMediaQuery.length) {
media.push(valueParser.stringify(lastMediaQuery));
media.push(stringifyCondition(lastMediaQuery));
}

// Only media query conditions
if (media.length && !sharedConditions.length) {
return media;
}

// remove remaining whitespace to get a more consistent key
return media.map((m) => m.replace(/\s/g, ''));
// Only layer and supports conditions
if (!media.length && sharedConditions.length) {
return [sharedConditions.join(separator)];
}

const sharedConditionsString = sharedConditions.join(separator);

return media.map((m) => {
return sharedConditionsString + separator + m;
});
}

rule.ruleName = ruleName;
Expand Down

0 comments on commit 643576c

Please sign in to comment.