Skip to content

Commit 0d5a240

Browse files
committed
feat: add duplicate not and nested and flattening
1 parent 0784226 commit 0d5a240

File tree

2 files changed

+133
-8
lines changed

2 files changed

+133
-8
lines changed

packages/style-value-parser/src/at-queries-from-tokens/__tests__/media-query-test.js

+90
Original file line numberDiff line numberDiff line change
@@ -1873,6 +1873,30 @@ describe('Test CSS Type: @media queries', () => {
18731873
expect(MediaQuery.parser.parseToEnd(query).toString()).toEqual(query);
18741874
});
18751875

1876+
// nested not queries are broken
1877+
test.skip('@media not (not (not (min-width: 400px)))', () => {
1878+
const query = '@media not (not (not (min-width: 400px)))';
1879+
const mediaQuery = MediaQuery.parser.parseToEnd(query);
1880+
expect(mediaQuery).toMatchInlineSnapshot(`
1881+
MediaQuery {
1882+
"queries": {
1883+
"rule": {
1884+
"key": "min-width",
1885+
"type": "pair",
1886+
"value": {
1887+
"signCharacter": undefined,
1888+
"type": "integer",
1889+
"unit": "px",
1890+
"value": 400,
1891+
},
1892+
},
1893+
"type": "not",
1894+
},
1895+
}
1896+
`);
1897+
expect(mediaQuery.toString()).toEqual('@media (min-width: 400px)');
1898+
});
1899+
18761900
// not queries with multiple clauses are broken
18771901
test.skip('@media not ((min-width: 500px) and (max-width: 600px) and (max-width: 400px))', () => {
18781902
const query =
@@ -1918,4 +1942,70 @@ describe('Test CSS Type: @media queries', () => {
19181942
`);
19191943
expect(MediaQuery.parser.parseToEnd(query).toString()).toEqual(query);
19201944
});
1945+
test('flattens nested and rules', () => {
1946+
const query =
1947+
'@media (min-width: 400px) and ((max-width: 700px) and (orientation: landscape))';
1948+
const mediaQuery = MediaQuery.parser.parseToEnd(query);
1949+
expect(mediaQuery).toMatchInlineSnapshot(`
1950+
MediaQuery {
1951+
"queries": {
1952+
"rules": [
1953+
{
1954+
"key": "min-width",
1955+
"type": "pair",
1956+
"value": {
1957+
"signCharacter": undefined,
1958+
"type": "integer",
1959+
"unit": "px",
1960+
"value": 400,
1961+
},
1962+
},
1963+
{
1964+
"key": "max-width",
1965+
"type": "pair",
1966+
"value": {
1967+
"signCharacter": undefined,
1968+
"type": "integer",
1969+
"unit": "px",
1970+
"value": 700,
1971+
},
1972+
},
1973+
{
1974+
"key": "orientation",
1975+
"type": "pair",
1976+
"value": "landscape",
1977+
},
1978+
],
1979+
"type": "and",
1980+
},
1981+
}
1982+
`);
1983+
expect(mediaQuery.toString()).toEqual(
1984+
'@media (min-width: 400px) and (max-width: 700px) and (orientation: landscape)',
1985+
);
1986+
});
1987+
1988+
// blocked by double not fix above
1989+
test.skip('removes duplicate nots', () => {
1990+
const query = '@media not (not (min-width: 400px))';
1991+
const mediaQuery = MediaQuery.parser.parseToEnd(query);
1992+
expect(mediaQuery).toMatchInlineSnapshot(`
1993+
MediaQuery {
1994+
"queries": {
1995+
"rule": {
1996+
"key": "min-width",
1997+
"type": "pair",
1998+
"value": {
1999+
"signCharacter": undefined,
2000+
"type": "integer",
2001+
"unit": "px",
2002+
"value": 400,
2003+
},
2004+
},
2005+
"type": "not",
2006+
},
2007+
}
2008+
`);
2009+
expect(mediaQuery.toString()).toEqual('@media (min-width: 400px)');
2010+
});
19212011
});

packages/style-value-parser/src/at-queries-from-tokens/media-query.js

+43-8
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,42 @@ export class MediaQuery {
318318
return '';
319319
}
320320
}
321+
static normalize(rule: MediaQueryRule): MediaQueryRule {
322+
switch (rule.type) {
323+
case 'and': {
324+
const flattened: MediaQueryRule[] = [];
325+
for (const r of rule.rules) {
326+
const norm = MediaQuery.normalize(r);
327+
if (norm.type === 'and') {
328+
flattened.push(...norm.rules);
329+
} else {
330+
flattened.push(norm);
331+
}
332+
}
333+
return { type: 'and', rules: flattened };
334+
}
335+
case 'or':
336+
return {
337+
type: 'or',
338+
rules: rule.rules.map((r) => MediaQuery.normalize(r)),
339+
};
340+
case 'not': {
341+
let count = 1;
342+
let inner = MediaQuery.normalize(rule.rule);
343+
while (inner.type === 'not') {
344+
count++;
345+
inner = MediaQuery.normalize(inner.rule);
346+
}
347+
if (inner.type === 'pair' || inner.type === 'word-rule') {
348+
return count % 2 === 0 ? inner : { type: 'not', rule: inner };
349+
}
350+
return inner;
351+
}
352+
default:
353+
return rule;
354+
}
355+
}
356+
321357
static get parser(): TokenParser<MediaQuery> {
322358
const leadingNotParser = TokenParser.sequence(
323359
TokenParser.tokens.Ident.map(
@@ -379,13 +415,12 @@ export class MediaQuery {
379415
),
380416
)
381417
.separatedBy(TokenParser.tokens.Whitespace)
382-
.map(
383-
([_at, querySets]) =>
384-
new MediaQuery(
385-
querySets.length > 1
386-
? { type: 'or', rules: querySets }
387-
: querySets[0],
388-
),
389-
);
418+
.map(([_at, querySets]) => {
419+
const rule =
420+
querySets.length > 1
421+
? { type: 'or', rules: querySets }
422+
: querySets[0];
423+
return new MediaQuery(this.normalize(rule));
424+
});
390425
}
391426
}

0 commit comments

Comments
 (0)