Merge pull request #49 from leonsenft/add-vendor-prefixed-calc
Adds support for vendor-prefixed calc()
diff --git a/lib/parser.dart b/lib/parser.dart
index 54f3f4d..3dff61e 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -375,27 +375,19 @@
List<MediaQuery> processMediaQueryList() {
var mediaQueries = <MediaQuery>[];
- bool firstTime = true;
- var mediaQuery;
do {
- mediaQuery = processMediaQuery(firstTime == true);
+ var mediaQuery = processMediaQuery();
if (mediaQuery != null) {
mediaQueries.add(mediaQuery);
- firstTime = false;
- continue;
+ } else {
+ break;
}
-
- // Any more more media types separated by comma.
- if (!_maybeEat(TokenKind.COMMA)) break;
-
- // Yep more media types start again.
- firstTime = true;
- } while ((!firstTime && mediaQuery != null) || firstTime);
+ } while (_maybeEat(TokenKind.COMMA));
return mediaQueries;
}
- MediaQuery processMediaQuery([bool startQuery = true]) {
+ MediaQuery processMediaQuery() {
// Grammar: [ONLY | NOT]? S* media_type S*
// [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]*
@@ -407,41 +399,39 @@
var unaryOp = TokenKind.matchMediaOperator(op, 0, opLen);
if (unaryOp != -1) {
if (isChecked) {
- if (startQuery && unaryOp != TokenKind.MEDIA_OP_NOT ||
+ if (unaryOp != TokenKind.MEDIA_OP_NOT ||
unaryOp != TokenKind.MEDIA_OP_ONLY) {
_warning("Only the unary operators NOT and ONLY allowed",
_makeSpan(start));
}
- if (!startQuery && unaryOp != TokenKind.MEDIA_OP_AND) {
- _warning("Only the binary AND operator allowed", _makeSpan(start));
- }
}
_next();
start = _peekToken.span;
}
var type;
- if (startQuery && unaryOp != TokenKind.MEDIA_OP_AND) {
- // Get the media type.
- if (_peekIdentifier()) type = identifier();
- }
+ // Get the media type.
+ if (_peekIdentifier()) type = identifier();
var exprs = <MediaExpression>[];
- if (unaryOp == -1 || unaryOp == TokenKind.MEDIA_OP_AND) {
- var andOp = false;
- while (true) {
- var expr = processMediaExpression(andOp);
- if (expr == null) break;
-
- exprs.add(expr);
+ while (true) {
+ // Parse AND if query has a media_type or previous expression.
+ var andOp = exprs.isNotEmpty || type != null;
+ if (andOp) {
op = _peekToken.text;
opLen = op.length;
- andOp = TokenKind.matchMediaOperator(op, 0, opLen) ==
- TokenKind.MEDIA_OP_AND;
- if (!andOp) break;
+ if (TokenKind.matchMediaOperator(op, 0, opLen) !=
+ TokenKind.MEDIA_OP_AND) {
+ break;
+ }
_next();
}
+
+ var expr = processMediaExpression(andOp);
+ if (expr == null) break;
+
+ exprs.add(expr);
}
if (unaryOp != -1 || type != null || exprs.length > 0) {
@@ -457,17 +447,16 @@
if (_maybeEat(TokenKind.LPAREN)) {
if (_peekIdentifier()) {
var feature = identifier(); // Media feature.
- while (_maybeEat(TokenKind.COLON)) {
- var startExpr = _peekToken.span;
- var exprs = processExpr();
- if (_maybeEat(TokenKind.RPAREN)) {
- return new MediaExpression(
- andOperator, feature, exprs, _makeSpan(startExpr));
- } else if (isChecked) {
- _warning("Missing parenthesis around media expression",
- _makeSpan(start));
- return null;
- }
+ var exprs = _maybeEat(TokenKind.COLON)
+ ? processExpr()
+ : new Expressions(_makeSpan(_peekToken.span));
+ if (_maybeEat(TokenKind.RPAREN)) {
+ return new MediaExpression(
+ andOperator, feature, exprs, _makeSpan(start));
+ } else if (isChecked) {
+ _warning(
+ "Missing parenthesis around media expression", _makeSpan(start));
+ return null;
}
} else if (isChecked) {
_warning("Missing media feature in media expression", _makeSpan(start));
@@ -781,6 +770,9 @@
return processDocumentDirective();
case TokenKind.DIRECTIVE_SUPPORTS:
return processSupportsDirective();
+ case TokenKind.DIRECTIVE_VIEWPORT:
+ case TokenKind.DIRECTIVE_MS_VIEWPORT:
+ return processViewportDirective();
}
return null;
}
@@ -1122,6 +1114,13 @@
return new SupportsConditionInParens(declaration, _makeSpan(start));
}
+ ViewportDirective processViewportDirective() {
+ var start = _peekToken.span;
+ var name = _next().text;
+ var declarations = processDeclarations();
+ return new ViewportDirective(name, declarations, _makeSpan(start));
+ }
+
RuleSet processRuleSet([SelectorGroup selectorGroup]) {
if (selectorGroup == null) {
selectorGroup = processSelectorGroup();
diff --git a/lib/src/css_printer.dart b/lib/src/css_printer.dart
index 0304fd7..ac26acb 100644
--- a/lib/src/css_printer.dart
+++ b/lib/src/css_printer.dart
@@ -54,8 +54,11 @@
void visitMediaExpression(MediaExpression node) {
emit(node.andOperator ? ' AND ' : ' ');
- emit('(${node.mediaFeature}:');
- visitExpressions(node.exprs);
+ emit('(${node.mediaFeature}');
+ if (node.exprs.expressions.isNotEmpty) {
+ emit(':');
+ visitExpressions(node.exprs);
+ }
emit(')');
}
@@ -68,11 +71,11 @@
}
}
- void emitMediaQueries(queries) {
+ void emitMediaQueries(List<MediaQuery> queries) {
var queriesLen = queries.length;
for (var i = 0; i < queriesLen; i++) {
var query = queries[i];
- if (query.hasMediaType && i > 0) emit(',');
+ if (i > 0) emit(',');
visitMediaQuery(query);
}
}
@@ -128,6 +131,12 @@
}
}
+ void visitViewportDirective(ViewportDirective node) {
+ emit('@${node.name}$_sp{$_newLine');
+ node.declarations.visit(this);
+ emit('}');
+ }
+
void visitMediaDirective(MediaDirective node) {
emit('$_newLine@media');
emitMediaQueries(node.mediaQueries);
diff --git a/lib/src/tokenkind.dart b/lib/src/tokenkind.dart
index 48696ab..14cf3c9 100644
--- a/lib/src/tokenkind.dart
+++ b/lib/src/tokenkind.dart
@@ -163,6 +163,8 @@
static const int DIRECTIVE_EXTEND = 657;
static const int DIRECTIVE_MOZ_DOCUMENT = 658;
static const int DIRECTIVE_SUPPORTS = 659;
+ static const int DIRECTIVE_VIEWPORT = 660;
+ static const int DIRECTIVE_MS_VIEWPORT = 661;
// Media query operators
static const int MEDIA_OP_ONLY = 665; // Unary.
@@ -222,6 +224,8 @@
const {'type': TokenKind.DIRECTIVE_EXTEND, 'value': 'extend'},
const {'type': TokenKind.DIRECTIVE_MOZ_DOCUMENT, 'value': '-moz-document'},
const {'type': TokenKind.DIRECTIVE_SUPPORTS, 'value': 'supports'},
+ const {'type': TokenKind.DIRECTIVE_VIEWPORT, 'value': 'viewport'},
+ const {'type': TokenKind.DIRECTIVE_MS_VIEWPORT, 'value': '-ms-viewport'},
];
static const List<Map<String, dynamic>> MEDIA_OPERATORS = const [
diff --git a/lib/src/tree.dart b/lib/src/tree.dart
index d6b9dbf..509e708 100644
--- a/lib/src/tree.dart
+++ b/lib/src/tree.dart
@@ -540,6 +540,19 @@
visit(VisitorBase visitor) => visitor.visitSupportsDisjunction(this);
}
+class ViewportDirective extends Directive {
+ final String name;
+ final DeclarationGroup declarations;
+
+ ViewportDirective(this.name, this.declarations, SourceSpan span)
+ : super(span);
+
+ ViewportDirective clone() =>
+ new ViewportDirective(name, declarations.clone(), span);
+
+ visit(VisitorBase visitor) => visitor.visitViewportDirective(this);
+}
+
class ImportDirective extends Directive {
/** import name specified. */
final String import;
diff --git a/lib/src/tree_printer.dart b/lib/src/tree_printer.dart
index 7ae67e4..ba35dec 100644
--- a/lib/src/tree_printer.dart
+++ b/lib/src/tree_printer.dart
@@ -134,6 +134,13 @@
output.depth--;
}
+ void visitViewportDirective(ViewportDirective node) {
+ heading('ViewportDirective', node);
+ output.depth++;
+ super.visitViewportDirective(node);
+ output.depth--;
+ }
+
void visitPageDirective(PageDirective node) {
heading('PageDirective', node);
output.depth++;
diff --git a/lib/visitor.dart b/lib/visitor.dart
index ad94033..eff9e7b 100644
--- a/lib/visitor.dart
+++ b/lib/visitor.dart
@@ -26,6 +26,7 @@
visitSupportsNegation(SupportsNegation node);
visitSupportsConjunction(SupportsConjunction node);
visitSupportsDisjunction(SupportsDisjunction node);
+ visitViewportDirective(ViewportDirective node);
visitMediaExpression(MediaExpression node);
visitMediaQuery(MediaQuery node);
visitMediaDirective(MediaDirective node);
@@ -184,6 +185,10 @@
_visitNodeList(node.conditions);
}
+ visitViewportDirective(ViewportDirective node) {
+ node.declarations.visit(this);
+ }
+
visitMediaDirective(MediaDirective node) {
for (var mediaQuery in node.mediaQueries) {
visitMediaQuery(mediaQuery);
diff --git a/test/declaration_test.dart b/test/declaration_test.dart
index 199322b..733a198 100644
--- a/test/declaration_test.dart
+++ b/test/declaration_test.dart
@@ -411,13 +411,13 @@
input = '''
@media only screen and (min-device-width: 4000px) and
- (min-device-height: 2000px), screen (another: 100px) {
+ (min-device-height: 2000px), screen AND (another: 100px) {
html {
font-size: 10em;
}
}''';
generated = '@media ONLY screen AND (min-device-width:4000px) '
- 'AND (min-device-height:2000px), screen (another:100px) {\n'
+ 'AND (min-device-height:2000px), screen AND (another:100px) {\n'
'html {\n font-size: 10em;\n}\n}';
stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
@@ -427,14 +427,14 @@
expect(prettyPrint(stylesheet), generated);
input = '''
-@media screen,print (min-device-width: 4000px) and
- (min-device-height: 2000px), screen (another: 100px) {
+@media screen,print AND (min-device-width: 4000px) and
+ (min-device-height: 2000px), screen AND (another: 100px) {
html {
font-size: 10em;
}
}''';
- generated = '@media screen, print (min-device-width:4000px) AND '
- '(min-device-height:2000px), screen (another:100px) {\n'
+ generated = '@media screen, print AND (min-device-width:4000px) AND '
+ '(min-device-height:2000px), screen AND (another:100px) {\n'
'html {\n font-size: 10em;\n}\n}';
stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
@@ -444,15 +444,29 @@
expect(prettyPrint(stylesheet), generated);
input = '''
-@import "test.css" ONLY screen, NOT print (min-device-width: 4000px);''';
- generated =
- '@import "test.css" ONLY screen, NOT print (min-device-width:4000px);';
+@import "test.css" ONLY screen, NOT print AND (min-device-width: 4000px);''';
+ generated = '@import "test.css" ONLY screen, '
+ 'NOT print AND (min-device-width:4000px);';
stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
expect(stylesheet != null, true);
expect(errors.isEmpty, true, reason: errors.toString());
expect(prettyPrint(stylesheet), generated);
+
+ var css = '@media (min-device-width:400px) {\n}';
+ expectCss(css, css);
+
+ css = '@media all AND (tranform-3d), (-webkit-transform-3d) {\n}';
+ expectCss(css, css);
+
+ // Test that AND operator is required between media type and expressions.
+ css = '@media screen (min-device-width:400px';
+ stylesheet = parseCss(css, errors: errors..clear(), opts: simpleOptions);
+ expect(errors, isNotEmpty);
+ expect(
+ errors.first.message, contains('expected { after media before ruleset'));
+ expect(errors.first.span.text, '(');
}
void testMozDocument() {
@@ -603,6 +617,36 @@
expect(errors.first.span.text, 'or');
}
+void testViewport() {
+ // No declarations.
+ var css = '@viewport {\n}';
+ expectCss(css, css);
+
+ // All declarations.
+ css = '''
+@viewport {
+ min-width: auto;
+ max-width: 800px;
+ width: 400px;
+ min-height: 50%;
+ max-height: 200px;
+ height: 100px 200px;
+ zoom: auto;
+ min-zoom: 0.75;
+ max-zoom: 40%;
+ user-zoom: fixed;
+ orientation: landscape;
+}''';
+ expectCss(css, css);
+
+ // Vendor specific.
+ css = '''
+@-ms-viewport {
+ width: device-width;
+}''';
+ expectCss(css, css);
+}
+
void testFontFace() {
var errors = <Message>[];
@@ -1285,6 +1329,7 @@
test('Media Queries', testMediaQueries);
test('Document', testMozDocument);
test('Supports', testSupports);
+ test('Viewport', testViewport);
test('Font-Face', testFontFace);
test('CSS file', testCssFile);
test('Compact Emitter', testCompactEmitter);