Skip to content

Commit 03f0140

Browse files
committed
Add support for parsing @supports at rule.
This change allows to parse a css3 standards compliant @supports rule, e.g. `@supports (display: flex)`.
1 parent 411344d commit 03f0140

File tree

4 files changed

+293
-1
lines changed

4 files changed

+293
-1
lines changed

src/css/Parser.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ Parser.prototype = function(){
131131
this._document();
132132
this._skipCruft();
133133
break;
134+
case Tokens.SUPPORTS_SYM:
135+
this._supports();
136+
this._skipCruft();
137+
break;
134138
case Tokens.UNKNOWN_SYM: //unknown @ rule
135139
tokenStream.get();
136140
if (!this.options.strict){
@@ -334,6 +338,137 @@ Parser.prototype = function(){
334338

335339
},
336340

341+
_supports: function(emit){
342+
/*
343+
* supports_rule
344+
* : SUPPORTS_SYM S* supports_condition S* group_rule_body
345+
* ;
346+
*/
347+
var tokenStream = this._tokenStream,
348+
line,
349+
col;
350+
351+
if (tokenStream.match(Tokens.SUPPORTS_SYM)){
352+
line = tokenStream.token().startLine;
353+
col = tokenStream.token().startCol;
354+
355+
this._readWhitespace();
356+
this._supports_condition();
357+
this._readWhitespace();
358+
359+
tokenStream.mustMatch(Tokens.LBRACE);
360+
this._readWhitespace();
361+
362+
if (emit !== false){
363+
this.fire({
364+
type: "startsupports",
365+
line: line,
366+
col: col
367+
});
368+
}
369+
370+
while(true) {
371+
if (!this._ruleset()){
372+
break;
373+
}
374+
}
375+
376+
tokenStream.mustMatch(Tokens.RBRACE);
377+
this._readWhitespace();
378+
379+
this.fire({
380+
type: "endsupports",
381+
line: line,
382+
col: col
383+
});
384+
}
385+
},
386+
387+
_supports_condition: function(){
388+
/*
389+
* supports_condition
390+
* : supports_negation | supports_conjunction | supports_disjunction |
391+
* supports_condition_in_parens
392+
* ;
393+
*/
394+
var tokenStream = this._tokenStream,
395+
ident;
396+
397+
if (tokenStream.match(Tokens.IDENT)){
398+
ident = tokenStream.token().value.toLowerCase();
399+
400+
if (ident === "not"){
401+
tokenStream.mustMatch(Tokens.S);
402+
this._supports_condition_in_parens();
403+
} else {
404+
tokenStream.unget();
405+
}
406+
} else{
407+
this._supports_condition_in_parens();
408+
this._readWhitespace();
409+
410+
while(tokenStream.peek() === Tokens.IDENT){
411+
ident = tokenStream.LT(1).value.toLowerCase();
412+
if(ident === "and" || ident === "or"){
413+
tokenStream.mustMatch(Tokens.IDENT);
414+
this._readWhitespace();
415+
this._supports_condition_in_parens();
416+
this._readWhitespace();
417+
}
418+
}
419+
}
420+
},
421+
422+
_supports_condition_in_parens: function(){
423+
/*
424+
* supports_condition_in_parens
425+
* : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition |
426+
* general_enclosed
427+
* ;
428+
*/
429+
var tokenStream = this._tokenStream,
430+
ident;
431+
432+
if (tokenStream.match(Tokens.LPAREN)){
433+
this._readWhitespace();
434+
if(tokenStream.match(Tokens.IDENT)){
435+
// look ahead for not keyword, if not given, continue with declaration condition.
436+
ident = tokenStream.token().value.toLowerCase();
437+
if(ident === "not"){
438+
this._readWhitespace();
439+
this._supports_condition();
440+
this._readWhitespace();
441+
tokenStream.mustMatch(Tokens.RPAREN);
442+
}else{
443+
tokenStream.unget();
444+
this._supports_declaration_condition(false);
445+
}
446+
}else{
447+
this._supports_condition();
448+
this._readWhitespace();
449+
tokenStream.mustMatch(Tokens.RPAREN);
450+
}
451+
}else{
452+
this._supports_declaration_condition();
453+
}
454+
},
455+
456+
_supports_declaration_condition: function(requireStartParen){
457+
/*
458+
* supports_declaration_condition
459+
* : '(' S* declaration ')'
460+
* ;
461+
*/
462+
var tokenStream = this._tokenStream;
463+
464+
if(requireStartParen !== false){
465+
tokenStream.mustMatch(Tokens.LPAREN);
466+
}
467+
this._readWhitespace();
468+
this._declaration();
469+
tokenStream.mustMatch(Tokens.RPAREN);
470+
},
471+
337472
_media: function(){
338473
/*
339474
* media

src/css/Tokens.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var Tokens = [
3131
{ name: "FONT_FACE_SYM", text: "@font-face"},
3232
{ name: "CHARSET_SYM", text: "@charset"},
3333
{ name: "NAMESPACE_SYM", text: "@namespace"},
34+
{ name: "SUPPORTS_SYM", text: "@supports"},
3435
{ name: "VIEWPORT_SYM", text: ["@viewport", "@-ms-viewport", "@-o-viewport"]},
3536
{ name: "DOCUMENT_SYM", text: ["@document", "@-moz-document"]},
3637
{ name: "UNKNOWN_SYM" },

tests/css/Parser.js

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1850,8 +1850,163 @@
18501850
Assert.isInstanceOf(SelectorPart, result.parts[0], "First part should be a SelectorPart.");
18511851
Assert.areEqual("#1a2b3c", result.parts[0].toString(), "Selector should be correct.");
18521852
Assert.areEqual(1, result.parts.length, "Should be one part.");
1853-
}
1853+
},
1854+
1855+
testSupportsWithSingleCondition: function(){
1856+
var parser = new Parser({ strict: true}),
1857+
valid = true;
1858+
1859+
parser.addListener("error", function(event) {
1860+
valid = false;
1861+
});
1862+
1863+
var result = parser.parse("@supports (display: table-cell) {}");
1864+
parser._verifyEnd();
1865+
Assert.isTrue(valid);
1866+
},
18541867

1868+
testSupportsWithSingleNotCondition: function(){
1869+
var parser = new Parser({ strict: true}),
1870+
valid = true;
1871+
1872+
parser.addListener("error", function(event) {
1873+
valid = false;
1874+
});
1875+
1876+
var result = parser.parse("@supports not ( display: table-cell) {}");
1877+
parser._verifyEnd();
1878+
Assert.isTrue(valid);
1879+
},
1880+
1881+
testSupportsWithNestedNotCondition: function(){
1882+
var parser = new Parser({ strict: true}),
1883+
valid = true;
1884+
1885+
parser.addListener("error", function(event) {
1886+
valid = false;
1887+
});
1888+
1889+
var result = parser.parse("@supports not (not (not (display: table-cell) )) {}");
1890+
parser._verifyEnd();
1891+
Assert.isTrue(valid);
1892+
},
1893+
1894+
testSupportsAndCondition: function(){
1895+
var parser = new Parser({ strict: true}),
1896+
valid = true;
1897+
1898+
parser.addListener("error", function(event) {
1899+
valid = false;
1900+
});
1901+
1902+
var result = parser.parse("@supports (display: table-cell) and (display: flex) {}");
1903+
parser._verifyEnd();
1904+
Assert.isTrue(valid);
1905+
},
1906+
1907+
testSupportsAndFollowedByNotCondition: function(){
1908+
var parser = new Parser({ strict: true}),
1909+
valid = true;
1910+
1911+
parser.addListener("error", function(event) {
1912+
valid = false;
1913+
});
1914+
1915+
var result = parser.parse("@supports (display: table-cell) and (not (display: flex)) {}");
1916+
parser._verifyEnd();
1917+
Assert.isTrue(valid);
1918+
},
1919+
1920+
testSupportsNotFollowedByAndCondition: function(){
1921+
var parser = new Parser({ strict: true}),
1922+
valid = true;
1923+
1924+
parser.addListener("error", function(event) {
1925+
valid = false;
1926+
});
1927+
1928+
var result = parser.parse("@supports (not (display: table-cell)) and (display: flex) {}");
1929+
parser._verifyEnd();
1930+
Assert.isTrue(valid);
1931+
},
1932+
1933+
testSupportsAndAndAndCondition: function(){
1934+
var parser = new Parser({ strict: true}),
1935+
valid = true;
1936+
1937+
parser.addListener("error", function(event) {
1938+
valid = false;
1939+
});
1940+
1941+
var result = parser.parse("@supports (display: table-cell) and (display: flex) and (display: flex) {}");
1942+
parser._verifyEnd();
1943+
Assert.isTrue(valid);
1944+
},
1945+
1946+
testSupportsOrCondition: function(){
1947+
var parser = new Parser({ strict: true}),
1948+
valid = true;
1949+
1950+
parser.addListener("error", function(event) {
1951+
valid = false;
1952+
});
1953+
1954+
var result = parser.parse("@supports (display: table-cell) or (display: flex) {}");
1955+
parser._verifyEnd();
1956+
Assert.isTrue(valid);
1957+
},
1958+
1959+
testSupportsOrFollowedByNotCondition: function(){
1960+
var parser = new Parser({ strict: true}),
1961+
valid = true;
1962+
1963+
parser.addListener("error", function(event) {
1964+
valid = false;
1965+
});
1966+
1967+
var result = parser.parse("@supports (display: table-cell) or (not (display: flex)) {}");
1968+
parser._verifyEnd();
1969+
Assert.isTrue(valid);
1970+
},
1971+
1972+
testSupportsNotFollowedByOrCondition: function(){
1973+
var parser = new Parser({ strict: true}),
1974+
valid = true;
1975+
1976+
parser.addListener("error", function(event) {
1977+
valid = false;
1978+
});
1979+
1980+
var result = parser.parse("@supports (not (display: table-cell)) or (display: flex) {}");
1981+
parser._verifyEnd();
1982+
Assert.isTrue(valid);
1983+
},
1984+
1985+
testSupportsAndWithNestedOrCondition: function(){
1986+
var parser = new Parser({ strict: true}),
1987+
valid = true;
1988+
1989+
parser.addListener("error", function(event) {
1990+
valid = false;
1991+
});
1992+
1993+
var result = parser.parse("@supports (display: table-cell) and ((display: flex) or (display: table)) {}");
1994+
parser._verifyEnd();
1995+
Assert.isTrue(valid);
1996+
},
1997+
1998+
testSupportsAndWithNestedComplexOrCondition: function(){
1999+
var parser = new Parser({ strict: true}),
2000+
valid = true;
2001+
2002+
parser.addListener("error", function(event) {
2003+
valid = false;
2004+
});
2005+
2006+
var result = parser.parse("@supports (display: table-cell) and ((display: flex) or ((display: table) and (not (position: absolute)))) {}");
2007+
parser._verifyEnd();
2008+
Assert.isTrue(valid);
2009+
}
18552010
}));
18562011

18572012
suite.add(new YUITest.TestCase({

tests/css/TokenStream.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
"@media" : CSSTokens.MEDIA_SYM,
159159
"@font-face" : CSSTokens.FONT_FACE_SYM,
160160
"@namespace" : CSSTokens.NAMESPACE_SYM,
161+
"@supports" : CSSTokens.SUPPORTS_SYM,
161162
"@top-left-corner" : CSSTokens.TOPLEFTCORNER_SYM,
162163
"@top-left" : CSSTokens.TOPLEFT_SYM,
163164
"@top-right-corner" : CSSTokens.TOPRIGHTCORNER_SYM,

0 commit comments

Comments
 (0)