Skip to content

Commit afaa6cb

Browse files
committed
Improve parser to be more strict and throw for invalid media queries
1 parent 315b8f2 commit afaa6cb

File tree

2 files changed

+64
-10
lines changed

2 files changed

+64
-10
lines changed

index.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ exports.parse = parseQuery;
1111

1212
// -----------------------------------------------------------------------------
1313

14-
var RE_MEDIA_QUERY = /(?:(only|not)?\s*([^\s\(\)]+)(?:\s*and)?\s*)?(.+)?/i,
15-
RE_MQ_EXPRESSION = /\(\s*([^\s\:\)]+)\s*(?:\:\s*([^\s\)]+))?\s*\)/,
14+
var RE_MEDIA_QUERY = /^(?:(only|not)?\s*([_a-z][_a-z0-9-]*)|(\([^\)]+\)))(?:\s*and\s*(.*))?$/i,
15+
RE_MQ_EXPRESSION = /^\(\s*([_a-z-][_a-z0-9-]*)\s*(?:\:\s*([^\)]+))?\s*\)$/,
1616
RE_MQ_FEATURE = /^(?:(min|max)-)?(.+)/,
17-
RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?$/,
18-
RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?$/;
17+
RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?\s*$/,
18+
RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/;
1919

2020
function matchQuery(mediaQuery, values) {
2121
return parseQuery(mediaQuery).some(function (query) {
@@ -88,21 +88,44 @@ function parseQuery(mediaQuery) {
8888
return mediaQuery.split(',').map(function (query) {
8989
query = query.trim();
9090

91-
var captures = query.match(RE_MEDIA_QUERY),
92-
modifier = captures[1],
91+
var captures = query.match(RE_MEDIA_QUERY);
92+
93+
// Media Query must be valid.
94+
if (!captures) {
95+
throw new SyntaxError('Invalid CSS media query: ' + query);
96+
}
97+
98+
var modifier = captures[1],
9399
type = captures[2],
94-
expressions = captures[3] || '',
100+
expressions = ((captures[3] || '') + (captures[4] || '')).trim(),
95101
parsed = {};
96102

97103
parsed.inverse = !!modifier && modifier.toLowerCase() === 'not';
98104
parsed.type = type ? type.toLowerCase() : 'all';
99105

106+
// Check for media query expressions.
107+
if (!expressions) {
108+
parsed.expressions = [];
109+
return parsed;
110+
}
111+
100112
// Split expressions into a list.
101-
expressions = expressions.match(/\([^\)]+\)/g) || [];
113+
expressions = expressions.match(/\([^\)]+\)/g);
114+
115+
// Media Query must be valid.
116+
if (!expressions) {
117+
throw new SyntaxError('Invalid CSS media query: ' + query);
118+
}
102119

103120
parsed.expressions = expressions.map(function (expression) {
104-
var captures = expression.match(RE_MQ_EXPRESSION),
105-
feature = captures[1].toLowerCase().match(RE_MQ_FEATURE);
121+
var captures = expression.match(RE_MQ_EXPRESSION);
122+
123+
// Media Query must be valid.
124+
if (!captures) {
125+
throw new SyntaxError('Invalid CSS media query: ' + query);
126+
}
127+
128+
var feature = captures[1].toLowerCase().match(RE_MQ_FEATURE);
106129

107130
return {
108131
modifier: feature[1],

test/unit-tests.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,37 @@ describe('mediaQuery.parse()', function () {
2323
}
2424
]);
2525
});
26+
27+
it('should parse common retina media query list', function () {
28+
var parsed = mediaQuery.parse(
29+
'only screen and (-webkit-min-device-pixel-ratio: 2),\n' +
30+
'only screen and ( min--moz-device-pixel-ratio: 2),\n' +
31+
'only screen and ( -o-min-device-pixel-ratio: 2/1),\n' +
32+
'only screen and ( min-device-pixel-ratio: 2),\n' +
33+
'only screen and ( min-resolution: 192dpi),\n' +
34+
'only screen and ( min-resolution: 2dppx)'
35+
);
36+
37+
expect(parsed).to.be.an.array;
38+
expect(parsed).to.have.length(6);
39+
expect(parsed[0].expressions[0].feature).to.equal('-webkit-min-device-pixel-ratio');
40+
expect(parsed[1].expressions[0].modifier).to.equal('min');
41+
});
42+
43+
it('should throw a SyntaxError when a media query is invalid', function () {
44+
function parse(query) {
45+
return function () { mediaQuery.parse(query); };
46+
}
47+
48+
expect(parse('some crap')).to.throw(SyntaxError);
49+
expect(parse('48em')).to.throw(SyntaxError);
50+
expect(parse('screen and crap')).to.throw(SyntaxError);
51+
expect(parse('screen and (48em)')).to.throw(SyntaxError);
52+
expect(parse('screen and (foo:)')).to.throw(SyntaxError);
53+
expect(parse('()')).to.throw(SyntaxError);
54+
expect(parse('(foo) (bar)')).to.throw(SyntaxError);
55+
expect(parse('(foo:) and (bar)')).to.throw(SyntaxError);
56+
});
2657
});
2758

2859
describe('mediaQuery.match()', function () {

0 commit comments

Comments
 (0)