Skip to content

Commit 422ae7b

Browse files
committed
Merge pull request #196 from ideadapt/feature/at-rule-supports
parser for @supports at rule
2 parents b86847c + 03f0140 commit 422ae7b

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
@@ -132,6 +132,10 @@ Parser.prototype = function(){
132132
this._document();
133133
this._skipCruft();
134134
break;
135+
case Tokens.SUPPORTS_SYM:
136+
this._supports();
137+
this._skipCruft();
138+
break;
135139
case Tokens.UNKNOWN_SYM: //unknown @ rule
136140
tokenStream.get();
137141
if (!this.options.strict){
@@ -335,6 +339,137 @@ Parser.prototype = function(){
335339

336340
},
337341

342+
_supports: function(emit){
343+
/*
344+
* supports_rule
345+
* : SUPPORTS_SYM S* supports_condition S* group_rule_body
346+
* ;
347+
*/
348+
var tokenStream = this._tokenStream,
349+
line,
350+
col;
351+
352+
if (tokenStream.match(Tokens.SUPPORTS_SYM)){
353+
line = tokenStream.token().startLine;
354+
col = tokenStream.token().startCol;
355+
356+
this._readWhitespace();
357+
this._supports_condition();
358+
this._readWhitespace();
359+
360+
tokenStream.mustMatch(Tokens.LBRACE);
361+
this._readWhitespace();
362+
363+
if (emit !== false){
364+
this.fire({
365+
type: "startsupports",
366+
line: line,
367+
col: col
368+
});
369+
}
370+
371+
while(true) {
372+
if (!this._ruleset()){
373+
break;
374+
}
375+
}
376+
377+
tokenStream.mustMatch(Tokens.RBRACE);
378+
this._readWhitespace();
379+
380+
this.fire({
381+
type: "endsupports",
382+
line: line,
383+
col: col
384+
});
385+
}
386+
},
387+
388+
_supports_condition: function(){
389+
/*
390+
* supports_condition
391+
* : supports_negation | supports_conjunction | supports_disjunction |
392+
* supports_condition_in_parens
393+
* ;
394+
*/
395+
var tokenStream = this._tokenStream,
396+
ident;
397+
398+
if (tokenStream.match(Tokens.IDENT)){
399+
ident = tokenStream.token().value.toLowerCase();
400+
401+
if (ident === "not"){
402+
tokenStream.mustMatch(Tokens.S);
403+
this._supports_condition_in_parens();
404+
} else {
405+
tokenStream.unget();
406+
}
407+
} else{
408+
this._supports_condition_in_parens();
409+
this._readWhitespace();
410+
411+
while(tokenStream.peek() === Tokens.IDENT){
412+
ident = tokenStream.LT(1).value.toLowerCase();
413+
if(ident === "and" || ident === "or"){
414+
tokenStream.mustMatch(Tokens.IDENT);
415+
this._readWhitespace();
416+
this._supports_condition_in_parens();
417+
this._readWhitespace();
418+
}
419+
}
420+
}
421+
},
422+
423+
_supports_condition_in_parens: function(){
424+
/*
425+
* supports_condition_in_parens
426+
* : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition |
427+
* general_enclosed
428+
* ;
429+
*/
430+
var tokenStream = this._tokenStream,
431+
ident;
432+
433+
if (tokenStream.match(Tokens.LPAREN)){
434+
this._readWhitespace();
435+
if(tokenStream.match(Tokens.IDENT)){
436+
// look ahead for not keyword, if not given, continue with declaration condition.
437+
ident = tokenStream.token().value.toLowerCase();
438+
if(ident === "not"){
439+
this._readWhitespace();
440+
this._supports_condition();
441+
this._readWhitespace();
442+
tokenStream.mustMatch(Tokens.RPAREN);
443+
}else{
444+
tokenStream.unget();
445+
this._supports_declaration_condition(false);
446+
}
447+
}else{
448+
this._supports_condition();
449+
this._readWhitespace();
450+
tokenStream.mustMatch(Tokens.RPAREN);
451+
}
452+
}else{
453+
this._supports_declaration_condition();
454+
}
455+
},
456+
457+
_supports_declaration_condition: function(requireStartParen){
458+
/*
459+
* supports_declaration_condition
460+
* : '(' S* declaration ')'
461+
* ;
462+
*/
463+
var tokenStream = this._tokenStream;
464+
465+
if(requireStartParen !== false){
466+
tokenStream.mustMatch(Tokens.LPAREN);
467+
}
468+
this._readWhitespace();
469+
this._declaration();
470+
tokenStream.mustMatch(Tokens.RPAREN);
471+
},
472+
338473
_media: function(){
339474
/*
340475
* 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
@@ -1912,8 +1912,163 @@
19121912
Assert.isInstanceOf(SelectorPart, result.parts[0], "First part should be a SelectorPart.");
19131913
Assert.areEqual("#1a2b3c", result.parts[0].toString(), "Selector should be correct.");
19141914
Assert.areEqual(1, result.parts.length, "Should be one part.");
1915-
}
1915+
},
1916+
1917+
testSupportsWithSingleCondition: function(){
1918+
var parser = new Parser({ strict: true}),
1919+
valid = true;
1920+
1921+
parser.addListener("error", function(event) {
1922+
valid = false;
1923+
});
1924+
1925+
var result = parser.parse("@supports (display: table-cell) {}");
1926+
parser._verifyEnd();
1927+
Assert.isTrue(valid);
1928+
},
19161929

1930+
testSupportsWithSingleNotCondition: function(){
1931+
var parser = new Parser({ strict: true}),
1932+
valid = true;
1933+
1934+
parser.addListener("error", function(event) {
1935+
valid = false;
1936+
});
1937+
1938+
var result = parser.parse("@supports not ( display: table-cell) {}");
1939+
parser._verifyEnd();
1940+
Assert.isTrue(valid);
1941+
},
1942+
1943+
testSupportsWithNestedNotCondition: function(){
1944+
var parser = new Parser({ strict: true}),
1945+
valid = true;
1946+
1947+
parser.addListener("error", function(event) {
1948+
valid = false;
1949+
});
1950+
1951+
var result = parser.parse("@supports not (not (not (display: table-cell) )) {}");
1952+
parser._verifyEnd();
1953+
Assert.isTrue(valid);
1954+
},
1955+
1956+
testSupportsAndCondition: function(){
1957+
var parser = new Parser({ strict: true}),
1958+
valid = true;
1959+
1960+
parser.addListener("error", function(event) {
1961+
valid = false;
1962+
});
1963+
1964+
var result = parser.parse("@supports (display: table-cell) and (display: flex) {}");
1965+
parser._verifyEnd();
1966+
Assert.isTrue(valid);
1967+
},
1968+
1969+
testSupportsAndFollowedByNotCondition: function(){
1970+
var parser = new Parser({ strict: true}),
1971+
valid = true;
1972+
1973+
parser.addListener("error", function(event) {
1974+
valid = false;
1975+
});
1976+
1977+
var result = parser.parse("@supports (display: table-cell) and (not (display: flex)) {}");
1978+
parser._verifyEnd();
1979+
Assert.isTrue(valid);
1980+
},
1981+
1982+
testSupportsNotFollowedByAndCondition: function(){
1983+
var parser = new Parser({ strict: true}),
1984+
valid = true;
1985+
1986+
parser.addListener("error", function(event) {
1987+
valid = false;
1988+
});
1989+
1990+
var result = parser.parse("@supports (not (display: table-cell)) and (display: flex) {}");
1991+
parser._verifyEnd();
1992+
Assert.isTrue(valid);
1993+
},
1994+
1995+
testSupportsAndAndAndCondition: function(){
1996+
var parser = new Parser({ strict: true}),
1997+
valid = true;
1998+
1999+
parser.addListener("error", function(event) {
2000+
valid = false;
2001+
});
2002+
2003+
var result = parser.parse("@supports (display: table-cell) and (display: flex) and (display: flex) {}");
2004+
parser._verifyEnd();
2005+
Assert.isTrue(valid);
2006+
},
2007+
2008+
testSupportsOrCondition: function(){
2009+
var parser = new Parser({ strict: true}),
2010+
valid = true;
2011+
2012+
parser.addListener("error", function(event) {
2013+
valid = false;
2014+
});
2015+
2016+
var result = parser.parse("@supports (display: table-cell) or (display: flex) {}");
2017+
parser._verifyEnd();
2018+
Assert.isTrue(valid);
2019+
},
2020+
2021+
testSupportsOrFollowedByNotCondition: function(){
2022+
var parser = new Parser({ strict: true}),
2023+
valid = true;
2024+
2025+
parser.addListener("error", function(event) {
2026+
valid = false;
2027+
});
2028+
2029+
var result = parser.parse("@supports (display: table-cell) or (not (display: flex)) {}");
2030+
parser._verifyEnd();
2031+
Assert.isTrue(valid);
2032+
},
2033+
2034+
testSupportsNotFollowedByOrCondition: function(){
2035+
var parser = new Parser({ strict: true}),
2036+
valid = true;
2037+
2038+
parser.addListener("error", function(event) {
2039+
valid = false;
2040+
});
2041+
2042+
var result = parser.parse("@supports (not (display: table-cell)) or (display: flex) {}");
2043+
parser._verifyEnd();
2044+
Assert.isTrue(valid);
2045+
},
2046+
2047+
testSupportsAndWithNestedOrCondition: function(){
2048+
var parser = new Parser({ strict: true}),
2049+
valid = true;
2050+
2051+
parser.addListener("error", function(event) {
2052+
valid = false;
2053+
});
2054+
2055+
var result = parser.parse("@supports (display: table-cell) and ((display: flex) or (display: table)) {}");
2056+
parser._verifyEnd();
2057+
Assert.isTrue(valid);
2058+
},
2059+
2060+
testSupportsAndWithNestedComplexOrCondition: function(){
2061+
var parser = new Parser({ strict: true}),
2062+
valid = true;
2063+
2064+
parser.addListener("error", function(event) {
2065+
valid = false;
2066+
});
2067+
2068+
var result = parser.parse("@supports (display: table-cell) and ((display: flex) or ((display: table) and (not (position: absolute)))) {}");
2069+
parser._verifyEnd();
2070+
Assert.isTrue(valid);
2071+
}
19172072
}));
19182073

19192074
suite.add(new YUITest.TestCase({

tests/css/TokenStream.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
"@media" : CSSTokens.MEDIA_SYM,
167167
"@font-face" : CSSTokens.FONT_FACE_SYM,
168168
"@namespace" : CSSTokens.NAMESPACE_SYM,
169+
"@supports" : CSSTokens.SUPPORTS_SYM,
169170
"@top-left-corner" : CSSTokens.TOPLEFTCORNER_SYM,
170171
"@top-left" : CSSTokens.TOPLEFT_SYM,
171172
"@top-right-corner" : CSSTokens.TOPRIGHTCORNER_SYM,

0 commit comments

Comments
 (0)