|
1 /*! |
|
2 CSSLint v1.0.4 |
|
3 Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. |
|
4 |
|
5 Permission is hereby granted, free of charge, to any person obtaining a copy |
|
6 of this software and associated documentation files (the 'Software'), to deal |
|
7 in the Software without restriction, including without limitation the rights |
|
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
9 copies of the Software, and to permit persons to whom the Software is |
|
10 furnished to do so, subject to the following conditions: |
|
11 |
|
12 The above copyright notice and this permission notice shall be included in |
|
13 all copies or substantial portions of the Software. |
|
14 |
|
15 THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
21 THE SOFTWARE. |
|
22 |
|
23 */ |
|
24 |
|
25 var CSSLint = (function(){ |
|
26 var module = module || {}, |
|
27 exports = exports || {}; |
|
28 |
|
29 /*! |
|
30 Parser-Lib |
|
31 Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved. |
|
32 |
|
33 Permission is hereby granted, free of charge, to any person obtaining a copy |
|
34 of this software and associated documentation files (the "Software"), to deal |
|
35 in the Software without restriction, including without limitation the rights |
|
36 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
37 copies of the Software, and to permit persons to whom the Software is |
|
38 furnished to do so, subject to the following conditions: |
|
39 |
|
40 The above copyright notice and this permission notice shall be included in |
|
41 all copies or substantial portions of the Software. |
|
42 |
|
43 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
44 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
45 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
46 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
47 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
48 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
49 THE SOFTWARE. |
|
50 */ |
|
51 /* Version v1.1.0, Build time: 6-December-2016 10:31:29 */ |
|
52 var parserlib = (function () { |
|
53 var require; |
|
54 require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ |
|
55 "use strict"; |
|
56 |
|
57 /* exported Colors */ |
|
58 |
|
59 var Colors = module.exports = { |
|
60 __proto__ :null, |
|
61 aliceblue :"#f0f8ff", |
|
62 antiquewhite :"#faebd7", |
|
63 aqua :"#00ffff", |
|
64 aquamarine :"#7fffd4", |
|
65 azure :"#f0ffff", |
|
66 beige :"#f5f5dc", |
|
67 bisque :"#ffe4c4", |
|
68 black :"#000000", |
|
69 blanchedalmond :"#ffebcd", |
|
70 blue :"#0000ff", |
|
71 blueviolet :"#8a2be2", |
|
72 brown :"#a52a2a", |
|
73 burlywood :"#deb887", |
|
74 cadetblue :"#5f9ea0", |
|
75 chartreuse :"#7fff00", |
|
76 chocolate :"#d2691e", |
|
77 coral :"#ff7f50", |
|
78 cornflowerblue :"#6495ed", |
|
79 cornsilk :"#fff8dc", |
|
80 crimson :"#dc143c", |
|
81 cyan :"#00ffff", |
|
82 darkblue :"#00008b", |
|
83 darkcyan :"#008b8b", |
|
84 darkgoldenrod :"#b8860b", |
|
85 darkgray :"#a9a9a9", |
|
86 darkgrey :"#a9a9a9", |
|
87 darkgreen :"#006400", |
|
88 darkkhaki :"#bdb76b", |
|
89 darkmagenta :"#8b008b", |
|
90 darkolivegreen :"#556b2f", |
|
91 darkorange :"#ff8c00", |
|
92 darkorchid :"#9932cc", |
|
93 darkred :"#8b0000", |
|
94 darksalmon :"#e9967a", |
|
95 darkseagreen :"#8fbc8f", |
|
96 darkslateblue :"#483d8b", |
|
97 darkslategray :"#2f4f4f", |
|
98 darkslategrey :"#2f4f4f", |
|
99 darkturquoise :"#00ced1", |
|
100 darkviolet :"#9400d3", |
|
101 deeppink :"#ff1493", |
|
102 deepskyblue :"#00bfff", |
|
103 dimgray :"#696969", |
|
104 dimgrey :"#696969", |
|
105 dodgerblue :"#1e90ff", |
|
106 firebrick :"#b22222", |
|
107 floralwhite :"#fffaf0", |
|
108 forestgreen :"#228b22", |
|
109 fuchsia :"#ff00ff", |
|
110 gainsboro :"#dcdcdc", |
|
111 ghostwhite :"#f8f8ff", |
|
112 gold :"#ffd700", |
|
113 goldenrod :"#daa520", |
|
114 gray :"#808080", |
|
115 grey :"#808080", |
|
116 green :"#008000", |
|
117 greenyellow :"#adff2f", |
|
118 honeydew :"#f0fff0", |
|
119 hotpink :"#ff69b4", |
|
120 indianred :"#cd5c5c", |
|
121 indigo :"#4b0082", |
|
122 ivory :"#fffff0", |
|
123 khaki :"#f0e68c", |
|
124 lavender :"#e6e6fa", |
|
125 lavenderblush :"#fff0f5", |
|
126 lawngreen :"#7cfc00", |
|
127 lemonchiffon :"#fffacd", |
|
128 lightblue :"#add8e6", |
|
129 lightcoral :"#f08080", |
|
130 lightcyan :"#e0ffff", |
|
131 lightgoldenrodyellow :"#fafad2", |
|
132 lightgray :"#d3d3d3", |
|
133 lightgrey :"#d3d3d3", |
|
134 lightgreen :"#90ee90", |
|
135 lightpink :"#ffb6c1", |
|
136 lightsalmon :"#ffa07a", |
|
137 lightseagreen :"#20b2aa", |
|
138 lightskyblue :"#87cefa", |
|
139 lightslategray :"#778899", |
|
140 lightslategrey :"#778899", |
|
141 lightsteelblue :"#b0c4de", |
|
142 lightyellow :"#ffffe0", |
|
143 lime :"#00ff00", |
|
144 limegreen :"#32cd32", |
|
145 linen :"#faf0e6", |
|
146 magenta :"#ff00ff", |
|
147 maroon :"#800000", |
|
148 mediumaquamarine:"#66cdaa", |
|
149 mediumblue :"#0000cd", |
|
150 mediumorchid :"#ba55d3", |
|
151 mediumpurple :"#9370d8", |
|
152 mediumseagreen :"#3cb371", |
|
153 mediumslateblue :"#7b68ee", |
|
154 mediumspringgreen :"#00fa9a", |
|
155 mediumturquoise :"#48d1cc", |
|
156 mediumvioletred :"#c71585", |
|
157 midnightblue :"#191970", |
|
158 mintcream :"#f5fffa", |
|
159 mistyrose :"#ffe4e1", |
|
160 moccasin :"#ffe4b5", |
|
161 navajowhite :"#ffdead", |
|
162 navy :"#000080", |
|
163 oldlace :"#fdf5e6", |
|
164 olive :"#808000", |
|
165 olivedrab :"#6b8e23", |
|
166 orange :"#ffa500", |
|
167 orangered :"#ff4500", |
|
168 orchid :"#da70d6", |
|
169 palegoldenrod :"#eee8aa", |
|
170 palegreen :"#98fb98", |
|
171 paleturquoise :"#afeeee", |
|
172 palevioletred :"#d87093", |
|
173 papayawhip :"#ffefd5", |
|
174 peachpuff :"#ffdab9", |
|
175 peru :"#cd853f", |
|
176 pink :"#ffc0cb", |
|
177 plum :"#dda0dd", |
|
178 powderblue :"#b0e0e6", |
|
179 purple :"#800080", |
|
180 red :"#ff0000", |
|
181 rosybrown :"#bc8f8f", |
|
182 royalblue :"#4169e1", |
|
183 saddlebrown :"#8b4513", |
|
184 salmon :"#fa8072", |
|
185 sandybrown :"#f4a460", |
|
186 seagreen :"#2e8b57", |
|
187 seashell :"#fff5ee", |
|
188 sienna :"#a0522d", |
|
189 silver :"#c0c0c0", |
|
190 skyblue :"#87ceeb", |
|
191 slateblue :"#6a5acd", |
|
192 slategray :"#708090", |
|
193 slategrey :"#708090", |
|
194 snow :"#fffafa", |
|
195 springgreen :"#00ff7f", |
|
196 steelblue :"#4682b4", |
|
197 tan :"#d2b48c", |
|
198 teal :"#008080", |
|
199 thistle :"#d8bfd8", |
|
200 tomato :"#ff6347", |
|
201 turquoise :"#40e0d0", |
|
202 violet :"#ee82ee", |
|
203 wheat :"#f5deb3", |
|
204 white :"#ffffff", |
|
205 whitesmoke :"#f5f5f5", |
|
206 yellow :"#ffff00", |
|
207 yellowgreen :"#9acd32", |
|
208 //'currentColor' color keyword https://www.w3.org/TR/css3-color/#currentcolor |
|
209 currentColor :"The value of the 'color' property.", |
|
210 //CSS2 system colors https://www.w3.org/TR/css3-color/#css2-system |
|
211 activeBorder :"Active window border.", |
|
212 activecaption :"Active window caption.", |
|
213 appworkspace :"Background color of multiple document interface.", |
|
214 background :"Desktop background.", |
|
215 buttonface :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.", |
|
216 buttonhighlight :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.", |
|
217 buttonshadow :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.", |
|
218 buttontext :"Text on push buttons.", |
|
219 captiontext :"Text in caption, size box, and scrollbar arrow box.", |
|
220 graytext :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.", |
|
221 greytext :"Greyed (disabled) text. This color is set to #000 if the current display driver does not support a solid grey color.", |
|
222 highlight :"Item(s) selected in a control.", |
|
223 highlighttext :"Text of item(s) selected in a control.", |
|
224 inactiveborder :"Inactive window border.", |
|
225 inactivecaption :"Inactive window caption.", |
|
226 inactivecaptiontext :"Color of text in an inactive caption.", |
|
227 infobackground :"Background color for tooltip controls.", |
|
228 infotext :"Text color for tooltip controls.", |
|
229 menu :"Menu background.", |
|
230 menutext :"Text in menus.", |
|
231 scrollbar :"Scroll bar gray area.", |
|
232 threeddarkshadow :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", |
|
233 threedface :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", |
|
234 threedhighlight :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", |
|
235 threedlightshadow :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", |
|
236 threedshadow :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", |
|
237 window :"Window background.", |
|
238 windowframe :"Window frame.", |
|
239 windowtext :"Text in windows." |
|
240 }; |
|
241 |
|
242 },{}],2:[function(require,module,exports){ |
|
243 "use strict"; |
|
244 |
|
245 module.exports = Combinator; |
|
246 |
|
247 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
248 |
|
249 var Parser = require("./Parser"); |
|
250 |
|
251 /** |
|
252 * Represents a selector combinator (whitespace, +, >). |
|
253 * @namespace parserlib.css |
|
254 * @class Combinator |
|
255 * @extends parserlib.util.SyntaxUnit |
|
256 * @constructor |
|
257 * @param {String} text The text representation of the unit. |
|
258 * @param {int} line The line of text on which the unit resides. |
|
259 * @param {int} col The column of text on which the unit resides. |
|
260 */ |
|
261 function Combinator(text, line, col) { |
|
262 |
|
263 SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); |
|
264 |
|
265 /** |
|
266 * The type of modifier. |
|
267 * @type String |
|
268 * @property type |
|
269 */ |
|
270 this.type = "unknown"; |
|
271 |
|
272 //pretty simple |
|
273 if (/^\s+$/.test(text)) { |
|
274 this.type = "descendant"; |
|
275 } else if (text === ">") { |
|
276 this.type = "child"; |
|
277 } else if (text === "+") { |
|
278 this.type = "adjacent-sibling"; |
|
279 } else if (text === "~") { |
|
280 this.type = "sibling"; |
|
281 } |
|
282 |
|
283 } |
|
284 |
|
285 Combinator.prototype = new SyntaxUnit(); |
|
286 Combinator.prototype.constructor = Combinator; |
|
287 |
|
288 |
|
289 },{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){ |
|
290 "use strict"; |
|
291 |
|
292 module.exports = Matcher; |
|
293 |
|
294 var StringReader = require("../util/StringReader"); |
|
295 var SyntaxError = require("../util/SyntaxError"); |
|
296 |
|
297 /** |
|
298 * This class implements a combinator library for matcher functions. |
|
299 * The combinators are described at: |
|
300 * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators |
|
301 */ |
|
302 function Matcher(matchFunc, toString) { |
|
303 this.match = function(expression) { |
|
304 // Save/restore marks to ensure that failed matches always restore |
|
305 // the original location in the expression. |
|
306 var result; |
|
307 expression.mark(); |
|
308 result = matchFunc(expression); |
|
309 if (result) { |
|
310 expression.drop(); |
|
311 } else { |
|
312 expression.restore(); |
|
313 } |
|
314 return result; |
|
315 }; |
|
316 this.toString = typeof toString === "function" ? toString : function() { |
|
317 return toString; |
|
318 }; |
|
319 } |
|
320 |
|
321 /** Precedence table of combinators. */ |
|
322 Matcher.prec = { |
|
323 MOD: 5, |
|
324 SEQ: 4, |
|
325 ANDAND: 3, |
|
326 OROR: 2, |
|
327 ALT: 1 |
|
328 }; |
|
329 |
|
330 /** Simple recursive-descent grammar to build matchers from strings. */ |
|
331 Matcher.parse = function(str) { |
|
332 var reader, eat, expr, oror, andand, seq, mod, term, result; |
|
333 reader = new StringReader(str); |
|
334 eat = function(matcher) { |
|
335 var result = reader.readMatch(matcher); |
|
336 if (result === null) { |
|
337 throw new SyntaxError( |
|
338 "Expected "+matcher, reader.getLine(), reader.getCol()); |
|
339 } |
|
340 return result; |
|
341 }; |
|
342 expr = function() { |
|
343 // expr = oror (" | " oror)* |
|
344 var m = [ oror() ]; |
|
345 while (reader.readMatch(" | ") !== null) { |
|
346 m.push(oror()); |
|
347 } |
|
348 return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m); |
|
349 }; |
|
350 oror = function() { |
|
351 // oror = andand ( " || " andand)* |
|
352 var m = [ andand() ]; |
|
353 while (reader.readMatch(" || ") !== null) { |
|
354 m.push(andand()); |
|
355 } |
|
356 return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m); |
|
357 }; |
|
358 andand = function() { |
|
359 // andand = seq ( " && " seq)* |
|
360 var m = [ seq() ]; |
|
361 while (reader.readMatch(" && ") !== null) { |
|
362 m.push(seq()); |
|
363 } |
|
364 return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m); |
|
365 }; |
|
366 seq = function() { |
|
367 // seq = mod ( " " mod)* |
|
368 var m = [ mod() ]; |
|
369 while (reader.readMatch(/^ (?![&|\]])/) !== null) { |
|
370 m.push(mod()); |
|
371 } |
|
372 return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m); |
|
373 }; |
|
374 mod = function() { |
|
375 // mod = term ( "?" | "*" | "+" | "#" | "{<num>,<num>}" )? |
|
376 var m = term(); |
|
377 if (reader.readMatch("?") !== null) { |
|
378 return m.question(); |
|
379 } else if (reader.readMatch("*") !== null) { |
|
380 return m.star(); |
|
381 } else if (reader.readMatch("+") !== null) { |
|
382 return m.plus(); |
|
383 } else if (reader.readMatch("#") !== null) { |
|
384 return m.hash(); |
|
385 } else if (reader.readMatch(/^\{\s*/) !== null) { |
|
386 var min = eat(/^\d+/); |
|
387 eat(/^\s*,\s*/); |
|
388 var max = eat(/^\d+/); |
|
389 eat(/^\s*\}/); |
|
390 return m.braces(+min, +max); |
|
391 } |
|
392 return m; |
|
393 }; |
|
394 term = function() { |
|
395 // term = <nt> | literal | "[ " expression " ]" |
|
396 if (reader.readMatch("[ ") !== null) { |
|
397 var m = expr(); |
|
398 eat(" ]"); |
|
399 return m; |
|
400 } |
|
401 return Matcher.fromType(eat(/^[^ ?*+#{]+/)); |
|
402 }; |
|
403 result = expr(); |
|
404 if (!reader.eof()) { |
|
405 throw new SyntaxError( |
|
406 "Expected end of string", reader.getLine(), reader.getCol()); |
|
407 } |
|
408 return result; |
|
409 }; |
|
410 |
|
411 /** |
|
412 * Convert a string to a matcher (parsing simple alternations), |
|
413 * or do nothing if the argument is already a matcher. |
|
414 */ |
|
415 Matcher.cast = function(m) { |
|
416 if (m instanceof Matcher) { |
|
417 return m; |
|
418 } |
|
419 return Matcher.parse(m); |
|
420 }; |
|
421 |
|
422 /** |
|
423 * Create a matcher for a single type. |
|
424 */ |
|
425 Matcher.fromType = function(type) { |
|
426 // Late require of ValidationTypes to break a dependency cycle. |
|
427 var ValidationTypes = require("./ValidationTypes"); |
|
428 return new Matcher(function(expression) { |
|
429 return expression.hasNext() && ValidationTypes.isType(expression, type); |
|
430 }, type); |
|
431 }; |
|
432 |
|
433 /** |
|
434 * Create a matcher for one or more juxtaposed words, which all must |
|
435 * occur, in the given order. |
|
436 */ |
|
437 Matcher.seq = function() { |
|
438 var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); |
|
439 if (ms.length === 1) { |
|
440 return ms[0]; |
|
441 } |
|
442 return new Matcher(function(expression) { |
|
443 var i, result = true; |
|
444 for (i = 0; result && i < ms.length; i++) { |
|
445 result = ms[i].match(expression); |
|
446 } |
|
447 return result; |
|
448 }, function(prec) { |
|
449 var p = Matcher.prec.SEQ; |
|
450 var s = ms.map(function(m) { |
|
451 return m.toString(p); |
|
452 }).join(" "); |
|
453 if (prec > p) { |
|
454 s = "[ " + s + " ]"; |
|
455 } |
|
456 return s; |
|
457 }); |
|
458 }; |
|
459 |
|
460 /** |
|
461 * Create a matcher for one or more alternatives, where exactly one |
|
462 * must occur. |
|
463 */ |
|
464 Matcher.alt = function() { |
|
465 var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); |
|
466 if (ms.length === 1) { |
|
467 return ms[0]; |
|
468 } |
|
469 return new Matcher(function(expression) { |
|
470 var i, result = false; |
|
471 for (i = 0; !result && i < ms.length; i++) { |
|
472 result = ms[i].match(expression); |
|
473 } |
|
474 return result; |
|
475 }, function(prec) { |
|
476 var p = Matcher.prec.ALT; |
|
477 var s = ms.map(function(m) { |
|
478 return m.toString(p); |
|
479 }).join(" | "); |
|
480 if (prec > p) { |
|
481 s = "[ " + s + " ]"; |
|
482 } |
|
483 return s; |
|
484 }); |
|
485 }; |
|
486 |
|
487 /** |
|
488 * Create a matcher for two or more options. This implements the |
|
489 * double bar (||) and double ampersand (&&) operators, as well as |
|
490 * variants of && where some of the alternatives are optional. |
|
491 * This will backtrack through even successful matches to try to |
|
492 * maximize the number of items matched. |
|
493 */ |
|
494 Matcher.many = function(required) { |
|
495 var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) { |
|
496 if (v.expand) { |
|
497 // Insert all of the options for the given complex rule as |
|
498 // individual options. |
|
499 var ValidationTypes = require("./ValidationTypes"); |
|
500 acc.push.apply(acc, ValidationTypes.complex[v.expand].options); |
|
501 } else { |
|
502 acc.push(Matcher.cast(v)); |
|
503 } |
|
504 return acc; |
|
505 }, []); |
|
506 |
|
507 if (required === true) { |
|
508 required = ms.map(function() { |
|
509 return true; |
|
510 }); |
|
511 } |
|
512 |
|
513 var result = new Matcher(function(expression) { |
|
514 var seen = [], max = 0, pass = 0; |
|
515 var success = function(matchCount) { |
|
516 if (pass === 0) { |
|
517 max = Math.max(matchCount, max); |
|
518 return matchCount === ms.length; |
|
519 } else { |
|
520 return matchCount === max; |
|
521 } |
|
522 }; |
|
523 var tryMatch = function(matchCount) { |
|
524 for (var i = 0; i < ms.length; i++) { |
|
525 if (seen[i]) { |
|
526 continue; |
|
527 } |
|
528 expression.mark(); |
|
529 if (ms[i].match(expression)) { |
|
530 seen[i] = true; |
|
531 // Increase matchCount iff this was a required element |
|
532 // (or if all the elements are optional) |
|
533 if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) { |
|
534 expression.drop(); |
|
535 return true; |
|
536 } |
|
537 // Backtrack: try *not* matching using this rule, and |
|
538 // let's see if it leads to a better overall match. |
|
539 expression.restore(); |
|
540 seen[i] = false; |
|
541 } else { |
|
542 expression.drop(); |
|
543 } |
|
544 } |
|
545 return success(matchCount); |
|
546 }; |
|
547 if (!tryMatch(0)) { |
|
548 // Couldn't get a complete match, retrace our steps to make the |
|
549 // match with the maximum # of required elements. |
|
550 pass++; |
|
551 tryMatch(0); |
|
552 } |
|
553 |
|
554 if (required === false) { |
|
555 return max > 0; |
|
556 } |
|
557 // Use finer-grained specification of which matchers are required. |
|
558 for (var i = 0; i < ms.length; i++) { |
|
559 if (required[i] && !seen[i]) { |
|
560 return false; |
|
561 } |
|
562 } |
|
563 return true; |
|
564 }, function(prec) { |
|
565 var p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND; |
|
566 var s = ms.map(function(m, i) { |
|
567 if (required !== false && !required[i]) { |
|
568 return m.toString(Matcher.prec.MOD) + "?"; |
|
569 } |
|
570 return m.toString(p); |
|
571 }).join(required === false ? " || " : " && "); |
|
572 if (prec > p) { |
|
573 s = "[ " + s + " ]"; |
|
574 } |
|
575 return s; |
|
576 }); |
|
577 result.options = ms; |
|
578 return result; |
|
579 }; |
|
580 |
|
581 /** |
|
582 * Create a matcher for two or more options, where all options are |
|
583 * mandatory but they may appear in any order. |
|
584 */ |
|
585 Matcher.andand = function() { |
|
586 var args = Array.prototype.slice.call(arguments); |
|
587 args.unshift(true); |
|
588 return Matcher.many.apply(Matcher, args); |
|
589 }; |
|
590 |
|
591 /** |
|
592 * Create a matcher for two or more options, where options are |
|
593 * optional and may appear in any order, but at least one must be |
|
594 * present. |
|
595 */ |
|
596 Matcher.oror = function() { |
|
597 var args = Array.prototype.slice.call(arguments); |
|
598 args.unshift(false); |
|
599 return Matcher.many.apply(Matcher, args); |
|
600 }; |
|
601 |
|
602 /** Instance methods on Matchers. */ |
|
603 Matcher.prototype = { |
|
604 constructor: Matcher, |
|
605 // These are expected to be overridden in every instance. |
|
606 match: function() { throw new Error("unimplemented"); }, |
|
607 toString: function() { throw new Error("unimplemented"); }, |
|
608 // This returns a standalone function to do the matching. |
|
609 func: function() { return this.match.bind(this); }, |
|
610 // Basic combinators |
|
611 then: function(m) { return Matcher.seq(this, m); }, |
|
612 or: function(m) { return Matcher.alt(this, m); }, |
|
613 andand: function(m) { return Matcher.many(true, this, m); }, |
|
614 oror: function(m) { return Matcher.many(false, this, m); }, |
|
615 // Component value multipliers |
|
616 star: function() { return this.braces(0, Infinity, "*"); }, |
|
617 plus: function() { return this.braces(1, Infinity, "+"); }, |
|
618 question: function() { return this.braces(0, 1, "?"); }, |
|
619 hash: function() { |
|
620 return this.braces(1, Infinity, "#", Matcher.cast(",")); |
|
621 }, |
|
622 braces: function(min, max, marker, optSep) { |
|
623 var m1 = this, m2 = optSep ? optSep.then(this) : this; |
|
624 if (!marker) { |
|
625 marker = "{" + min + "," + max + "}"; |
|
626 } |
|
627 return new Matcher(function(expression) { |
|
628 var result = true, i; |
|
629 for (i = 0; i < max; i++) { |
|
630 if (i > 0 && optSep) { |
|
631 result = m2.match(expression); |
|
632 } else { |
|
633 result = m1.match(expression); |
|
634 } |
|
635 if (!result) { |
|
636 break; |
|
637 } |
|
638 } |
|
639 return i >= min; |
|
640 }, function() { |
|
641 return m1.toString(Matcher.prec.MOD) + marker; |
|
642 }); |
|
643 } |
|
644 }; |
|
645 |
|
646 },{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){ |
|
647 "use strict"; |
|
648 |
|
649 module.exports = MediaFeature; |
|
650 |
|
651 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
652 |
|
653 var Parser = require("./Parser"); |
|
654 |
|
655 /** |
|
656 * Represents a media feature, such as max-width:500. |
|
657 * @namespace parserlib.css |
|
658 * @class MediaFeature |
|
659 * @extends parserlib.util.SyntaxUnit |
|
660 * @constructor |
|
661 * @param {SyntaxUnit} name The name of the feature. |
|
662 * @param {SyntaxUnit} value The value of the feature or null if none. |
|
663 */ |
|
664 function MediaFeature(name, value) { |
|
665 |
|
666 SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); |
|
667 |
|
668 /** |
|
669 * The name of the media feature |
|
670 * @type String |
|
671 * @property name |
|
672 */ |
|
673 this.name = name; |
|
674 |
|
675 /** |
|
676 * The value for the feature or null if there is none. |
|
677 * @type SyntaxUnit |
|
678 * @property value |
|
679 */ |
|
680 this.value = value; |
|
681 } |
|
682 |
|
683 MediaFeature.prototype = new SyntaxUnit(); |
|
684 MediaFeature.prototype.constructor = MediaFeature; |
|
685 |
|
686 |
|
687 },{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){ |
|
688 "use strict"; |
|
689 |
|
690 module.exports = MediaQuery; |
|
691 |
|
692 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
693 |
|
694 var Parser = require("./Parser"); |
|
695 |
|
696 /** |
|
697 * Represents an individual media query. |
|
698 * @namespace parserlib.css |
|
699 * @class MediaQuery |
|
700 * @extends parserlib.util.SyntaxUnit |
|
701 * @constructor |
|
702 * @param {String} modifier The modifier "not" or "only" (or null). |
|
703 * @param {String} mediaType The type of media (i.e., "print"). |
|
704 * @param {Array} parts Array of selectors parts making up this selector. |
|
705 * @param {int} line The line of text on which the unit resides. |
|
706 * @param {int} col The column of text on which the unit resides. |
|
707 */ |
|
708 function MediaQuery(modifier, mediaType, features, line, col) { |
|
709 |
|
710 SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); |
|
711 |
|
712 /** |
|
713 * The media modifier ("not" or "only") |
|
714 * @type String |
|
715 * @property modifier |
|
716 */ |
|
717 this.modifier = modifier; |
|
718 |
|
719 /** |
|
720 * The mediaType (i.e., "print") |
|
721 * @type String |
|
722 * @property mediaType |
|
723 */ |
|
724 this.mediaType = mediaType; |
|
725 |
|
726 /** |
|
727 * The parts that make up the selector. |
|
728 * @type Array |
|
729 * @property features |
|
730 */ |
|
731 this.features = features; |
|
732 |
|
733 } |
|
734 |
|
735 MediaQuery.prototype = new SyntaxUnit(); |
|
736 MediaQuery.prototype.constructor = MediaQuery; |
|
737 |
|
738 |
|
739 },{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){ |
|
740 "use strict"; |
|
741 |
|
742 module.exports = Parser; |
|
743 |
|
744 var EventTarget = require("../util/EventTarget"); |
|
745 var SyntaxError = require("../util/SyntaxError"); |
|
746 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
747 |
|
748 var Combinator = require("./Combinator"); |
|
749 var MediaFeature = require("./MediaFeature"); |
|
750 var MediaQuery = require("./MediaQuery"); |
|
751 var PropertyName = require("./PropertyName"); |
|
752 var PropertyValue = require("./PropertyValue"); |
|
753 var PropertyValuePart = require("./PropertyValuePart"); |
|
754 var Selector = require("./Selector"); |
|
755 var SelectorPart = require("./SelectorPart"); |
|
756 var SelectorSubPart = require("./SelectorSubPart"); |
|
757 var TokenStream = require("./TokenStream"); |
|
758 var Tokens = require("./Tokens"); |
|
759 var Validation = require("./Validation"); |
|
760 |
|
761 /** |
|
762 * A CSS3 parser. |
|
763 * @namespace parserlib.css |
|
764 * @class Parser |
|
765 * @constructor |
|
766 * @param {Object} options (Optional) Various options for the parser: |
|
767 * starHack (true|false) to allow IE6 star hack as valid, |
|
768 * underscoreHack (true|false) to interpret leading underscores |
|
769 * as IE6-7 targeting for known properties, ieFilters (true|false) |
|
770 * to indicate that IE < 8 filters should be accepted and not throw |
|
771 * syntax errors. |
|
772 */ |
|
773 function Parser(options) { |
|
774 |
|
775 //inherit event functionality |
|
776 EventTarget.call(this); |
|
777 |
|
778 |
|
779 this.options = options || {}; |
|
780 |
|
781 this._tokenStream = null; |
|
782 } |
|
783 |
|
784 //Static constants |
|
785 Parser.DEFAULT_TYPE = 0; |
|
786 Parser.COMBINATOR_TYPE = 1; |
|
787 Parser.MEDIA_FEATURE_TYPE = 2; |
|
788 Parser.MEDIA_QUERY_TYPE = 3; |
|
789 Parser.PROPERTY_NAME_TYPE = 4; |
|
790 Parser.PROPERTY_VALUE_TYPE = 5; |
|
791 Parser.PROPERTY_VALUE_PART_TYPE = 6; |
|
792 Parser.SELECTOR_TYPE = 7; |
|
793 Parser.SELECTOR_PART_TYPE = 8; |
|
794 Parser.SELECTOR_SUB_PART_TYPE = 9; |
|
795 |
|
796 Parser.prototype = function() { |
|
797 |
|
798 var proto = new EventTarget(), //new prototype |
|
799 prop, |
|
800 additions = { |
|
801 __proto__: null, |
|
802 |
|
803 //restore constructor |
|
804 constructor: Parser, |
|
805 |
|
806 //instance constants - yuck |
|
807 DEFAULT_TYPE : 0, |
|
808 COMBINATOR_TYPE : 1, |
|
809 MEDIA_FEATURE_TYPE : 2, |
|
810 MEDIA_QUERY_TYPE : 3, |
|
811 PROPERTY_NAME_TYPE : 4, |
|
812 PROPERTY_VALUE_TYPE : 5, |
|
813 PROPERTY_VALUE_PART_TYPE : 6, |
|
814 SELECTOR_TYPE : 7, |
|
815 SELECTOR_PART_TYPE : 8, |
|
816 SELECTOR_SUB_PART_TYPE : 9, |
|
817 |
|
818 //----------------------------------------------------------------- |
|
819 // Grammar |
|
820 //----------------------------------------------------------------- |
|
821 |
|
822 _stylesheet: function() { |
|
823 |
|
824 /* |
|
825 * stylesheet |
|
826 * : [ CHARSET_SYM S* STRING S* ';' ]? |
|
827 * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* |
|
828 * [ namespace [S|CDO|CDC]* ]* |
|
829 * [ [ ruleset | media | page | font_face | keyframes_rule | supports_rule ] [S|CDO|CDC]* ]* |
|
830 * ; |
|
831 */ |
|
832 |
|
833 var tokenStream = this._tokenStream, |
|
834 count, |
|
835 token, |
|
836 tt; |
|
837 |
|
838 this.fire("startstylesheet"); |
|
839 |
|
840 //try to read character set |
|
841 this._charset(); |
|
842 |
|
843 this._skipCruft(); |
|
844 |
|
845 //try to read imports - may be more than one |
|
846 while (tokenStream.peek() === Tokens.IMPORT_SYM) { |
|
847 this._import(); |
|
848 this._skipCruft(); |
|
849 } |
|
850 |
|
851 //try to read namespaces - may be more than one |
|
852 while (tokenStream.peek() === Tokens.NAMESPACE_SYM) { |
|
853 this._namespace(); |
|
854 this._skipCruft(); |
|
855 } |
|
856 |
|
857 //get the next token |
|
858 tt = tokenStream.peek(); |
|
859 |
|
860 //try to read the rest |
|
861 while (tt > Tokens.EOF) { |
|
862 |
|
863 try { |
|
864 |
|
865 switch (tt) { |
|
866 case Tokens.MEDIA_SYM: |
|
867 this._media(); |
|
868 this._skipCruft(); |
|
869 break; |
|
870 case Tokens.PAGE_SYM: |
|
871 this._page(); |
|
872 this._skipCruft(); |
|
873 break; |
|
874 case Tokens.FONT_FACE_SYM: |
|
875 this._font_face(); |
|
876 this._skipCruft(); |
|
877 break; |
|
878 case Tokens.KEYFRAMES_SYM: |
|
879 this._keyframes(); |
|
880 this._skipCruft(); |
|
881 break; |
|
882 case Tokens.VIEWPORT_SYM: |
|
883 this._viewport(); |
|
884 this._skipCruft(); |
|
885 break; |
|
886 case Tokens.DOCUMENT_SYM: |
|
887 this._document(); |
|
888 this._skipCruft(); |
|
889 break; |
|
890 case Tokens.SUPPORTS_SYM: |
|
891 this._supports(); |
|
892 this._skipCruft(); |
|
893 break; |
|
894 case Tokens.UNKNOWN_SYM: //unknown @ rule |
|
895 tokenStream.get(); |
|
896 if (!this.options.strict) { |
|
897 |
|
898 //fire error event |
|
899 this.fire({ |
|
900 type: "error", |
|
901 error: null, |
|
902 message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", |
|
903 line: tokenStream.LT(0).startLine, |
|
904 col: tokenStream.LT(0).startCol |
|
905 }); |
|
906 |
|
907 //skip braces |
|
908 count=0; |
|
909 while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) === Tokens.LBRACE) { |
|
910 count++; //keep track of nesting depth |
|
911 } |
|
912 |
|
913 while (count) { |
|
914 tokenStream.advance([Tokens.RBRACE]); |
|
915 count--; |
|
916 } |
|
917 |
|
918 } else { |
|
919 //not a syntax error, rethrow it |
|
920 throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); |
|
921 } |
|
922 break; |
|
923 case Tokens.S: |
|
924 this._readWhitespace(); |
|
925 break; |
|
926 default: |
|
927 if (!this._ruleset()) { |
|
928 |
|
929 //error handling for known issues |
|
930 switch (tt) { |
|
931 case Tokens.CHARSET_SYM: |
|
932 token = tokenStream.LT(1); |
|
933 this._charset(false); |
|
934 throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); |
|
935 case Tokens.IMPORT_SYM: |
|
936 token = tokenStream.LT(1); |
|
937 this._import(false); |
|
938 throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); |
|
939 case Tokens.NAMESPACE_SYM: |
|
940 token = tokenStream.LT(1); |
|
941 this._namespace(false); |
|
942 throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); |
|
943 default: |
|
944 tokenStream.get(); //get the last token |
|
945 this._unexpectedToken(tokenStream.token()); |
|
946 } |
|
947 |
|
948 } |
|
949 } |
|
950 } catch (ex) { |
|
951 if (ex instanceof SyntaxError && !this.options.strict) { |
|
952 this.fire({ |
|
953 type: "error", |
|
954 error: ex, |
|
955 message: ex.message, |
|
956 line: ex.line, |
|
957 col: ex.col |
|
958 }); |
|
959 } else { |
|
960 throw ex; |
|
961 } |
|
962 } |
|
963 |
|
964 tt = tokenStream.peek(); |
|
965 } |
|
966 |
|
967 if (tt !== Tokens.EOF) { |
|
968 this._unexpectedToken(tokenStream.token()); |
|
969 } |
|
970 |
|
971 this.fire("endstylesheet"); |
|
972 }, |
|
973 |
|
974 _charset: function(emit) { |
|
975 var tokenStream = this._tokenStream, |
|
976 charset, |
|
977 token, |
|
978 line, |
|
979 col; |
|
980 |
|
981 if (tokenStream.match(Tokens.CHARSET_SYM)) { |
|
982 line = tokenStream.token().startLine; |
|
983 col = tokenStream.token().startCol; |
|
984 |
|
985 this._readWhitespace(); |
|
986 tokenStream.mustMatch(Tokens.STRING); |
|
987 |
|
988 token = tokenStream.token(); |
|
989 charset = token.value; |
|
990 |
|
991 this._readWhitespace(); |
|
992 tokenStream.mustMatch(Tokens.SEMICOLON); |
|
993 |
|
994 if (emit !== false) { |
|
995 this.fire({ |
|
996 type: "charset", |
|
997 charset:charset, |
|
998 line: line, |
|
999 col: col |
|
1000 }); |
|
1001 } |
|
1002 } |
|
1003 }, |
|
1004 |
|
1005 _import: function(emit) { |
|
1006 /* |
|
1007 * import |
|
1008 * : IMPORT_SYM S* |
|
1009 * [STRING|URI] S* media_query_list? ';' S* |
|
1010 */ |
|
1011 |
|
1012 var tokenStream = this._tokenStream, |
|
1013 uri, |
|
1014 importToken, |
|
1015 mediaList = []; |
|
1016 |
|
1017 //read import symbol |
|
1018 tokenStream.mustMatch(Tokens.IMPORT_SYM); |
|
1019 importToken = tokenStream.token(); |
|
1020 this._readWhitespace(); |
|
1021 |
|
1022 tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); |
|
1023 |
|
1024 //grab the URI value |
|
1025 uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1"); |
|
1026 |
|
1027 this._readWhitespace(); |
|
1028 |
|
1029 mediaList = this._media_query_list(); |
|
1030 |
|
1031 //must end with a semicolon |
|
1032 tokenStream.mustMatch(Tokens.SEMICOLON); |
|
1033 this._readWhitespace(); |
|
1034 |
|
1035 if (emit !== false) { |
|
1036 this.fire({ |
|
1037 type: "import", |
|
1038 uri: uri, |
|
1039 media: mediaList, |
|
1040 line: importToken.startLine, |
|
1041 col: importToken.startCol |
|
1042 }); |
|
1043 } |
|
1044 |
|
1045 }, |
|
1046 |
|
1047 _namespace: function(emit) { |
|
1048 /* |
|
1049 * namespace |
|
1050 * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* |
|
1051 */ |
|
1052 |
|
1053 var tokenStream = this._tokenStream, |
|
1054 line, |
|
1055 col, |
|
1056 prefix, |
|
1057 uri; |
|
1058 |
|
1059 //read import symbol |
|
1060 tokenStream.mustMatch(Tokens.NAMESPACE_SYM); |
|
1061 line = tokenStream.token().startLine; |
|
1062 col = tokenStream.token().startCol; |
|
1063 this._readWhitespace(); |
|
1064 |
|
1065 //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT |
|
1066 if (tokenStream.match(Tokens.IDENT)) { |
|
1067 prefix = tokenStream.token().value; |
|
1068 this._readWhitespace(); |
|
1069 } |
|
1070 |
|
1071 tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); |
|
1072 /*if (!tokenStream.match(Tokens.STRING)){ |
|
1073 tokenStream.mustMatch(Tokens.URI); |
|
1074 }*/ |
|
1075 |
|
1076 //grab the URI value |
|
1077 uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); |
|
1078 |
|
1079 this._readWhitespace(); |
|
1080 |
|
1081 //must end with a semicolon |
|
1082 tokenStream.mustMatch(Tokens.SEMICOLON); |
|
1083 this._readWhitespace(); |
|
1084 |
|
1085 if (emit !== false) { |
|
1086 this.fire({ |
|
1087 type: "namespace", |
|
1088 prefix: prefix, |
|
1089 uri: uri, |
|
1090 line: line, |
|
1091 col: col |
|
1092 }); |
|
1093 } |
|
1094 |
|
1095 }, |
|
1096 |
|
1097 _supports: function(emit) { |
|
1098 /* |
|
1099 * supports_rule |
|
1100 * : SUPPORTS_SYM S* supports_condition S* group_rule_body |
|
1101 * ; |
|
1102 */ |
|
1103 var tokenStream = this._tokenStream, |
|
1104 line, |
|
1105 col; |
|
1106 |
|
1107 if (tokenStream.match(Tokens.SUPPORTS_SYM)) { |
|
1108 line = tokenStream.token().startLine; |
|
1109 col = tokenStream.token().startCol; |
|
1110 |
|
1111 this._readWhitespace(); |
|
1112 this._supports_condition(); |
|
1113 this._readWhitespace(); |
|
1114 |
|
1115 tokenStream.mustMatch(Tokens.LBRACE); |
|
1116 this._readWhitespace(); |
|
1117 |
|
1118 if (emit !== false) { |
|
1119 this.fire({ |
|
1120 type: "startsupports", |
|
1121 line: line, |
|
1122 col: col |
|
1123 }); |
|
1124 } |
|
1125 |
|
1126 while (true) { |
|
1127 if (!this._ruleset()) { |
|
1128 break; |
|
1129 } |
|
1130 } |
|
1131 |
|
1132 tokenStream.mustMatch(Tokens.RBRACE); |
|
1133 this._readWhitespace(); |
|
1134 |
|
1135 this.fire({ |
|
1136 type: "endsupports", |
|
1137 line: line, |
|
1138 col: col |
|
1139 }); |
|
1140 } |
|
1141 }, |
|
1142 |
|
1143 _supports_condition: function() { |
|
1144 /* |
|
1145 * supports_condition |
|
1146 * : supports_negation | supports_conjunction | supports_disjunction | |
|
1147 * supports_condition_in_parens |
|
1148 * ; |
|
1149 */ |
|
1150 var tokenStream = this._tokenStream, |
|
1151 ident; |
|
1152 |
|
1153 if (tokenStream.match(Tokens.IDENT)) { |
|
1154 ident = tokenStream.token().value.toLowerCase(); |
|
1155 |
|
1156 if (ident === "not") { |
|
1157 tokenStream.mustMatch(Tokens.S); |
|
1158 this._supports_condition_in_parens(); |
|
1159 } else { |
|
1160 tokenStream.unget(); |
|
1161 } |
|
1162 } else { |
|
1163 this._supports_condition_in_parens(); |
|
1164 this._readWhitespace(); |
|
1165 |
|
1166 while (tokenStream.peek() === Tokens.IDENT) { |
|
1167 ident = tokenStream.LT(1).value.toLowerCase(); |
|
1168 if (ident === "and" || ident === "or") { |
|
1169 tokenStream.mustMatch(Tokens.IDENT); |
|
1170 this._readWhitespace(); |
|
1171 this._supports_condition_in_parens(); |
|
1172 this._readWhitespace(); |
|
1173 } |
|
1174 } |
|
1175 } |
|
1176 }, |
|
1177 |
|
1178 _supports_condition_in_parens: function() { |
|
1179 /* |
|
1180 * supports_condition_in_parens |
|
1181 * : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition | |
|
1182 * general_enclosed |
|
1183 * ; |
|
1184 */ |
|
1185 var tokenStream = this._tokenStream, |
|
1186 ident; |
|
1187 |
|
1188 if (tokenStream.match(Tokens.LPAREN)) { |
|
1189 this._readWhitespace(); |
|
1190 if (tokenStream.match(Tokens.IDENT)) { |
|
1191 // look ahead for not keyword, if not given, continue with declaration condition. |
|
1192 ident = tokenStream.token().value.toLowerCase(); |
|
1193 if (ident === "not") { |
|
1194 this._readWhitespace(); |
|
1195 this._supports_condition(); |
|
1196 this._readWhitespace(); |
|
1197 tokenStream.mustMatch(Tokens.RPAREN); |
|
1198 } else { |
|
1199 tokenStream.unget(); |
|
1200 this._supports_declaration_condition(false); |
|
1201 } |
|
1202 } else { |
|
1203 this._supports_condition(); |
|
1204 this._readWhitespace(); |
|
1205 tokenStream.mustMatch(Tokens.RPAREN); |
|
1206 } |
|
1207 } else { |
|
1208 this._supports_declaration_condition(); |
|
1209 } |
|
1210 }, |
|
1211 |
|
1212 _supports_declaration_condition: function(requireStartParen) { |
|
1213 /* |
|
1214 * supports_declaration_condition |
|
1215 * : '(' S* declaration ')' |
|
1216 * ; |
|
1217 */ |
|
1218 var tokenStream = this._tokenStream; |
|
1219 |
|
1220 if (requireStartParen !== false) { |
|
1221 tokenStream.mustMatch(Tokens.LPAREN); |
|
1222 } |
|
1223 this._readWhitespace(); |
|
1224 this._declaration(); |
|
1225 tokenStream.mustMatch(Tokens.RPAREN); |
|
1226 }, |
|
1227 |
|
1228 _media: function() { |
|
1229 /* |
|
1230 * media |
|
1231 * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* |
|
1232 * ; |
|
1233 */ |
|
1234 var tokenStream = this._tokenStream, |
|
1235 line, |
|
1236 col, |
|
1237 mediaList;// = []; |
|
1238 |
|
1239 //look for @media |
|
1240 tokenStream.mustMatch(Tokens.MEDIA_SYM); |
|
1241 line = tokenStream.token().startLine; |
|
1242 col = tokenStream.token().startCol; |
|
1243 |
|
1244 this._readWhitespace(); |
|
1245 |
|
1246 mediaList = this._media_query_list(); |
|
1247 |
|
1248 tokenStream.mustMatch(Tokens.LBRACE); |
|
1249 this._readWhitespace(); |
|
1250 |
|
1251 this.fire({ |
|
1252 type: "startmedia", |
|
1253 media: mediaList, |
|
1254 line: line, |
|
1255 col: col |
|
1256 }); |
|
1257 |
|
1258 while (true) { |
|
1259 if (tokenStream.peek() === Tokens.PAGE_SYM) { |
|
1260 this._page(); |
|
1261 } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM) { |
|
1262 this._font_face(); |
|
1263 } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM) { |
|
1264 this._viewport(); |
|
1265 } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM) { |
|
1266 this._document(); |
|
1267 } else if (tokenStream.peek() === Tokens.SUPPORTS_SYM) { |
|
1268 this._supports(); |
|
1269 } else if (tokenStream.peek() === Tokens.MEDIA_SYM) { |
|
1270 this._media(); |
|
1271 } else if (!this._ruleset()) { |
|
1272 break; |
|
1273 } |
|
1274 } |
|
1275 |
|
1276 tokenStream.mustMatch(Tokens.RBRACE); |
|
1277 this._readWhitespace(); |
|
1278 |
|
1279 this.fire({ |
|
1280 type: "endmedia", |
|
1281 media: mediaList, |
|
1282 line: line, |
|
1283 col: col |
|
1284 }); |
|
1285 }, |
|
1286 |
|
1287 |
|
1288 //CSS3 Media Queries |
|
1289 _media_query_list: function() { |
|
1290 /* |
|
1291 * media_query_list |
|
1292 * : S* [media_query [ ',' S* media_query ]* ]? |
|
1293 * ; |
|
1294 */ |
|
1295 var tokenStream = this._tokenStream, |
|
1296 mediaList = []; |
|
1297 |
|
1298 |
|
1299 this._readWhitespace(); |
|
1300 |
|
1301 if (tokenStream.peek() === Tokens.IDENT || tokenStream.peek() === Tokens.LPAREN) { |
|
1302 mediaList.push(this._media_query()); |
|
1303 } |
|
1304 |
|
1305 while (tokenStream.match(Tokens.COMMA)) { |
|
1306 this._readWhitespace(); |
|
1307 mediaList.push(this._media_query()); |
|
1308 } |
|
1309 |
|
1310 return mediaList; |
|
1311 }, |
|
1312 |
|
1313 /* |
|
1314 * Note: "expression" in the grammar maps to the _media_expression |
|
1315 * method. |
|
1316 |
|
1317 */ |
|
1318 _media_query: function() { |
|
1319 /* |
|
1320 * media_query |
|
1321 * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* |
|
1322 * | expression [ AND S* expression ]* |
|
1323 * ; |
|
1324 */ |
|
1325 var tokenStream = this._tokenStream, |
|
1326 type = null, |
|
1327 ident = null, |
|
1328 token = null, |
|
1329 expressions = []; |
|
1330 |
|
1331 if (tokenStream.match(Tokens.IDENT)) { |
|
1332 ident = tokenStream.token().value.toLowerCase(); |
|
1333 |
|
1334 //since there's no custom tokens for these, need to manually check |
|
1335 if (ident !== "only" && ident !== "not") { |
|
1336 tokenStream.unget(); |
|
1337 ident = null; |
|
1338 } else { |
|
1339 token = tokenStream.token(); |
|
1340 } |
|
1341 } |
|
1342 |
|
1343 this._readWhitespace(); |
|
1344 |
|
1345 if (tokenStream.peek() === Tokens.IDENT) { |
|
1346 type = this._media_type(); |
|
1347 if (token === null) { |
|
1348 token = tokenStream.token(); |
|
1349 } |
|
1350 } else if (tokenStream.peek() === Tokens.LPAREN) { |
|
1351 if (token === null) { |
|
1352 token = tokenStream.LT(1); |
|
1353 } |
|
1354 expressions.push(this._media_expression()); |
|
1355 } |
|
1356 |
|
1357 if (type === null && expressions.length === 0) { |
|
1358 return null; |
|
1359 } else { |
|
1360 this._readWhitespace(); |
|
1361 while (tokenStream.match(Tokens.IDENT)) { |
|
1362 if (tokenStream.token().value.toLowerCase() !== "and") { |
|
1363 this._unexpectedToken(tokenStream.token()); |
|
1364 } |
|
1365 |
|
1366 this._readWhitespace(); |
|
1367 expressions.push(this._media_expression()); |
|
1368 } |
|
1369 } |
|
1370 |
|
1371 return new MediaQuery(ident, type, expressions, token.startLine, token.startCol); |
|
1372 }, |
|
1373 |
|
1374 //CSS3 Media Queries |
|
1375 _media_type: function() { |
|
1376 /* |
|
1377 * media_type |
|
1378 * : IDENT |
|
1379 * ; |
|
1380 */ |
|
1381 return this._media_feature(); |
|
1382 }, |
|
1383 |
|
1384 /** |
|
1385 * Note: in CSS3 Media Queries, this is called "expression". |
|
1386 * Renamed here to avoid conflict with CSS3 Selectors |
|
1387 * definition of "expression". Also note that "expr" in the |
|
1388 * grammar now maps to "expression" from CSS3 selectors. |
|
1389 * @method _media_expression |
|
1390 * @private |
|
1391 */ |
|
1392 _media_expression: function() { |
|
1393 /* |
|
1394 * expression |
|
1395 * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* |
|
1396 * ; |
|
1397 */ |
|
1398 var tokenStream = this._tokenStream, |
|
1399 feature = null, |
|
1400 token, |
|
1401 expression = null; |
|
1402 |
|
1403 tokenStream.mustMatch(Tokens.LPAREN); |
|
1404 |
|
1405 feature = this._media_feature(); |
|
1406 this._readWhitespace(); |
|
1407 |
|
1408 if (tokenStream.match(Tokens.COLON)) { |
|
1409 this._readWhitespace(); |
|
1410 token = tokenStream.LT(1); |
|
1411 expression = this._expression(); |
|
1412 } |
|
1413 |
|
1414 tokenStream.mustMatch(Tokens.RPAREN); |
|
1415 this._readWhitespace(); |
|
1416 |
|
1417 return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null); |
|
1418 }, |
|
1419 |
|
1420 //CSS3 Media Queries |
|
1421 _media_feature: function() { |
|
1422 /* |
|
1423 * media_feature |
|
1424 * : IDENT |
|
1425 * ; |
|
1426 */ |
|
1427 var tokenStream = this._tokenStream; |
|
1428 |
|
1429 this._readWhitespace(); |
|
1430 |
|
1431 tokenStream.mustMatch(Tokens.IDENT); |
|
1432 |
|
1433 return SyntaxUnit.fromToken(tokenStream.token()); |
|
1434 }, |
|
1435 |
|
1436 //CSS3 Paged Media |
|
1437 _page: function() { |
|
1438 /* |
|
1439 * page: |
|
1440 * PAGE_SYM S* IDENT? pseudo_page? S* |
|
1441 * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* |
|
1442 * ; |
|
1443 */ |
|
1444 var tokenStream = this._tokenStream, |
|
1445 line, |
|
1446 col, |
|
1447 identifier = null, |
|
1448 pseudoPage = null; |
|
1449 |
|
1450 //look for @page |
|
1451 tokenStream.mustMatch(Tokens.PAGE_SYM); |
|
1452 line = tokenStream.token().startLine; |
|
1453 col = tokenStream.token().startCol; |
|
1454 |
|
1455 this._readWhitespace(); |
|
1456 |
|
1457 if (tokenStream.match(Tokens.IDENT)) { |
|
1458 identifier = tokenStream.token().value; |
|
1459 |
|
1460 //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. |
|
1461 if (identifier.toLowerCase() === "auto") { |
|
1462 this._unexpectedToken(tokenStream.token()); |
|
1463 } |
|
1464 } |
|
1465 |
|
1466 //see if there's a colon upcoming |
|
1467 if (tokenStream.peek() === Tokens.COLON) { |
|
1468 pseudoPage = this._pseudo_page(); |
|
1469 } |
|
1470 |
|
1471 this._readWhitespace(); |
|
1472 |
|
1473 this.fire({ |
|
1474 type: "startpage", |
|
1475 id: identifier, |
|
1476 pseudo: pseudoPage, |
|
1477 line: line, |
|
1478 col: col |
|
1479 }); |
|
1480 |
|
1481 this._readDeclarations(true, true); |
|
1482 |
|
1483 this.fire({ |
|
1484 type: "endpage", |
|
1485 id: identifier, |
|
1486 pseudo: pseudoPage, |
|
1487 line: line, |
|
1488 col: col |
|
1489 }); |
|
1490 |
|
1491 }, |
|
1492 |
|
1493 //CSS3 Paged Media |
|
1494 _margin: function() { |
|
1495 /* |
|
1496 * margin : |
|
1497 * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* |
|
1498 * ; |
|
1499 */ |
|
1500 var tokenStream = this._tokenStream, |
|
1501 line, |
|
1502 col, |
|
1503 marginSym = this._margin_sym(); |
|
1504 |
|
1505 if (marginSym) { |
|
1506 line = tokenStream.token().startLine; |
|
1507 col = tokenStream.token().startCol; |
|
1508 |
|
1509 this.fire({ |
|
1510 type: "startpagemargin", |
|
1511 margin: marginSym, |
|
1512 line: line, |
|
1513 col: col |
|
1514 }); |
|
1515 |
|
1516 this._readDeclarations(true); |
|
1517 |
|
1518 this.fire({ |
|
1519 type: "endpagemargin", |
|
1520 margin: marginSym, |
|
1521 line: line, |
|
1522 col: col |
|
1523 }); |
|
1524 return true; |
|
1525 } else { |
|
1526 return false; |
|
1527 } |
|
1528 }, |
|
1529 |
|
1530 //CSS3 Paged Media |
|
1531 _margin_sym: function() { |
|
1532 |
|
1533 /* |
|
1534 * margin_sym : |
|
1535 * TOPLEFTCORNER_SYM | |
|
1536 * TOPLEFT_SYM | |
|
1537 * TOPCENTER_SYM | |
|
1538 * TOPRIGHT_SYM | |
|
1539 * TOPRIGHTCORNER_SYM | |
|
1540 * BOTTOMLEFTCORNER_SYM | |
|
1541 * BOTTOMLEFT_SYM | |
|
1542 * BOTTOMCENTER_SYM | |
|
1543 * BOTTOMRIGHT_SYM | |
|
1544 * BOTTOMRIGHTCORNER_SYM | |
|
1545 * LEFTTOP_SYM | |
|
1546 * LEFTMIDDLE_SYM | |
|
1547 * LEFTBOTTOM_SYM | |
|
1548 * RIGHTTOP_SYM | |
|
1549 * RIGHTMIDDLE_SYM | |
|
1550 * RIGHTBOTTOM_SYM |
|
1551 * ; |
|
1552 */ |
|
1553 |
|
1554 var tokenStream = this._tokenStream; |
|
1555 |
|
1556 if (tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, |
|
1557 Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, |
|
1558 Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, |
|
1559 Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, |
|
1560 Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, |
|
1561 Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, |
|
1562 Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) { |
|
1563 return SyntaxUnit.fromToken(tokenStream.token()); |
|
1564 } else { |
|
1565 return null; |
|
1566 } |
|
1567 |
|
1568 }, |
|
1569 |
|
1570 _pseudo_page: function() { |
|
1571 /* |
|
1572 * pseudo_page |
|
1573 * : ':' IDENT |
|
1574 * ; |
|
1575 */ |
|
1576 |
|
1577 var tokenStream = this._tokenStream; |
|
1578 |
|
1579 tokenStream.mustMatch(Tokens.COLON); |
|
1580 tokenStream.mustMatch(Tokens.IDENT); |
|
1581 |
|
1582 //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed |
|
1583 |
|
1584 return tokenStream.token().value; |
|
1585 }, |
|
1586 |
|
1587 _font_face: function() { |
|
1588 /* |
|
1589 * font_face |
|
1590 * : FONT_FACE_SYM S* |
|
1591 * '{' S* declaration [ ';' S* declaration ]* '}' S* |
|
1592 * ; |
|
1593 */ |
|
1594 var tokenStream = this._tokenStream, |
|
1595 line, |
|
1596 col; |
|
1597 |
|
1598 //look for @page |
|
1599 tokenStream.mustMatch(Tokens.FONT_FACE_SYM); |
|
1600 line = tokenStream.token().startLine; |
|
1601 col = tokenStream.token().startCol; |
|
1602 |
|
1603 this._readWhitespace(); |
|
1604 |
|
1605 this.fire({ |
|
1606 type: "startfontface", |
|
1607 line: line, |
|
1608 col: col |
|
1609 }); |
|
1610 |
|
1611 this._readDeclarations(true); |
|
1612 |
|
1613 this.fire({ |
|
1614 type: "endfontface", |
|
1615 line: line, |
|
1616 col: col |
|
1617 }); |
|
1618 }, |
|
1619 |
|
1620 _viewport: function() { |
|
1621 /* |
|
1622 * viewport |
|
1623 * : VIEWPORT_SYM S* |
|
1624 * '{' S* declaration? [ ';' S* declaration? ]* '}' S* |
|
1625 * ; |
|
1626 */ |
|
1627 var tokenStream = this._tokenStream, |
|
1628 line, |
|
1629 col; |
|
1630 |
|
1631 tokenStream.mustMatch(Tokens.VIEWPORT_SYM); |
|
1632 line = tokenStream.token().startLine; |
|
1633 col = tokenStream.token().startCol; |
|
1634 |
|
1635 this._readWhitespace(); |
|
1636 |
|
1637 this.fire({ |
|
1638 type: "startviewport", |
|
1639 line: line, |
|
1640 col: col |
|
1641 }); |
|
1642 |
|
1643 this._readDeclarations(true); |
|
1644 |
|
1645 this.fire({ |
|
1646 type: "endviewport", |
|
1647 line: line, |
|
1648 col: col |
|
1649 }); |
|
1650 |
|
1651 }, |
|
1652 |
|
1653 _document: function() { |
|
1654 /* |
|
1655 * document |
|
1656 * : DOCUMENT_SYM S* |
|
1657 * _document_function [ ',' S* _document_function ]* S* |
|
1658 * '{' S* ruleset* '}' |
|
1659 * ; |
|
1660 */ |
|
1661 |
|
1662 var tokenStream = this._tokenStream, |
|
1663 token, |
|
1664 functions = [], |
|
1665 prefix = ""; |
|
1666 |
|
1667 tokenStream.mustMatch(Tokens.DOCUMENT_SYM); |
|
1668 token = tokenStream.token(); |
|
1669 if (/^@\-([^\-]+)\-/.test(token.value)) { |
|
1670 prefix = RegExp.$1; |
|
1671 } |
|
1672 |
|
1673 this._readWhitespace(); |
|
1674 functions.push(this._document_function()); |
|
1675 |
|
1676 while (tokenStream.match(Tokens.COMMA)) { |
|
1677 this._readWhitespace(); |
|
1678 functions.push(this._document_function()); |
|
1679 } |
|
1680 |
|
1681 tokenStream.mustMatch(Tokens.LBRACE); |
|
1682 this._readWhitespace(); |
|
1683 |
|
1684 this.fire({ |
|
1685 type: "startdocument", |
|
1686 functions: functions, |
|
1687 prefix: prefix, |
|
1688 line: token.startLine, |
|
1689 col: token.startCol |
|
1690 }); |
|
1691 |
|
1692 var ok = true; |
|
1693 while (ok) { |
|
1694 switch (tokenStream.peek()) { |
|
1695 case Tokens.PAGE_SYM: |
|
1696 this._page(); |
|
1697 break; |
|
1698 case Tokens.FONT_FACE_SYM: |
|
1699 this._font_face(); |
|
1700 break; |
|
1701 case Tokens.VIEWPORT_SYM: |
|
1702 this._viewport(); |
|
1703 break; |
|
1704 case Tokens.MEDIA_SYM: |
|
1705 this._media(); |
|
1706 break; |
|
1707 case Tokens.KEYFRAMES_SYM: |
|
1708 this._keyframes(); |
|
1709 break; |
|
1710 case Tokens.DOCUMENT_SYM: |
|
1711 this._document(); |
|
1712 break; |
|
1713 default: |
|
1714 ok = Boolean(this._ruleset()); |
|
1715 } |
|
1716 } |
|
1717 |
|
1718 tokenStream.mustMatch(Tokens.RBRACE); |
|
1719 token = tokenStream.token(); |
|
1720 this._readWhitespace(); |
|
1721 |
|
1722 this.fire({ |
|
1723 type: "enddocument", |
|
1724 functions: functions, |
|
1725 prefix: prefix, |
|
1726 line: token.startLine, |
|
1727 col: token.startCol |
|
1728 }); |
|
1729 }, |
|
1730 |
|
1731 _document_function: function() { |
|
1732 /* |
|
1733 * document_function |
|
1734 * : function | URI S* |
|
1735 * ; |
|
1736 */ |
|
1737 |
|
1738 var tokenStream = this._tokenStream, |
|
1739 value; |
|
1740 |
|
1741 if (tokenStream.match(Tokens.URI)) { |
|
1742 value = tokenStream.token().value; |
|
1743 this._readWhitespace(); |
|
1744 } else { |
|
1745 value = this._function(); |
|
1746 } |
|
1747 |
|
1748 return value; |
|
1749 }, |
|
1750 |
|
1751 _operator: function(inFunction) { |
|
1752 |
|
1753 /* |
|
1754 * operator (outside function) |
|
1755 * : '/' S* | ',' S* | /( empty )/ |
|
1756 * operator (inside function) |
|
1757 * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/ |
|
1758 * ; |
|
1759 */ |
|
1760 |
|
1761 var tokenStream = this._tokenStream, |
|
1762 token = null; |
|
1763 |
|
1764 if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) || |
|
1765 (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) { |
|
1766 token = tokenStream.token(); |
|
1767 this._readWhitespace(); |
|
1768 } |
|
1769 return token ? PropertyValuePart.fromToken(token) : null; |
|
1770 |
|
1771 }, |
|
1772 |
|
1773 _combinator: function() { |
|
1774 |
|
1775 /* |
|
1776 * combinator |
|
1777 * : PLUS S* | GREATER S* | TILDE S* | S+ |
|
1778 * ; |
|
1779 */ |
|
1780 |
|
1781 var tokenStream = this._tokenStream, |
|
1782 value = null, |
|
1783 token; |
|
1784 |
|
1785 if (tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])) { |
|
1786 token = tokenStream.token(); |
|
1787 value = new Combinator(token.value, token.startLine, token.startCol); |
|
1788 this._readWhitespace(); |
|
1789 } |
|
1790 |
|
1791 return value; |
|
1792 }, |
|
1793 |
|
1794 _unary_operator: function() { |
|
1795 |
|
1796 /* |
|
1797 * unary_operator |
|
1798 * : '-' | '+' |
|
1799 * ; |
|
1800 */ |
|
1801 |
|
1802 var tokenStream = this._tokenStream; |
|
1803 |
|
1804 if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])) { |
|
1805 return tokenStream.token().value; |
|
1806 } else { |
|
1807 return null; |
|
1808 } |
|
1809 }, |
|
1810 |
|
1811 _property: function() { |
|
1812 |
|
1813 /* |
|
1814 * property |
|
1815 * : IDENT S* |
|
1816 * ; |
|
1817 */ |
|
1818 |
|
1819 var tokenStream = this._tokenStream, |
|
1820 value = null, |
|
1821 hack = null, |
|
1822 tokenValue, |
|
1823 token, |
|
1824 line, |
|
1825 col; |
|
1826 |
|
1827 //check for star hack - throws error if not allowed |
|
1828 if (tokenStream.peek() === Tokens.STAR && this.options.starHack) { |
|
1829 tokenStream.get(); |
|
1830 token = tokenStream.token(); |
|
1831 hack = token.value; |
|
1832 line = token.startLine; |
|
1833 col = token.startCol; |
|
1834 } |
|
1835 |
|
1836 if (tokenStream.match(Tokens.IDENT)) { |
|
1837 token = tokenStream.token(); |
|
1838 tokenValue = token.value; |
|
1839 |
|
1840 //check for underscore hack - no error if not allowed because it's valid CSS syntax |
|
1841 if (tokenValue.charAt(0) === "_" && this.options.underscoreHack) { |
|
1842 hack = "_"; |
|
1843 tokenValue = tokenValue.substring(1); |
|
1844 } |
|
1845 |
|
1846 value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); |
|
1847 this._readWhitespace(); |
|
1848 } |
|
1849 |
|
1850 return value; |
|
1851 }, |
|
1852 |
|
1853 //Augmented with CSS3 Selectors |
|
1854 _ruleset: function() { |
|
1855 /* |
|
1856 * ruleset |
|
1857 * : selectors_group |
|
1858 * '{' S* declaration? [ ';' S* declaration? ]* '}' S* |
|
1859 * ; |
|
1860 */ |
|
1861 |
|
1862 var tokenStream = this._tokenStream, |
|
1863 tt, |
|
1864 selectors; |
|
1865 |
|
1866 |
|
1867 /* |
|
1868 * Error Recovery: If even a single selector fails to parse, |
|
1869 * then the entire ruleset should be thrown away. |
|
1870 */ |
|
1871 try { |
|
1872 selectors = this._selectors_group(); |
|
1873 } catch (ex) { |
|
1874 if (ex instanceof SyntaxError && !this.options.strict) { |
|
1875 |
|
1876 //fire error event |
|
1877 this.fire({ |
|
1878 type: "error", |
|
1879 error: ex, |
|
1880 message: ex.message, |
|
1881 line: ex.line, |
|
1882 col: ex.col |
|
1883 }); |
|
1884 |
|
1885 //skip over everything until closing brace |
|
1886 tt = tokenStream.advance([Tokens.RBRACE]); |
|
1887 if (tt === Tokens.RBRACE) { |
|
1888 //if there's a right brace, the rule is finished so don't do anything |
|
1889 } else { |
|
1890 //otherwise, rethrow the error because it wasn't handled properly |
|
1891 throw ex; |
|
1892 } |
|
1893 |
|
1894 } else { |
|
1895 //not a syntax error, rethrow it |
|
1896 throw ex; |
|
1897 } |
|
1898 |
|
1899 //trigger parser to continue |
|
1900 return true; |
|
1901 } |
|
1902 |
|
1903 //if it got here, all selectors parsed |
|
1904 if (selectors) { |
|
1905 |
|
1906 this.fire({ |
|
1907 type: "startrule", |
|
1908 selectors: selectors, |
|
1909 line: selectors[0].line, |
|
1910 col: selectors[0].col |
|
1911 }); |
|
1912 |
|
1913 this._readDeclarations(true); |
|
1914 |
|
1915 this.fire({ |
|
1916 type: "endrule", |
|
1917 selectors: selectors, |
|
1918 line: selectors[0].line, |
|
1919 col: selectors[0].col |
|
1920 }); |
|
1921 |
|
1922 } |
|
1923 |
|
1924 return selectors; |
|
1925 |
|
1926 }, |
|
1927 |
|
1928 //CSS3 Selectors |
|
1929 _selectors_group: function() { |
|
1930 |
|
1931 /* |
|
1932 * selectors_group |
|
1933 * : selector [ COMMA S* selector ]* |
|
1934 * ; |
|
1935 */ |
|
1936 var tokenStream = this._tokenStream, |
|
1937 selectors = [], |
|
1938 selector; |
|
1939 |
|
1940 selector = this._selector(); |
|
1941 if (selector !== null) { |
|
1942 |
|
1943 selectors.push(selector); |
|
1944 while (tokenStream.match(Tokens.COMMA)) { |
|
1945 this._readWhitespace(); |
|
1946 selector = this._selector(); |
|
1947 if (selector !== null) { |
|
1948 selectors.push(selector); |
|
1949 } else { |
|
1950 this._unexpectedToken(tokenStream.LT(1)); |
|
1951 } |
|
1952 } |
|
1953 } |
|
1954 |
|
1955 return selectors.length ? selectors : null; |
|
1956 }, |
|
1957 |
|
1958 //CSS3 Selectors |
|
1959 _selector: function() { |
|
1960 /* |
|
1961 * selector |
|
1962 * : simple_selector_sequence [ combinator simple_selector_sequence ]* |
|
1963 * ; |
|
1964 */ |
|
1965 |
|
1966 var tokenStream = this._tokenStream, |
|
1967 selector = [], |
|
1968 nextSelector = null, |
|
1969 combinator = null, |
|
1970 ws = null; |
|
1971 |
|
1972 //if there's no simple selector, then there's no selector |
|
1973 nextSelector = this._simple_selector_sequence(); |
|
1974 if (nextSelector === null) { |
|
1975 return null; |
|
1976 } |
|
1977 |
|
1978 selector.push(nextSelector); |
|
1979 |
|
1980 do { |
|
1981 |
|
1982 //look for a combinator |
|
1983 combinator = this._combinator(); |
|
1984 |
|
1985 if (combinator !== null) { |
|
1986 selector.push(combinator); |
|
1987 nextSelector = this._simple_selector_sequence(); |
|
1988 |
|
1989 //there must be a next selector |
|
1990 if (nextSelector === null) { |
|
1991 this._unexpectedToken(tokenStream.LT(1)); |
|
1992 } else { |
|
1993 |
|
1994 //nextSelector is an instance of SelectorPart |
|
1995 selector.push(nextSelector); |
|
1996 } |
|
1997 } else { |
|
1998 |
|
1999 //if there's not whitespace, we're done |
|
2000 if (this._readWhitespace()) { |
|
2001 |
|
2002 //add whitespace separator |
|
2003 ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol); |
|
2004 |
|
2005 //combinator is not required |
|
2006 combinator = this._combinator(); |
|
2007 |
|
2008 //selector is required if there's a combinator |
|
2009 nextSelector = this._simple_selector_sequence(); |
|
2010 if (nextSelector === null) { |
|
2011 if (combinator !== null) { |
|
2012 this._unexpectedToken(tokenStream.LT(1)); |
|
2013 } |
|
2014 } else { |
|
2015 |
|
2016 if (combinator !== null) { |
|
2017 selector.push(combinator); |
|
2018 } else { |
|
2019 selector.push(ws); |
|
2020 } |
|
2021 |
|
2022 selector.push(nextSelector); |
|
2023 } |
|
2024 } else { |
|
2025 break; |
|
2026 } |
|
2027 |
|
2028 } |
|
2029 } while (true); |
|
2030 |
|
2031 return new Selector(selector, selector[0].line, selector[0].col); |
|
2032 }, |
|
2033 |
|
2034 //CSS3 Selectors |
|
2035 _simple_selector_sequence: function() { |
|
2036 /* |
|
2037 * simple_selector_sequence |
|
2038 * : [ type_selector | universal ] |
|
2039 * [ HASH | class | attrib | pseudo | negation ]* |
|
2040 * | [ HASH | class | attrib | pseudo | negation ]+ |
|
2041 * ; |
|
2042 */ |
|
2043 |
|
2044 var tokenStream = this._tokenStream, |
|
2045 |
|
2046 //parts of a simple selector |
|
2047 elementName = null, |
|
2048 modifiers = [], |
|
2049 |
|
2050 //complete selector text |
|
2051 selectorText= "", |
|
2052 |
|
2053 //the different parts after the element name to search for |
|
2054 components = [ |
|
2055 //HASH |
|
2056 function() { |
|
2057 return tokenStream.match(Tokens.HASH) ? |
|
2058 new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : |
|
2059 null; |
|
2060 }, |
|
2061 this._class, |
|
2062 this._attrib, |
|
2063 this._pseudo, |
|
2064 this._negation |
|
2065 ], |
|
2066 i = 0, |
|
2067 len = components.length, |
|
2068 component = null, |
|
2069 line, |
|
2070 col; |
|
2071 |
|
2072 |
|
2073 //get starting line and column for the selector |
|
2074 line = tokenStream.LT(1).startLine; |
|
2075 col = tokenStream.LT(1).startCol; |
|
2076 |
|
2077 elementName = this._type_selector(); |
|
2078 if (!elementName) { |
|
2079 elementName = this._universal(); |
|
2080 } |
|
2081 |
|
2082 if (elementName !== null) { |
|
2083 selectorText += elementName; |
|
2084 } |
|
2085 |
|
2086 while (true) { |
|
2087 |
|
2088 //whitespace means we're done |
|
2089 if (tokenStream.peek() === Tokens.S) { |
|
2090 break; |
|
2091 } |
|
2092 |
|
2093 //check for each component |
|
2094 while (i < len && component === null) { |
|
2095 component = components[i++].call(this); |
|
2096 } |
|
2097 |
|
2098 if (component === null) { |
|
2099 |
|
2100 //we don't have a selector |
|
2101 if (selectorText === "") { |
|
2102 return null; |
|
2103 } else { |
|
2104 break; |
|
2105 } |
|
2106 } else { |
|
2107 i = 0; |
|
2108 modifiers.push(component); |
|
2109 selectorText += component.toString(); |
|
2110 component = null; |
|
2111 } |
|
2112 } |
|
2113 |
|
2114 |
|
2115 return selectorText !== "" ? |
|
2116 new SelectorPart(elementName, modifiers, selectorText, line, col) : |
|
2117 null; |
|
2118 }, |
|
2119 |
|
2120 //CSS3 Selectors |
|
2121 _type_selector: function() { |
|
2122 /* |
|
2123 * type_selector |
|
2124 * : [ namespace_prefix ]? element_name |
|
2125 * ; |
|
2126 */ |
|
2127 |
|
2128 var tokenStream = this._tokenStream, |
|
2129 ns = this._namespace_prefix(), |
|
2130 elementName = this._element_name(); |
|
2131 |
|
2132 if (!elementName) { |
|
2133 /* |
|
2134 * Need to back out the namespace that was read due to both |
|
2135 * type_selector and universal reading namespace_prefix |
|
2136 * first. Kind of hacky, but only way I can figure out |
|
2137 * right now how to not change the grammar. |
|
2138 */ |
|
2139 if (ns) { |
|
2140 tokenStream.unget(); |
|
2141 if (ns.length > 1) { |
|
2142 tokenStream.unget(); |
|
2143 } |
|
2144 } |
|
2145 |
|
2146 return null; |
|
2147 } else { |
|
2148 if (ns) { |
|
2149 elementName.text = ns + elementName.text; |
|
2150 elementName.col -= ns.length; |
|
2151 } |
|
2152 return elementName; |
|
2153 } |
|
2154 }, |
|
2155 |
|
2156 //CSS3 Selectors |
|
2157 _class: function() { |
|
2158 /* |
|
2159 * class |
|
2160 * : '.' IDENT |
|
2161 * ; |
|
2162 */ |
|
2163 |
|
2164 var tokenStream = this._tokenStream, |
|
2165 token; |
|
2166 |
|
2167 if (tokenStream.match(Tokens.DOT)) { |
|
2168 tokenStream.mustMatch(Tokens.IDENT); |
|
2169 token = tokenStream.token(); |
|
2170 return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); |
|
2171 } else { |
|
2172 return null; |
|
2173 } |
|
2174 |
|
2175 }, |
|
2176 |
|
2177 //CSS3 Selectors |
|
2178 _element_name: function() { |
|
2179 /* |
|
2180 * element_name |
|
2181 * : IDENT |
|
2182 * ; |
|
2183 */ |
|
2184 |
|
2185 var tokenStream = this._tokenStream, |
|
2186 token; |
|
2187 |
|
2188 if (tokenStream.match(Tokens.IDENT)) { |
|
2189 token = tokenStream.token(); |
|
2190 return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); |
|
2191 |
|
2192 } else { |
|
2193 return null; |
|
2194 } |
|
2195 }, |
|
2196 |
|
2197 //CSS3 Selectors |
|
2198 _namespace_prefix: function() { |
|
2199 /* |
|
2200 * namespace_prefix |
|
2201 * : [ IDENT | '*' ]? '|' |
|
2202 * ; |
|
2203 */ |
|
2204 var tokenStream = this._tokenStream, |
|
2205 value = ""; |
|
2206 |
|
2207 //verify that this is a namespace prefix |
|
2208 if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE) { |
|
2209 |
|
2210 if (tokenStream.match([Tokens.IDENT, Tokens.STAR])) { |
|
2211 value += tokenStream.token().value; |
|
2212 } |
|
2213 |
|
2214 tokenStream.mustMatch(Tokens.PIPE); |
|
2215 value += "|"; |
|
2216 |
|
2217 } |
|
2218 |
|
2219 return value.length ? value : null; |
|
2220 }, |
|
2221 |
|
2222 //CSS3 Selectors |
|
2223 _universal: function() { |
|
2224 /* |
|
2225 * universal |
|
2226 * : [ namespace_prefix ]? '*' |
|
2227 * ; |
|
2228 */ |
|
2229 var tokenStream = this._tokenStream, |
|
2230 value = "", |
|
2231 ns; |
|
2232 |
|
2233 ns = this._namespace_prefix(); |
|
2234 if (ns) { |
|
2235 value += ns; |
|
2236 } |
|
2237 |
|
2238 if (tokenStream.match(Tokens.STAR)) { |
|
2239 value += "*"; |
|
2240 } |
|
2241 |
|
2242 return value.length ? value : null; |
|
2243 |
|
2244 }, |
|
2245 |
|
2246 //CSS3 Selectors |
|
2247 _attrib: function() { |
|
2248 /* |
|
2249 * attrib |
|
2250 * : '[' S* [ namespace_prefix ]? IDENT S* |
|
2251 * [ [ PREFIXMATCH | |
|
2252 * SUFFIXMATCH | |
|
2253 * SUBSTRINGMATCH | |
|
2254 * '=' | |
|
2255 * INCLUDES | |
|
2256 * DASHMATCH ] S* [ IDENT | STRING ] S* |
|
2257 * ]? ']' |
|
2258 * ; |
|
2259 */ |
|
2260 |
|
2261 var tokenStream = this._tokenStream, |
|
2262 value = null, |
|
2263 ns, |
|
2264 token; |
|
2265 |
|
2266 if (tokenStream.match(Tokens.LBRACKET)) { |
|
2267 token = tokenStream.token(); |
|
2268 value = token.value; |
|
2269 value += this._readWhitespace(); |
|
2270 |
|
2271 ns = this._namespace_prefix(); |
|
2272 |
|
2273 if (ns) { |
|
2274 value += ns; |
|
2275 } |
|
2276 |
|
2277 tokenStream.mustMatch(Tokens.IDENT); |
|
2278 value += tokenStream.token().value; |
|
2279 value += this._readWhitespace(); |
|
2280 |
|
2281 if (tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH, |
|
2282 Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])) { |
|
2283 |
|
2284 value += tokenStream.token().value; |
|
2285 value += this._readWhitespace(); |
|
2286 |
|
2287 tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); |
|
2288 value += tokenStream.token().value; |
|
2289 value += this._readWhitespace(); |
|
2290 } |
|
2291 |
|
2292 tokenStream.mustMatch(Tokens.RBRACKET); |
|
2293 |
|
2294 return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); |
|
2295 } else { |
|
2296 return null; |
|
2297 } |
|
2298 }, |
|
2299 |
|
2300 //CSS3 Selectors |
|
2301 _pseudo: function() { |
|
2302 |
|
2303 /* |
|
2304 * pseudo |
|
2305 * : ':' ':'? [ IDENT | functional_pseudo ] |
|
2306 * ; |
|
2307 */ |
|
2308 |
|
2309 var tokenStream = this._tokenStream, |
|
2310 pseudo = null, |
|
2311 colons = ":", |
|
2312 line, |
|
2313 col; |
|
2314 |
|
2315 if (tokenStream.match(Tokens.COLON)) { |
|
2316 |
|
2317 if (tokenStream.match(Tokens.COLON)) { |
|
2318 colons += ":"; |
|
2319 } |
|
2320 |
|
2321 if (tokenStream.match(Tokens.IDENT)) { |
|
2322 pseudo = tokenStream.token().value; |
|
2323 line = tokenStream.token().startLine; |
|
2324 col = tokenStream.token().startCol - colons.length; |
|
2325 } else if (tokenStream.peek() === Tokens.FUNCTION) { |
|
2326 line = tokenStream.LT(1).startLine; |
|
2327 col = tokenStream.LT(1).startCol - colons.length; |
|
2328 pseudo = this._functional_pseudo(); |
|
2329 } |
|
2330 |
|
2331 if (pseudo) { |
|
2332 pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); |
|
2333 } else { |
|
2334 var startLine = tokenStream.LT(1).startLine, |
|
2335 startCol = tokenStream.LT(0).startCol; |
|
2336 throw new SyntaxError("Expected a `FUNCTION` or `IDENT` after colon at line " + startLine + ", col " + startCol + ".", startLine, startCol); |
|
2337 } |
|
2338 } |
|
2339 |
|
2340 return pseudo; |
|
2341 }, |
|
2342 |
|
2343 //CSS3 Selectors |
|
2344 _functional_pseudo: function() { |
|
2345 /* |
|
2346 * functional_pseudo |
|
2347 * : FUNCTION S* expression ')' |
|
2348 * ; |
|
2349 */ |
|
2350 |
|
2351 var tokenStream = this._tokenStream, |
|
2352 value = null; |
|
2353 |
|
2354 if (tokenStream.match(Tokens.FUNCTION)) { |
|
2355 value = tokenStream.token().value; |
|
2356 value += this._readWhitespace(); |
|
2357 value += this._expression(); |
|
2358 tokenStream.mustMatch(Tokens.RPAREN); |
|
2359 value += ")"; |
|
2360 } |
|
2361 |
|
2362 return value; |
|
2363 }, |
|
2364 |
|
2365 //CSS3 Selectors |
|
2366 _expression: function() { |
|
2367 /* |
|
2368 * expression |
|
2369 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ |
|
2370 * ; |
|
2371 */ |
|
2372 |
|
2373 var tokenStream = this._tokenStream, |
|
2374 value = ""; |
|
2375 |
|
2376 while (tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, |
|
2377 Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, |
|
2378 Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, |
|
2379 Tokens.RESOLUTION, Tokens.SLASH])) { |
|
2380 |
|
2381 value += tokenStream.token().value; |
|
2382 value += this._readWhitespace(); |
|
2383 } |
|
2384 |
|
2385 return value.length ? value : null; |
|
2386 |
|
2387 }, |
|
2388 |
|
2389 //CSS3 Selectors |
|
2390 _negation: function() { |
|
2391 /* |
|
2392 * negation |
|
2393 * : NOT S* negation_arg S* ')' |
|
2394 * ; |
|
2395 */ |
|
2396 |
|
2397 var tokenStream = this._tokenStream, |
|
2398 line, |
|
2399 col, |
|
2400 value = "", |
|
2401 arg, |
|
2402 subpart = null; |
|
2403 |
|
2404 if (tokenStream.match(Tokens.NOT)) { |
|
2405 value = tokenStream.token().value; |
|
2406 line = tokenStream.token().startLine; |
|
2407 col = tokenStream.token().startCol; |
|
2408 value += this._readWhitespace(); |
|
2409 arg = this._negation_arg(); |
|
2410 value += arg; |
|
2411 value += this._readWhitespace(); |
|
2412 tokenStream.match(Tokens.RPAREN); |
|
2413 value += tokenStream.token().value; |
|
2414 |
|
2415 subpart = new SelectorSubPart(value, "not", line, col); |
|
2416 subpart.args.push(arg); |
|
2417 } |
|
2418 |
|
2419 return subpart; |
|
2420 }, |
|
2421 |
|
2422 //CSS3 Selectors |
|
2423 _negation_arg: function() { |
|
2424 /* |
|
2425 * negation_arg |
|
2426 * : type_selector | universal | HASH | class | attrib | pseudo |
|
2427 * ; |
|
2428 */ |
|
2429 |
|
2430 var tokenStream = this._tokenStream, |
|
2431 args = [ |
|
2432 this._type_selector, |
|
2433 this._universal, |
|
2434 function() { |
|
2435 return tokenStream.match(Tokens.HASH) ? |
|
2436 new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : |
|
2437 null; |
|
2438 }, |
|
2439 this._class, |
|
2440 this._attrib, |
|
2441 this._pseudo |
|
2442 ], |
|
2443 arg = null, |
|
2444 i = 0, |
|
2445 len = args.length, |
|
2446 line, |
|
2447 col, |
|
2448 part; |
|
2449 |
|
2450 line = tokenStream.LT(1).startLine; |
|
2451 col = tokenStream.LT(1).startCol; |
|
2452 |
|
2453 while (i < len && arg === null) { |
|
2454 |
|
2455 arg = args[i].call(this); |
|
2456 i++; |
|
2457 } |
|
2458 |
|
2459 //must be a negation arg |
|
2460 if (arg === null) { |
|
2461 this._unexpectedToken(tokenStream.LT(1)); |
|
2462 } |
|
2463 |
|
2464 //it's an element name |
|
2465 if (arg.type === "elementName") { |
|
2466 part = new SelectorPart(arg, [], arg.toString(), line, col); |
|
2467 } else { |
|
2468 part = new SelectorPart(null, [arg], arg.toString(), line, col); |
|
2469 } |
|
2470 |
|
2471 return part; |
|
2472 }, |
|
2473 |
|
2474 _declaration: function() { |
|
2475 |
|
2476 /* |
|
2477 * declaration |
|
2478 * : property ':' S* expr prio? |
|
2479 * | /( empty )/ |
|
2480 * ; |
|
2481 */ |
|
2482 |
|
2483 var tokenStream = this._tokenStream, |
|
2484 property = null, |
|
2485 expr = null, |
|
2486 prio = null, |
|
2487 invalid = null, |
|
2488 propertyName= ""; |
|
2489 |
|
2490 property = this._property(); |
|
2491 if (property !== null) { |
|
2492 |
|
2493 tokenStream.mustMatch(Tokens.COLON); |
|
2494 this._readWhitespace(); |
|
2495 |
|
2496 expr = this._expr(); |
|
2497 |
|
2498 //if there's no parts for the value, it's an error |
|
2499 if (!expr || expr.length === 0) { |
|
2500 this._unexpectedToken(tokenStream.LT(1)); |
|
2501 } |
|
2502 |
|
2503 prio = this._prio(); |
|
2504 |
|
2505 /* |
|
2506 * If hacks should be allowed, then only check the root |
|
2507 * property. If hacks should not be allowed, treat |
|
2508 * _property or *property as invalid properties. |
|
2509 */ |
|
2510 propertyName = property.toString(); |
|
2511 if (this.options.starHack && property.hack === "*" || |
|
2512 this.options.underscoreHack && property.hack === "_") { |
|
2513 |
|
2514 propertyName = property.text; |
|
2515 } |
|
2516 |
|
2517 try { |
|
2518 this._validateProperty(propertyName, expr); |
|
2519 } catch (ex) { |
|
2520 invalid = ex; |
|
2521 } |
|
2522 |
|
2523 this.fire({ |
|
2524 type: "property", |
|
2525 property: property, |
|
2526 value: expr, |
|
2527 important: prio, |
|
2528 line: property.line, |
|
2529 col: property.col, |
|
2530 invalid: invalid |
|
2531 }); |
|
2532 |
|
2533 return true; |
|
2534 } else { |
|
2535 return false; |
|
2536 } |
|
2537 }, |
|
2538 |
|
2539 _prio: function() { |
|
2540 /* |
|
2541 * prio |
|
2542 * : IMPORTANT_SYM S* |
|
2543 * ; |
|
2544 */ |
|
2545 |
|
2546 var tokenStream = this._tokenStream, |
|
2547 result = tokenStream.match(Tokens.IMPORTANT_SYM); |
|
2548 |
|
2549 this._readWhitespace(); |
|
2550 return result; |
|
2551 }, |
|
2552 |
|
2553 _expr: function(inFunction) { |
|
2554 /* |
|
2555 * expr |
|
2556 * : term [ operator term ]* |
|
2557 * ; |
|
2558 */ |
|
2559 |
|
2560 var values = [], |
|
2561 //valueParts = [], |
|
2562 value = null, |
|
2563 operator = null; |
|
2564 |
|
2565 value = this._term(inFunction); |
|
2566 if (value !== null) { |
|
2567 |
|
2568 values.push(value); |
|
2569 |
|
2570 do { |
|
2571 operator = this._operator(inFunction); |
|
2572 |
|
2573 //if there's an operator, keep building up the value parts |
|
2574 if (operator) { |
|
2575 values.push(operator); |
|
2576 } /*else { |
|
2577 //if there's not an operator, you have a full value |
|
2578 values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); |
|
2579 valueParts = []; |
|
2580 }*/ |
|
2581 |
|
2582 value = this._term(inFunction); |
|
2583 |
|
2584 if (value === null) { |
|
2585 break; |
|
2586 } else { |
|
2587 values.push(value); |
|
2588 } |
|
2589 } while (true); |
|
2590 } |
|
2591 |
|
2592 //cleanup |
|
2593 /*if (valueParts.length) { |
|
2594 values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); |
|
2595 }*/ |
|
2596 |
|
2597 return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null; |
|
2598 }, |
|
2599 |
|
2600 _term: function(inFunction) { |
|
2601 |
|
2602 /* |
|
2603 * term |
|
2604 * : unary_operator? |
|
2605 * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | |
|
2606 * TIME S* | FREQ S* | function | ie_function ] |
|
2607 * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor |
|
2608 * ; |
|
2609 */ |
|
2610 |
|
2611 var tokenStream = this._tokenStream, |
|
2612 unary = null, |
|
2613 value = null, |
|
2614 endChar = null, |
|
2615 part = null, |
|
2616 token, |
|
2617 line, |
|
2618 col; |
|
2619 |
|
2620 //returns the operator or null |
|
2621 unary = this._unary_operator(); |
|
2622 if (unary !== null) { |
|
2623 line = tokenStream.token().startLine; |
|
2624 col = tokenStream.token().startCol; |
|
2625 } |
|
2626 |
|
2627 //exception for IE filters |
|
2628 if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters) { |
|
2629 |
|
2630 value = this._ie_function(); |
|
2631 if (unary === null) { |
|
2632 line = tokenStream.token().startLine; |
|
2633 col = tokenStream.token().startCol; |
|
2634 } |
|
2635 |
|
2636 //see if it's a simple block |
|
2637 } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) { |
|
2638 |
|
2639 token = tokenStream.token(); |
|
2640 endChar = token.endChar; |
|
2641 value = token.value + this._expr(inFunction).text; |
|
2642 if (unary === null) { |
|
2643 line = tokenStream.token().startLine; |
|
2644 col = tokenStream.token().startCol; |
|
2645 } |
|
2646 tokenStream.mustMatch(Tokens.type(endChar)); |
|
2647 value += endChar; |
|
2648 this._readWhitespace(); |
|
2649 |
|
2650 //see if there's a simple match |
|
2651 } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, |
|
2652 Tokens.ANGLE, Tokens.TIME, |
|
2653 Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) { |
|
2654 |
|
2655 value = tokenStream.token().value; |
|
2656 if (unary === null) { |
|
2657 line = tokenStream.token().startLine; |
|
2658 col = tokenStream.token().startCol; |
|
2659 // Correct potentially-inaccurate IDENT parsing in |
|
2660 // PropertyValuePart constructor. |
|
2661 part = PropertyValuePart.fromToken(tokenStream.token()); |
|
2662 } |
|
2663 this._readWhitespace(); |
|
2664 } else { |
|
2665 |
|
2666 //see if it's a color |
|
2667 token = this._hexcolor(); |
|
2668 if (token === null) { |
|
2669 |
|
2670 //if there's no unary, get the start of the next token for line/col info |
|
2671 if (unary === null) { |
|
2672 line = tokenStream.LT(1).startLine; |
|
2673 col = tokenStream.LT(1).startCol; |
|
2674 } |
|
2675 |
|
2676 //has to be a function |
|
2677 if (value === null) { |
|
2678 |
|
2679 /* |
|
2680 * This checks for alpha(opacity=0) style of IE |
|
2681 * functions. IE_FUNCTION only presents progid: style. |
|
2682 */ |
|
2683 if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters) { |
|
2684 value = this._ie_function(); |
|
2685 } else { |
|
2686 value = this._function(); |
|
2687 } |
|
2688 } |
|
2689 |
|
2690 /*if (value === null) { |
|
2691 return null; |
|
2692 //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); |
|
2693 }*/ |
|
2694 |
|
2695 } else { |
|
2696 value = token.value; |
|
2697 if (unary === null) { |
|
2698 line = token.startLine; |
|
2699 col = token.startCol; |
|
2700 } |
|
2701 } |
|
2702 |
|
2703 } |
|
2704 |
|
2705 return part !== null ? part : value !== null ? |
|
2706 new PropertyValuePart(unary !== null ? unary + value : value, line, col) : |
|
2707 null; |
|
2708 |
|
2709 }, |
|
2710 |
|
2711 _function: function() { |
|
2712 |
|
2713 /* |
|
2714 * function |
|
2715 * : FUNCTION S* expr ')' S* |
|
2716 * ; |
|
2717 */ |
|
2718 |
|
2719 var tokenStream = this._tokenStream, |
|
2720 functionText = null, |
|
2721 expr = null, |
|
2722 lt; |
|
2723 |
|
2724 if (tokenStream.match(Tokens.FUNCTION)) { |
|
2725 functionText = tokenStream.token().value; |
|
2726 this._readWhitespace(); |
|
2727 expr = this._expr(true); |
|
2728 functionText += expr; |
|
2729 |
|
2730 //START: Horrible hack in case it's an IE filter |
|
2731 if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS) { |
|
2732 do { |
|
2733 |
|
2734 if (this._readWhitespace()) { |
|
2735 functionText += tokenStream.token().value; |
|
2736 } |
|
2737 |
|
2738 //might be second time in the loop |
|
2739 if (tokenStream.LA(0) === Tokens.COMMA) { |
|
2740 functionText += tokenStream.token().value; |
|
2741 } |
|
2742 |
|
2743 tokenStream.match(Tokens.IDENT); |
|
2744 functionText += tokenStream.token().value; |
|
2745 |
|
2746 tokenStream.match(Tokens.EQUALS); |
|
2747 functionText += tokenStream.token().value; |
|
2748 |
|
2749 //functionText += this._term(); |
|
2750 lt = tokenStream.peek(); |
|
2751 while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { |
|
2752 tokenStream.get(); |
|
2753 functionText += tokenStream.token().value; |
|
2754 lt = tokenStream.peek(); |
|
2755 } |
|
2756 } while (tokenStream.match([Tokens.COMMA, Tokens.S])); |
|
2757 } |
|
2758 |
|
2759 //END: Horrible Hack |
|
2760 |
|
2761 tokenStream.match(Tokens.RPAREN); |
|
2762 functionText += ")"; |
|
2763 this._readWhitespace(); |
|
2764 } |
|
2765 |
|
2766 return functionText; |
|
2767 }, |
|
2768 |
|
2769 _ie_function: function() { |
|
2770 |
|
2771 /* (My own extension) |
|
2772 * ie_function |
|
2773 * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* |
|
2774 * ; |
|
2775 */ |
|
2776 |
|
2777 var tokenStream = this._tokenStream, |
|
2778 functionText = null, |
|
2779 lt; |
|
2780 |
|
2781 //IE function can begin like a regular function, too |
|
2782 if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) { |
|
2783 functionText = tokenStream.token().value; |
|
2784 |
|
2785 do { |
|
2786 |
|
2787 if (this._readWhitespace()) { |
|
2788 functionText += tokenStream.token().value; |
|
2789 } |
|
2790 |
|
2791 //might be second time in the loop |
|
2792 if (tokenStream.LA(0) === Tokens.COMMA) { |
|
2793 functionText += tokenStream.token().value; |
|
2794 } |
|
2795 |
|
2796 tokenStream.match(Tokens.IDENT); |
|
2797 functionText += tokenStream.token().value; |
|
2798 |
|
2799 tokenStream.match(Tokens.EQUALS); |
|
2800 functionText += tokenStream.token().value; |
|
2801 |
|
2802 //functionText += this._term(); |
|
2803 lt = tokenStream.peek(); |
|
2804 while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { |
|
2805 tokenStream.get(); |
|
2806 functionText += tokenStream.token().value; |
|
2807 lt = tokenStream.peek(); |
|
2808 } |
|
2809 } while (tokenStream.match([Tokens.COMMA, Tokens.S])); |
|
2810 |
|
2811 tokenStream.match(Tokens.RPAREN); |
|
2812 functionText += ")"; |
|
2813 this._readWhitespace(); |
|
2814 } |
|
2815 |
|
2816 return functionText; |
|
2817 }, |
|
2818 |
|
2819 _hexcolor: function() { |
|
2820 /* |
|
2821 * There is a constraint on the color that it must |
|
2822 * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) |
|
2823 * after the "#"; e.g., "#000" is OK, but "#abcd" is not. |
|
2824 * |
|
2825 * hexcolor |
|
2826 * : HASH S* |
|
2827 * ; |
|
2828 */ |
|
2829 |
|
2830 var tokenStream = this._tokenStream, |
|
2831 token = null, |
|
2832 color; |
|
2833 |
|
2834 if (tokenStream.match(Tokens.HASH)) { |
|
2835 |
|
2836 //need to do some validation here |
|
2837 |
|
2838 token = tokenStream.token(); |
|
2839 color = token.value; |
|
2840 if (!/#[a-f0-9]{3,6}/i.test(color)) { |
|
2841 throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); |
|
2842 } |
|
2843 this._readWhitespace(); |
|
2844 } |
|
2845 |
|
2846 return token; |
|
2847 }, |
|
2848 |
|
2849 //----------------------------------------------------------------- |
|
2850 // Animations methods |
|
2851 //----------------------------------------------------------------- |
|
2852 |
|
2853 _keyframes: function() { |
|
2854 |
|
2855 /* |
|
2856 * keyframes: |
|
2857 * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { |
|
2858 * ; |
|
2859 */ |
|
2860 var tokenStream = this._tokenStream, |
|
2861 token, |
|
2862 tt, |
|
2863 name, |
|
2864 prefix = ""; |
|
2865 |
|
2866 tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); |
|
2867 token = tokenStream.token(); |
|
2868 if (/^@\-([^\-]+)\-/.test(token.value)) { |
|
2869 prefix = RegExp.$1; |
|
2870 } |
|
2871 |
|
2872 this._readWhitespace(); |
|
2873 name = this._keyframe_name(); |
|
2874 |
|
2875 this._readWhitespace(); |
|
2876 tokenStream.mustMatch(Tokens.LBRACE); |
|
2877 |
|
2878 this.fire({ |
|
2879 type: "startkeyframes", |
|
2880 name: name, |
|
2881 prefix: prefix, |
|
2882 line: token.startLine, |
|
2883 col: token.startCol |
|
2884 }); |
|
2885 |
|
2886 this._readWhitespace(); |
|
2887 tt = tokenStream.peek(); |
|
2888 |
|
2889 //check for key |
|
2890 while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) { |
|
2891 this._keyframe_rule(); |
|
2892 this._readWhitespace(); |
|
2893 tt = tokenStream.peek(); |
|
2894 } |
|
2895 |
|
2896 this.fire({ |
|
2897 type: "endkeyframes", |
|
2898 name: name, |
|
2899 prefix: prefix, |
|
2900 line: token.startLine, |
|
2901 col: token.startCol |
|
2902 }); |
|
2903 |
|
2904 this._readWhitespace(); |
|
2905 tokenStream.mustMatch(Tokens.RBRACE); |
|
2906 this._readWhitespace(); |
|
2907 |
|
2908 }, |
|
2909 |
|
2910 _keyframe_name: function() { |
|
2911 |
|
2912 /* |
|
2913 * keyframe_name: |
|
2914 * : IDENT |
|
2915 * | STRING |
|
2916 * ; |
|
2917 */ |
|
2918 var tokenStream = this._tokenStream; |
|
2919 |
|
2920 tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); |
|
2921 return SyntaxUnit.fromToken(tokenStream.token()); |
|
2922 }, |
|
2923 |
|
2924 _keyframe_rule: function() { |
|
2925 |
|
2926 /* |
|
2927 * keyframe_rule: |
|
2928 * : key_list S* |
|
2929 * '{' S* declaration [ ';' S* declaration ]* '}' S* |
|
2930 * ; |
|
2931 */ |
|
2932 var keyList = this._key_list(); |
|
2933 |
|
2934 this.fire({ |
|
2935 type: "startkeyframerule", |
|
2936 keys: keyList, |
|
2937 line: keyList[0].line, |
|
2938 col: keyList[0].col |
|
2939 }); |
|
2940 |
|
2941 this._readDeclarations(true); |
|
2942 |
|
2943 this.fire({ |
|
2944 type: "endkeyframerule", |
|
2945 keys: keyList, |
|
2946 line: keyList[0].line, |
|
2947 col: keyList[0].col |
|
2948 }); |
|
2949 |
|
2950 }, |
|
2951 |
|
2952 _key_list: function() { |
|
2953 |
|
2954 /* |
|
2955 * key_list: |
|
2956 * : key [ S* ',' S* key]* |
|
2957 * ; |
|
2958 */ |
|
2959 var tokenStream = this._tokenStream, |
|
2960 keyList = []; |
|
2961 |
|
2962 //must be least one key |
|
2963 keyList.push(this._key()); |
|
2964 |
|
2965 this._readWhitespace(); |
|
2966 |
|
2967 while (tokenStream.match(Tokens.COMMA)) { |
|
2968 this._readWhitespace(); |
|
2969 keyList.push(this._key()); |
|
2970 this._readWhitespace(); |
|
2971 } |
|
2972 |
|
2973 return keyList; |
|
2974 }, |
|
2975 |
|
2976 _key: function() { |
|
2977 /* |
|
2978 * There is a restriction that IDENT can be only "from" or "to". |
|
2979 * |
|
2980 * key |
|
2981 * : PERCENTAGE |
|
2982 * | IDENT |
|
2983 * ; |
|
2984 */ |
|
2985 |
|
2986 var tokenStream = this._tokenStream, |
|
2987 token; |
|
2988 |
|
2989 if (tokenStream.match(Tokens.PERCENTAGE)) { |
|
2990 return SyntaxUnit.fromToken(tokenStream.token()); |
|
2991 } else if (tokenStream.match(Tokens.IDENT)) { |
|
2992 token = tokenStream.token(); |
|
2993 |
|
2994 if (/from|to/i.test(token.value)) { |
|
2995 return SyntaxUnit.fromToken(token); |
|
2996 } |
|
2997 |
|
2998 tokenStream.unget(); |
|
2999 } |
|
3000 |
|
3001 //if it gets here, there wasn't a valid token, so time to explode |
|
3002 this._unexpectedToken(tokenStream.LT(1)); |
|
3003 }, |
|
3004 |
|
3005 //----------------------------------------------------------------- |
|
3006 // Helper methods |
|
3007 //----------------------------------------------------------------- |
|
3008 |
|
3009 /** |
|
3010 * Not part of CSS grammar, but useful for skipping over |
|
3011 * combination of white space and HTML-style comments. |
|
3012 * @return {void} |
|
3013 * @method _skipCruft |
|
3014 * @private |
|
3015 */ |
|
3016 _skipCruft: function() { |
|
3017 while (this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])) { |
|
3018 //noop |
|
3019 } |
|
3020 }, |
|
3021 |
|
3022 /** |
|
3023 * Not part of CSS grammar, but this pattern occurs frequently |
|
3024 * in the official CSS grammar. Split out here to eliminate |
|
3025 * duplicate code. |
|
3026 * @param {Boolean} checkStart Indicates if the rule should check |
|
3027 * for the left brace at the beginning. |
|
3028 * @param {Boolean} readMargins Indicates if the rule should check |
|
3029 * for margin patterns. |
|
3030 * @return {void} |
|
3031 * @method _readDeclarations |
|
3032 * @private |
|
3033 */ |
|
3034 _readDeclarations: function(checkStart, readMargins) { |
|
3035 /* |
|
3036 * Reads the pattern |
|
3037 * S* '{' S* declaration [ ';' S* declaration ]* '}' S* |
|
3038 * or |
|
3039 * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* |
|
3040 * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. |
|
3041 * A semicolon is only necessary following a declaration if there's another declaration |
|
3042 * or margin afterwards. |
|
3043 */ |
|
3044 var tokenStream = this._tokenStream, |
|
3045 tt; |
|
3046 |
|
3047 |
|
3048 this._readWhitespace(); |
|
3049 |
|
3050 if (checkStart) { |
|
3051 tokenStream.mustMatch(Tokens.LBRACE); |
|
3052 } |
|
3053 |
|
3054 this._readWhitespace(); |
|
3055 |
|
3056 try { |
|
3057 |
|
3058 while (true) { |
|
3059 |
|
3060 if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())) { |
|
3061 //noop |
|
3062 } else if (this._declaration()) { |
|
3063 if (!tokenStream.match(Tokens.SEMICOLON)) { |
|
3064 break; |
|
3065 } |
|
3066 } else { |
|
3067 break; |
|
3068 } |
|
3069 |
|
3070 //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ |
|
3071 // break; |
|
3072 //} |
|
3073 this._readWhitespace(); |
|
3074 } |
|
3075 |
|
3076 tokenStream.mustMatch(Tokens.RBRACE); |
|
3077 this._readWhitespace(); |
|
3078 |
|
3079 } catch (ex) { |
|
3080 if (ex instanceof SyntaxError && !this.options.strict) { |
|
3081 |
|
3082 //fire error event |
|
3083 this.fire({ |
|
3084 type: "error", |
|
3085 error: ex, |
|
3086 message: ex.message, |
|
3087 line: ex.line, |
|
3088 col: ex.col |
|
3089 }); |
|
3090 |
|
3091 //see if there's another declaration |
|
3092 tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); |
|
3093 if (tt === Tokens.SEMICOLON) { |
|
3094 //if there's a semicolon, then there might be another declaration |
|
3095 this._readDeclarations(false, readMargins); |
|
3096 } else if (tt !== Tokens.RBRACE) { |
|
3097 //if there's a right brace, the rule is finished so don't do anything |
|
3098 //otherwise, rethrow the error because it wasn't handled properly |
|
3099 throw ex; |
|
3100 } |
|
3101 |
|
3102 } else { |
|
3103 //not a syntax error, rethrow it |
|
3104 throw ex; |
|
3105 } |
|
3106 } |
|
3107 |
|
3108 }, |
|
3109 |
|
3110 /** |
|
3111 * In some cases, you can end up with two white space tokens in a |
|
3112 * row. Instead of making a change in every function that looks for |
|
3113 * white space, this function is used to match as much white space |
|
3114 * as necessary. |
|
3115 * @method _readWhitespace |
|
3116 * @return {String} The white space if found, empty string if not. |
|
3117 * @private |
|
3118 */ |
|
3119 _readWhitespace: function() { |
|
3120 |
|
3121 var tokenStream = this._tokenStream, |
|
3122 ws = ""; |
|
3123 |
|
3124 while (tokenStream.match(Tokens.S)) { |
|
3125 ws += tokenStream.token().value; |
|
3126 } |
|
3127 |
|
3128 return ws; |
|
3129 }, |
|
3130 |
|
3131 |
|
3132 /** |
|
3133 * Throws an error when an unexpected token is found. |
|
3134 * @param {Object} token The token that was found. |
|
3135 * @method _unexpectedToken |
|
3136 * @return {void} |
|
3137 * @private |
|
3138 */ |
|
3139 _unexpectedToken: function(token) { |
|
3140 throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); |
|
3141 }, |
|
3142 |
|
3143 /** |
|
3144 * Helper method used for parsing subparts of a style sheet. |
|
3145 * @return {void} |
|
3146 * @method _verifyEnd |
|
3147 * @private |
|
3148 */ |
|
3149 _verifyEnd: function() { |
|
3150 if (this._tokenStream.LA(1) !== Tokens.EOF) { |
|
3151 this._unexpectedToken(this._tokenStream.LT(1)); |
|
3152 } |
|
3153 }, |
|
3154 |
|
3155 //----------------------------------------------------------------- |
|
3156 // Validation methods |
|
3157 //----------------------------------------------------------------- |
|
3158 _validateProperty: function(property, value) { |
|
3159 Validation.validate(property, value); |
|
3160 }, |
|
3161 |
|
3162 //----------------------------------------------------------------- |
|
3163 // Parsing methods |
|
3164 //----------------------------------------------------------------- |
|
3165 |
|
3166 parse: function(input) { |
|
3167 this._tokenStream = new TokenStream(input, Tokens); |
|
3168 this._stylesheet(); |
|
3169 }, |
|
3170 |
|
3171 parseStyleSheet: function(input) { |
|
3172 //just passthrough |
|
3173 return this.parse(input); |
|
3174 }, |
|
3175 |
|
3176 parseMediaQuery: function(input) { |
|
3177 this._tokenStream = new TokenStream(input, Tokens); |
|
3178 var result = this._media_query(); |
|
3179 |
|
3180 //if there's anything more, then it's an invalid selector |
|
3181 this._verifyEnd(); |
|
3182 |
|
3183 //otherwise return result |
|
3184 return result; |
|
3185 }, |
|
3186 |
|
3187 /** |
|
3188 * Parses a property value (everything after the semicolon). |
|
3189 * @return {parserlib.css.PropertyValue} The property value. |
|
3190 * @throws parserlib.util.SyntaxError If an unexpected token is found. |
|
3191 * @method parserPropertyValue |
|
3192 */ |
|
3193 parsePropertyValue: function(input) { |
|
3194 |
|
3195 this._tokenStream = new TokenStream(input, Tokens); |
|
3196 this._readWhitespace(); |
|
3197 |
|
3198 var result = this._expr(); |
|
3199 |
|
3200 //okay to have a trailing white space |
|
3201 this._readWhitespace(); |
|
3202 |
|
3203 //if there's anything more, then it's an invalid selector |
|
3204 this._verifyEnd(); |
|
3205 |
|
3206 //otherwise return result |
|
3207 return result; |
|
3208 }, |
|
3209 |
|
3210 /** |
|
3211 * Parses a complete CSS rule, including selectors and |
|
3212 * properties. |
|
3213 * @param {String} input The text to parser. |
|
3214 * @return {Boolean} True if the parse completed successfully, false if not. |
|
3215 * @method parseRule |
|
3216 */ |
|
3217 parseRule: function(input) { |
|
3218 this._tokenStream = new TokenStream(input, Tokens); |
|
3219 |
|
3220 //skip any leading white space |
|
3221 this._readWhitespace(); |
|
3222 |
|
3223 var result = this._ruleset(); |
|
3224 |
|
3225 //skip any trailing white space |
|
3226 this._readWhitespace(); |
|
3227 |
|
3228 //if there's anything more, then it's an invalid selector |
|
3229 this._verifyEnd(); |
|
3230 |
|
3231 //otherwise return result |
|
3232 return result; |
|
3233 }, |
|
3234 |
|
3235 /** |
|
3236 * Parses a single CSS selector (no comma) |
|
3237 * @param {String} input The text to parse as a CSS selector. |
|
3238 * @return {Selector} An object representing the selector. |
|
3239 * @throws parserlib.util.SyntaxError If an unexpected token is found. |
|
3240 * @method parseSelector |
|
3241 */ |
|
3242 parseSelector: function(input) { |
|
3243 |
|
3244 this._tokenStream = new TokenStream(input, Tokens); |
|
3245 |
|
3246 //skip any leading white space |
|
3247 this._readWhitespace(); |
|
3248 |
|
3249 var result = this._selector(); |
|
3250 |
|
3251 //skip any trailing white space |
|
3252 this._readWhitespace(); |
|
3253 |
|
3254 //if there's anything more, then it's an invalid selector |
|
3255 this._verifyEnd(); |
|
3256 |
|
3257 //otherwise return result |
|
3258 return result; |
|
3259 }, |
|
3260 |
|
3261 /** |
|
3262 * Parses an HTML style attribute: a set of CSS declarations |
|
3263 * separated by semicolons. |
|
3264 * @param {String} input The text to parse as a style attribute |
|
3265 * @return {void} |
|
3266 * @method parseStyleAttribute |
|
3267 */ |
|
3268 parseStyleAttribute: function(input) { |
|
3269 input += "}"; // for error recovery in _readDeclarations() |
|
3270 this._tokenStream = new TokenStream(input, Tokens); |
|
3271 this._readDeclarations(); |
|
3272 } |
|
3273 }; |
|
3274 |
|
3275 //copy over onto prototype |
|
3276 for (prop in additions) { |
|
3277 if (Object.prototype.hasOwnProperty.call(additions, prop)) { |
|
3278 proto[prop] = additions[prop]; |
|
3279 } |
|
3280 } |
|
3281 |
|
3282 return proto; |
|
3283 }(); |
|
3284 |
|
3285 |
|
3286 /* |
|
3287 nth |
|
3288 : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | |
|
3289 ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* |
|
3290 ; |
|
3291 */ |
|
3292 |
|
3293 },{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){ |
|
3294 "use strict"; |
|
3295 |
|
3296 /* exported Properties */ |
|
3297 |
|
3298 var Properties = module.exports = { |
|
3299 __proto__: null, |
|
3300 |
|
3301 //A |
|
3302 "align-items" : "flex-start | flex-end | center | baseline | stretch", |
|
3303 "align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", |
|
3304 "align-self" : "auto | flex-start | flex-end | center | baseline | stretch", |
|
3305 "all" : "initial | inherit | unset", |
|
3306 "-webkit-align-items" : "flex-start | flex-end | center | baseline | stretch", |
|
3307 "-webkit-align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", |
|
3308 "-webkit-align-self" : "auto | flex-start | flex-end | center | baseline | stretch", |
|
3309 "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>", |
|
3310 "alignment-baseline" : "auto | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", |
|
3311 "animation" : 1, |
|
3312 "animation-delay" : "<time>#", |
|
3313 "animation-direction" : "<single-animation-direction>#", |
|
3314 "animation-duration" : "<time>#", |
|
3315 "animation-fill-mode" : "[ none | forwards | backwards | both ]#", |
|
3316 "animation-iteration-count" : "[ <number> | infinite ]#", |
|
3317 "animation-name" : "[ none | <single-animation-name> ]#", |
|
3318 "animation-play-state" : "[ running | paused ]#", |
|
3319 "animation-timing-function" : 1, |
|
3320 |
|
3321 //vendor prefixed |
|
3322 "-moz-animation-delay" : "<time>#", |
|
3323 "-moz-animation-direction" : "[ normal | alternate ]#", |
|
3324 "-moz-animation-duration" : "<time>#", |
|
3325 "-moz-animation-iteration-count" : "[ <number> | infinite ]#", |
|
3326 "-moz-animation-name" : "[ none | <single-animation-name> ]#", |
|
3327 "-moz-animation-play-state" : "[ running | paused ]#", |
|
3328 |
|
3329 "-ms-animation-delay" : "<time>#", |
|
3330 "-ms-animation-direction" : "[ normal | alternate ]#", |
|
3331 "-ms-animation-duration" : "<time>#", |
|
3332 "-ms-animation-iteration-count" : "[ <number> | infinite ]#", |
|
3333 "-ms-animation-name" : "[ none | <single-animation-name> ]#", |
|
3334 "-ms-animation-play-state" : "[ running | paused ]#", |
|
3335 |
|
3336 "-webkit-animation-delay" : "<time>#", |
|
3337 "-webkit-animation-direction" : "[ normal | alternate ]#", |
|
3338 "-webkit-animation-duration" : "<time>#", |
|
3339 "-webkit-animation-fill-mode" : "[ none | forwards | backwards | both ]#", |
|
3340 "-webkit-animation-iteration-count" : "[ <number> | infinite ]#", |
|
3341 "-webkit-animation-name" : "[ none | <single-animation-name> ]#", |
|
3342 "-webkit-animation-play-state" : "[ running | paused ]#", |
|
3343 |
|
3344 "-o-animation-delay" : "<time>#", |
|
3345 "-o-animation-direction" : "[ normal | alternate ]#", |
|
3346 "-o-animation-duration" : "<time>#", |
|
3347 "-o-animation-iteration-count" : "[ <number> | infinite ]#", |
|
3348 "-o-animation-name" : "[ none | <single-animation-name> ]#", |
|
3349 "-o-animation-play-state" : "[ running | paused ]#", |
|
3350 |
|
3351 "appearance" : "none | auto", |
|
3352 "-moz-appearance" : "none | button | button-arrow-down | button-arrow-next | button-arrow-previous | button-arrow-up | button-bevel | button-focus | caret | checkbox | checkbox-container | checkbox-label | checkmenuitem | dualbutton | groupbox | listbox | listitem | menuarrow | menubar | menucheckbox | menuimage | menuitem | menuitemtext | menulist | menulist-button | menulist-text | menulist-textfield | menupopup | menuradio | menuseparator | meterbar | meterchunk | progressbar | progressbar-vertical | progresschunk | progresschunk-vertical | radio | radio-container | radio-label | radiomenuitem | range | range-thumb | resizer | resizerpanel | scale-horizontal | scalethumbend | scalethumb-horizontal | scalethumbstart | scalethumbtick | scalethumb-vertical | scale-vertical | scrollbarbutton-down | scrollbarbutton-left | scrollbarbutton-right | scrollbarbutton-up | scrollbarthumb-horizontal | scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical | searchfield | separator | sheet | spinner | spinner-downbutton | spinner-textfield | spinner-upbutton | splitter | statusbar | statusbarpanel | tab | tabpanel | tabpanels | tab-scroll-arrow-back | tab-scroll-arrow-forward | textfield | textfield-multiline | toolbar | toolbarbutton | toolbarbutton-dropdown | toolbargripper | toolbox | tooltip | treeheader | treeheadercell | treeheadersortarrow | treeitem | treeline | treetwisty | treetwistyopen | treeview | -moz-mac-unified-toolbar | -moz-win-borderless-glass | -moz-win-browsertabbar-toolbox | -moz-win-communicationstext | -moz-win-communications-toolbox | -moz-win-exclude-glass | -moz-win-glass | -moz-win-mediatext | -moz-win-media-toolbox | -moz-window-button-box | -moz-window-button-box-maximized | -moz-window-button-close | -moz-window-button-maximize | -moz-window-button-minimize | -moz-window-button-restore | -moz-window-frame-bottom | -moz-window-frame-left | -moz-window-frame-right | -moz-window-titlebar | -moz-window-titlebar-maximized", |
|
3353 "-ms-appearance" : "none | icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal", |
|
3354 "-webkit-appearance" : "none | button | button-bevel | caps-lock-indicator | caret | checkbox | default-button | listbox | listitem | media-fullscreen-button | media-mute-button | media-play-button | media-seek-back-button | media-seek-forward-button | media-slider | media-sliderthumb | menulist | menulist-button | menulist-text | menulist-textfield | push-button | radio | searchfield | searchfield-cancel-button | searchfield-decoration | searchfield-results-button | searchfield-results-decoration | slider-horizontal | slider-vertical | sliderthumb-horizontal | sliderthumb-vertical | square-button | textarea | textfield | scrollbarbutton-down | scrollbarbutton-left | scrollbarbutton-right | scrollbarbutton-up | scrollbargripper-horizontal | scrollbargripper-vertical | scrollbarthumb-horizontal | scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical", |
|
3355 "-o-appearance" : "none | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal", |
|
3356 |
|
3357 "azimuth" : "<azimuth>", |
|
3358 |
|
3359 //B |
|
3360 "backface-visibility" : "visible | hidden", |
|
3361 "background" : 1, |
|
3362 "background-attachment" : "<attachment>#", |
|
3363 "background-clip" : "<box>#", |
|
3364 "background-color" : "<color>", |
|
3365 "background-image" : "<bg-image>#", |
|
3366 "background-origin" : "<box>#", |
|
3367 "background-position" : "<bg-position>", |
|
3368 "background-repeat" : "<repeat-style>#", |
|
3369 "background-size" : "<bg-size>#", |
|
3370 "baseline-shift" : "baseline | sub | super | <percentage> | <length>", |
|
3371 "behavior" : 1, |
|
3372 "binding" : 1, |
|
3373 "bleed" : "<length>", |
|
3374 "bookmark-label" : "<content> | <attr> | <string>", |
|
3375 "bookmark-level" : "none | <integer>", |
|
3376 "bookmark-state" : "open | closed", |
|
3377 "bookmark-target" : "none | <uri> | <attr>", |
|
3378 "border" : "<border-width> || <border-style> || <color>", |
|
3379 "border-bottom" : "<border-width> || <border-style> || <color>", |
|
3380 "border-bottom-color" : "<color>", |
|
3381 "border-bottom-left-radius" : "<x-one-radius>", |
|
3382 "border-bottom-right-radius" : "<x-one-radius>", |
|
3383 "border-bottom-style" : "<border-style>", |
|
3384 "border-bottom-width" : "<border-width>", |
|
3385 "border-collapse" : "collapse | separate", |
|
3386 "border-color" : "<color>{1,4}", |
|
3387 "border-image" : 1, |
|
3388 "border-image-outset" : "[ <length> | <number> ]{1,4}", |
|
3389 "border-image-repeat" : "[ stretch | repeat | round ]{1,2}", |
|
3390 "border-image-slice" : "<border-image-slice>", |
|
3391 "border-image-source" : "<image> | none", |
|
3392 "border-image-width" : "[ <length> | <percentage> | <number> | auto ]{1,4}", |
|
3393 "border-left" : "<border-width> || <border-style> || <color>", |
|
3394 "border-left-color" : "<color>", |
|
3395 "border-left-style" : "<border-style>", |
|
3396 "border-left-width" : "<border-width>", |
|
3397 "border-radius" : "<border-radius>", |
|
3398 "border-right" : "<border-width> || <border-style> || <color>", |
|
3399 "border-right-color" : "<color>", |
|
3400 "border-right-style" : "<border-style>", |
|
3401 "border-right-width" : "<border-width>", |
|
3402 "border-spacing" : "<length>{1,2}", |
|
3403 "border-style" : "<border-style>{1,4}", |
|
3404 "border-top" : "<border-width> || <border-style> || <color>", |
|
3405 "border-top-color" : "<color>", |
|
3406 "border-top-left-radius" : "<x-one-radius>", |
|
3407 "border-top-right-radius" : "<x-one-radius>", |
|
3408 "border-top-style" : "<border-style>", |
|
3409 "border-top-width" : "<border-width>", |
|
3410 "border-width" : "<border-width>{1,4}", |
|
3411 "bottom" : "<margin-width>", |
|
3412 "-moz-box-align" : "start | end | center | baseline | stretch", |
|
3413 "-moz-box-decoration-break" : "slice | clone", |
|
3414 "-moz-box-direction" : "normal | reverse", |
|
3415 "-moz-box-flex" : "<number>", |
|
3416 "-moz-box-flex-group" : "<integer>", |
|
3417 "-moz-box-lines" : "single | multiple", |
|
3418 "-moz-box-ordinal-group" : "<integer>", |
|
3419 "-moz-box-orient" : "horizontal | vertical | inline-axis | block-axis", |
|
3420 "-moz-box-pack" : "start | end | center | justify", |
|
3421 "-o-box-decoration-break" : "slice | clone", |
|
3422 "-webkit-box-align" : "start | end | center | baseline | stretch", |
|
3423 "-webkit-box-decoration-break" : "slice | clone", |
|
3424 "-webkit-box-direction" : "normal | reverse", |
|
3425 "-webkit-box-flex" : "<number>", |
|
3426 "-webkit-box-flex-group" : "<integer>", |
|
3427 "-webkit-box-lines" : "single | multiple", |
|
3428 "-webkit-box-ordinal-group" : "<integer>", |
|
3429 "-webkit-box-orient" : "horizontal | vertical | inline-axis | block-axis", |
|
3430 "-webkit-box-pack" : "start | end | center | justify", |
|
3431 "box-decoration-break" : "slice | clone", |
|
3432 "box-shadow" : "<box-shadow>", |
|
3433 "box-sizing" : "content-box | border-box", |
|
3434 "break-after" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column", |
|
3435 "break-before" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column", |
|
3436 "break-inside" : "auto | avoid | avoid-page | avoid-column", |
|
3437 |
|
3438 //C |
|
3439 "caption-side" : "top | bottom", |
|
3440 "clear" : "none | right | left | both", |
|
3441 "clip" : "<shape> | auto", |
|
3442 "-webkit-clip-path" : "<clip-source> | <clip-path> | none", |
|
3443 "clip-path" : "<clip-source> | <clip-path> | none", |
|
3444 "clip-rule" : "nonzero | evenodd", |
|
3445 "color" : "<color>", |
|
3446 "color-interpolation" : "auto | sRGB | linearRGB", |
|
3447 "color-interpolation-filters" : "auto | sRGB | linearRGB", |
|
3448 "color-profile" : 1, |
|
3449 "color-rendering" : "auto | optimizeSpeed | optimizeQuality", |
|
3450 "column-count" : "<integer> | auto", //https://www.w3.org/TR/css3-multicol/ |
|
3451 "column-fill" : "auto | balance", |
|
3452 "column-gap" : "<length> | normal", |
|
3453 "column-rule" : "<border-width> || <border-style> || <color>", |
|
3454 "column-rule-color" : "<color>", |
|
3455 "column-rule-style" : "<border-style>", |
|
3456 "column-rule-width" : "<border-width>", |
|
3457 "column-span" : "none | all", |
|
3458 "column-width" : "<length> | auto", |
|
3459 "columns" : 1, |
|
3460 "content" : 1, |
|
3461 "counter-increment" : 1, |
|
3462 "counter-reset" : 1, |
|
3463 "crop" : "<shape> | auto", |
|
3464 "cue" : "cue-after | cue-before", |
|
3465 "cue-after" : 1, |
|
3466 "cue-before" : 1, |
|
3467 "cursor" : 1, |
|
3468 |
|
3469 //D |
|
3470 "direction" : "ltr | rtl", |
|
3471 "display" : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | grid | inline-grid | run-in | ruby | ruby-base | ruby-text | ruby-base-container | ruby-text-container | contents | none | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker | -webkit-box | -webkit-inline-box | -ms-flexbox | -ms-inline-flexbox | flex | -webkit-flex | inline-flex | -webkit-inline-flex", |
|
3472 "dominant-baseline" : "auto | use-script | no-change | reset-size | ideographic | alphabetic | hanging | mathematical | central | middle | text-after-edge | text-before-edge", |
|
3473 "drop-initial-after-adjust" : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>", |
|
3474 "drop-initial-after-align" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", |
|
3475 "drop-initial-before-adjust" : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>", |
|
3476 "drop-initial-before-align" : "caps-height | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", |
|
3477 "drop-initial-size" : "auto | line | <length> | <percentage>", |
|
3478 "drop-initial-value" : "<integer>", |
|
3479 |
|
3480 //E |
|
3481 "elevation" : "<angle> | below | level | above | higher | lower", |
|
3482 "empty-cells" : "show | hide", |
|
3483 "enable-background" : 1, |
|
3484 |
|
3485 //F |
|
3486 "fill" : "<paint>", |
|
3487 "fill-opacity" : "<opacity-value>", |
|
3488 "fill-rule" : "nonzero | evenodd", |
|
3489 "filter" : "<filter-function-list> | none", |
|
3490 "fit" : "fill | hidden | meet | slice", |
|
3491 "fit-position" : 1, |
|
3492 "flex" : "<flex>", |
|
3493 "flex-basis" : "<width>", |
|
3494 "flex-direction" : "row | row-reverse | column | column-reverse", |
|
3495 "flex-flow" : "<flex-direction> || <flex-wrap>", |
|
3496 "flex-grow" : "<number>", |
|
3497 "flex-shrink" : "<number>", |
|
3498 "flex-wrap" : "nowrap | wrap | wrap-reverse", |
|
3499 "-webkit-flex" : "<flex>", |
|
3500 "-webkit-flex-basis" : "<width>", |
|
3501 "-webkit-flex-direction" : "row | row-reverse | column | column-reverse", |
|
3502 "-webkit-flex-flow" : "<flex-direction> || <flex-wrap>", |
|
3503 "-webkit-flex-grow" : "<number>", |
|
3504 "-webkit-flex-shrink" : "<number>", |
|
3505 "-webkit-flex-wrap" : "nowrap | wrap | wrap-reverse", |
|
3506 "-ms-flex" : "<flex>", |
|
3507 "-ms-flex-align" : "start | end | center | stretch | baseline", |
|
3508 "-ms-flex-direction" : "row | row-reverse | column | column-reverse", |
|
3509 "-ms-flex-order" : "<number>", |
|
3510 "-ms-flex-pack" : "start | end | center | justify", |
|
3511 "-ms-flex-wrap" : "nowrap | wrap | wrap-reverse", |
|
3512 "float" : "left | right | none", |
|
3513 "float-offset" : 1, |
|
3514 "flood-color" : 1, |
|
3515 "flood-opacity" : "<opacity-value>", |
|
3516 "font" : "<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar", |
|
3517 "font-family" : "<font-family>", |
|
3518 "font-feature-settings" : "<feature-tag-value> | normal", |
|
3519 "font-kerning" : "auto | normal | none", |
|
3520 "font-size" : "<font-size>", |
|
3521 "font-size-adjust" : "<number> | none", |
|
3522 "font-stretch" : "<font-stretch>", |
|
3523 "font-style" : "<font-style>", |
|
3524 "font-variant" : "<font-variant> | normal | none", |
|
3525 "font-variant-alternates" : "<font-variant-alternates> | normal", |
|
3526 "font-variant-caps" : "<font-variant-caps> | normal", |
|
3527 "font-variant-east-asian" : "<font-variant-east-asian> | normal", |
|
3528 "font-variant-ligatures" : "<font-variant-ligatures> | normal | none", |
|
3529 "font-variant-numeric" : "<font-variant-numeric> | normal", |
|
3530 "font-variant-position" : "normal | sub | super", |
|
3531 "font-weight" : "<font-weight>", |
|
3532 |
|
3533 //G |
|
3534 "glyph-orientation-horizontal" : "<glyph-angle>", |
|
3535 "glyph-orientation-vertical" : "auto | <glyph-angle>", |
|
3536 "grid" : 1, |
|
3537 "grid-area" : 1, |
|
3538 "grid-auto-columns" : 1, |
|
3539 "grid-auto-flow" : 1, |
|
3540 "grid-auto-position" : 1, |
|
3541 "grid-auto-rows" : 1, |
|
3542 "grid-cell-stacking" : "columns | rows | layer", |
|
3543 "grid-column" : 1, |
|
3544 "grid-columns" : 1, |
|
3545 "grid-column-align" : "start | end | center | stretch", |
|
3546 "grid-column-sizing" : 1, |
|
3547 "grid-column-start" : 1, |
|
3548 "grid-column-end" : 1, |
|
3549 "grid-column-span" : "<integer>", |
|
3550 "grid-flow" : "none | rows | columns", |
|
3551 "grid-layer" : "<integer>", |
|
3552 "grid-row" : 1, |
|
3553 "grid-rows" : 1, |
|
3554 "grid-row-align" : "start | end | center | stretch", |
|
3555 "grid-row-start" : 1, |
|
3556 "grid-row-end" : 1, |
|
3557 "grid-row-span" : "<integer>", |
|
3558 "grid-row-sizing" : 1, |
|
3559 "grid-template" : 1, |
|
3560 "grid-template-areas" : 1, |
|
3561 "grid-template-columns" : 1, |
|
3562 "grid-template-rows" : 1, |
|
3563 |
|
3564 //H |
|
3565 "hanging-punctuation" : 1, |
|
3566 "height" : "<margin-width> | <content-sizing>", |
|
3567 "hyphenate-after" : "<integer> | auto", |
|
3568 "hyphenate-before" : "<integer> | auto", |
|
3569 "hyphenate-character" : "<string> | auto", |
|
3570 "hyphenate-lines" : "no-limit | <integer>", |
|
3571 "hyphenate-resource" : 1, |
|
3572 "hyphens" : "none | manual | auto", |
|
3573 |
|
3574 //I |
|
3575 "icon" : 1, |
|
3576 "image-orientation" : "angle | auto", |
|
3577 "image-rendering" : "auto | optimizeSpeed | optimizeQuality", |
|
3578 "image-resolution" : 1, |
|
3579 "ime-mode" : "auto | normal | active | inactive | disabled", |
|
3580 "inline-box-align" : "last | <integer>", |
|
3581 |
|
3582 //J |
|
3583 "justify-content" : "flex-start | flex-end | center | space-between | space-around", |
|
3584 "-webkit-justify-content" : "flex-start | flex-end | center | space-between | space-around", |
|
3585 |
|
3586 //K |
|
3587 "kerning" : "auto | <length>", |
|
3588 |
|
3589 //L |
|
3590 "left" : "<margin-width>", |
|
3591 "letter-spacing" : "<length> | normal", |
|
3592 "line-height" : "<line-height>", |
|
3593 "line-break" : "auto | loose | normal | strict", |
|
3594 "line-stacking" : 1, |
|
3595 "line-stacking-ruby" : "exclude-ruby | include-ruby", |
|
3596 "line-stacking-shift" : "consider-shifts | disregard-shifts", |
|
3597 "line-stacking-strategy" : "inline-line-height | block-line-height | max-height | grid-height", |
|
3598 "list-style" : 1, |
|
3599 "list-style-image" : "<uri> | none", |
|
3600 "list-style-position" : "inside | outside", |
|
3601 "list-style-type" : "disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none", |
|
3602 |
|
3603 //M |
|
3604 "margin" : "<margin-width>{1,4}", |
|
3605 "margin-bottom" : "<margin-width>", |
|
3606 "margin-left" : "<margin-width>", |
|
3607 "margin-right" : "<margin-width>", |
|
3608 "margin-top" : "<margin-width>", |
|
3609 "mark" : 1, |
|
3610 "mark-after" : 1, |
|
3611 "mark-before" : 1, |
|
3612 "marker" : 1, |
|
3613 "marker-end" : 1, |
|
3614 "marker-mid" : 1, |
|
3615 "marker-start" : 1, |
|
3616 "marks" : 1, |
|
3617 "marquee-direction" : 1, |
|
3618 "marquee-play-count" : 1, |
|
3619 "marquee-speed" : 1, |
|
3620 "marquee-style" : 1, |
|
3621 "mask" : 1, |
|
3622 "max-height" : "<length> | <percentage> | <content-sizing> | none", |
|
3623 "max-width" : "<length> | <percentage> | <content-sizing> | none", |
|
3624 "min-height" : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats", |
|
3625 "min-width" : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats", |
|
3626 "move-to" : 1, |
|
3627 |
|
3628 //N |
|
3629 "nav-down" : 1, |
|
3630 "nav-index" : 1, |
|
3631 "nav-left" : 1, |
|
3632 "nav-right" : 1, |
|
3633 "nav-up" : 1, |
|
3634 |
|
3635 //O |
|
3636 "object-fit" : "fill | contain | cover | none | scale-down", |
|
3637 "object-position" : "<position>", |
|
3638 "opacity" : "<opacity-value>", |
|
3639 "order" : "<integer>", |
|
3640 "-webkit-order" : "<integer>", |
|
3641 "orphans" : "<integer>", |
|
3642 "outline" : 1, |
|
3643 "outline-color" : "<color> | invert", |
|
3644 "outline-offset" : 1, |
|
3645 "outline-style" : "<border-style>", |
|
3646 "outline-width" : "<border-width>", |
|
3647 "overflow" : "visible | hidden | scroll | auto", |
|
3648 "overflow-style" : 1, |
|
3649 "overflow-wrap" : "normal | break-word", |
|
3650 "overflow-x" : 1, |
|
3651 "overflow-y" : 1, |
|
3652 |
|
3653 //P |
|
3654 "padding" : "<padding-width>{1,4}", |
|
3655 "padding-bottom" : "<padding-width>", |
|
3656 "padding-left" : "<padding-width>", |
|
3657 "padding-right" : "<padding-width>", |
|
3658 "padding-top" : "<padding-width>", |
|
3659 "page" : 1, |
|
3660 "page-break-after" : "auto | always | avoid | left | right", |
|
3661 "page-break-before" : "auto | always | avoid | left | right", |
|
3662 "page-break-inside" : "auto | avoid", |
|
3663 "page-policy" : 1, |
|
3664 "pause" : 1, |
|
3665 "pause-after" : 1, |
|
3666 "pause-before" : 1, |
|
3667 "perspective" : 1, |
|
3668 "perspective-origin" : 1, |
|
3669 "phonemes" : 1, |
|
3670 "pitch" : 1, |
|
3671 "pitch-range" : 1, |
|
3672 "play-during" : 1, |
|
3673 "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all", |
|
3674 "position" : "static | relative | absolute | fixed", |
|
3675 "presentation-level" : 1, |
|
3676 "punctuation-trim" : 1, |
|
3677 |
|
3678 //Q |
|
3679 "quotes" : 1, |
|
3680 |
|
3681 //R |
|
3682 "rendering-intent" : 1, |
|
3683 "resize" : 1, |
|
3684 "rest" : 1, |
|
3685 "rest-after" : 1, |
|
3686 "rest-before" : 1, |
|
3687 "richness" : 1, |
|
3688 "right" : "<margin-width>", |
|
3689 "rotation" : 1, |
|
3690 "rotation-point" : 1, |
|
3691 "ruby-align" : 1, |
|
3692 "ruby-overhang" : 1, |
|
3693 "ruby-position" : 1, |
|
3694 "ruby-span" : 1, |
|
3695 |
|
3696 //S |
|
3697 "shape-rendering" : "auto | optimizeSpeed | crispEdges | geometricPrecision", |
|
3698 "size" : 1, |
|
3699 "speak" : "normal | none | spell-out", |
|
3700 "speak-header" : "once | always", |
|
3701 "speak-numeral" : "digits | continuous", |
|
3702 "speak-punctuation" : "code | none", |
|
3703 "speech-rate" : 1, |
|
3704 "src" : 1, |
|
3705 "stop-color" : 1, |
|
3706 "stop-opacity" : "<opacity-value>", |
|
3707 "stress" : 1, |
|
3708 "string-set" : 1, |
|
3709 "stroke" : "<paint>", |
|
3710 "stroke-dasharray" : "none | <dasharray>", |
|
3711 "stroke-dashoffset" : "<percentage> | <length>", |
|
3712 "stroke-linecap" : "butt | round | square", |
|
3713 "stroke-linejoin" : "miter | round | bevel", |
|
3714 "stroke-miterlimit" : "<miterlimit>", |
|
3715 "stroke-opacity" : "<opacity-value>", |
|
3716 "stroke-width" : "<percentage> | <length>", |
|
3717 |
|
3718 "table-layout" : "auto | fixed", |
|
3719 "tab-size" : "<integer> | <length>", |
|
3720 "target" : 1, |
|
3721 "target-name" : 1, |
|
3722 "target-new" : 1, |
|
3723 "target-position" : 1, |
|
3724 "text-align" : "left | right | center | justify | match-parent | start | end", |
|
3725 "text-align-last" : 1, |
|
3726 "text-anchor" : "start | middle | end", |
|
3727 "text-decoration" : "<text-decoration-line> || <text-decoration-style> || <text-decoration-color>", |
|
3728 "text-decoration-color" : "<text-decoration-color>", |
|
3729 "text-decoration-line" : "<text-decoration-line>", |
|
3730 "text-decoration-style" : "<text-decoration-style>", |
|
3731 "text-emphasis" : 1, |
|
3732 "text-height" : 1, |
|
3733 "text-indent" : "<length> | <percentage>", |
|
3734 "text-justify" : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida", |
|
3735 "text-outline" : 1, |
|
3736 "text-overflow" : 1, |
|
3737 "text-rendering" : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision", |
|
3738 "text-shadow" : 1, |
|
3739 "text-transform" : "capitalize | uppercase | lowercase | none", |
|
3740 "text-wrap" : "normal | none | avoid", |
|
3741 "top" : "<margin-width>", |
|
3742 "-ms-touch-action" : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation", |
|
3743 "touch-action" : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation", |
|
3744 "transform" : 1, |
|
3745 "transform-origin" : 1, |
|
3746 "transform-style" : 1, |
|
3747 "transition" : 1, |
|
3748 "transition-delay" : 1, |
|
3749 "transition-duration" : 1, |
|
3750 "transition-property" : 1, |
|
3751 "transition-timing-function" : 1, |
|
3752 |
|
3753 //U |
|
3754 "unicode-bidi" : "normal | embed | isolate | bidi-override | isolate-override | plaintext", |
|
3755 "user-modify" : "read-only | read-write | write-only", |
|
3756 "user-select" : "none | text | toggle | element | elements | all", |
|
3757 |
|
3758 //V |
|
3759 "vertical-align" : "auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | <percentage> | <length>", |
|
3760 "visibility" : "visible | hidden | collapse", |
|
3761 "voice-balance" : 1, |
|
3762 "voice-duration" : 1, |
|
3763 "voice-family" : 1, |
|
3764 "voice-pitch" : 1, |
|
3765 "voice-pitch-range" : 1, |
|
3766 "voice-rate" : 1, |
|
3767 "voice-stress" : 1, |
|
3768 "voice-volume" : 1, |
|
3769 "volume" : 1, |
|
3770 |
|
3771 //W |
|
3772 "white-space" : "normal | pre | nowrap | pre-wrap | pre-line | -pre-wrap | -o-pre-wrap | -moz-pre-wrap | -hp-pre-wrap", // https://perishablepress.com/wrapping-content/ |
|
3773 "white-space-collapse" : 1, |
|
3774 "widows" : "<integer>", |
|
3775 "width" : "<length> | <percentage> | <content-sizing> | auto", |
|
3776 "will-change" : "<will-change>", |
|
3777 "word-break" : "normal | keep-all | break-all", |
|
3778 "word-spacing" : "<length> | normal", |
|
3779 "word-wrap" : "normal | break-word", |
|
3780 "writing-mode" : "horizontal-tb | vertical-rl | vertical-lr | lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb", |
|
3781 |
|
3782 //Z |
|
3783 "z-index" : "<integer> | auto", |
|
3784 "zoom" : "<number> | <percentage> | normal" |
|
3785 }; |
|
3786 |
|
3787 },{}],8:[function(require,module,exports){ |
|
3788 "use strict"; |
|
3789 |
|
3790 module.exports = PropertyName; |
|
3791 |
|
3792 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
3793 |
|
3794 var Parser = require("./Parser"); |
|
3795 |
|
3796 /** |
|
3797 * Represents a selector combinator (whitespace, +, >). |
|
3798 * @namespace parserlib.css |
|
3799 * @class PropertyName |
|
3800 * @extends parserlib.util.SyntaxUnit |
|
3801 * @constructor |
|
3802 * @param {String} text The text representation of the unit. |
|
3803 * @param {String} hack The type of IE hack applied ("*", "_", or null). |
|
3804 * @param {int} line The line of text on which the unit resides. |
|
3805 * @param {int} col The column of text on which the unit resides. |
|
3806 */ |
|
3807 function PropertyName(text, hack, line, col) { |
|
3808 |
|
3809 SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE); |
|
3810 |
|
3811 /** |
|
3812 * The type of IE hack applied ("*", "_", or null). |
|
3813 * @type String |
|
3814 * @property hack |
|
3815 */ |
|
3816 this.hack = hack; |
|
3817 |
|
3818 } |
|
3819 |
|
3820 PropertyName.prototype = new SyntaxUnit(); |
|
3821 PropertyName.prototype.constructor = PropertyName; |
|
3822 PropertyName.prototype.toString = function() { |
|
3823 return (this.hack ? this.hack : "") + this.text; |
|
3824 }; |
|
3825 |
|
3826 },{"../util/SyntaxUnit":26,"./Parser":6}],9:[function(require,module,exports){ |
|
3827 "use strict"; |
|
3828 |
|
3829 module.exports = PropertyValue; |
|
3830 |
|
3831 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
3832 |
|
3833 var Parser = require("./Parser"); |
|
3834 |
|
3835 /** |
|
3836 * Represents a single part of a CSS property value, meaning that it represents |
|
3837 * just everything single part between ":" and ";". If there are multiple values |
|
3838 * separated by commas, this type represents just one of the values. |
|
3839 * @param {String[]} parts An array of value parts making up this value. |
|
3840 * @param {int} line The line of text on which the unit resides. |
|
3841 * @param {int} col The column of text on which the unit resides. |
|
3842 * @namespace parserlib.css |
|
3843 * @class PropertyValue |
|
3844 * @extends parserlib.util.SyntaxUnit |
|
3845 * @constructor |
|
3846 */ |
|
3847 function PropertyValue(parts, line, col) { |
|
3848 |
|
3849 SyntaxUnit.call(this, parts.join(" "), line, col, Parser.PROPERTY_VALUE_TYPE); |
|
3850 |
|
3851 /** |
|
3852 * The parts that make up the selector. |
|
3853 * @type Array |
|
3854 * @property parts |
|
3855 */ |
|
3856 this.parts = parts; |
|
3857 |
|
3858 } |
|
3859 |
|
3860 PropertyValue.prototype = new SyntaxUnit(); |
|
3861 PropertyValue.prototype.constructor = PropertyValue; |
|
3862 |
|
3863 |
|
3864 },{"../util/SyntaxUnit":26,"./Parser":6}],10:[function(require,module,exports){ |
|
3865 "use strict"; |
|
3866 |
|
3867 module.exports = PropertyValueIterator; |
|
3868 |
|
3869 /** |
|
3870 * A utility class that allows for easy iteration over the various parts of a |
|
3871 * property value. |
|
3872 * @param {parserlib.css.PropertyValue} value The property value to iterate over. |
|
3873 * @namespace parserlib.css |
|
3874 * @class PropertyValueIterator |
|
3875 * @constructor |
|
3876 */ |
|
3877 function PropertyValueIterator(value) { |
|
3878 |
|
3879 /** |
|
3880 * Iterator value |
|
3881 * @type int |
|
3882 * @property _i |
|
3883 * @private |
|
3884 */ |
|
3885 this._i = 0; |
|
3886 |
|
3887 /** |
|
3888 * The parts that make up the value. |
|
3889 * @type Array |
|
3890 * @property _parts |
|
3891 * @private |
|
3892 */ |
|
3893 this._parts = value.parts; |
|
3894 |
|
3895 /** |
|
3896 * Keeps track of bookmarks along the way. |
|
3897 * @type Array |
|
3898 * @property _marks |
|
3899 * @private |
|
3900 */ |
|
3901 this._marks = []; |
|
3902 |
|
3903 /** |
|
3904 * Holds the original property value. |
|
3905 * @type parserlib.css.PropertyValue |
|
3906 * @property value |
|
3907 */ |
|
3908 this.value = value; |
|
3909 |
|
3910 } |
|
3911 |
|
3912 /** |
|
3913 * Returns the total number of parts in the value. |
|
3914 * @return {int} The total number of parts in the value. |
|
3915 * @method count |
|
3916 */ |
|
3917 PropertyValueIterator.prototype.count = function() { |
|
3918 return this._parts.length; |
|
3919 }; |
|
3920 |
|
3921 /** |
|
3922 * Indicates if the iterator is positioned at the first item. |
|
3923 * @return {Boolean} True if positioned at first item, false if not. |
|
3924 * @method isFirst |
|
3925 */ |
|
3926 PropertyValueIterator.prototype.isFirst = function() { |
|
3927 return this._i === 0; |
|
3928 }; |
|
3929 |
|
3930 /** |
|
3931 * Indicates if there are more parts of the property value. |
|
3932 * @return {Boolean} True if there are more parts, false if not. |
|
3933 * @method hasNext |
|
3934 */ |
|
3935 PropertyValueIterator.prototype.hasNext = function() { |
|
3936 return this._i < this._parts.length; |
|
3937 }; |
|
3938 |
|
3939 /** |
|
3940 * Marks the current spot in the iteration so it can be restored to |
|
3941 * later on. |
|
3942 * @return {void} |
|
3943 * @method mark |
|
3944 */ |
|
3945 PropertyValueIterator.prototype.mark = function() { |
|
3946 this._marks.push(this._i); |
|
3947 }; |
|
3948 |
|
3949 /** |
|
3950 * Returns the next part of the property value or null if there is no next |
|
3951 * part. Does not move the internal counter forward. |
|
3952 * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next |
|
3953 * part. |
|
3954 * @method peek |
|
3955 */ |
|
3956 PropertyValueIterator.prototype.peek = function(count) { |
|
3957 return this.hasNext() ? this._parts[this._i + (count || 0)] : null; |
|
3958 }; |
|
3959 |
|
3960 /** |
|
3961 * Returns the next part of the property value or null if there is no next |
|
3962 * part. |
|
3963 * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next |
|
3964 * part. |
|
3965 * @method next |
|
3966 */ |
|
3967 PropertyValueIterator.prototype.next = function() { |
|
3968 return this.hasNext() ? this._parts[this._i++] : null; |
|
3969 }; |
|
3970 |
|
3971 /** |
|
3972 * Returns the previous part of the property value or null if there is no |
|
3973 * previous part. |
|
3974 * @return {parserlib.css.PropertyValuePart} The previous part of the |
|
3975 * property value or null if there is no previous part. |
|
3976 * @method previous |
|
3977 */ |
|
3978 PropertyValueIterator.prototype.previous = function() { |
|
3979 return this._i > 0 ? this._parts[--this._i] : null; |
|
3980 }; |
|
3981 |
|
3982 /** |
|
3983 * Restores the last saved bookmark. |
|
3984 * @return {void} |
|
3985 * @method restore |
|
3986 */ |
|
3987 PropertyValueIterator.prototype.restore = function() { |
|
3988 if (this._marks.length) { |
|
3989 this._i = this._marks.pop(); |
|
3990 } |
|
3991 }; |
|
3992 |
|
3993 /** |
|
3994 * Drops the last saved bookmark. |
|
3995 * @return {void} |
|
3996 * @method drop |
|
3997 */ |
|
3998 PropertyValueIterator.prototype.drop = function() { |
|
3999 this._marks.pop(); |
|
4000 }; |
|
4001 |
|
4002 },{}],11:[function(require,module,exports){ |
|
4003 "use strict"; |
|
4004 |
|
4005 module.exports = PropertyValuePart; |
|
4006 |
|
4007 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
4008 |
|
4009 var Colors = require("./Colors"); |
|
4010 var Parser = require("./Parser"); |
|
4011 var Tokens = require("./Tokens"); |
|
4012 |
|
4013 /** |
|
4014 * Represents a single part of a CSS property value, meaning that it represents |
|
4015 * just one part of the data between ":" and ";". |
|
4016 * @param {String} text The text representation of the unit. |
|
4017 * @param {int} line The line of text on which the unit resides. |
|
4018 * @param {int} col The column of text on which the unit resides. |
|
4019 * @namespace parserlib.css |
|
4020 * @class PropertyValuePart |
|
4021 * @extends parserlib.util.SyntaxUnit |
|
4022 * @constructor |
|
4023 */ |
|
4024 function PropertyValuePart(text, line, col, optionalHint) { |
|
4025 var hint = optionalHint || {}; |
|
4026 |
|
4027 SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE); |
|
4028 |
|
4029 /** |
|
4030 * Indicates the type of value unit. |
|
4031 * @type String |
|
4032 * @property type |
|
4033 */ |
|
4034 this.type = "unknown"; |
|
4035 |
|
4036 //figure out what type of data it is |
|
4037 |
|
4038 var temp; |
|
4039 |
|
4040 //it is a measurement? |
|
4041 if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)) { //dimension |
|
4042 this.type = "dimension"; |
|
4043 this.value = +RegExp.$1; |
|
4044 this.units = RegExp.$2; |
|
4045 |
|
4046 //try to narrow down |
|
4047 switch (this.units.toLowerCase()) { |
|
4048 |
|
4049 case "em": |
|
4050 case "rem": |
|
4051 case "ex": |
|
4052 case "px": |
|
4053 case "cm": |
|
4054 case "mm": |
|
4055 case "in": |
|
4056 case "pt": |
|
4057 case "pc": |
|
4058 case "ch": |
|
4059 case "vh": |
|
4060 case "vw": |
|
4061 case "vmax": |
|
4062 case "vmin": |
|
4063 this.type = "length"; |
|
4064 break; |
|
4065 |
|
4066 case "fr": |
|
4067 this.type = "grid"; |
|
4068 break; |
|
4069 |
|
4070 case "deg": |
|
4071 case "rad": |
|
4072 case "grad": |
|
4073 case "turn": |
|
4074 this.type = "angle"; |
|
4075 break; |
|
4076 |
|
4077 case "ms": |
|
4078 case "s": |
|
4079 this.type = "time"; |
|
4080 break; |
|
4081 |
|
4082 case "hz": |
|
4083 case "khz": |
|
4084 this.type = "frequency"; |
|
4085 break; |
|
4086 |
|
4087 case "dpi": |
|
4088 case "dpcm": |
|
4089 this.type = "resolution"; |
|
4090 break; |
|
4091 |
|
4092 //default |
|
4093 |
|
4094 } |
|
4095 |
|
4096 } else if (/^([+\-]?[\d\.]+)%$/i.test(text)) { //percentage |
|
4097 this.type = "percentage"; |
|
4098 this.value = +RegExp.$1; |
|
4099 } else if (/^([+\-]?\d+)$/i.test(text)) { //integer |
|
4100 this.type = "integer"; |
|
4101 this.value = +RegExp.$1; |
|
4102 } else if (/^([+\-]?[\d\.]+)$/i.test(text)) { //number |
|
4103 this.type = "number"; |
|
4104 this.value = +RegExp.$1; |
|
4105 |
|
4106 } else if (/^#([a-f0-9]{3,6})/i.test(text)) { //hexcolor |
|
4107 this.type = "color"; |
|
4108 temp = RegExp.$1; |
|
4109 if (temp.length === 3) { |
|
4110 this.red = parseInt(temp.charAt(0)+temp.charAt(0), 16); |
|
4111 this.green = parseInt(temp.charAt(1)+temp.charAt(1), 16); |
|
4112 this.blue = parseInt(temp.charAt(2)+temp.charAt(2), 16); |
|
4113 } else { |
|
4114 this.red = parseInt(temp.substring(0, 2), 16); |
|
4115 this.green = parseInt(temp.substring(2, 4), 16); |
|
4116 this.blue = parseInt(temp.substring(4, 6), 16); |
|
4117 } |
|
4118 } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)) { //rgb() color with absolute numbers |
|
4119 this.type = "color"; |
|
4120 this.red = +RegExp.$1; |
|
4121 this.green = +RegExp.$2; |
|
4122 this.blue = +RegExp.$3; |
|
4123 } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)) { //rgb() color with percentages |
|
4124 this.type = "color"; |
|
4125 this.red = +RegExp.$1 * 255 / 100; |
|
4126 this.green = +RegExp.$2 * 255 / 100; |
|
4127 this.blue = +RegExp.$3 * 255 / 100; |
|
4128 } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)) { //rgba() color with absolute numbers |
|
4129 this.type = "color"; |
|
4130 this.red = +RegExp.$1; |
|
4131 this.green = +RegExp.$2; |
|
4132 this.blue = +RegExp.$3; |
|
4133 this.alpha = +RegExp.$4; |
|
4134 } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)) { //rgba() color with percentages |
|
4135 this.type = "color"; |
|
4136 this.red = +RegExp.$1 * 255 / 100; |
|
4137 this.green = +RegExp.$2 * 255 / 100; |
|
4138 this.blue = +RegExp.$3 * 255 / 100; |
|
4139 this.alpha = +RegExp.$4; |
|
4140 } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)) { //hsl() |
|
4141 this.type = "color"; |
|
4142 this.hue = +RegExp.$1; |
|
4143 this.saturation = +RegExp.$2 / 100; |
|
4144 this.lightness = +RegExp.$3 / 100; |
|
4145 } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)) { //hsla() color with percentages |
|
4146 this.type = "color"; |
|
4147 this.hue = +RegExp.$1; |
|
4148 this.saturation = +RegExp.$2 / 100; |
|
4149 this.lightness = +RegExp.$3 / 100; |
|
4150 this.alpha = +RegExp.$4; |
|
4151 } else if (/^url\(("([^\\"]|\\.)*")\)/i.test(text)) { //URI |
|
4152 // generated by TokenStream.readURI, so always double-quoted. |
|
4153 this.type = "uri"; |
|
4154 this.uri = PropertyValuePart.parseString(RegExp.$1); |
|
4155 } else if (/^([^\(]+)\(/i.test(text)) { |
|
4156 this.type = "function"; |
|
4157 this.name = RegExp.$1; |
|
4158 this.value = text; |
|
4159 } else if (/^"([^\n\r\f\\"]|\\\r\n|\\[^\r0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*"/i.test(text)) { //double-quoted string |
|
4160 this.type = "string"; |
|
4161 this.value = PropertyValuePart.parseString(text); |
|
4162 } else if (/^'([^\n\r\f\\']|\\\r\n|\\[^\r0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*'/i.test(text)) { //single-quoted string |
|
4163 this.type = "string"; |
|
4164 this.value = PropertyValuePart.parseString(text); |
|
4165 } else if (Colors[text.toLowerCase()]) { //named color |
|
4166 this.type = "color"; |
|
4167 temp = Colors[text.toLowerCase()].substring(1); |
|
4168 this.red = parseInt(temp.substring(0, 2), 16); |
|
4169 this.green = parseInt(temp.substring(2, 4), 16); |
|
4170 this.blue = parseInt(temp.substring(4, 6), 16); |
|
4171 } else if (/^[,\/]$/.test(text)) { |
|
4172 this.type = "operator"; |
|
4173 this.value = text; |
|
4174 } else if (/^-?[a-z_\u00A0-\uFFFF][a-z0-9\-_\u00A0-\uFFFF]*$/i.test(text)) { |
|
4175 this.type = "identifier"; |
|
4176 this.value = text; |
|
4177 } |
|
4178 |
|
4179 // There can be ambiguity with escape sequences in identifiers, as |
|
4180 // well as with "color" parts which are also "identifiers", so record |
|
4181 // an explicit hint when the token generating this PropertyValuePart |
|
4182 // was an identifier. |
|
4183 this.wasIdent = Boolean(hint.ident); |
|
4184 |
|
4185 } |
|
4186 |
|
4187 PropertyValuePart.prototype = new SyntaxUnit(); |
|
4188 PropertyValuePart.prototype.constructor = PropertyValuePart; |
|
4189 |
|
4190 /** |
|
4191 * Helper method to parse a CSS string. |
|
4192 */ |
|
4193 PropertyValuePart.parseString = function(str) { |
|
4194 str = str.slice(1, -1); // Strip surrounding single/double quotes |
|
4195 var replacer = function(match, esc) { |
|
4196 if (/^(\n|\r\n|\r|\f)$/.test(esc)) { |
|
4197 return ""; |
|
4198 } |
|
4199 var m = /^[0-9a-f]{1,6}/i.exec(esc); |
|
4200 if (m) { |
|
4201 var codePoint = parseInt(m[0], 16); |
|
4202 if (String.fromCodePoint) { |
|
4203 return String.fromCodePoint(codePoint); |
|
4204 } else { |
|
4205 // XXX No support for surrogates on old JavaScript engines. |
|
4206 return String.fromCharCode(codePoint); |
|
4207 } |
|
4208 } |
|
4209 return esc; |
|
4210 }; |
|
4211 return str.replace(/\\(\r\n|[^\r0-9a-f]|[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)/ig, |
|
4212 replacer); |
|
4213 }; |
|
4214 |
|
4215 /** |
|
4216 * Helper method to serialize a CSS string. |
|
4217 */ |
|
4218 PropertyValuePart.serializeString = function(value) { |
|
4219 var replacer = function(match, c) { |
|
4220 if (c === "\"") { |
|
4221 return "\\" + c; |
|
4222 } |
|
4223 var cp = String.codePointAt ? String.codePointAt(0) : |
|
4224 // We only escape non-surrogate chars, so using charCodeAt |
|
4225 // is harmless here. |
|
4226 String.charCodeAt(0); |
|
4227 return "\\" + cp.toString(16) + " "; |
|
4228 }; |
|
4229 return "\"" + value.replace(/["\r\n\f]/g, replacer) + "\""; |
|
4230 }; |
|
4231 |
|
4232 /** |
|
4233 * Create a new syntax unit based solely on the given token. |
|
4234 * Convenience method for creating a new syntax unit when |
|
4235 * it represents a single token instead of multiple. |
|
4236 * @param {Object} token The token object to represent. |
|
4237 * @return {parserlib.css.PropertyValuePart} The object representing the token. |
|
4238 * @static |
|
4239 * @method fromToken |
|
4240 */ |
|
4241 PropertyValuePart.fromToken = function(token) { |
|
4242 var part = new PropertyValuePart(token.value, token.startLine, token.startCol, { |
|
4243 // Tokens can have escaped characters that would fool the type |
|
4244 // identification in the PropertyValuePart constructor, so pass |
|
4245 // in a hint if this was an identifier. |
|
4246 ident: token.type === Tokens.IDENT |
|
4247 }); |
|
4248 return part; |
|
4249 }; |
|
4250 |
|
4251 },{"../util/SyntaxUnit":26,"./Colors":1,"./Parser":6,"./Tokens":18}],12:[function(require,module,exports){ |
|
4252 "use strict"; |
|
4253 |
|
4254 var Pseudos = module.exports = { |
|
4255 __proto__: null, |
|
4256 ":first-letter": 1, |
|
4257 ":first-line": 1, |
|
4258 ":before": 1, |
|
4259 ":after": 1 |
|
4260 }; |
|
4261 |
|
4262 Pseudos.ELEMENT = 1; |
|
4263 Pseudos.CLASS = 2; |
|
4264 |
|
4265 Pseudos.isElement = function(pseudo) { |
|
4266 return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] === Pseudos.ELEMENT; |
|
4267 }; |
|
4268 |
|
4269 },{}],13:[function(require,module,exports){ |
|
4270 "use strict"; |
|
4271 |
|
4272 module.exports = Selector; |
|
4273 |
|
4274 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
4275 |
|
4276 var Parser = require("./Parser"); |
|
4277 var Specificity = require("./Specificity"); |
|
4278 |
|
4279 /** |
|
4280 * Represents an entire single selector, including all parts but not |
|
4281 * including multiple selectors (those separated by commas). |
|
4282 * @namespace parserlib.css |
|
4283 * @class Selector |
|
4284 * @extends parserlib.util.SyntaxUnit |
|
4285 * @constructor |
|
4286 * @param {Array} parts Array of selectors parts making up this selector. |
|
4287 * @param {int} line The line of text on which the unit resides. |
|
4288 * @param {int} col The column of text on which the unit resides. |
|
4289 */ |
|
4290 function Selector(parts, line, col) { |
|
4291 |
|
4292 SyntaxUnit.call(this, parts.join(" "), line, col, Parser.SELECTOR_TYPE); |
|
4293 |
|
4294 /** |
|
4295 * The parts that make up the selector. |
|
4296 * @type Array |
|
4297 * @property parts |
|
4298 */ |
|
4299 this.parts = parts; |
|
4300 |
|
4301 /** |
|
4302 * The specificity of the selector. |
|
4303 * @type parserlib.css.Specificity |
|
4304 * @property specificity |
|
4305 */ |
|
4306 this.specificity = Specificity.calculate(this); |
|
4307 |
|
4308 } |
|
4309 |
|
4310 Selector.prototype = new SyntaxUnit(); |
|
4311 Selector.prototype.constructor = Selector; |
|
4312 |
|
4313 |
|
4314 },{"../util/SyntaxUnit":26,"./Parser":6,"./Specificity":16}],14:[function(require,module,exports){ |
|
4315 "use strict"; |
|
4316 |
|
4317 module.exports = SelectorPart; |
|
4318 |
|
4319 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
4320 |
|
4321 var Parser = require("./Parser"); |
|
4322 |
|
4323 /** |
|
4324 * Represents a single part of a selector string, meaning a single set of |
|
4325 * element name and modifiers. This does not include combinators such as |
|
4326 * spaces, +, >, etc. |
|
4327 * @namespace parserlib.css |
|
4328 * @class SelectorPart |
|
4329 * @extends parserlib.util.SyntaxUnit |
|
4330 * @constructor |
|
4331 * @param {String} elementName The element name in the selector or null |
|
4332 * if there is no element name. |
|
4333 * @param {Array} modifiers Array of individual modifiers for the element. |
|
4334 * May be empty if there are none. |
|
4335 * @param {String} text The text representation of the unit. |
|
4336 * @param {int} line The line of text on which the unit resides. |
|
4337 * @param {int} col The column of text on which the unit resides. |
|
4338 */ |
|
4339 function SelectorPart(elementName, modifiers, text, line, col) { |
|
4340 |
|
4341 SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE); |
|
4342 |
|
4343 /** |
|
4344 * The tag name of the element to which this part |
|
4345 * of the selector affects. |
|
4346 * @type String |
|
4347 * @property elementName |
|
4348 */ |
|
4349 this.elementName = elementName; |
|
4350 |
|
4351 /** |
|
4352 * The parts that come after the element name, such as class names, IDs, |
|
4353 * pseudo classes/elements, etc. |
|
4354 * @type Array |
|
4355 * @property modifiers |
|
4356 */ |
|
4357 this.modifiers = modifiers; |
|
4358 |
|
4359 } |
|
4360 |
|
4361 SelectorPart.prototype = new SyntaxUnit(); |
|
4362 SelectorPart.prototype.constructor = SelectorPart; |
|
4363 |
|
4364 |
|
4365 },{"../util/SyntaxUnit":26,"./Parser":6}],15:[function(require,module,exports){ |
|
4366 "use strict"; |
|
4367 |
|
4368 module.exports = SelectorSubPart; |
|
4369 |
|
4370 var SyntaxUnit = require("../util/SyntaxUnit"); |
|
4371 |
|
4372 var Parser = require("./Parser"); |
|
4373 |
|
4374 /** |
|
4375 * Represents a selector modifier string, meaning a class name, element name, |
|
4376 * element ID, pseudo rule, etc. |
|
4377 * @namespace parserlib.css |
|
4378 * @class SelectorSubPart |
|
4379 * @extends parserlib.util.SyntaxUnit |
|
4380 * @constructor |
|
4381 * @param {String} text The text representation of the unit. |
|
4382 * @param {String} type The type of selector modifier. |
|
4383 * @param {int} line The line of text on which the unit resides. |
|
4384 * @param {int} col The column of text on which the unit resides. |
|
4385 */ |
|
4386 function SelectorSubPart(text, type, line, col) { |
|
4387 |
|
4388 SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_TYPE); |
|
4389 |
|
4390 /** |
|
4391 * The type of modifier. |
|
4392 * @type String |
|
4393 * @property type |
|
4394 */ |
|
4395 this.type = type; |
|
4396 |
|
4397 /** |
|
4398 * Some subparts have arguments, this represents them. |
|
4399 * @type Array |
|
4400 * @property args |
|
4401 */ |
|
4402 this.args = []; |
|
4403 |
|
4404 } |
|
4405 |
|
4406 SelectorSubPart.prototype = new SyntaxUnit(); |
|
4407 SelectorSubPart.prototype.constructor = SelectorSubPart; |
|
4408 |
|
4409 |
|
4410 },{"../util/SyntaxUnit":26,"./Parser":6}],16:[function(require,module,exports){ |
|
4411 "use strict"; |
|
4412 |
|
4413 module.exports = Specificity; |
|
4414 |
|
4415 var Pseudos = require("./Pseudos"); |
|
4416 var SelectorPart = require("./SelectorPart"); |
|
4417 |
|
4418 /** |
|
4419 * Represents a selector's specificity. |
|
4420 * @namespace parserlib.css |
|
4421 * @class Specificity |
|
4422 * @constructor |
|
4423 * @param {int} a Should be 1 for inline styles, zero for stylesheet styles |
|
4424 * @param {int} b Number of ID selectors |
|
4425 * @param {int} c Number of classes and pseudo classes |
|
4426 * @param {int} d Number of element names and pseudo elements |
|
4427 */ |
|
4428 function Specificity(a, b, c, d) { |
|
4429 this.a = a; |
|
4430 this.b = b; |
|
4431 this.c = c; |
|
4432 this.d = d; |
|
4433 } |
|
4434 |
|
4435 Specificity.prototype = { |
|
4436 constructor: Specificity, |
|
4437 |
|
4438 /** |
|
4439 * Compare this specificity to another. |
|
4440 * @param {Specificity} other The other specificity to compare to. |
|
4441 * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal. |
|
4442 * @method compare |
|
4443 */ |
|
4444 compare: function(other) { |
|
4445 var comps = ["a", "b", "c", "d"], |
|
4446 i, len; |
|
4447 |
|
4448 for (i=0, len=comps.length; i < len; i++) { |
|
4449 if (this[comps[i]] < other[comps[i]]) { |
|
4450 return -1; |
|
4451 } else if (this[comps[i]] > other[comps[i]]) { |
|
4452 return 1; |
|
4453 } |
|
4454 } |
|
4455 |
|
4456 return 0; |
|
4457 }, |
|
4458 |
|
4459 /** |
|
4460 * Creates a numeric value for the specificity. |
|
4461 * @return {int} The numeric value for the specificity. |
|
4462 * @method valueOf |
|
4463 */ |
|
4464 valueOf: function() { |
|
4465 return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d; |
|
4466 }, |
|
4467 |
|
4468 /** |
|
4469 * Returns a string representation for specificity. |
|
4470 * @return {String} The string representation of specificity. |
|
4471 * @method toString |
|
4472 */ |
|
4473 toString: function() { |
|
4474 return this.a + "," + this.b + "," + this.c + "," + this.d; |
|
4475 } |
|
4476 |
|
4477 }; |
|
4478 |
|
4479 /** |
|
4480 * Calculates the specificity of the given selector. |
|
4481 * @param {parserlib.css.Selector} The selector to calculate specificity for. |
|
4482 * @return {parserlib.css.Specificity} The specificity of the selector. |
|
4483 * @static |
|
4484 * @method calculate |
|
4485 */ |
|
4486 Specificity.calculate = function(selector) { |
|
4487 |
|
4488 var i, len, |
|
4489 part, |
|
4490 b=0, c=0, d=0; |
|
4491 |
|
4492 function updateValues(part) { |
|
4493 |
|
4494 var i, j, len, num, |
|
4495 elementName = part.elementName ? part.elementName.text : "", |
|
4496 modifier; |
|
4497 |
|
4498 if (elementName && elementName.charAt(elementName.length-1) !== "*") { |
|
4499 d++; |
|
4500 } |
|
4501 |
|
4502 for (i=0, len=part.modifiers.length; i < len; i++) { |
|
4503 modifier = part.modifiers[i]; |
|
4504 switch (modifier.type) { |
|
4505 case "class": |
|
4506 case "attribute": |
|
4507 c++; |
|
4508 break; |
|
4509 |
|
4510 case "id": |
|
4511 b++; |
|
4512 break; |
|
4513 |
|
4514 case "pseudo": |
|
4515 if (Pseudos.isElement(modifier.text)) { |
|
4516 d++; |
|
4517 } else { |
|
4518 c++; |
|
4519 } |
|
4520 break; |
|
4521 |
|
4522 case "not": |
|
4523 for (j=0, num=modifier.args.length; j < num; j++) { |
|
4524 updateValues(modifier.args[j]); |
|
4525 } |
|
4526 } |
|
4527 } |
|
4528 } |
|
4529 |
|
4530 for (i=0, len=selector.parts.length; i < len; i++) { |
|
4531 part = selector.parts[i]; |
|
4532 |
|
4533 if (part instanceof SelectorPart) { |
|
4534 updateValues(part); |
|
4535 } |
|
4536 } |
|
4537 |
|
4538 return new Specificity(0, b, c, d); |
|
4539 }; |
|
4540 |
|
4541 },{"./Pseudos":12,"./SelectorPart":14}],17:[function(require,module,exports){ |
|
4542 "use strict"; |
|
4543 |
|
4544 module.exports = TokenStream; |
|
4545 |
|
4546 var TokenStreamBase = require("../util/TokenStreamBase"); |
|
4547 |
|
4548 var PropertyValuePart = require("./PropertyValuePart"); |
|
4549 var Tokens = require("./Tokens"); |
|
4550 |
|
4551 var h = /^[0-9a-fA-F]$/, |
|
4552 nonascii = /^[\u00A0-\uFFFF]$/, |
|
4553 nl = /\n|\r\n|\r|\f/, |
|
4554 whitespace = /\u0009|\u000a|\u000c|\u000d|\u0020/; |
|
4555 |
|
4556 //----------------------------------------------------------------------------- |
|
4557 // Helper functions |
|
4558 //----------------------------------------------------------------------------- |
|
4559 |
|
4560 |
|
4561 function isHexDigit(c) { |
|
4562 return c !== null && h.test(c); |
|
4563 } |
|
4564 |
|
4565 function isDigit(c) { |
|
4566 return c !== null && /\d/.test(c); |
|
4567 } |
|
4568 |
|
4569 function isWhitespace(c) { |
|
4570 return c !== null && whitespace.test(c); |
|
4571 } |
|
4572 |
|
4573 function isNewLine(c) { |
|
4574 return c !== null && nl.test(c); |
|
4575 } |
|
4576 |
|
4577 function isNameStart(c) { |
|
4578 return c !== null && /[a-z_\u00A0-\uFFFF\\]/i.test(c); |
|
4579 } |
|
4580 |
|
4581 function isNameChar(c) { |
|
4582 return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c)); |
|
4583 } |
|
4584 |
|
4585 function isIdentStart(c) { |
|
4586 return c !== null && (isNameStart(c) || /\-\\/.test(c)); |
|
4587 } |
|
4588 |
|
4589 function mix(receiver, supplier) { |
|
4590 for (var prop in supplier) { |
|
4591 if (Object.prototype.hasOwnProperty.call(supplier, prop)) { |
|
4592 receiver[prop] = supplier[prop]; |
|
4593 } |
|
4594 } |
|
4595 return receiver; |
|
4596 } |
|
4597 |
|
4598 //----------------------------------------------------------------------------- |
|
4599 // CSS Token Stream |
|
4600 //----------------------------------------------------------------------------- |
|
4601 |
|
4602 |
|
4603 /** |
|
4604 * A token stream that produces CSS tokens. |
|
4605 * @param {String|Reader} input The source of text to tokenize. |
|
4606 * @constructor |
|
4607 * @class TokenStream |
|
4608 * @namespace parserlib.css |
|
4609 */ |
|
4610 function TokenStream(input) { |
|
4611 TokenStreamBase.call(this, input, Tokens); |
|
4612 } |
|
4613 |
|
4614 TokenStream.prototype = mix(new TokenStreamBase(), { |
|
4615 |
|
4616 /** |
|
4617 * Overrides the TokenStreamBase method of the same name |
|
4618 * to produce CSS tokens. |
|
4619 * @return {Object} A token object representing the next token. |
|
4620 * @method _getToken |
|
4621 * @private |
|
4622 */ |
|
4623 _getToken: function() { |
|
4624 |
|
4625 var c, |
|
4626 reader = this._reader, |
|
4627 token = null, |
|
4628 startLine = reader.getLine(), |
|
4629 startCol = reader.getCol(); |
|
4630 |
|
4631 c = reader.read(); |
|
4632 |
|
4633 |
|
4634 while (c) { |
|
4635 switch (c) { |
|
4636 |
|
4637 /* |
|
4638 * Potential tokens: |
|
4639 * - COMMENT |
|
4640 * - SLASH |
|
4641 * - CHAR |
|
4642 */ |
|
4643 case "/": |
|
4644 |
|
4645 if (reader.peek() === "*") { |
|
4646 token = this.commentToken(c, startLine, startCol); |
|
4647 } else { |
|
4648 token = this.charToken(c, startLine, startCol); |
|
4649 } |
|
4650 break; |
|
4651 |
|
4652 /* |
|
4653 * Potential tokens: |
|
4654 * - DASHMATCH |
|
4655 * - INCLUDES |
|
4656 * - PREFIXMATCH |
|
4657 * - SUFFIXMATCH |
|
4658 * - SUBSTRINGMATCH |
|
4659 * - CHAR |
|
4660 */ |
|
4661 case "|": |
|
4662 case "~": |
|
4663 case "^": |
|
4664 case "$": |
|
4665 case "*": |
|
4666 if (reader.peek() === "=") { |
|
4667 token = this.comparisonToken(c, startLine, startCol); |
|
4668 } else { |
|
4669 token = this.charToken(c, startLine, startCol); |
|
4670 } |
|
4671 break; |
|
4672 |
|
4673 /* |
|
4674 * Potential tokens: |
|
4675 * - STRING |
|
4676 * - INVALID |
|
4677 */ |
|
4678 case "\"": |
|
4679 case "'": |
|
4680 token = this.stringToken(c, startLine, startCol); |
|
4681 break; |
|
4682 |
|
4683 /* |
|
4684 * Potential tokens: |
|
4685 * - HASH |
|
4686 * - CHAR |
|
4687 */ |
|
4688 case "#": |
|
4689 if (isNameChar(reader.peek())) { |
|
4690 token = this.hashToken(c, startLine, startCol); |
|
4691 } else { |
|
4692 token = this.charToken(c, startLine, startCol); |
|
4693 } |
|
4694 break; |
|
4695 |
|
4696 /* |
|
4697 * Potential tokens: |
|
4698 * - DOT |
|
4699 * - NUMBER |
|
4700 * - DIMENSION |
|
4701 * - PERCENTAGE |
|
4702 */ |
|
4703 case ".": |
|
4704 if (isDigit(reader.peek())) { |
|
4705 token = this.numberToken(c, startLine, startCol); |
|
4706 } else { |
|
4707 token = this.charToken(c, startLine, startCol); |
|
4708 } |
|
4709 break; |
|
4710 |
|
4711 /* |
|
4712 * Potential tokens: |
|
4713 * - CDC |
|
4714 * - MINUS |
|
4715 * - NUMBER |
|
4716 * - DIMENSION |
|
4717 * - PERCENTAGE |
|
4718 */ |
|
4719 case "-": |
|
4720 if (reader.peek() === "-") { //could be closing HTML-style comment |
|
4721 token = this.htmlCommentEndToken(c, startLine, startCol); |
|
4722 } else if (isNameStart(reader.peek())) { |
|
4723 token = this.identOrFunctionToken(c, startLine, startCol); |
|
4724 } else { |
|
4725 token = this.charToken(c, startLine, startCol); |
|
4726 } |
|
4727 break; |
|
4728 |
|
4729 /* |
|
4730 * Potential tokens: |
|
4731 * - IMPORTANT_SYM |
|
4732 * - CHAR |
|
4733 */ |
|
4734 case "!": |
|
4735 token = this.importantToken(c, startLine, startCol); |
|
4736 break; |
|
4737 |
|
4738 /* |
|
4739 * Any at-keyword or CHAR |
|
4740 */ |
|
4741 case "@": |
|
4742 token = this.atRuleToken(c, startLine, startCol); |
|
4743 break; |
|
4744 |
|
4745 /* |
|
4746 * Potential tokens: |
|
4747 * - NOT |
|
4748 * - CHAR |
|
4749 */ |
|
4750 case ":": |
|
4751 token = this.notToken(c, startLine, startCol); |
|
4752 break; |
|
4753 |
|
4754 /* |
|
4755 * Potential tokens: |
|
4756 * - CDO |
|
4757 * - CHAR |
|
4758 */ |
|
4759 case "<": |
|
4760 token = this.htmlCommentStartToken(c, startLine, startCol); |
|
4761 break; |
|
4762 |
|
4763 /* |
|
4764 * Potential tokens: |
|
4765 * - IDENT |
|
4766 * - CHAR |
|
4767 */ |
|
4768 case "\\": |
|
4769 if (/[^\r\n\f]/.test(reader.peek())) { |
|
4770 token = this.identOrFunctionToken(this.readEscape(c, true), startLine, startCol); |
|
4771 } else { |
|
4772 token = this.charToken(c, startLine, startCol); |
|
4773 } |
|
4774 break; |
|
4775 |
|
4776 /* |
|
4777 * Potential tokens: |
|
4778 * - UNICODE_RANGE |
|
4779 * - URL |
|
4780 * - CHAR |
|
4781 */ |
|
4782 case "U": |
|
4783 case "u": |
|
4784 if (reader.peek() === "+") { |
|
4785 token = this.unicodeRangeToken(c, startLine, startCol); |
|
4786 break; |
|
4787 } |
|
4788 /* falls through */ |
|
4789 default: |
|
4790 |
|
4791 /* |
|
4792 * Potential tokens: |
|
4793 * - NUMBER |
|
4794 * - DIMENSION |
|
4795 * - LENGTH |
|
4796 * - FREQ |
|
4797 * - TIME |
|
4798 * - EMS |
|
4799 * - EXS |
|
4800 * - ANGLE |
|
4801 */ |
|
4802 if (isDigit(c)) { |
|
4803 token = this.numberToken(c, startLine, startCol); |
|
4804 } else |
|
4805 |
|
4806 /* |
|
4807 * Potential tokens: |
|
4808 * - S |
|
4809 */ |
|
4810 if (isWhitespace(c)) { |
|
4811 token = this.whitespaceToken(c, startLine, startCol); |
|
4812 } else |
|
4813 |
|
4814 /* |
|
4815 * Potential tokens: |
|
4816 * - IDENT |
|
4817 */ |
|
4818 if (isIdentStart(c)) { |
|
4819 token = this.identOrFunctionToken(c, startLine, startCol); |
|
4820 } else { |
|
4821 /* |
|
4822 * Potential tokens: |
|
4823 * - CHAR |
|
4824 * - PLUS |
|
4825 */ |
|
4826 token = this.charToken(c, startLine, startCol); |
|
4827 } |
|
4828 |
|
4829 } |
|
4830 |
|
4831 //make sure this token is wanted |
|
4832 //TODO: check channel |
|
4833 break; |
|
4834 } |
|
4835 |
|
4836 if (!token && c === null) { |
|
4837 token = this.createToken(Tokens.EOF, null, startLine, startCol); |
|
4838 } |
|
4839 |
|
4840 return token; |
|
4841 }, |
|
4842 |
|
4843 //------------------------------------------------------------------------- |
|
4844 // Methods to create tokens |
|
4845 //------------------------------------------------------------------------- |
|
4846 |
|
4847 /** |
|
4848 * Produces a token based on available data and the current |
|
4849 * reader position information. This method is called by other |
|
4850 * private methods to create tokens and is never called directly. |
|
4851 * @param {int} tt The token type. |
|
4852 * @param {String} value The text value of the token. |
|
4853 * @param {int} startLine The beginning line for the character. |
|
4854 * @param {int} startCol The beginning column for the character. |
|
4855 * @param {Object} options (Optional) Specifies a channel property |
|
4856 * to indicate that a different channel should be scanned |
|
4857 * and/or a hide property indicating that the token should |
|
4858 * be hidden. |
|
4859 * @return {Object} A token object. |
|
4860 * @method createToken |
|
4861 */ |
|
4862 createToken: function(tt, value, startLine, startCol, options) { |
|
4863 var reader = this._reader; |
|
4864 options = options || {}; |
|
4865 |
|
4866 return { |
|
4867 value: value, |
|
4868 type: tt, |
|
4869 channel: options.channel, |
|
4870 endChar: options.endChar, |
|
4871 hide: options.hide || false, |
|
4872 startLine: startLine, |
|
4873 startCol: startCol, |
|
4874 endLine: reader.getLine(), |
|
4875 endCol: reader.getCol() |
|
4876 }; |
|
4877 }, |
|
4878 |
|
4879 //------------------------------------------------------------------------- |
|
4880 // Methods to create specific tokens |
|
4881 //------------------------------------------------------------------------- |
|
4882 |
|
4883 /** |
|
4884 * Produces a token for any at-rule. If the at-rule is unknown, then |
|
4885 * the token is for a single "@" character. |
|
4886 * @param {String} first The first character for the token. |
|
4887 * @param {int} startLine The beginning line for the character. |
|
4888 * @param {int} startCol The beginning column for the character. |
|
4889 * @return {Object} A token object. |
|
4890 * @method atRuleToken |
|
4891 */ |
|
4892 atRuleToken: function(first, startLine, startCol) { |
|
4893 var rule = first, |
|
4894 reader = this._reader, |
|
4895 tt = Tokens.CHAR, |
|
4896 ident; |
|
4897 |
|
4898 /* |
|
4899 * First, mark where we are. There are only four @ rules, |
|
4900 * so anything else is really just an invalid token. |
|
4901 * Basically, if this doesn't match one of the known @ |
|
4902 * rules, just return '@' as an unknown token and allow |
|
4903 * parsing to continue after that point. |
|
4904 */ |
|
4905 reader.mark(); |
|
4906 |
|
4907 //try to find the at-keyword |
|
4908 ident = this.readName(); |
|
4909 rule = first + ident; |
|
4910 tt = Tokens.type(rule.toLowerCase()); |
|
4911 |
|
4912 //if it's not valid, use the first character only and reset the reader |
|
4913 if (tt === Tokens.CHAR || tt === Tokens.UNKNOWN) { |
|
4914 if (rule.length > 1) { |
|
4915 tt = Tokens.UNKNOWN_SYM; |
|
4916 } else { |
|
4917 tt = Tokens.CHAR; |
|
4918 rule = first; |
|
4919 reader.reset(); |
|
4920 } |
|
4921 } |
|
4922 |
|
4923 return this.createToken(tt, rule, startLine, startCol); |
|
4924 }, |
|
4925 |
|
4926 /** |
|
4927 * Produces a character token based on the given character |
|
4928 * and location in the stream. If there's a special (non-standard) |
|
4929 * token name, this is used; otherwise CHAR is used. |
|
4930 * @param {String} c The character for the token. |
|
4931 * @param {int} startLine The beginning line for the character. |
|
4932 * @param {int} startCol The beginning column for the character. |
|
4933 * @return {Object} A token object. |
|
4934 * @method charToken |
|
4935 */ |
|
4936 charToken: function(c, startLine, startCol) { |
|
4937 var tt = Tokens.type(c); |
|
4938 var opts = {}; |
|
4939 |
|
4940 if (tt === -1) { |
|
4941 tt = Tokens.CHAR; |
|
4942 } else { |
|
4943 opts.endChar = Tokens[tt].endChar; |
|
4944 } |
|
4945 |
|
4946 return this.createToken(tt, c, startLine, startCol, opts); |
|
4947 }, |
|
4948 |
|
4949 /** |
|
4950 * Produces a character token based on the given character |
|
4951 * and location in the stream. If there's a special (non-standard) |
|
4952 * token name, this is used; otherwise CHAR is used. |
|
4953 * @param {String} first The first character for the token. |
|
4954 * @param {int} startLine The beginning line for the character. |
|
4955 * @param {int} startCol The beginning column for the character. |
|
4956 * @return {Object} A token object. |
|
4957 * @method commentToken |
|
4958 */ |
|
4959 commentToken: function(first, startLine, startCol) { |
|
4960 var comment = this.readComment(first); |
|
4961 |
|
4962 return this.createToken(Tokens.COMMENT, comment, startLine, startCol); |
|
4963 }, |
|
4964 |
|
4965 /** |
|
4966 * Produces a comparison token based on the given character |
|
4967 * and location in the stream. The next character must be |
|
4968 * read and is already known to be an equals sign. |
|
4969 * @param {String} c The character for the token. |
|
4970 * @param {int} startLine The beginning line for the character. |
|
4971 * @param {int} startCol The beginning column for the character. |
|
4972 * @return {Object} A token object. |
|
4973 * @method comparisonToken |
|
4974 */ |
|
4975 comparisonToken: function(c, startLine, startCol) { |
|
4976 var reader = this._reader, |
|
4977 comparison = c + reader.read(), |
|
4978 tt = Tokens.type(comparison) || Tokens.CHAR; |
|
4979 |
|
4980 return this.createToken(tt, comparison, startLine, startCol); |
|
4981 }, |
|
4982 |
|
4983 /** |
|
4984 * Produces a hash token based on the specified information. The |
|
4985 * first character provided is the pound sign (#) and then this |
|
4986 * method reads a name afterward. |
|
4987 * @param {String} first The first character (#) in the hash name. |
|
4988 * @param {int} startLine The beginning line for the character. |
|
4989 * @param {int} startCol The beginning column for the character. |
|
4990 * @return {Object} A token object. |
|
4991 * @method hashToken |
|
4992 */ |
|
4993 hashToken: function(first, startLine, startCol) { |
|
4994 var name = this.readName(first); |
|
4995 |
|
4996 return this.createToken(Tokens.HASH, name, startLine, startCol); |
|
4997 }, |
|
4998 |
|
4999 /** |
|
5000 * Produces a CDO or CHAR token based on the specified information. The |
|
5001 * first character is provided and the rest is read by the function to determine |
|
5002 * the correct token to create. |
|
5003 * @param {String} first The first character in the token. |
|
5004 * @param {int} startLine The beginning line for the character. |
|
5005 * @param {int} startCol The beginning column for the character. |
|
5006 * @return {Object} A token object. |
|
5007 * @method htmlCommentStartToken |
|
5008 */ |
|
5009 htmlCommentStartToken: function(first, startLine, startCol) { |
|
5010 var reader = this._reader, |
|
5011 text = first; |
|
5012 |
|
5013 reader.mark(); |
|
5014 text += reader.readCount(3); |
|
5015 |
|
5016 if (text === "<!--") { |
|
5017 return this.createToken(Tokens.CDO, text, startLine, startCol); |
|
5018 } else { |
|
5019 reader.reset(); |
|
5020 return this.charToken(first, startLine, startCol); |
|
5021 } |
|
5022 }, |
|
5023 |
|
5024 /** |
|
5025 * Produces a CDC or CHAR token based on the specified information. The |
|
5026 * first character is provided and the rest is read by the function to determine |
|
5027 * the correct token to create. |
|
5028 * @param {String} first The first character in the token. |
|
5029 * @param {int} startLine The beginning line for the character. |
|
5030 * @param {int} startCol The beginning column for the character. |
|
5031 * @return {Object} A token object. |
|
5032 * @method htmlCommentEndToken |
|
5033 */ |
|
5034 htmlCommentEndToken: function(first, startLine, startCol) { |
|
5035 var reader = this._reader, |
|
5036 text = first; |
|
5037 |
|
5038 reader.mark(); |
|
5039 text += reader.readCount(2); |
|
5040 |
|
5041 if (text === "-->") { |
|
5042 return this.createToken(Tokens.CDC, text, startLine, startCol); |
|
5043 } else { |
|
5044 reader.reset(); |
|
5045 return this.charToken(first, startLine, startCol); |
|
5046 } |
|
5047 }, |
|
5048 |
|
5049 /** |
|
5050 * Produces an IDENT or FUNCTION token based on the specified information. The |
|
5051 * first character is provided and the rest is read by the function to determine |
|
5052 * the correct token to create. |
|
5053 * @param {String} first The first character in the identifier. |
|
5054 * @param {int} startLine The beginning line for the character. |
|
5055 * @param {int} startCol The beginning column for the character. |
|
5056 * @return {Object} A token object. |
|
5057 * @method identOrFunctionToken |
|
5058 */ |
|
5059 identOrFunctionToken: function(first, startLine, startCol) { |
|
5060 var reader = this._reader, |
|
5061 ident = this.readName(first), |
|
5062 tt = Tokens.IDENT, |
|
5063 uriFns = ["url(", "url-prefix(", "domain("], |
|
5064 uri; |
|
5065 |
|
5066 //if there's a left paren immediately after, it's a URI or function |
|
5067 if (reader.peek() === "(") { |
|
5068 ident += reader.read(); |
|
5069 if (uriFns.indexOf(ident.toLowerCase()) > -1) { |
|
5070 reader.mark(); |
|
5071 uri = this.readURI(ident); |
|
5072 if (uri === null) { |
|
5073 //didn't find a valid URL or there's no closing paren |
|
5074 reader.reset(); |
|
5075 tt = Tokens.FUNCTION; |
|
5076 } else { |
|
5077 tt = Tokens.URI; |
|
5078 ident = uri; |
|
5079 } |
|
5080 } else { |
|
5081 tt = Tokens.FUNCTION; |
|
5082 } |
|
5083 } else if (reader.peek() === ":") { //might be an IE function |
|
5084 |
|
5085 //IE-specific functions always being with progid: |
|
5086 if (ident.toLowerCase() === "progid") { |
|
5087 ident += reader.readTo("("); |
|
5088 tt = Tokens.IE_FUNCTION; |
|
5089 } |
|
5090 } |
|
5091 |
|
5092 return this.createToken(tt, ident, startLine, startCol); |
|
5093 }, |
|
5094 |
|
5095 /** |
|
5096 * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The |
|
5097 * first character is provided and the rest is read by the function to determine |
|
5098 * the correct token to create. |
|
5099 * @param {String} first The first character in the token. |
|
5100 * @param {int} startLine The beginning line for the character. |
|
5101 * @param {int} startCol The beginning column for the character. |
|
5102 * @return {Object} A token object. |
|
5103 * @method importantToken |
|
5104 */ |
|
5105 importantToken: function(first, startLine, startCol) { |
|
5106 var reader = this._reader, |
|
5107 important = first, |
|
5108 tt = Tokens.CHAR, |
|
5109 temp, |
|
5110 c; |
|
5111 |
|
5112 reader.mark(); |
|
5113 c = reader.read(); |
|
5114 |
|
5115 while (c) { |
|
5116 |
|
5117 //there can be a comment in here |
|
5118 if (c === "/") { |
|
5119 |
|
5120 //if the next character isn't a star, then this isn't a valid !important token |
|
5121 if (reader.peek() !== "*") { |
|
5122 break; |
|
5123 } else { |
|
5124 temp = this.readComment(c); |
|
5125 if (temp === "") { //broken! |
|
5126 break; |
|
5127 } |
|
5128 } |
|
5129 } else if (isWhitespace(c)) { |
|
5130 important += c + this.readWhitespace(); |
|
5131 } else if (/i/i.test(c)) { |
|
5132 temp = reader.readCount(8); |
|
5133 if (/mportant/i.test(temp)) { |
|
5134 important += c + temp; |
|
5135 tt = Tokens.IMPORTANT_SYM; |
|
5136 |
|
5137 } |
|
5138 break; //we're done |
|
5139 } else { |
|
5140 break; |
|
5141 } |
|
5142 |
|
5143 c = reader.read(); |
|
5144 } |
|
5145 |
|
5146 if (tt === Tokens.CHAR) { |
|
5147 reader.reset(); |
|
5148 return this.charToken(first, startLine, startCol); |
|
5149 } else { |
|
5150 return this.createToken(tt, important, startLine, startCol); |
|
5151 } |
|
5152 |
|
5153 |
|
5154 }, |
|
5155 |
|
5156 /** |
|
5157 * Produces a NOT or CHAR token based on the specified information. The |
|
5158 * first character is provided and the rest is read by the function to determine |
|
5159 * the correct token to create. |
|
5160 * @param {String} first The first character in the token. |
|
5161 * @param {int} startLine The beginning line for the character. |
|
5162 * @param {int} startCol The beginning column for the character. |
|
5163 * @return {Object} A token object. |
|
5164 * @method notToken |
|
5165 */ |
|
5166 notToken: function(first, startLine, startCol) { |
|
5167 var reader = this._reader, |
|
5168 text = first; |
|
5169 |
|
5170 reader.mark(); |
|
5171 text += reader.readCount(4); |
|
5172 |
|
5173 if (text.toLowerCase() === ":not(") { |
|
5174 return this.createToken(Tokens.NOT, text, startLine, startCol); |
|
5175 } else { |
|
5176 reader.reset(); |
|
5177 return this.charToken(first, startLine, startCol); |
|
5178 } |
|
5179 }, |
|
5180 |
|
5181 /** |
|
5182 * Produces a number token based on the given character |
|
5183 * and location in the stream. This may return a token of |
|
5184 * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION, |
|
5185 * or PERCENTAGE. |
|
5186 * @param {String} first The first character for the token. |
|
5187 * @param {int} startLine The beginning line for the character. |
|
5188 * @param {int} startCol The beginning column for the character. |
|
5189 * @return {Object} A token object. |
|
5190 * @method numberToken |
|
5191 */ |
|
5192 numberToken: function(first, startLine, startCol) { |
|
5193 var reader = this._reader, |
|
5194 value = this.readNumber(first), |
|
5195 ident, |
|
5196 tt = Tokens.NUMBER, |
|
5197 c = reader.peek(); |
|
5198 |
|
5199 if (isIdentStart(c)) { |
|
5200 ident = this.readName(reader.read()); |
|
5201 value += ident; |
|
5202 |
|
5203 if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vmax$|^vmin$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)) { |
|
5204 tt = Tokens.LENGTH; |
|
5205 } else if (/^deg|^rad$|^grad$|^turn$/i.test(ident)) { |
|
5206 tt = Tokens.ANGLE; |
|
5207 } else if (/^ms$|^s$/i.test(ident)) { |
|
5208 tt = Tokens.TIME; |
|
5209 } else if (/^hz$|^khz$/i.test(ident)) { |
|
5210 tt = Tokens.FREQ; |
|
5211 } else if (/^dpi$|^dpcm$/i.test(ident)) { |
|
5212 tt = Tokens.RESOLUTION; |
|
5213 } else { |
|
5214 tt = Tokens.DIMENSION; |
|
5215 } |
|
5216 |
|
5217 } else if (c === "%") { |
|
5218 value += reader.read(); |
|
5219 tt = Tokens.PERCENTAGE; |
|
5220 } |
|
5221 |
|
5222 return this.createToken(tt, value, startLine, startCol); |
|
5223 }, |
|
5224 |
|
5225 /** |
|
5226 * Produces a string token based on the given character |
|
5227 * and location in the stream. Since strings may be indicated |
|
5228 * by single or double quotes, a failure to match starting |
|
5229 * and ending quotes results in an INVALID token being generated. |
|
5230 * The first character in the string is passed in and then |
|
5231 * the rest are read up to and including the final quotation mark. |
|
5232 * @param {String} first The first character in the string. |
|
5233 * @param {int} startLine The beginning line for the character. |
|
5234 * @param {int} startCol The beginning column for the character. |
|
5235 * @return {Object} A token object. |
|
5236 * @method stringToken |
|
5237 */ |
|
5238 stringToken: function(first, startLine, startCol) { |
|
5239 var delim = first, |
|
5240 string = first, |
|
5241 reader = this._reader, |
|
5242 tt = Tokens.STRING, |
|
5243 c = reader.read(), |
|
5244 i; |
|
5245 |
|
5246 while (c) { |
|
5247 string += c; |
|
5248 |
|
5249 if (c === "\\") { |
|
5250 c = reader.read(); |
|
5251 if (c === null) { |
|
5252 break; // premature EOF after backslash |
|
5253 } else if (/[^\r\n\f0-9a-f]/i.test(c)) { |
|
5254 // single-character escape |
|
5255 string += c; |
|
5256 } else { |
|
5257 // read up to six hex digits |
|
5258 for (i=0; isHexDigit(c) && i<6; i++) { |
|
5259 string += c; |
|
5260 c = reader.read(); |
|
5261 } |
|
5262 // swallow trailing newline or space |
|
5263 if (c === "\r" && reader.peek() === "\n") { |
|
5264 string += c; |
|
5265 c = reader.read(); |
|
5266 } |
|
5267 if (isWhitespace(c)) { |
|
5268 string += c; |
|
5269 } else { |
|
5270 // This character is null or not part of the escape; |
|
5271 // jump back to the top to process it. |
|
5272 continue; |
|
5273 } |
|
5274 } |
|
5275 } else if (c === delim) { |
|
5276 break; // delimiter found. |
|
5277 } else if (isNewLine(reader.peek())) { |
|
5278 // newline without an escapement: it's an invalid string |
|
5279 tt = Tokens.INVALID; |
|
5280 break; |
|
5281 } |
|
5282 c = reader.read(); |
|
5283 } |
|
5284 |
|
5285 //if c is null, that means we're out of input and the string was never closed |
|
5286 if (c === null) { |
|
5287 tt = Tokens.INVALID; |
|
5288 } |
|
5289 |
|
5290 return this.createToken(tt, string, startLine, startCol); |
|
5291 }, |
|
5292 |
|
5293 unicodeRangeToken: function(first, startLine, startCol) { |
|
5294 var reader = this._reader, |
|
5295 value = first, |
|
5296 temp, |
|
5297 tt = Tokens.CHAR; |
|
5298 |
|
5299 //then it should be a unicode range |
|
5300 if (reader.peek() === "+") { |
|
5301 reader.mark(); |
|
5302 value += reader.read(); |
|
5303 value += this.readUnicodeRangePart(true); |
|
5304 |
|
5305 //ensure there's an actual unicode range here |
|
5306 if (value.length === 2) { |
|
5307 reader.reset(); |
|
5308 } else { |
|
5309 |
|
5310 tt = Tokens.UNICODE_RANGE; |
|
5311 |
|
5312 //if there's a ? in the first part, there can't be a second part |
|
5313 if (value.indexOf("?") === -1) { |
|
5314 |
|
5315 if (reader.peek() === "-") { |
|
5316 reader.mark(); |
|
5317 temp = reader.read(); |
|
5318 temp += this.readUnicodeRangePart(false); |
|
5319 |
|
5320 //if there's not another value, back up and just take the first |
|
5321 if (temp.length === 1) { |
|
5322 reader.reset(); |
|
5323 } else { |
|
5324 value += temp; |
|
5325 } |
|
5326 } |
|
5327 |
|
5328 } |
|
5329 } |
|
5330 } |
|
5331 |
|
5332 return this.createToken(tt, value, startLine, startCol); |
|
5333 }, |
|
5334 |
|
5335 /** |
|
5336 * Produces a S token based on the specified information. Since whitespace |
|
5337 * may have multiple characters, this consumes all whitespace characters |
|
5338 * into a single token. |
|
5339 * @param {String} first The first character in the token. |
|
5340 * @param {int} startLine The beginning line for the character. |
|
5341 * @param {int} startCol The beginning column for the character. |
|
5342 * @return {Object} A token object. |
|
5343 * @method whitespaceToken |
|
5344 */ |
|
5345 whitespaceToken: function(first, startLine, startCol) { |
|
5346 var value = first + this.readWhitespace(); |
|
5347 return this.createToken(Tokens.S, value, startLine, startCol); |
|
5348 }, |
|
5349 |
|
5350 |
|
5351 //------------------------------------------------------------------------- |
|
5352 // Methods to read values from the string stream |
|
5353 //------------------------------------------------------------------------- |
|
5354 |
|
5355 readUnicodeRangePart: function(allowQuestionMark) { |
|
5356 var reader = this._reader, |
|
5357 part = "", |
|
5358 c = reader.peek(); |
|
5359 |
|
5360 //first read hex digits |
|
5361 while (isHexDigit(c) && part.length < 6) { |
|
5362 reader.read(); |
|
5363 part += c; |
|
5364 c = reader.peek(); |
|
5365 } |
|
5366 |
|
5367 //then read question marks if allowed |
|
5368 if (allowQuestionMark) { |
|
5369 while (c === "?" && part.length < 6) { |
|
5370 reader.read(); |
|
5371 part += c; |
|
5372 c = reader.peek(); |
|
5373 } |
|
5374 } |
|
5375 |
|
5376 //there can't be any other characters after this point |
|
5377 |
|
5378 return part; |
|
5379 }, |
|
5380 |
|
5381 readWhitespace: function() { |
|
5382 var reader = this._reader, |
|
5383 whitespace = "", |
|
5384 c = reader.peek(); |
|
5385 |
|
5386 while (isWhitespace(c)) { |
|
5387 reader.read(); |
|
5388 whitespace += c; |
|
5389 c = reader.peek(); |
|
5390 } |
|
5391 |
|
5392 return whitespace; |
|
5393 }, |
|
5394 readNumber: function(first) { |
|
5395 var reader = this._reader, |
|
5396 number = first, |
|
5397 hasDot = (first === "."), |
|
5398 c = reader.peek(); |
|
5399 |
|
5400 |
|
5401 while (c) { |
|
5402 if (isDigit(c)) { |
|
5403 number += reader.read(); |
|
5404 } else if (c === ".") { |
|
5405 if (hasDot) { |
|
5406 break; |
|
5407 } else { |
|
5408 hasDot = true; |
|
5409 number += reader.read(); |
|
5410 } |
|
5411 } else { |
|
5412 break; |
|
5413 } |
|
5414 |
|
5415 c = reader.peek(); |
|
5416 } |
|
5417 |
|
5418 return number; |
|
5419 }, |
|
5420 |
|
5421 // returns null w/o resetting reader if string is invalid. |
|
5422 readString: function() { |
|
5423 var token = this.stringToken(this._reader.read(), 0, 0); |
|
5424 return token.type === Tokens.INVALID ? null : token.value; |
|
5425 }, |
|
5426 |
|
5427 // returns null w/o resetting reader if URI is invalid. |
|
5428 readURI: function(first) { |
|
5429 var reader = this._reader, |
|
5430 uri = first, |
|
5431 inner = "", |
|
5432 c = reader.peek(); |
|
5433 |
|
5434 //skip whitespace before |
|
5435 while (c && isWhitespace(c)) { |
|
5436 reader.read(); |
|
5437 c = reader.peek(); |
|
5438 } |
|
5439 |
|
5440 //it's a string |
|
5441 if (c === "'" || c === "\"") { |
|
5442 inner = this.readString(); |
|
5443 if (inner !== null) { |
|
5444 inner = PropertyValuePart.parseString(inner); |
|
5445 } |
|
5446 } else { |
|
5447 inner = this.readUnquotedURL(); |
|
5448 } |
|
5449 |
|
5450 c = reader.peek(); |
|
5451 |
|
5452 //skip whitespace after |
|
5453 while (c && isWhitespace(c)) { |
|
5454 reader.read(); |
|
5455 c = reader.peek(); |
|
5456 } |
|
5457 |
|
5458 //if there was no inner value or the next character isn't closing paren, it's not a URI |
|
5459 if (inner === null || c !== ")") { |
|
5460 uri = null; |
|
5461 } else { |
|
5462 // Ensure argument to URL is always double-quoted |
|
5463 // (This simplifies later processing in PropertyValuePart.) |
|
5464 uri += PropertyValuePart.serializeString(inner) + reader.read(); |
|
5465 } |
|
5466 |
|
5467 return uri; |
|
5468 }, |
|
5469 // This method never fails, although it may return an empty string. |
|
5470 readUnquotedURL: function(first) { |
|
5471 var reader = this._reader, |
|
5472 url = first || "", |
|
5473 c; |
|
5474 |
|
5475 for (c = reader.peek(); c; c = reader.peek()) { |
|
5476 // Note that the grammar at |
|
5477 // https://www.w3.org/TR/CSS2/grammar.html#scanner |
|
5478 // incorrectly includes the backslash character in the |
|
5479 // `url` production, although it is correctly omitted in |
|
5480 // the `baduri1` production. |
|
5481 if (nonascii.test(c) || /^[\-!#$%&*-\[\]-~]$/.test(c)) { |
|
5482 url += c; |
|
5483 reader.read(); |
|
5484 } else if (c === "\\") { |
|
5485 if (/^[^\r\n\f]$/.test(reader.peek(2))) { |
|
5486 url += this.readEscape(reader.read(), true); |
|
5487 } else { |
|
5488 break; // bad escape sequence. |
|
5489 } |
|
5490 } else { |
|
5491 break; // bad character |
|
5492 } |
|
5493 } |
|
5494 |
|
5495 return url; |
|
5496 }, |
|
5497 |
|
5498 readName: function(first) { |
|
5499 var reader = this._reader, |
|
5500 ident = first || "", |
|
5501 c; |
|
5502 |
|
5503 for (c = reader.peek(); c; c = reader.peek()) { |
|
5504 if (c === "\\") { |
|
5505 if (/^[^\r\n\f]$/.test(reader.peek(2))) { |
|
5506 ident += this.readEscape(reader.read(), true); |
|
5507 } else { |
|
5508 // Bad escape sequence. |
|
5509 break; |
|
5510 } |
|
5511 } else if (isNameChar(c)) { |
|
5512 ident += reader.read(); |
|
5513 } else { |
|
5514 break; |
|
5515 } |
|
5516 } |
|
5517 |
|
5518 return ident; |
|
5519 }, |
|
5520 |
|
5521 readEscape: function(first, unescape) { |
|
5522 var reader = this._reader, |
|
5523 cssEscape = first || "", |
|
5524 i = 0, |
|
5525 c = reader.peek(); |
|
5526 |
|
5527 if (isHexDigit(c)) { |
|
5528 do { |
|
5529 cssEscape += reader.read(); |
|
5530 c = reader.peek(); |
|
5531 } while (c && isHexDigit(c) && ++i < 6); |
|
5532 } |
|
5533 |
|
5534 if (cssEscape.length === 1) { |
|
5535 if (/^[^\r\n\f0-9a-f]$/.test(c)) { |
|
5536 reader.read(); |
|
5537 if (unescape) { |
|
5538 return c; |
|
5539 } |
|
5540 } else { |
|
5541 // We should never get here (readName won't call readEscape |
|
5542 // if the escape sequence is bad). |
|
5543 throw new Error("Bad escape sequence."); |
|
5544 } |
|
5545 } else if (c === "\r") { |
|
5546 reader.read(); |
|
5547 if (reader.peek() === "\n") { |
|
5548 c += reader.read(); |
|
5549 } |
|
5550 } else if (/^[ \t\n\f]$/.test(c)) { |
|
5551 reader.read(); |
|
5552 } else { |
|
5553 c = ""; |
|
5554 } |
|
5555 |
|
5556 if (unescape) { |
|
5557 var cp = parseInt(cssEscape.slice(first.length), 16); |
|
5558 return String.fromCodePoint ? String.fromCodePoint(cp) : |
|
5559 String.fromCharCode(cp); |
|
5560 } |
|
5561 return cssEscape + c; |
|
5562 }, |
|
5563 |
|
5564 readComment: function(first) { |
|
5565 var reader = this._reader, |
|
5566 comment = first || "", |
|
5567 c = reader.read(); |
|
5568 |
|
5569 if (c === "*") { |
|
5570 while (c) { |
|
5571 comment += c; |
|
5572 |
|
5573 //look for end of comment |
|
5574 if (comment.length > 2 && c === "*" && reader.peek() === "/") { |
|
5575 comment += reader.read(); |
|
5576 break; |
|
5577 } |
|
5578 |
|
5579 c = reader.read(); |
|
5580 } |
|
5581 |
|
5582 return comment; |
|
5583 } else { |
|
5584 return ""; |
|
5585 } |
|
5586 |
|
5587 } |
|
5588 }); |
|
5589 |
|
5590 },{"../util/TokenStreamBase":27,"./PropertyValuePart":11,"./Tokens":18}],18:[function(require,module,exports){ |
|
5591 "use strict"; |
|
5592 |
|
5593 var Tokens = module.exports = [ |
|
5594 |
|
5595 /* |
|
5596 * The following token names are defined in CSS3 Grammar: https://www.w3.org/TR/css3-syntax/#lexical |
|
5597 */ |
|
5598 |
|
5599 // HTML-style comments |
|
5600 { name: "CDO" }, |
|
5601 { name: "CDC" }, |
|
5602 |
|
5603 // ignorables |
|
5604 { name: "S", whitespace: true/*, channel: "ws"*/ }, |
|
5605 { name: "COMMENT", comment: true, hide: true, channel: "comment" }, |
|
5606 |
|
5607 // attribute equality |
|
5608 { name: "INCLUDES", text: "~=" }, |
|
5609 { name: "DASHMATCH", text: "|=" }, |
|
5610 { name: "PREFIXMATCH", text: "^=" }, |
|
5611 { name: "SUFFIXMATCH", text: "$=" }, |
|
5612 { name: "SUBSTRINGMATCH", text: "*=" }, |
|
5613 |
|
5614 // identifier types |
|
5615 { name: "STRING" }, |
|
5616 { name: "IDENT" }, |
|
5617 { name: "HASH" }, |
|
5618 |
|
5619 // at-keywords |
|
5620 { name: "IMPORT_SYM", text: "@import" }, |
|
5621 { name: "PAGE_SYM", text: "@page" }, |
|
5622 { name: "MEDIA_SYM", text: "@media" }, |
|
5623 { name: "FONT_FACE_SYM", text: "@font-face" }, |
|
5624 { name: "CHARSET_SYM", text: "@charset" }, |
|
5625 { name: "NAMESPACE_SYM", text: "@namespace" }, |
|
5626 { name: "SUPPORTS_SYM", text: "@supports" }, |
|
5627 { name: "VIEWPORT_SYM", text: ["@viewport", "@-ms-viewport", "@-o-viewport"] }, |
|
5628 { name: "DOCUMENT_SYM", text: ["@document", "@-moz-document"] }, |
|
5629 { name: "UNKNOWN_SYM" }, |
|
5630 //{ name: "ATKEYWORD"}, |
|
5631 |
|
5632 // CSS3 animations |
|
5633 { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, |
|
5634 |
|
5635 // important symbol |
|
5636 { name: "IMPORTANT_SYM" }, |
|
5637 |
|
5638 // measurements |
|
5639 { name: "LENGTH" }, |
|
5640 { name: "ANGLE" }, |
|
5641 { name: "TIME" }, |
|
5642 { name: "FREQ" }, |
|
5643 { name: "DIMENSION" }, |
|
5644 { name: "PERCENTAGE" }, |
|
5645 { name: "NUMBER" }, |
|
5646 |
|
5647 // functions |
|
5648 { name: "URI" }, |
|
5649 { name: "FUNCTION" }, |
|
5650 |
|
5651 // Unicode ranges |
|
5652 { name: "UNICODE_RANGE" }, |
|
5653 |
|
5654 /* |
|
5655 * The following token names are defined in CSS3 Selectors: https://www.w3.org/TR/css3-selectors/#selector-syntax |
|
5656 */ |
|
5657 |
|
5658 // invalid string |
|
5659 { name: "INVALID" }, |
|
5660 |
|
5661 // combinators |
|
5662 { name: "PLUS", text: "+" }, |
|
5663 { name: "GREATER", text: ">" }, |
|
5664 { name: "COMMA", text: "," }, |
|
5665 { name: "TILDE", text: "~" }, |
|
5666 |
|
5667 // modifier |
|
5668 { name: "NOT" }, |
|
5669 |
|
5670 /* |
|
5671 * Defined in CSS3 Paged Media |
|
5672 */ |
|
5673 { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner" }, |
|
5674 { name: "TOPLEFT_SYM", text: "@top-left" }, |
|
5675 { name: "TOPCENTER_SYM", text: "@top-center" }, |
|
5676 { name: "TOPRIGHT_SYM", text: "@top-right" }, |
|
5677 { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner" }, |
|
5678 { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner" }, |
|
5679 { name: "BOTTOMLEFT_SYM", text: "@bottom-left" }, |
|
5680 { name: "BOTTOMCENTER_SYM", text: "@bottom-center" }, |
|
5681 { name: "BOTTOMRIGHT_SYM", text: "@bottom-right" }, |
|
5682 { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner" }, |
|
5683 { name: "LEFTTOP_SYM", text: "@left-top" }, |
|
5684 { name: "LEFTMIDDLE_SYM", text: "@left-middle" }, |
|
5685 { name: "LEFTBOTTOM_SYM", text: "@left-bottom" }, |
|
5686 { name: "RIGHTTOP_SYM", text: "@right-top" }, |
|
5687 { name: "RIGHTMIDDLE_SYM", text: "@right-middle" }, |
|
5688 { name: "RIGHTBOTTOM_SYM", text: "@right-bottom" }, |
|
5689 |
|
5690 /* |
|
5691 * The following token names are defined in CSS3 Media Queries: https://www.w3.org/TR/css3-mediaqueries/#syntax |
|
5692 */ |
|
5693 /*{ name: "MEDIA_ONLY", state: "media"}, |
|
5694 { name: "MEDIA_NOT", state: "media"}, |
|
5695 { name: "MEDIA_AND", state: "media"},*/ |
|
5696 { name: "RESOLUTION", state: "media" }, |
|
5697 |
|
5698 /* |
|
5699 * The following token names are not defined in any CSS specification but are used by the lexer. |
|
5700 */ |
|
5701 |
|
5702 // not a real token, but useful for stupid IE filters |
|
5703 { name: "IE_FUNCTION" }, |
|
5704 |
|
5705 // part of CSS3 grammar but not the Flex code |
|
5706 { name: "CHAR" }, |
|
5707 |
|
5708 // TODO: Needed? |
|
5709 // Not defined as tokens, but might as well be |
|
5710 { |
|
5711 name: "PIPE", |
|
5712 text: "|" |
|
5713 }, |
|
5714 { |
|
5715 name: "SLASH", |
|
5716 text: "/" |
|
5717 }, |
|
5718 { |
|
5719 name: "MINUS", |
|
5720 text: "-" |
|
5721 }, |
|
5722 { |
|
5723 name: "STAR", |
|
5724 text: "*" |
|
5725 }, |
|
5726 |
|
5727 { |
|
5728 name: "LBRACE", |
|
5729 endChar: "}", |
|
5730 text: "{" |
|
5731 }, |
|
5732 { |
|
5733 name: "RBRACE", |
|
5734 text: "}" |
|
5735 }, |
|
5736 { |
|
5737 name: "LBRACKET", |
|
5738 endChar: "]", |
|
5739 text: "[" |
|
5740 }, |
|
5741 { |
|
5742 name: "RBRACKET", |
|
5743 text: "]" |
|
5744 }, |
|
5745 { |
|
5746 name: "EQUALS", |
|
5747 text: "=" |
|
5748 }, |
|
5749 { |
|
5750 name: "COLON", |
|
5751 text: ":" |
|
5752 }, |
|
5753 { |
|
5754 name: "SEMICOLON", |
|
5755 text: ";" |
|
5756 }, |
|
5757 { |
|
5758 name: "LPAREN", |
|
5759 endChar: ")", |
|
5760 text: "(" |
|
5761 }, |
|
5762 { |
|
5763 name: "RPAREN", |
|
5764 text: ")" |
|
5765 }, |
|
5766 { |
|
5767 name: "DOT", |
|
5768 text: "." |
|
5769 } |
|
5770 ]; |
|
5771 |
|
5772 (function() { |
|
5773 var nameMap = [], |
|
5774 typeMap = Object.create(null); |
|
5775 |
|
5776 Tokens.UNKNOWN = -1; |
|
5777 Tokens.unshift({ name:"EOF" }); |
|
5778 for (var i=0, len = Tokens.length; i < len; i++) { |
|
5779 nameMap.push(Tokens[i].name); |
|
5780 Tokens[Tokens[i].name] = i; |
|
5781 if (Tokens[i].text) { |
|
5782 if (Tokens[i].text instanceof Array) { |
|
5783 for (var j=0; j < Tokens[i].text.length; j++) { |
|
5784 typeMap[Tokens[i].text[j]] = i; |
|
5785 } |
|
5786 } else { |
|
5787 typeMap[Tokens[i].text] = i; |
|
5788 } |
|
5789 } |
|
5790 } |
|
5791 |
|
5792 Tokens.name = function(tt) { |
|
5793 return nameMap[tt]; |
|
5794 }; |
|
5795 |
|
5796 Tokens.type = function(c) { |
|
5797 return typeMap[c] || -1; |
|
5798 }; |
|
5799 })(); |
|
5800 |
|
5801 },{}],19:[function(require,module,exports){ |
|
5802 "use strict"; |
|
5803 |
|
5804 /* exported Validation */ |
|
5805 |
|
5806 var Matcher = require("./Matcher"); |
|
5807 var Properties = require("./Properties"); |
|
5808 var ValidationTypes = require("./ValidationTypes"); |
|
5809 var ValidationError = require("./ValidationError"); |
|
5810 var PropertyValueIterator = require("./PropertyValueIterator"); |
|
5811 |
|
5812 var Validation = module.exports = { |
|
5813 |
|
5814 validate: function(property, value) { |
|
5815 |
|
5816 //normalize name |
|
5817 var name = property.toString().toLowerCase(), |
|
5818 expression = new PropertyValueIterator(value), |
|
5819 spec = Properties[name], |
|
5820 part; |
|
5821 |
|
5822 if (!spec) { |
|
5823 if (name.indexOf("-") !== 0) { //vendor prefixed are ok |
|
5824 throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col); |
|
5825 } |
|
5826 } else if (typeof spec !== "number") { |
|
5827 |
|
5828 // All properties accept some CSS-wide values. |
|
5829 // https://drafts.csswg.org/css-values-3/#common-keywords |
|
5830 if (ValidationTypes.isAny(expression, "inherit | initial | unset")) { |
|
5831 if (expression.hasNext()) { |
|
5832 part = expression.next(); |
|
5833 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); |
|
5834 } |
|
5835 return; |
|
5836 } |
|
5837 |
|
5838 // Property-specific validation. |
|
5839 this.singleProperty(spec, expression); |
|
5840 |
|
5841 } |
|
5842 |
|
5843 }, |
|
5844 |
|
5845 singleProperty: function(types, expression) { |
|
5846 |
|
5847 var result = false, |
|
5848 value = expression.value, |
|
5849 part; |
|
5850 |
|
5851 result = Matcher.parse(types).match(expression); |
|
5852 |
|
5853 if (!result) { |
|
5854 if (expression.hasNext() && !expression.isFirst()) { |
|
5855 part = expression.peek(); |
|
5856 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); |
|
5857 } else { |
|
5858 throw new ValidationError("Expected (" + ValidationTypes.describe(types) + ") but found '" + value + "'.", value.line, value.col); |
|
5859 } |
|
5860 } else if (expression.hasNext()) { |
|
5861 part = expression.next(); |
|
5862 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); |
|
5863 } |
|
5864 |
|
5865 } |
|
5866 |
|
5867 }; |
|
5868 |
|
5869 },{"./Matcher":3,"./Properties":7,"./PropertyValueIterator":10,"./ValidationError":20,"./ValidationTypes":21}],20:[function(require,module,exports){ |
|
5870 "use strict"; |
|
5871 |
|
5872 module.exports = ValidationError; |
|
5873 |
|
5874 /** |
|
5875 * Type to use when a validation error occurs. |
|
5876 * @class ValidationError |
|
5877 * @namespace parserlib.util |
|
5878 * @constructor |
|
5879 * @param {String} message The error message. |
|
5880 * @param {int} line The line at which the error occurred. |
|
5881 * @param {int} col The column at which the error occurred. |
|
5882 */ |
|
5883 function ValidationError(message, line, col) { |
|
5884 |
|
5885 /** |
|
5886 * The column at which the error occurred. |
|
5887 * @type int |
|
5888 * @property col |
|
5889 */ |
|
5890 this.col = col; |
|
5891 |
|
5892 /** |
|
5893 * The line at which the error occurred. |
|
5894 * @type int |
|
5895 * @property line |
|
5896 */ |
|
5897 this.line = line; |
|
5898 |
|
5899 /** |
|
5900 * The text representation of the unit. |
|
5901 * @type String |
|
5902 * @property text |
|
5903 */ |
|
5904 this.message = message; |
|
5905 |
|
5906 } |
|
5907 |
|
5908 //inherit from Error |
|
5909 ValidationError.prototype = new Error(); |
|
5910 |
|
5911 },{}],21:[function(require,module,exports){ |
|
5912 "use strict"; |
|
5913 |
|
5914 var ValidationTypes = module.exports; |
|
5915 |
|
5916 var Matcher = require("./Matcher"); |
|
5917 |
|
5918 function copy(to, from) { |
|
5919 Object.keys(from).forEach(function(prop) { |
|
5920 to[prop] = from[prop]; |
|
5921 }); |
|
5922 } |
|
5923 copy(ValidationTypes, { |
|
5924 |
|
5925 isLiteral: function (part, literals) { |
|
5926 var text = part.text.toString().toLowerCase(), |
|
5927 args = literals.split(" | "), |
|
5928 i, len, found = false; |
|
5929 |
|
5930 for (i=0, len=args.length; i < len && !found; i++) { |
|
5931 if (args[i].charAt(0) === "<") { |
|
5932 found = this.simple[args[i]](part); |
|
5933 } else if (args[i].slice(-2) === "()") { |
|
5934 found = (part.type === "function" && |
|
5935 part.name === args[i].slice(0, -2)); |
|
5936 } else if (text === args[i].toLowerCase()) { |
|
5937 found = true; |
|
5938 } |
|
5939 } |
|
5940 |
|
5941 return found; |
|
5942 }, |
|
5943 |
|
5944 isSimple: function(type) { |
|
5945 return Boolean(this.simple[type]); |
|
5946 }, |
|
5947 |
|
5948 isComplex: function(type) { |
|
5949 return Boolean(this.complex[type]); |
|
5950 }, |
|
5951 |
|
5952 describe: function(type) { |
|
5953 if (this.complex[type] instanceof Matcher) { |
|
5954 return this.complex[type].toString(0); |
|
5955 } |
|
5956 return type; |
|
5957 }, |
|
5958 |
|
5959 /** |
|
5960 * Determines if the next part(s) of the given expression |
|
5961 * are any of the given types. |
|
5962 */ |
|
5963 isAny: function (expression, types) { |
|
5964 var args = types.split(" | "), |
|
5965 i, len, found = false; |
|
5966 |
|
5967 for (i=0, len=args.length; i < len && !found && expression.hasNext(); i++) { |
|
5968 found = this.isType(expression, args[i]); |
|
5969 } |
|
5970 |
|
5971 return found; |
|
5972 }, |
|
5973 |
|
5974 /** |
|
5975 * Determines if the next part(s) of the given expression |
|
5976 * are one of a group. |
|
5977 */ |
|
5978 isAnyOfGroup: function(expression, types) { |
|
5979 var args = types.split(" || "), |
|
5980 i, len, found = false; |
|
5981 |
|
5982 for (i=0, len=args.length; i < len && !found; i++) { |
|
5983 found = this.isType(expression, args[i]); |
|
5984 } |
|
5985 |
|
5986 return found ? args[i-1] : false; |
|
5987 }, |
|
5988 |
|
5989 /** |
|
5990 * Determines if the next part(s) of the given expression |
|
5991 * are of a given type. |
|
5992 */ |
|
5993 isType: function (expression, type) { |
|
5994 var part = expression.peek(), |
|
5995 result = false; |
|
5996 |
|
5997 if (type.charAt(0) !== "<") { |
|
5998 result = this.isLiteral(part, type); |
|
5999 if (result) { |
|
6000 expression.next(); |
|
6001 } |
|
6002 } else if (this.simple[type]) { |
|
6003 result = this.simple[type](part); |
|
6004 if (result) { |
|
6005 expression.next(); |
|
6006 } |
|
6007 } else if (this.complex[type] instanceof Matcher) { |
|
6008 result = this.complex[type].match(expression); |
|
6009 } else { |
|
6010 result = this.complex[type](expression); |
|
6011 } |
|
6012 |
|
6013 return result; |
|
6014 }, |
|
6015 |
|
6016 |
|
6017 simple: { |
|
6018 __proto__: null, |
|
6019 |
|
6020 "<absolute-size>": |
|
6021 "xx-small | x-small | small | medium | large | x-large | xx-large", |
|
6022 |
|
6023 "<animateable-feature>": |
|
6024 "scroll-position | contents | <animateable-feature-name>", |
|
6025 |
|
6026 "<animateable-feature-name>": function(part) { |
|
6027 return this["<ident>"](part) && |
|
6028 !/^(unset|initial|inherit|will-change|auto|scroll-position|contents)$/i.test(part); |
|
6029 }, |
|
6030 |
|
6031 "<angle>": function(part) { |
|
6032 return part.type === "angle"; |
|
6033 }, |
|
6034 |
|
6035 "<attachment>": "scroll | fixed | local", |
|
6036 |
|
6037 "<attr>": "attr()", |
|
6038 |
|
6039 // inset() = inset( <shape-arg>{1,4} [round <border-radius>]? ) |
|
6040 // circle() = circle( [<shape-radius>]? [at <position>]? ) |
|
6041 // ellipse() = ellipse( [<shape-radius>{2}]? [at <position>]? ) |
|
6042 // polygon() = polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# ) |
|
6043 "<basic-shape>": "inset() | circle() | ellipse() | polygon()", |
|
6044 |
|
6045 "<bg-image>": "<image> | <gradient> | none", |
|
6046 |
|
6047 "<border-style>": |
|
6048 "none | hidden | dotted | dashed | solid | double | groove | " + |
|
6049 "ridge | inset | outset", |
|
6050 |
|
6051 "<border-width>": "<length> | thin | medium | thick", |
|
6052 |
|
6053 "<box>": "padding-box | border-box | content-box", |
|
6054 |
|
6055 "<clip-source>": "<uri>", |
|
6056 |
|
6057 "<color>": function(part) { |
|
6058 return part.type === "color" || String(part) === "transparent" || String(part) === "currentColor"; |
|
6059 }, |
|
6060 |
|
6061 // The SVG <color> spec doesn't include "currentColor" or "transparent" as a color. |
|
6062 "<color-svg>": function(part) { |
|
6063 return part.type === "color"; |
|
6064 }, |
|
6065 |
|
6066 "<content>": "content()", |
|
6067 |
|
6068 // https://www.w3.org/TR/css3-sizing/#width-height-keywords |
|
6069 "<content-sizing>": |
|
6070 "fill-available | -moz-available | -webkit-fill-available | " + |
|
6071 "max-content | -moz-max-content | -webkit-max-content | " + |
|
6072 "min-content | -moz-min-content | -webkit-min-content | " + |
|
6073 "fit-content | -moz-fit-content | -webkit-fit-content", |
|
6074 |
|
6075 "<feature-tag-value>": function(part) { |
|
6076 return part.type === "function" && /^[A-Z0-9]{4}$/i.test(part); |
|
6077 }, |
|
6078 |
|
6079 // custom() isn't actually in the spec |
|
6080 "<filter-function>": |
|
6081 "blur() | brightness() | contrast() | custom() | " + |
|
6082 "drop-shadow() | grayscale() | hue-rotate() | invert() | " + |
|
6083 "opacity() | saturate() | sepia()", |
|
6084 |
|
6085 "<flex-basis>": "<width>", |
|
6086 |
|
6087 "<flex-direction>": "row | row-reverse | column | column-reverse", |
|
6088 |
|
6089 "<flex-grow>": "<number>", |
|
6090 |
|
6091 "<flex-shrink>": "<number>", |
|
6092 |
|
6093 "<flex-wrap>": "nowrap | wrap | wrap-reverse", |
|
6094 |
|
6095 "<font-size>": |
|
6096 "<absolute-size> | <relative-size> | <length> | <percentage>", |
|
6097 |
|
6098 "<font-stretch>": |
|
6099 "normal | ultra-condensed | extra-condensed | condensed | " + |
|
6100 "semi-condensed | semi-expanded | expanded | extra-expanded | " + |
|
6101 "ultra-expanded", |
|
6102 |
|
6103 "<font-style>": "normal | italic | oblique", |
|
6104 |
|
6105 "<font-variant-caps>": |
|
6106 "small-caps | all-small-caps | petite-caps | all-petite-caps | " + |
|
6107 "unicase | titling-caps", |
|
6108 |
|
6109 "<font-variant-css21>": "normal | small-caps", |
|
6110 |
|
6111 "<font-weight>": |
|
6112 "normal | bold | bolder | lighter | " + |
|
6113 "100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900", |
|
6114 |
|
6115 "<generic-family>": |
|
6116 "serif | sans-serif | cursive | fantasy | monospace", |
|
6117 |
|
6118 "<geometry-box>": "<shape-box> | fill-box | stroke-box | view-box", |
|
6119 |
|
6120 "<glyph-angle>": function(part) { |
|
6121 return part.type === "angle" && part.units === "deg"; |
|
6122 }, |
|
6123 |
|
6124 "<gradient>": function(part) { |
|
6125 return part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part); |
|
6126 }, |
|
6127 |
|
6128 "<icccolor>": |
|
6129 "cielab() | cielch() | cielchab() | " + |
|
6130 "icc-color() | icc-named-color()", |
|
6131 |
|
6132 //any identifier |
|
6133 "<ident>": function(part) { |
|
6134 return part.type === "identifier" || part.wasIdent; |
|
6135 }, |
|
6136 |
|
6137 "<ident-not-generic-family>": function(part) { |
|
6138 return this["<ident>"](part) && !this["<generic-family>"](part); |
|
6139 }, |
|
6140 |
|
6141 "<image>": "<uri>", |
|
6142 |
|
6143 "<integer>": function(part) { |
|
6144 return part.type === "integer"; |
|
6145 }, |
|
6146 |
|
6147 "<length>": function(part) { |
|
6148 if (part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)) { |
|
6149 return true; |
|
6150 } else { |
|
6151 return part.type === "length" || part.type === "number" || part.type === "integer" || String(part) === "0"; |
|
6152 } |
|
6153 }, |
|
6154 |
|
6155 "<line>": function(part) { |
|
6156 return part.type === "integer"; |
|
6157 }, |
|
6158 |
|
6159 "<line-height>": "<number> | <length> | <percentage> | normal", |
|
6160 |
|
6161 "<margin-width>": "<length> | <percentage> | auto", |
|
6162 |
|
6163 "<miterlimit>": function(part) { |
|
6164 return this["<number>"](part) && part.value >= 1; |
|
6165 }, |
|
6166 |
|
6167 "<nonnegative-length-or-percentage>": function(part) { |
|
6168 return (this["<length>"](part) || this["<percentage>"](part)) && |
|
6169 (String(part) === "0" || part.type === "function" || (part.value) >= 0); |
|
6170 }, |
|
6171 |
|
6172 "<nonnegative-number-or-percentage>": function(part) { |
|
6173 return (this["<number>"](part) || this["<percentage>"](part)) && |
|
6174 (String(part) === "0" || part.type === "function" || (part.value) >= 0); |
|
6175 }, |
|
6176 |
|
6177 "<number>": function(part) { |
|
6178 return part.type === "number" || this["<integer>"](part); |
|
6179 }, |
|
6180 |
|
6181 "<opacity-value>": function(part) { |
|
6182 return this["<number>"](part) && part.value >= 0 && part.value <= 1; |
|
6183 }, |
|
6184 |
|
6185 "<padding-width>": "<nonnegative-length-or-percentage>", |
|
6186 |
|
6187 "<percentage>": function(part) { |
|
6188 return part.type === "percentage" || String(part) === "0"; |
|
6189 }, |
|
6190 |
|
6191 "<relative-size>": "smaller | larger", |
|
6192 |
|
6193 "<shape>": "rect() | inset-rect()", |
|
6194 |
|
6195 "<shape-box>": "<box> | margin-box", |
|
6196 |
|
6197 "<single-animation-direction>": |
|
6198 "normal | reverse | alternate | alternate-reverse", |
|
6199 |
|
6200 "<single-animation-name>": function(part) { |
|
6201 return this["<ident>"](part) && |
|
6202 /^-?[a-z_][-a-z0-9_]+$/i.test(part) && |
|
6203 !/^(none|unset|initial|inherit)$/i.test(part); |
|
6204 }, |
|
6205 |
|
6206 "<string>": function(part) { |
|
6207 return part.type === "string"; |
|
6208 }, |
|
6209 |
|
6210 "<time>": function(part) { |
|
6211 return part.type === "time"; |
|
6212 }, |
|
6213 |
|
6214 "<uri>": function(part) { |
|
6215 return part.type === "uri"; |
|
6216 }, |
|
6217 |
|
6218 "<width>": "<margin-width>" |
|
6219 }, |
|
6220 |
|
6221 complex: { |
|
6222 __proto__: null, |
|
6223 |
|
6224 "<azimuth>": |
|
6225 "<angle>" + |
|
6226 " | " + |
|
6227 "[ [ left-side | far-left | left | center-left | center | " + |
|
6228 "center-right | right | far-right | right-side ] || behind ]" + |
|
6229 " | "+ |
|
6230 "leftwards | rightwards", |
|
6231 |
|
6232 "<bg-position>": "<position>#", |
|
6233 |
|
6234 "<bg-size>": |
|
6235 "[ <length> | <percentage> | auto ]{1,2} | cover | contain", |
|
6236 |
|
6237 "<border-image-slice>": |
|
6238 // [<number> | <percentage>]{1,4} && fill? |
|
6239 // *but* fill can appear between any of the numbers |
|
6240 Matcher.many([true /* first element is required */], |
|
6241 Matcher.cast("<nonnegative-number-or-percentage>"), |
|
6242 Matcher.cast("<nonnegative-number-or-percentage>"), |
|
6243 Matcher.cast("<nonnegative-number-or-percentage>"), |
|
6244 Matcher.cast("<nonnegative-number-or-percentage>"), |
|
6245 "fill"), |
|
6246 |
|
6247 "<border-radius>": |
|
6248 "<nonnegative-length-or-percentage>{1,4} " + |
|
6249 "[ / <nonnegative-length-or-percentage>{1,4} ]?", |
|
6250 |
|
6251 "<box-shadow>": "none | <shadow>#", |
|
6252 |
|
6253 "<clip-path>": "<basic-shape> || <geometry-box>", |
|
6254 |
|
6255 "<dasharray>": |
|
6256 // "list of comma and/or white space separated <length>s and |
|
6257 // <percentage>s". There is a non-negative constraint. |
|
6258 Matcher.cast("<nonnegative-length-or-percentage>") |
|
6259 .braces(1, Infinity, "#", Matcher.cast(",").question()), |
|
6260 |
|
6261 "<family-name>": |
|
6262 // <string> | <IDENT>+ |
|
6263 "<string> | <ident-not-generic-family> <ident>*", |
|
6264 |
|
6265 "<filter-function-list>": "[ <filter-function> | <uri> ]+", |
|
6266 |
|
6267 // https://www.w3.org/TR/2014/WD-css-flexbox-1-20140325/#flex-property |
|
6268 "<flex>": |
|
6269 "none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]", |
|
6270 |
|
6271 "<font-family>": "[ <generic-family> | <family-name> ]#", |
|
6272 |
|
6273 "<font-shorthand>": |
|
6274 "[ <font-style> || <font-variant-css21> || " + |
|
6275 "<font-weight> || <font-stretch> ]? <font-size> " + |
|
6276 "[ / <line-height> ]? <font-family>", |
|
6277 |
|
6278 "<font-variant-alternates>": |
|
6279 // stylistic(<feature-value-name>) |
|
6280 "stylistic() || " + |
|
6281 "historical-forms || " + |
|
6282 // styleset(<feature-value-name> #) |
|
6283 "styleset() || " + |
|
6284 // character-variant(<feature-value-name> #) |
|
6285 "character-variant() || " + |
|
6286 // swash(<feature-value-name>) |
|
6287 "swash() || " + |
|
6288 // ornaments(<feature-value-name>) |
|
6289 "ornaments() || " + |
|
6290 // annotation(<feature-value-name>) |
|
6291 "annotation()", |
|
6292 |
|
6293 "<font-variant-ligatures>": |
|
6294 // <common-lig-values> |
|
6295 "[ common-ligatures | no-common-ligatures ] || " + |
|
6296 // <discretionary-lig-values> |
|
6297 "[ discretionary-ligatures | no-discretionary-ligatures ] || " + |
|
6298 // <historical-lig-values> |
|
6299 "[ historical-ligatures | no-historical-ligatures ] || " + |
|
6300 // <contextual-alt-values> |
|
6301 "[ contextual | no-contextual ]", |
|
6302 |
|
6303 "<font-variant-numeric>": |
|
6304 // <numeric-figure-values> |
|
6305 "[ lining-nums | oldstyle-nums ] || " + |
|
6306 // <numeric-spacing-values> |
|
6307 "[ proportional-nums | tabular-nums ] || " + |
|
6308 // <numeric-fraction-values> |
|
6309 "[ diagonal-fractions | stacked-fractions ] || " + |
|
6310 "ordinal || slashed-zero", |
|
6311 |
|
6312 "<font-variant-east-asian>": |
|
6313 // <east-asian-variant-values> |
|
6314 "[ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] || " + |
|
6315 // <east-asian-width-values> |
|
6316 "[ full-width | proportional-width ] || " + |
|
6317 "ruby", |
|
6318 |
|
6319 // Note that <color> here is "as defined in the SVG spec", which |
|
6320 // is more restrictive that the <color> defined in the CSS spec. |
|
6321 // none | currentColor | <color> [<icccolor>]? | |
|
6322 // <funciri> [ none | currentColor | <color> [<icccolor>]? ]? |
|
6323 "<paint>": "<paint-basic> | <uri> <paint-basic>?", |
|
6324 |
|
6325 // Helper definition for <paint> above. |
|
6326 "<paint-basic>": "none | currentColor | <color-svg> <icccolor>?", |
|
6327 |
|
6328 "<position>": |
|
6329 // Because our `alt` combinator is ordered, we need to test these |
|
6330 // in order from longest possible match to shortest. |
|
6331 "[ center | [ left | right ] [ <percentage> | <length> ]? ] && " + |
|
6332 "[ center | [ top | bottom ] [ <percentage> | <length> ]? ]" + |
|
6333 " | " + |
|
6334 "[ left | center | right | <percentage> | <length> ] " + |
|
6335 "[ top | center | bottom | <percentage> | <length> ]" + |
|
6336 " | " + |
|
6337 "[ left | center | right | top | bottom | <percentage> | <length> ]", |
|
6338 |
|
6339 "<repeat-style>": |
|
6340 "repeat-x | repeat-y | [ repeat | space | round | no-repeat ]{1,2}", |
|
6341 |
|
6342 "<shadow>": |
|
6343 //inset? && [ <length>{2,4} && <color>? ] |
|
6344 Matcher.many([true /* length is required */], |
|
6345 Matcher.cast("<length>").braces(2, 4), "inset", "<color>"), |
|
6346 |
|
6347 "<text-decoration-color>": |
|
6348 "<color>", |
|
6349 |
|
6350 "<text-decoration-line>": |
|
6351 "none | [ underline || overline || line-through || blink ]", |
|
6352 |
|
6353 "<text-decoration-style>": |
|
6354 "solid | double | dotted | dashed | wavy", |
|
6355 |
|
6356 "<will-change>": |
|
6357 "auto | <animateable-feature>#", |
|
6358 |
|
6359 "<x-one-radius>": |
|
6360 //[ <length> | <percentage> ] [ <length> | <percentage> ]? |
|
6361 "[ <length> | <percentage> ]{1,2}" |
|
6362 } |
|
6363 }); |
|
6364 |
|
6365 Object.keys(ValidationTypes.simple).forEach(function(nt) { |
|
6366 var rule = ValidationTypes.simple[nt]; |
|
6367 if (typeof rule === "string") { |
|
6368 ValidationTypes.simple[nt] = function(part) { |
|
6369 return ValidationTypes.isLiteral(part, rule); |
|
6370 }; |
|
6371 } |
|
6372 }); |
|
6373 |
|
6374 Object.keys(ValidationTypes.complex).forEach(function(nt) { |
|
6375 var rule = ValidationTypes.complex[nt]; |
|
6376 if (typeof rule === "string") { |
|
6377 ValidationTypes.complex[nt] = Matcher.parse(rule); |
|
6378 } |
|
6379 }); |
|
6380 |
|
6381 // Because this is defined relative to other complex validation types, |
|
6382 // we need to define it *after* the rest of the types are initialized. |
|
6383 ValidationTypes.complex["<font-variant>"] = |
|
6384 Matcher.oror({ expand: "<font-variant-ligatures>" }, |
|
6385 { expand: "<font-variant-alternates>" }, |
|
6386 "<font-variant-caps>", |
|
6387 { expand: "<font-variant-numeric>" }, |
|
6388 { expand: "<font-variant-east-asian>" }); |
|
6389 |
|
6390 },{"./Matcher":3}],22:[function(require,module,exports){ |
|
6391 "use strict"; |
|
6392 |
|
6393 module.exports = { |
|
6394 Colors : require("./Colors"), |
|
6395 Combinator : require("./Combinator"), |
|
6396 Parser : require("./Parser"), |
|
6397 PropertyName : require("./PropertyName"), |
|
6398 PropertyValue : require("./PropertyValue"), |
|
6399 PropertyValuePart : require("./PropertyValuePart"), |
|
6400 Matcher : require("./Matcher"), |
|
6401 MediaFeature : require("./MediaFeature"), |
|
6402 MediaQuery : require("./MediaQuery"), |
|
6403 Selector : require("./Selector"), |
|
6404 SelectorPart : require("./SelectorPart"), |
|
6405 SelectorSubPart : require("./SelectorSubPart"), |
|
6406 Specificity : require("./Specificity"), |
|
6407 TokenStream : require("./TokenStream"), |
|
6408 Tokens : require("./Tokens"), |
|
6409 ValidationError : require("./ValidationError") |
|
6410 }; |
|
6411 |
|
6412 },{"./Colors":1,"./Combinator":2,"./Matcher":3,"./MediaFeature":4,"./MediaQuery":5,"./Parser":6,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./Specificity":16,"./TokenStream":17,"./Tokens":18,"./ValidationError":20}],23:[function(require,module,exports){ |
|
6413 "use strict"; |
|
6414 |
|
6415 module.exports = EventTarget; |
|
6416 |
|
6417 /** |
|
6418 * A generic base to inherit from for any object |
|
6419 * that needs event handling. |
|
6420 * @class EventTarget |
|
6421 * @constructor |
|
6422 */ |
|
6423 function EventTarget() { |
|
6424 |
|
6425 /** |
|
6426 * The array of listeners for various events. |
|
6427 * @type Object |
|
6428 * @property _listeners |
|
6429 * @private |
|
6430 */ |
|
6431 this._listeners = Object.create(null); |
|
6432 } |
|
6433 |
|
6434 EventTarget.prototype = { |
|
6435 |
|
6436 //restore constructor |
|
6437 constructor: EventTarget, |
|
6438 |
|
6439 /** |
|
6440 * Adds a listener for a given event type. |
|
6441 * @param {String} type The type of event to add a listener for. |
|
6442 * @param {Function} listener The function to call when the event occurs. |
|
6443 * @return {void} |
|
6444 * @method addListener |
|
6445 */ |
|
6446 addListener: function(type, listener) { |
|
6447 if (!this._listeners[type]) { |
|
6448 this._listeners[type] = []; |
|
6449 } |
|
6450 |
|
6451 this._listeners[type].push(listener); |
|
6452 }, |
|
6453 |
|
6454 /** |
|
6455 * Fires an event based on the passed-in object. |
|
6456 * @param {Object|String} event An object with at least a 'type' attribute |
|
6457 * or a string indicating the event name. |
|
6458 * @return {void} |
|
6459 * @method fire |
|
6460 */ |
|
6461 fire: function(event) { |
|
6462 if (typeof event === "string") { |
|
6463 event = { type: event }; |
|
6464 } |
|
6465 if (typeof event.target !== "undefined") { |
|
6466 event.target = this; |
|
6467 } |
|
6468 |
|
6469 if (typeof event.type === "undefined") { |
|
6470 throw new Error("Event object missing 'type' property."); |
|
6471 } |
|
6472 |
|
6473 if (this._listeners[event.type]) { |
|
6474 |
|
6475 //create a copy of the array and use that so listeners can't chane |
|
6476 var listeners = this._listeners[event.type].concat(); |
|
6477 for (var i=0, len=listeners.length; i < len; i++) { |
|
6478 listeners[i].call(this, event); |
|
6479 } |
|
6480 } |
|
6481 }, |
|
6482 |
|
6483 /** |
|
6484 * Removes a listener for a given event type. |
|
6485 * @param {String} type The type of event to remove a listener from. |
|
6486 * @param {Function} listener The function to remove from the event. |
|
6487 * @return {void} |
|
6488 * @method removeListener |
|
6489 */ |
|
6490 removeListener: function(type, listener) { |
|
6491 if (this._listeners[type]) { |
|
6492 var listeners = this._listeners[type]; |
|
6493 for (var i=0, len=listeners.length; i < len; i++) { |
|
6494 if (listeners[i] === listener) { |
|
6495 listeners.splice(i, 1); |
|
6496 break; |
|
6497 } |
|
6498 } |
|
6499 |
|
6500 |
|
6501 } |
|
6502 } |
|
6503 }; |
|
6504 |
|
6505 },{}],24:[function(require,module,exports){ |
|
6506 "use strict"; |
|
6507 |
|
6508 module.exports = StringReader; |
|
6509 |
|
6510 /** |
|
6511 * Convenient way to read through strings. |
|
6512 * @namespace parserlib.util |
|
6513 * @class StringReader |
|
6514 * @constructor |
|
6515 * @param {String} text The text to read. |
|
6516 */ |
|
6517 function StringReader(text) { |
|
6518 |
|
6519 /** |
|
6520 * The input text with line endings normalized. |
|
6521 * @property _input |
|
6522 * @type String |
|
6523 * @private |
|
6524 */ |
|
6525 this._input = text.replace(/(\r\n?|\n)/g, "\n"); |
|
6526 |
|
6527 |
|
6528 /** |
|
6529 * The row for the character to be read next. |
|
6530 * @property _line |
|
6531 * @type int |
|
6532 * @private |
|
6533 */ |
|
6534 this._line = 1; |
|
6535 |
|
6536 |
|
6537 /** |
|
6538 * The column for the character to be read next. |
|
6539 * @property _col |
|
6540 * @type int |
|
6541 * @private |
|
6542 */ |
|
6543 this._col = 1; |
|
6544 |
|
6545 /** |
|
6546 * The index of the character in the input to be read next. |
|
6547 * @property _cursor |
|
6548 * @type int |
|
6549 * @private |
|
6550 */ |
|
6551 this._cursor = 0; |
|
6552 } |
|
6553 |
|
6554 StringReader.prototype = { |
|
6555 |
|
6556 // restore constructor |
|
6557 constructor: StringReader, |
|
6558 |
|
6559 //------------------------------------------------------------------------- |
|
6560 // Position info |
|
6561 //------------------------------------------------------------------------- |
|
6562 |
|
6563 /** |
|
6564 * Returns the column of the character to be read next. |
|
6565 * @return {int} The column of the character to be read next. |
|
6566 * @method getCol |
|
6567 */ |
|
6568 getCol: function() { |
|
6569 return this._col; |
|
6570 }, |
|
6571 |
|
6572 /** |
|
6573 * Returns the row of the character to be read next. |
|
6574 * @return {int} The row of the character to be read next. |
|
6575 * @method getLine |
|
6576 */ |
|
6577 getLine: function() { |
|
6578 return this._line; |
|
6579 }, |
|
6580 |
|
6581 /** |
|
6582 * Determines if you're at the end of the input. |
|
6583 * @return {Boolean} True if there's no more input, false otherwise. |
|
6584 * @method eof |
|
6585 */ |
|
6586 eof: function() { |
|
6587 return this._cursor === this._input.length; |
|
6588 }, |
|
6589 |
|
6590 //------------------------------------------------------------------------- |
|
6591 // Basic reading |
|
6592 //------------------------------------------------------------------------- |
|
6593 |
|
6594 /** |
|
6595 * Reads the next character without advancing the cursor. |
|
6596 * @param {int} count How many characters to look ahead (default is 1). |
|
6597 * @return {String} The next character or null if there is no next character. |
|
6598 * @method peek |
|
6599 */ |
|
6600 peek: function(count) { |
|
6601 var c = null; |
|
6602 count = typeof count === "undefined" ? 1 : count; |
|
6603 |
|
6604 // if we're not at the end of the input... |
|
6605 if (this._cursor < this._input.length) { |
|
6606 |
|
6607 // get character and increment cursor and column |
|
6608 c = this._input.charAt(this._cursor + count - 1); |
|
6609 } |
|
6610 |
|
6611 return c; |
|
6612 }, |
|
6613 |
|
6614 /** |
|
6615 * Reads the next character from the input and adjusts the row and column |
|
6616 * accordingly. |
|
6617 * @return {String} The next character or null if there is no next character. |
|
6618 * @method read |
|
6619 */ |
|
6620 read: function() { |
|
6621 var c = null; |
|
6622 |
|
6623 // if we're not at the end of the input... |
|
6624 if (this._cursor < this._input.length) { |
|
6625 |
|
6626 // if the last character was a newline, increment row count |
|
6627 // and reset column count |
|
6628 if (this._input.charAt(this._cursor) === "\n") { |
|
6629 this._line++; |
|
6630 this._col=1; |
|
6631 } else { |
|
6632 this._col++; |
|
6633 } |
|
6634 |
|
6635 // get character and increment cursor and column |
|
6636 c = this._input.charAt(this._cursor++); |
|
6637 } |
|
6638 |
|
6639 return c; |
|
6640 }, |
|
6641 |
|
6642 //------------------------------------------------------------------------- |
|
6643 // Misc |
|
6644 //------------------------------------------------------------------------- |
|
6645 |
|
6646 /** |
|
6647 * Saves the current location so it can be returned to later. |
|
6648 * @method mark |
|
6649 * @return {void} |
|
6650 */ |
|
6651 mark: function() { |
|
6652 this._bookmark = { |
|
6653 cursor: this._cursor, |
|
6654 line: this._line, |
|
6655 col: this._col |
|
6656 }; |
|
6657 }, |
|
6658 |
|
6659 reset: function() { |
|
6660 if (this._bookmark) { |
|
6661 this._cursor = this._bookmark.cursor; |
|
6662 this._line = this._bookmark.line; |
|
6663 this._col = this._bookmark.col; |
|
6664 delete this._bookmark; |
|
6665 } |
|
6666 }, |
|
6667 |
|
6668 //------------------------------------------------------------------------- |
|
6669 // Advanced reading |
|
6670 //------------------------------------------------------------------------- |
|
6671 |
|
6672 /** |
|
6673 * Reads up to and including the given string. Throws an error if that |
|
6674 * string is not found. |
|
6675 * @param {String} pattern The string to read. |
|
6676 * @return {String} The string when it is found. |
|
6677 * @throws Error when the string pattern is not found. |
|
6678 * @method readTo |
|
6679 */ |
|
6680 readTo: function(pattern) { |
|
6681 |
|
6682 var buffer = "", |
|
6683 c; |
|
6684 |
|
6685 /* |
|
6686 * First, buffer must be the same length as the pattern. |
|
6687 * Then, buffer must end with the pattern or else reach the |
|
6688 * end of the input. |
|
6689 */ |
|
6690 while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) !== buffer.length - pattern.length) { |
|
6691 c = this.read(); |
|
6692 if (c) { |
|
6693 buffer += c; |
|
6694 } else { |
|
6695 throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + "."); |
|
6696 } |
|
6697 } |
|
6698 |
|
6699 return buffer; |
|
6700 |
|
6701 }, |
|
6702 |
|
6703 /** |
|
6704 * Reads characters while each character causes the given |
|
6705 * filter function to return true. The function is passed |
|
6706 * in each character and either returns true to continue |
|
6707 * reading or false to stop. |
|
6708 * @param {Function} filter The function to read on each character. |
|
6709 * @return {String} The string made up of all characters that passed the |
|
6710 * filter check. |
|
6711 * @method readWhile |
|
6712 */ |
|
6713 readWhile: function(filter) { |
|
6714 |
|
6715 var buffer = "", |
|
6716 c = this.peek(); |
|
6717 |
|
6718 while (c !== null && filter(c)) { |
|
6719 buffer += this.read(); |
|
6720 c = this.peek(); |
|
6721 } |
|
6722 |
|
6723 return buffer; |
|
6724 |
|
6725 }, |
|
6726 |
|
6727 /** |
|
6728 * Reads characters that match either text or a regular expression and |
|
6729 * returns those characters. If a match is found, the row and column |
|
6730 * are adjusted; if no match is found, the reader's state is unchanged. |
|
6731 * reading or false to stop. |
|
6732 * @param {String|RegExp} matcher If a string, then the literal string |
|
6733 * value is searched for. If a regular expression, then any string |
|
6734 * matching the pattern is search for. |
|
6735 * @return {String} The string made up of all characters that matched or |
|
6736 * null if there was no match. |
|
6737 * @method readMatch |
|
6738 */ |
|
6739 readMatch: function(matcher) { |
|
6740 |
|
6741 var source = this._input.substring(this._cursor), |
|
6742 value = null; |
|
6743 |
|
6744 // if it's a string, just do a straight match |
|
6745 if (typeof matcher === "string") { |
|
6746 if (source.slice(0, matcher.length) === matcher) { |
|
6747 value = this.readCount(matcher.length); |
|
6748 } |
|
6749 } else if (matcher instanceof RegExp) { |
|
6750 if (matcher.test(source)) { |
|
6751 value = this.readCount(RegExp.lastMatch.length); |
|
6752 } |
|
6753 } |
|
6754 |
|
6755 return value; |
|
6756 }, |
|
6757 |
|
6758 |
|
6759 /** |
|
6760 * Reads a given number of characters. If the end of the input is reached, |
|
6761 * it reads only the remaining characters and does not throw an error. |
|
6762 * @param {int} count The number of characters to read. |
|
6763 * @return {String} The string made up the read characters. |
|
6764 * @method readCount |
|
6765 */ |
|
6766 readCount: function(count) { |
|
6767 var buffer = ""; |
|
6768 |
|
6769 while (count--) { |
|
6770 buffer += this.read(); |
|
6771 } |
|
6772 |
|
6773 return buffer; |
|
6774 } |
|
6775 |
|
6776 }; |
|
6777 |
|
6778 },{}],25:[function(require,module,exports){ |
|
6779 "use strict"; |
|
6780 |
|
6781 module.exports = SyntaxError; |
|
6782 |
|
6783 /** |
|
6784 * Type to use when a syntax error occurs. |
|
6785 * @class SyntaxError |
|
6786 * @namespace parserlib.util |
|
6787 * @constructor |
|
6788 * @param {String} message The error message. |
|
6789 * @param {int} line The line at which the error occurred. |
|
6790 * @param {int} col The column at which the error occurred. |
|
6791 */ |
|
6792 function SyntaxError(message, line, col) { |
|
6793 Error.call(this); |
|
6794 this.name = this.constructor.name; |
|
6795 |
|
6796 /** |
|
6797 * The column at which the error occurred. |
|
6798 * @type int |
|
6799 * @property col |
|
6800 */ |
|
6801 this.col = col; |
|
6802 |
|
6803 /** |
|
6804 * The line at which the error occurred. |
|
6805 * @type int |
|
6806 * @property line |
|
6807 */ |
|
6808 this.line = line; |
|
6809 |
|
6810 /** |
|
6811 * The text representation of the unit. |
|
6812 * @type String |
|
6813 * @property text |
|
6814 */ |
|
6815 this.message = message; |
|
6816 |
|
6817 } |
|
6818 |
|
6819 //inherit from Error |
|
6820 SyntaxError.prototype = Object.create(Error.prototype); // jshint ignore:line |
|
6821 SyntaxError.prototype.constructor = SyntaxError; // jshint ignore:line |
|
6822 |
|
6823 },{}],26:[function(require,module,exports){ |
|
6824 "use strict"; |
|
6825 |
|
6826 module.exports = SyntaxUnit; |
|
6827 |
|
6828 /** |
|
6829 * Base type to represent a single syntactic unit. |
|
6830 * @class SyntaxUnit |
|
6831 * @namespace parserlib.util |
|
6832 * @constructor |
|
6833 * @param {String} text The text of the unit. |
|
6834 * @param {int} line The line of text on which the unit resides. |
|
6835 * @param {int} col The column of text on which the unit resides. |
|
6836 */ |
|
6837 function SyntaxUnit(text, line, col, type) { |
|
6838 |
|
6839 |
|
6840 /** |
|
6841 * The column of text on which the unit resides. |
|
6842 * @type int |
|
6843 * @property col |
|
6844 */ |
|
6845 this.col = col; |
|
6846 |
|
6847 /** |
|
6848 * The line of text on which the unit resides. |
|
6849 * @type int |
|
6850 * @property line |
|
6851 */ |
|
6852 this.line = line; |
|
6853 |
|
6854 /** |
|
6855 * The text representation of the unit. |
|
6856 * @type String |
|
6857 * @property text |
|
6858 */ |
|
6859 this.text = text; |
|
6860 |
|
6861 /** |
|
6862 * The type of syntax unit. |
|
6863 * @type int |
|
6864 * @property type |
|
6865 */ |
|
6866 this.type = type; |
|
6867 } |
|
6868 |
|
6869 /** |
|
6870 * Create a new syntax unit based solely on the given token. |
|
6871 * Convenience method for creating a new syntax unit when |
|
6872 * it represents a single token instead of multiple. |
|
6873 * @param {Object} token The token object to represent. |
|
6874 * @return {parserlib.util.SyntaxUnit} The object representing the token. |
|
6875 * @static |
|
6876 * @method fromToken |
|
6877 */ |
|
6878 SyntaxUnit.fromToken = function(token) { |
|
6879 return new SyntaxUnit(token.value, token.startLine, token.startCol); |
|
6880 }; |
|
6881 |
|
6882 SyntaxUnit.prototype = { |
|
6883 |
|
6884 //restore constructor |
|
6885 constructor: SyntaxUnit, |
|
6886 |
|
6887 /** |
|
6888 * Returns the text representation of the unit. |
|
6889 * @return {String} The text representation of the unit. |
|
6890 * @method valueOf |
|
6891 */ |
|
6892 valueOf: function() { |
|
6893 return this.toString(); |
|
6894 }, |
|
6895 |
|
6896 /** |
|
6897 * Returns the text representation of the unit. |
|
6898 * @return {String} The text representation of the unit. |
|
6899 * @method toString |
|
6900 */ |
|
6901 toString: function() { |
|
6902 return this.text; |
|
6903 } |
|
6904 |
|
6905 }; |
|
6906 |
|
6907 },{}],27:[function(require,module,exports){ |
|
6908 "use strict"; |
|
6909 |
|
6910 module.exports = TokenStreamBase; |
|
6911 |
|
6912 var StringReader = require("./StringReader"); |
|
6913 var SyntaxError = require("./SyntaxError"); |
|
6914 |
|
6915 /** |
|
6916 * Generic TokenStream providing base functionality. |
|
6917 * @class TokenStreamBase |
|
6918 * @namespace parserlib.util |
|
6919 * @constructor |
|
6920 * @param {String|StringReader} input The text to tokenize or a reader from |
|
6921 * which to read the input. |
|
6922 */ |
|
6923 function TokenStreamBase(input, tokenData) { |
|
6924 |
|
6925 /** |
|
6926 * The string reader for easy access to the text. |
|
6927 * @type StringReader |
|
6928 * @property _reader |
|
6929 * @private |
|
6930 */ |
|
6931 this._reader = new StringReader(input ? input.toString() : ""); |
|
6932 |
|
6933 /** |
|
6934 * Token object for the last consumed token. |
|
6935 * @type Token |
|
6936 * @property _token |
|
6937 * @private |
|
6938 */ |
|
6939 this._token = null; |
|
6940 |
|
6941 /** |
|
6942 * The array of token information. |
|
6943 * @type Array |
|
6944 * @property _tokenData |
|
6945 * @private |
|
6946 */ |
|
6947 this._tokenData = tokenData; |
|
6948 |
|
6949 /** |
|
6950 * Lookahead token buffer. |
|
6951 * @type Array |
|
6952 * @property _lt |
|
6953 * @private |
|
6954 */ |
|
6955 this._lt = []; |
|
6956 |
|
6957 /** |
|
6958 * Lookahead token buffer index. |
|
6959 * @type int |
|
6960 * @property _ltIndex |
|
6961 * @private |
|
6962 */ |
|
6963 this._ltIndex = 0; |
|
6964 |
|
6965 this._ltIndexCache = []; |
|
6966 } |
|
6967 |
|
6968 /** |
|
6969 * Accepts an array of token information and outputs |
|
6970 * an array of token data containing key-value mappings |
|
6971 * and matching functions that the TokenStream needs. |
|
6972 * @param {Array} tokens An array of token descriptors. |
|
6973 * @return {Array} An array of processed token data. |
|
6974 * @method createTokenData |
|
6975 * @static |
|
6976 */ |
|
6977 TokenStreamBase.createTokenData = function(tokens) { |
|
6978 |
|
6979 var nameMap = [], |
|
6980 typeMap = Object.create(null), |
|
6981 tokenData = tokens.concat([]), |
|
6982 i = 0, |
|
6983 len = tokenData.length+1; |
|
6984 |
|
6985 tokenData.UNKNOWN = -1; |
|
6986 tokenData.unshift({ name:"EOF" }); |
|
6987 |
|
6988 for (; i < len; i++) { |
|
6989 nameMap.push(tokenData[i].name); |
|
6990 tokenData[tokenData[i].name] = i; |
|
6991 if (tokenData[i].text) { |
|
6992 typeMap[tokenData[i].text] = i; |
|
6993 } |
|
6994 } |
|
6995 |
|
6996 tokenData.name = function(tt) { |
|
6997 return nameMap[tt]; |
|
6998 }; |
|
6999 |
|
7000 tokenData.type = function(c) { |
|
7001 return typeMap[c]; |
|
7002 }; |
|
7003 |
|
7004 return tokenData; |
|
7005 }; |
|
7006 |
|
7007 TokenStreamBase.prototype = { |
|
7008 |
|
7009 //restore constructor |
|
7010 constructor: TokenStreamBase, |
|
7011 |
|
7012 //------------------------------------------------------------------------- |
|
7013 // Matching methods |
|
7014 //------------------------------------------------------------------------- |
|
7015 |
|
7016 /** |
|
7017 * Determines if the next token matches the given token type. |
|
7018 * If so, that token is consumed; if not, the token is placed |
|
7019 * back onto the token stream. You can pass in any number of |
|
7020 * token types and this will return true if any of the token |
|
7021 * types is found. |
|
7022 * @param {int|int[]} tokenTypes Either a single token type or an array of |
|
7023 * token types that the next token might be. If an array is passed, |
|
7024 * it's assumed that the token can be any of these. |
|
7025 * @param {variant} channel (Optional) The channel to read from. If not |
|
7026 * provided, reads from the default (unnamed) channel. |
|
7027 * @return {Boolean} True if the token type matches, false if not. |
|
7028 * @method match |
|
7029 */ |
|
7030 match: function(tokenTypes, channel) { |
|
7031 |
|
7032 //always convert to an array, makes things easier |
|
7033 if (!(tokenTypes instanceof Array)) { |
|
7034 tokenTypes = [tokenTypes]; |
|
7035 } |
|
7036 |
|
7037 var tt = this.get(channel), |
|
7038 i = 0, |
|
7039 len = tokenTypes.length; |
|
7040 |
|
7041 while (i < len) { |
|
7042 if (tt === tokenTypes[i++]) { |
|
7043 return true; |
|
7044 } |
|
7045 } |
|
7046 |
|
7047 //no match found, put the token back |
|
7048 this.unget(); |
|
7049 return false; |
|
7050 }, |
|
7051 |
|
7052 /** |
|
7053 * Determines if the next token matches the given token type. |
|
7054 * If so, that token is consumed; if not, an error is thrown. |
|
7055 * @param {int|int[]} tokenTypes Either a single token type or an array of |
|
7056 * token types that the next token should be. If an array is passed, |
|
7057 * it's assumed that the token must be one of these. |
|
7058 * @return {void} |
|
7059 * @method mustMatch |
|
7060 */ |
|
7061 mustMatch: function(tokenTypes) { |
|
7062 |
|
7063 var token; |
|
7064 |
|
7065 //always convert to an array, makes things easier |
|
7066 if (!(tokenTypes instanceof Array)) { |
|
7067 tokenTypes = [tokenTypes]; |
|
7068 } |
|
7069 |
|
7070 if (!this.match.apply(this, arguments)) { |
|
7071 token = this.LT(1); |
|
7072 throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name + |
|
7073 " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); |
|
7074 } |
|
7075 }, |
|
7076 |
|
7077 //------------------------------------------------------------------------- |
|
7078 // Consuming methods |
|
7079 //------------------------------------------------------------------------- |
|
7080 |
|
7081 /** |
|
7082 * Keeps reading from the token stream until either one of the specified |
|
7083 * token types is found or until the end of the input is reached. |
|
7084 * @param {int|int[]} tokenTypes Either a single token type or an array of |
|
7085 * token types that the next token should be. If an array is passed, |
|
7086 * it's assumed that the token must be one of these. |
|
7087 * @param {variant} channel (Optional) The channel to read from. If not |
|
7088 * provided, reads from the default (unnamed) channel. |
|
7089 * @return {void} |
|
7090 * @method advance |
|
7091 */ |
|
7092 advance: function(tokenTypes, channel) { |
|
7093 |
|
7094 while (this.LA(0) !== 0 && !this.match(tokenTypes, channel)) { |
|
7095 this.get(); |
|
7096 } |
|
7097 |
|
7098 return this.LA(0); |
|
7099 }, |
|
7100 |
|
7101 /** |
|
7102 * Consumes the next token from the token stream. |
|
7103 * @return {int} The token type of the token that was just consumed. |
|
7104 * @method get |
|
7105 */ |
|
7106 get: function(channel) { |
|
7107 |
|
7108 var tokenInfo = this._tokenData, |
|
7109 i =0, |
|
7110 token, |
|
7111 info; |
|
7112 |
|
7113 //check the lookahead buffer first |
|
7114 if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length) { |
|
7115 |
|
7116 i++; |
|
7117 this._token = this._lt[this._ltIndex++]; |
|
7118 info = tokenInfo[this._token.type]; |
|
7119 |
|
7120 //obey channels logic |
|
7121 while ((info.channel !== undefined && channel !== info.channel) && |
|
7122 this._ltIndex < this._lt.length) { |
|
7123 this._token = this._lt[this._ltIndex++]; |
|
7124 info = tokenInfo[this._token.type]; |
|
7125 i++; |
|
7126 } |
|
7127 |
|
7128 //here be dragons |
|
7129 if ((info.channel === undefined || channel === info.channel) && |
|
7130 this._ltIndex <= this._lt.length) { |
|
7131 this._ltIndexCache.push(i); |
|
7132 return this._token.type; |
|
7133 } |
|
7134 } |
|
7135 |
|
7136 //call token retriever method |
|
7137 token = this._getToken(); |
|
7138 |
|
7139 //if it should be hidden, don't save a token |
|
7140 if (token.type > -1 && !tokenInfo[token.type].hide) { |
|
7141 |
|
7142 //apply token channel |
|
7143 token.channel = tokenInfo[token.type].channel; |
|
7144 |
|
7145 //save for later |
|
7146 this._token = token; |
|
7147 this._lt.push(token); |
|
7148 |
|
7149 //save space that will be moved (must be done before array is truncated) |
|
7150 this._ltIndexCache.push(this._lt.length - this._ltIndex + i); |
|
7151 |
|
7152 //keep the buffer under 5 items |
|
7153 if (this._lt.length > 5) { |
|
7154 this._lt.shift(); |
|
7155 } |
|
7156 |
|
7157 //also keep the shift buffer under 5 items |
|
7158 if (this._ltIndexCache.length > 5) { |
|
7159 this._ltIndexCache.shift(); |
|
7160 } |
|
7161 |
|
7162 //update lookahead index |
|
7163 this._ltIndex = this._lt.length; |
|
7164 } |
|
7165 |
|
7166 /* |
|
7167 * Skip to the next token if: |
|
7168 * 1. The token type is marked as hidden. |
|
7169 * 2. The token type has a channel specified and it isn't the current channel. |
|
7170 */ |
|
7171 info = tokenInfo[token.type]; |
|
7172 if (info && |
|
7173 (info.hide || |
|
7174 (info.channel !== undefined && channel !== info.channel))) { |
|
7175 return this.get(channel); |
|
7176 } else { |
|
7177 //return just the type |
|
7178 return token.type; |
|
7179 } |
|
7180 }, |
|
7181 |
|
7182 /** |
|
7183 * Looks ahead a certain number of tokens and returns the token type at |
|
7184 * that position. This will throw an error if you lookahead past the |
|
7185 * end of input, past the size of the lookahead buffer, or back past |
|
7186 * the first token in the lookahead buffer. |
|
7187 * @param {int} The index of the token type to retrieve. 0 for the |
|
7188 * current token, 1 for the next, -1 for the previous, etc. |
|
7189 * @return {int} The token type of the token in the given position. |
|
7190 * @method LA |
|
7191 */ |
|
7192 LA: function(index) { |
|
7193 var total = index, |
|
7194 tt; |
|
7195 if (index > 0) { |
|
7196 //TODO: Store 5 somewhere |
|
7197 if (index > 5) { |
|
7198 throw new Error("Too much lookahead."); |
|
7199 } |
|
7200 |
|
7201 //get all those tokens |
|
7202 while (total) { |
|
7203 tt = this.get(); |
|
7204 total--; |
|
7205 } |
|
7206 |
|
7207 //unget all those tokens |
|
7208 while (total < index) { |
|
7209 this.unget(); |
|
7210 total++; |
|
7211 } |
|
7212 } else if (index < 0) { |
|
7213 |
|
7214 if (this._lt[this._ltIndex+index]) { |
|
7215 tt = this._lt[this._ltIndex+index].type; |
|
7216 } else { |
|
7217 throw new Error("Too much lookbehind."); |
|
7218 } |
|
7219 |
|
7220 } else { |
|
7221 tt = this._token.type; |
|
7222 } |
|
7223 |
|
7224 return tt; |
|
7225 |
|
7226 }, |
|
7227 |
|
7228 /** |
|
7229 * Looks ahead a certain number of tokens and returns the token at |
|
7230 * that position. This will throw an error if you lookahead past the |
|
7231 * end of input, past the size of the lookahead buffer, or back past |
|
7232 * the first token in the lookahead buffer. |
|
7233 * @param {int} The index of the token type to retrieve. 0 for the |
|
7234 * current token, 1 for the next, -1 for the previous, etc. |
|
7235 * @return {Object} The token of the token in the given position. |
|
7236 * @method LA |
|
7237 */ |
|
7238 LT: function(index) { |
|
7239 |
|
7240 //lookahead first to prime the token buffer |
|
7241 this.LA(index); |
|
7242 |
|
7243 //now find the token, subtract one because _ltIndex is already at the next index |
|
7244 return this._lt[this._ltIndex+index-1]; |
|
7245 }, |
|
7246 |
|
7247 /** |
|
7248 * Returns the token type for the next token in the stream without |
|
7249 * consuming it. |
|
7250 * @return {int} The token type of the next token in the stream. |
|
7251 * @method peek |
|
7252 */ |
|
7253 peek: function() { |
|
7254 return this.LA(1); |
|
7255 }, |
|
7256 |
|
7257 /** |
|
7258 * Returns the actual token object for the last consumed token. |
|
7259 * @return {Token} The token object for the last consumed token. |
|
7260 * @method token |
|
7261 */ |
|
7262 token: function() { |
|
7263 return this._token; |
|
7264 }, |
|
7265 |
|
7266 /** |
|
7267 * Returns the name of the token for the given token type. |
|
7268 * @param {int} tokenType The type of token to get the name of. |
|
7269 * @return {String} The name of the token or "UNKNOWN_TOKEN" for any |
|
7270 * invalid token type. |
|
7271 * @method tokenName |
|
7272 */ |
|
7273 tokenName: function(tokenType) { |
|
7274 if (tokenType < 0 || tokenType > this._tokenData.length) { |
|
7275 return "UNKNOWN_TOKEN"; |
|
7276 } else { |
|
7277 return this._tokenData[tokenType].name; |
|
7278 } |
|
7279 }, |
|
7280 |
|
7281 /** |
|
7282 * Returns the token type value for the given token name. |
|
7283 * @param {String} tokenName The name of the token whose value should be returned. |
|
7284 * @return {int} The token type value for the given token name or -1 |
|
7285 * for an unknown token. |
|
7286 * @method tokenName |
|
7287 */ |
|
7288 tokenType: function(tokenName) { |
|
7289 return this._tokenData[tokenName] || -1; |
|
7290 }, |
|
7291 |
|
7292 /** |
|
7293 * Returns the last consumed token to the token stream. |
|
7294 * @method unget |
|
7295 */ |
|
7296 unget: function() { |
|
7297 //if (this._ltIndex > -1) { |
|
7298 if (this._ltIndexCache.length) { |
|
7299 this._ltIndex -= this._ltIndexCache.pop();//--; |
|
7300 this._token = this._lt[this._ltIndex - 1]; |
|
7301 } else { |
|
7302 throw new Error("Too much lookahead."); |
|
7303 } |
|
7304 } |
|
7305 |
|
7306 }; |
|
7307 |
|
7308 |
|
7309 },{"./StringReader":24,"./SyntaxError":25}],28:[function(require,module,exports){ |
|
7310 "use strict"; |
|
7311 |
|
7312 module.exports = { |
|
7313 StringReader : require("./StringReader"), |
|
7314 SyntaxError : require("./SyntaxError"), |
|
7315 SyntaxUnit : require("./SyntaxUnit"), |
|
7316 EventTarget : require("./EventTarget"), |
|
7317 TokenStreamBase : require("./TokenStreamBase") |
|
7318 }; |
|
7319 |
|
7320 },{"./EventTarget":23,"./StringReader":24,"./SyntaxError":25,"./SyntaxUnit":26,"./TokenStreamBase":27}],"parserlib":[function(require,module,exports){ |
|
7321 "use strict"; |
|
7322 |
|
7323 module.exports = { |
|
7324 css : require("./css"), |
|
7325 util : require("./util") |
|
7326 }; |
|
7327 |
|
7328 },{"./css":22,"./util":28}]},{},[]); |
|
7329 |
|
7330 return require('parserlib'); |
|
7331 })(); |
|
7332 var clone = (function() { |
|
7333 'use strict'; |
|
7334 |
|
7335 var nativeMap; |
|
7336 try { |
|
7337 nativeMap = Map; |
|
7338 } catch(_) { |
|
7339 // maybe a reference error because no `Map`. Give it a dummy value that no |
|
7340 // value will ever be an instanceof. |
|
7341 nativeMap = function() {}; |
|
7342 } |
|
7343 |
|
7344 var nativeSet; |
|
7345 try { |
|
7346 nativeSet = Set; |
|
7347 } catch(_) { |
|
7348 nativeSet = function() {}; |
|
7349 } |
|
7350 |
|
7351 var nativePromise; |
|
7352 try { |
|
7353 nativePromise = Promise; |
|
7354 } catch(_) { |
|
7355 nativePromise = function() {}; |
|
7356 } |
|
7357 |
|
7358 /** |
|
7359 * Clones (copies) an Object using deep copying. |
|
7360 * |
|
7361 * This function supports circular references by default, but if you are certain |
|
7362 * there are no circular references in your object, you can save some CPU time |
|
7363 * by calling clone(obj, false). |
|
7364 * |
|
7365 * Caution: if `circular` is false and `parent` contains circular references, |
|
7366 * your program may enter an infinite loop and crash. |
|
7367 * |
|
7368 * @param `parent` - the object to be cloned |
|
7369 * @param `circular` - set to true if the object to be cloned may contain |
|
7370 * circular references. (optional - true by default) |
|
7371 * @param `depth` - set to a number if the object is only to be cloned to |
|
7372 * a particular depth. (optional - defaults to Infinity) |
|
7373 * @param `prototype` - sets the prototype to be used when cloning an object. |
|
7374 * (optional - defaults to parent prototype). |
|
7375 * @param `includeNonEnumerable` - set to true if the non-enumerable properties |
|
7376 * should be cloned as well. Non-enumerable properties on the prototype |
|
7377 * chain will be ignored. (optional - false by default) |
|
7378 */ |
|
7379 function clone(parent, circular, depth, prototype, includeNonEnumerable) { |
|
7380 if (typeof circular === 'object') { |
|
7381 depth = circular.depth; |
|
7382 prototype = circular.prototype; |
|
7383 includeNonEnumerable = circular.includeNonEnumerable; |
|
7384 circular = circular.circular; |
|
7385 } |
|
7386 // maintain two arrays for circular references, where corresponding parents |
|
7387 // and children have the same index |
|
7388 var allParents = []; |
|
7389 var allChildren = []; |
|
7390 |
|
7391 var useBuffer = typeof Buffer != 'undefined'; |
|
7392 |
|
7393 if (typeof circular == 'undefined') |
|
7394 circular = true; |
|
7395 |
|
7396 if (typeof depth == 'undefined') |
|
7397 depth = Infinity; |
|
7398 |
|
7399 // recurse this function so we don't reset allParents and allChildren |
|
7400 function _clone(parent, depth) { |
|
7401 // cloning null always returns null |
|
7402 if (parent === null) |
|
7403 return null; |
|
7404 |
|
7405 if (depth === 0) |
|
7406 return parent; |
|
7407 |
|
7408 var child; |
|
7409 var proto; |
|
7410 if (typeof parent != 'object') { |
|
7411 return parent; |
|
7412 } |
|
7413 |
|
7414 if (parent instanceof nativeMap) { |
|
7415 child = new nativeMap(); |
|
7416 } else if (parent instanceof nativeSet) { |
|
7417 child = new nativeSet(); |
|
7418 } else if (parent instanceof nativePromise) { |
|
7419 child = new nativePromise(function (resolve, reject) { |
|
7420 parent.then(function(value) { |
|
7421 resolve(_clone(value, depth - 1)); |
|
7422 }, function(err) { |
|
7423 reject(_clone(err, depth - 1)); |
|
7424 }); |
|
7425 }); |
|
7426 } else if (clone.__isArray(parent)) { |
|
7427 child = []; |
|
7428 } else if (clone.__isRegExp(parent)) { |
|
7429 child = new RegExp(parent.source, __getRegExpFlags(parent)); |
|
7430 if (parent.lastIndex) child.lastIndex = parent.lastIndex; |
|
7431 } else if (clone.__isDate(parent)) { |
|
7432 child = new Date(parent.getTime()); |
|
7433 } else if (useBuffer && Buffer.isBuffer(parent)) { |
|
7434 child = new Buffer(parent.length); |
|
7435 parent.copy(child); |
|
7436 return child; |
|
7437 } else if (parent instanceof Error) { |
|
7438 child = Object.create(parent); |
|
7439 } else { |
|
7440 if (typeof prototype == 'undefined') { |
|
7441 proto = Object.getPrototypeOf(parent); |
|
7442 child = Object.create(proto); |
|
7443 } |
|
7444 else { |
|
7445 child = Object.create(prototype); |
|
7446 proto = prototype; |
|
7447 } |
|
7448 } |
|
7449 |
|
7450 if (circular) { |
|
7451 var index = allParents.indexOf(parent); |
|
7452 |
|
7453 if (index != -1) { |
|
7454 return allChildren[index]; |
|
7455 } |
|
7456 allParents.push(parent); |
|
7457 allChildren.push(child); |
|
7458 } |
|
7459 |
|
7460 if (parent instanceof nativeMap) { |
|
7461 var keyIterator = parent.keys(); |
|
7462 while(true) { |
|
7463 var next = keyIterator.next(); |
|
7464 if (next.done) { |
|
7465 break; |
|
7466 } |
|
7467 var keyChild = _clone(next.value, depth - 1); |
|
7468 var valueChild = _clone(parent.get(next.value), depth - 1); |
|
7469 child.set(keyChild, valueChild); |
|
7470 } |
|
7471 } |
|
7472 if (parent instanceof nativeSet) { |
|
7473 var iterator = parent.keys(); |
|
7474 while(true) { |
|
7475 var next = iterator.next(); |
|
7476 if (next.done) { |
|
7477 break; |
|
7478 } |
|
7479 var entryChild = _clone(next.value, depth - 1); |
|
7480 child.add(entryChild); |
|
7481 } |
|
7482 } |
|
7483 |
|
7484 for (var i in parent) { |
|
7485 var attrs; |
|
7486 if (proto) { |
|
7487 attrs = Object.getOwnPropertyDescriptor(proto, i); |
|
7488 } |
|
7489 |
|
7490 if (attrs && attrs.set == null) { |
|
7491 continue; |
|
7492 } |
|
7493 child[i] = _clone(parent[i], depth - 1); |
|
7494 } |
|
7495 |
|
7496 if (Object.getOwnPropertySymbols) { |
|
7497 var symbols = Object.getOwnPropertySymbols(parent); |
|
7498 for (var i = 0; i < symbols.length; i++) { |
|
7499 // Don't need to worry about cloning a symbol because it is a primitive, |
|
7500 // like a number or string. |
|
7501 var symbol = symbols[i]; |
|
7502 var descriptor = Object.getOwnPropertyDescriptor(parent, symbol); |
|
7503 if (descriptor && !descriptor.enumerable && !includeNonEnumerable) { |
|
7504 continue; |
|
7505 } |
|
7506 child[symbol] = _clone(parent[symbol], depth - 1); |
|
7507 if (!descriptor.enumerable) { |
|
7508 Object.defineProperty(child, symbol, { |
|
7509 enumerable: false |
|
7510 }); |
|
7511 } |
|
7512 } |
|
7513 } |
|
7514 |
|
7515 if (includeNonEnumerable) { |
|
7516 var allPropertyNames = Object.getOwnPropertyNames(parent); |
|
7517 for (var i = 0; i < allPropertyNames.length; i++) { |
|
7518 var propertyName = allPropertyNames[i]; |
|
7519 var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName); |
|
7520 if (descriptor && descriptor.enumerable) { |
|
7521 continue; |
|
7522 } |
|
7523 child[propertyName] = _clone(parent[propertyName], depth - 1); |
|
7524 Object.defineProperty(child, propertyName, { |
|
7525 enumerable: false |
|
7526 }); |
|
7527 } |
|
7528 } |
|
7529 |
|
7530 return child; |
|
7531 } |
|
7532 |
|
7533 return _clone(parent, depth); |
|
7534 } |
|
7535 |
|
7536 /** |
|
7537 * Simple flat clone using prototype, accepts only objects, usefull for property |
|
7538 * override on FLAT configuration object (no nested props). |
|
7539 * |
|
7540 * USE WITH CAUTION! This may not behave as you wish if you do not know how this |
|
7541 * works. |
|
7542 */ |
|
7543 clone.clonePrototype = function clonePrototype(parent) { |
|
7544 if (parent === null) |
|
7545 return null; |
|
7546 |
|
7547 var c = function () {}; |
|
7548 c.prototype = parent; |
|
7549 return new c(); |
|
7550 }; |
|
7551 |
|
7552 // private utility functions |
|
7553 |
|
7554 function __objToStr(o) { |
|
7555 return Object.prototype.toString.call(o); |
|
7556 } |
|
7557 clone.__objToStr = __objToStr; |
|
7558 |
|
7559 function __isDate(o) { |
|
7560 return typeof o === 'object' && __objToStr(o) === '[object Date]'; |
|
7561 } |
|
7562 clone.__isDate = __isDate; |
|
7563 |
|
7564 function __isArray(o) { |
|
7565 return typeof o === 'object' && __objToStr(o) === '[object Array]'; |
|
7566 } |
|
7567 clone.__isArray = __isArray; |
|
7568 |
|
7569 function __isRegExp(o) { |
|
7570 return typeof o === 'object' && __objToStr(o) === '[object RegExp]'; |
|
7571 } |
|
7572 clone.__isRegExp = __isRegExp; |
|
7573 |
|
7574 function __getRegExpFlags(re) { |
|
7575 var flags = ''; |
|
7576 if (re.global) flags += 'g'; |
|
7577 if (re.ignoreCase) flags += 'i'; |
|
7578 if (re.multiline) flags += 'm'; |
|
7579 return flags; |
|
7580 } |
|
7581 clone.__getRegExpFlags = __getRegExpFlags; |
|
7582 |
|
7583 return clone; |
|
7584 })(); |
|
7585 |
|
7586 if (typeof module === 'object' && module.exports) { |
|
7587 module.exports = clone; |
|
7588 } |
|
7589 |
|
7590 /** |
|
7591 * Main CSSLint object. |
|
7592 * @class CSSLint |
|
7593 * @static |
|
7594 * @extends parserlib.util.EventTarget |
|
7595 */ |
|
7596 |
|
7597 /* global parserlib, clone, Reporter */ |
|
7598 /* exported CSSLint */ |
|
7599 |
|
7600 var CSSLint = (function() { |
|
7601 "use strict"; |
|
7602 |
|
7603 var rules = [], |
|
7604 formatters = [], |
|
7605 embeddedRuleset = /\/\*\s*csslint([^\*]*)\*\//, |
|
7606 api = new parserlib.util.EventTarget(); |
|
7607 |
|
7608 api.version = "1.0.4"; |
|
7609 |
|
7610 //------------------------------------------------------------------------- |
|
7611 // Rule Management |
|
7612 //------------------------------------------------------------------------- |
|
7613 |
|
7614 /** |
|
7615 * Adds a new rule to the engine. |
|
7616 * @param {Object} rule The rule to add. |
|
7617 * @method addRule |
|
7618 */ |
|
7619 api.addRule = function(rule) { |
|
7620 rules.push(rule); |
|
7621 rules[rule.id] = rule; |
|
7622 }; |
|
7623 |
|
7624 /** |
|
7625 * Clears all rule from the engine. |
|
7626 * @method clearRules |
|
7627 */ |
|
7628 api.clearRules = function() { |
|
7629 rules = []; |
|
7630 }; |
|
7631 |
|
7632 /** |
|
7633 * Returns the rule objects. |
|
7634 * @return An array of rule objects. |
|
7635 * @method getRules |
|
7636 */ |
|
7637 api.getRules = function() { |
|
7638 return [].concat(rules).sort(function(a, b) { |
|
7639 return a.id > b.id ? 1 : 0; |
|
7640 }); |
|
7641 }; |
|
7642 |
|
7643 /** |
|
7644 * Returns a ruleset configuration object with all current rules. |
|
7645 * @return A ruleset object. |
|
7646 * @method getRuleset |
|
7647 */ |
|
7648 api.getRuleset = function() { |
|
7649 var ruleset = {}, |
|
7650 i = 0, |
|
7651 len = rules.length; |
|
7652 |
|
7653 while (i < len) { |
|
7654 ruleset[rules[i++].id] = 1; // by default, everything is a warning |
|
7655 } |
|
7656 |
|
7657 return ruleset; |
|
7658 }; |
|
7659 |
|
7660 /** |
|
7661 * Returns a ruleset object based on embedded rules. |
|
7662 * @param {String} text A string of css containing embedded rules. |
|
7663 * @param {Object} ruleset A ruleset object to modify. |
|
7664 * @return {Object} A ruleset object. |
|
7665 * @method getEmbeddedRuleset |
|
7666 */ |
|
7667 function applyEmbeddedRuleset(text, ruleset) { |
|
7668 var valueMap, |
|
7669 embedded = text && text.match(embeddedRuleset), |
|
7670 rules = embedded && embedded[1]; |
|
7671 |
|
7672 if (rules) { |
|
7673 valueMap = { |
|
7674 "true": 2, // true is error |
|
7675 "": 1, // blank is warning |
|
7676 "false": 0, // false is ignore |
|
7677 |
|
7678 "2": 2, // explicit error |
|
7679 "1": 1, // explicit warning |
|
7680 "0": 0 // explicit ignore |
|
7681 }; |
|
7682 |
|
7683 rules.toLowerCase().split(",").forEach(function(rule) { |
|
7684 var pair = rule.split(":"), |
|
7685 property = pair[0] || "", |
|
7686 value = pair[1] || ""; |
|
7687 |
|
7688 ruleset[property.trim()] = valueMap[value.trim()]; |
|
7689 }); |
|
7690 } |
|
7691 |
|
7692 return ruleset; |
|
7693 } |
|
7694 |
|
7695 //------------------------------------------------------------------------- |
|
7696 // Formatters |
|
7697 //------------------------------------------------------------------------- |
|
7698 |
|
7699 /** |
|
7700 * Adds a new formatter to the engine. |
|
7701 * @param {Object} formatter The formatter to add. |
|
7702 * @method addFormatter |
|
7703 */ |
|
7704 api.addFormatter = function(formatter) { |
|
7705 // formatters.push(formatter); |
|
7706 formatters[formatter.id] = formatter; |
|
7707 }; |
|
7708 |
|
7709 /** |
|
7710 * Retrieves a formatter for use. |
|
7711 * @param {String} formatId The name of the format to retrieve. |
|
7712 * @return {Object} The formatter or undefined. |
|
7713 * @method getFormatter |
|
7714 */ |
|
7715 api.getFormatter = function(formatId) { |
|
7716 return formatters[formatId]; |
|
7717 }; |
|
7718 |
|
7719 /** |
|
7720 * Formats the results in a particular format for a single file. |
|
7721 * @param {Object} result The results returned from CSSLint.verify(). |
|
7722 * @param {String} filename The filename for which the results apply. |
|
7723 * @param {String} formatId The name of the formatter to use. |
|
7724 * @param {Object} options (Optional) for special output handling. |
|
7725 * @return {String} A formatted string for the results. |
|
7726 * @method format |
|
7727 */ |
|
7728 api.format = function(results, filename, formatId, options) { |
|
7729 var formatter = this.getFormatter(formatId), |
|
7730 result = null; |
|
7731 |
|
7732 if (formatter) { |
|
7733 result = formatter.startFormat(); |
|
7734 result += formatter.formatResults(results, filename, options || {}); |
|
7735 result += formatter.endFormat(); |
|
7736 } |
|
7737 |
|
7738 return result; |
|
7739 }; |
|
7740 |
|
7741 /** |
|
7742 * Indicates if the given format is supported. |
|
7743 * @param {String} formatId The ID of the format to check. |
|
7744 * @return {Boolean} True if the format exists, false if not. |
|
7745 * @method hasFormat |
|
7746 */ |
|
7747 api.hasFormat = function(formatId) { |
|
7748 return formatters.hasOwnProperty(formatId); |
|
7749 }; |
|
7750 |
|
7751 //------------------------------------------------------------------------- |
|
7752 // Verification |
|
7753 //------------------------------------------------------------------------- |
|
7754 |
|
7755 /** |
|
7756 * Starts the verification process for the given CSS text. |
|
7757 * @param {String} text The CSS text to verify. |
|
7758 * @param {Object} ruleset (Optional) List of rules to apply. If null, then |
|
7759 * all rules are used. If a rule has a value of 1 then it's a warning, |
|
7760 * a value of 2 means it's an error. |
|
7761 * @return {Object} Results of the verification. |
|
7762 * @method verify |
|
7763 */ |
|
7764 api.verify = function(text, ruleset) { |
|
7765 |
|
7766 var i = 0, |
|
7767 reporter, |
|
7768 lines, |
|
7769 allow = {}, |
|
7770 ignore = [], |
|
7771 report, |
|
7772 parser = new parserlib.css.Parser({ |
|
7773 starHack: true, |
|
7774 ieFilters: true, |
|
7775 underscoreHack: true, |
|
7776 strict: false |
|
7777 }); |
|
7778 |
|
7779 // normalize line endings |
|
7780 lines = text.replace(/\n\r?/g, "$split$").split("$split$"); |
|
7781 |
|
7782 // find 'allow' comments |
|
7783 CSSLint.Util.forEach(lines, function (line, lineno) { |
|
7784 var allowLine = line && line.match(/\/\*[ \t]*csslint[ \t]+allow:[ \t]*([^\*]*)\*\//i), |
|
7785 allowRules = allowLine && allowLine[1], |
|
7786 allowRuleset = {}; |
|
7787 |
|
7788 if (allowRules) { |
|
7789 allowRules.toLowerCase().split(",").forEach(function(allowRule) { |
|
7790 allowRuleset[allowRule.trim()] = true; |
|
7791 }); |
|
7792 if (Object.keys(allowRuleset).length > 0) { |
|
7793 allow[lineno + 1] = allowRuleset; |
|
7794 } |
|
7795 } |
|
7796 }); |
|
7797 |
|
7798 var ignoreStart = null, |
|
7799 ignoreEnd = null; |
|
7800 CSSLint.Util.forEach(lines, function (line, lineno) { |
|
7801 // Keep oldest, "unclosest" ignore:start |
|
7802 if (ignoreStart === null && line.match(/\/\*[ \t]*csslint[ \t]+ignore:start[ \t]*\*\//i)) { |
|
7803 ignoreStart = lineno; |
|
7804 } |
|
7805 |
|
7806 if (line.match(/\/\*[ \t]*csslint[ \t]+ignore:end[ \t]*\*\//i)) { |
|
7807 ignoreEnd = lineno; |
|
7808 } |
|
7809 |
|
7810 if (ignoreStart !== null && ignoreEnd !== null) { |
|
7811 ignore.push([ignoreStart, ignoreEnd]); |
|
7812 ignoreStart = ignoreEnd = null; |
|
7813 } |
|
7814 }); |
|
7815 |
|
7816 // Close remaining ignore block, if any |
|
7817 if (ignoreStart !== null) { |
|
7818 ignore.push([ignoreStart, lines.length]); |
|
7819 } |
|
7820 |
|
7821 if (!ruleset) { |
|
7822 ruleset = this.getRuleset(); |
|
7823 } |
|
7824 |
|
7825 if (embeddedRuleset.test(text)) { |
|
7826 // defensively copy so that caller's version does not get modified |
|
7827 ruleset = clone(ruleset); |
|
7828 ruleset = applyEmbeddedRuleset(text, ruleset); |
|
7829 } |
|
7830 |
|
7831 reporter = new Reporter(lines, ruleset, allow, ignore); |
|
7832 |
|
7833 ruleset.errors = 2; // always report parsing errors as errors |
|
7834 for (i in ruleset) { |
|
7835 if (ruleset.hasOwnProperty(i) && ruleset[i]) { |
|
7836 if (rules[i]) { |
|
7837 rules[i].init(parser, reporter); |
|
7838 } |
|
7839 } |
|
7840 } |
|
7841 |
|
7842 |
|
7843 // capture most horrible error type |
|
7844 try { |
|
7845 parser.parse(text); |
|
7846 } catch (ex) { |
|
7847 reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {}); |
|
7848 } |
|
7849 |
|
7850 report = { |
|
7851 messages : reporter.messages, |
|
7852 stats : reporter.stats, |
|
7853 ruleset : reporter.ruleset, |
|
7854 allow : reporter.allow, |
|
7855 ignore : reporter.ignore |
|
7856 }; |
|
7857 |
|
7858 // sort by line numbers, rollups at the bottom |
|
7859 report.messages.sort(function (a, b) { |
|
7860 if (a.rollup && !b.rollup) { |
|
7861 return 1; |
|
7862 } else if (!a.rollup && b.rollup) { |
|
7863 return -1; |
|
7864 } else { |
|
7865 return a.line - b.line; |
|
7866 } |
|
7867 }); |
|
7868 |
|
7869 return report; |
|
7870 }; |
|
7871 |
|
7872 //------------------------------------------------------------------------- |
|
7873 // Publish the API |
|
7874 //------------------------------------------------------------------------- |
|
7875 |
|
7876 return api; |
|
7877 |
|
7878 })(); |
|
7879 |
|
7880 /** |
|
7881 * An instance of Report is used to report results of the |
|
7882 * verification back to the main API. |
|
7883 * @class Reporter |
|
7884 * @constructor |
|
7885 * @param {String[]} lines The text lines of the source. |
|
7886 * @param {Object} ruleset The set of rules to work with, including if |
|
7887 * they are errors or warnings. |
|
7888 * @param {Object} explicitly allowed lines |
|
7889 * @param {[][]} ingore list of line ranges to be ignored |
|
7890 */ |
|
7891 function Reporter(lines, ruleset, allow, ignore) { |
|
7892 "use strict"; |
|
7893 |
|
7894 /** |
|
7895 * List of messages being reported. |
|
7896 * @property messages |
|
7897 * @type String[] |
|
7898 */ |
|
7899 this.messages = []; |
|
7900 |
|
7901 /** |
|
7902 * List of statistics being reported. |
|
7903 * @property stats |
|
7904 * @type String[] |
|
7905 */ |
|
7906 this.stats = []; |
|
7907 |
|
7908 /** |
|
7909 * Lines of code being reported on. Used to provide contextual information |
|
7910 * for messages. |
|
7911 * @property lines |
|
7912 * @type String[] |
|
7913 */ |
|
7914 this.lines = lines; |
|
7915 |
|
7916 /** |
|
7917 * Information about the rules. Used to determine whether an issue is an |
|
7918 * error or warning. |
|
7919 * @property ruleset |
|
7920 * @type Object |
|
7921 */ |
|
7922 this.ruleset = ruleset; |
|
7923 |
|
7924 /** |
|
7925 * Lines with specific rule messages to leave out of the report. |
|
7926 * @property allow |
|
7927 * @type Object |
|
7928 */ |
|
7929 this.allow = allow; |
|
7930 if (!this.allow) { |
|
7931 this.allow = {}; |
|
7932 } |
|
7933 |
|
7934 /** |
|
7935 * Linesets not to include in the report. |
|
7936 * @property ignore |
|
7937 * @type [][] |
|
7938 */ |
|
7939 this.ignore = ignore; |
|
7940 if (!this.ignore) { |
|
7941 this.ignore = []; |
|
7942 } |
|
7943 } |
|
7944 |
|
7945 Reporter.prototype = { |
|
7946 |
|
7947 // restore constructor |
|
7948 constructor: Reporter, |
|
7949 |
|
7950 /** |
|
7951 * Report an error. |
|
7952 * @param {String} message The message to store. |
|
7953 * @param {int} line The line number. |
|
7954 * @param {int} col The column number. |
|
7955 * @param {Object} rule The rule this message relates to. |
|
7956 * @method error |
|
7957 */ |
|
7958 error: function(message, line, col, rule) { |
|
7959 "use strict"; |
|
7960 this.messages.push({ |
|
7961 type : "error", |
|
7962 line : line, |
|
7963 col : col, |
|
7964 message : message, |
|
7965 evidence: this.lines[line-1], |
|
7966 rule : rule || {} |
|
7967 }); |
|
7968 }, |
|
7969 |
|
7970 /** |
|
7971 * Report an warning. |
|
7972 * @param {String} message The message to store. |
|
7973 * @param {int} line The line number. |
|
7974 * @param {int} col The column number. |
|
7975 * @param {Object} rule The rule this message relates to. |
|
7976 * @method warn |
|
7977 * @deprecated Use report instead. |
|
7978 */ |
|
7979 warn: function(message, line, col, rule) { |
|
7980 "use strict"; |
|
7981 this.report(message, line, col, rule); |
|
7982 }, |
|
7983 |
|
7984 /** |
|
7985 * Report an issue. |
|
7986 * @param {String} message The message to store. |
|
7987 * @param {int} line The line number. |
|
7988 * @param {int} col The column number. |
|
7989 * @param {Object} rule The rule this message relates to. |
|
7990 * @method report |
|
7991 */ |
|
7992 report: function(message, line, col, rule) { |
|
7993 "use strict"; |
|
7994 |
|
7995 // Check if rule violation should be allowed |
|
7996 if (this.allow.hasOwnProperty(line) && this.allow[line].hasOwnProperty(rule.id)) { |
|
7997 return; |
|
7998 } |
|
7999 |
|
8000 var ignore = false; |
|
8001 CSSLint.Util.forEach(this.ignore, function (range) { |
|
8002 if (range[0] <= line && line <= range[1]) { |
|
8003 ignore = true; |
|
8004 } |
|
8005 }); |
|
8006 if (ignore) { |
|
8007 return; |
|
8008 } |
|
8009 |
|
8010 this.messages.push({ |
|
8011 type : this.ruleset[rule.id] === 2 ? "error" : "warning", |
|
8012 line : line, |
|
8013 col : col, |
|
8014 message : message, |
|
8015 evidence: this.lines[line-1], |
|
8016 rule : rule |
|
8017 }); |
|
8018 }, |
|
8019 |
|
8020 /** |
|
8021 * Report some informational text. |
|
8022 * @param {String} message The message to store. |
|
8023 * @param {int} line The line number. |
|
8024 * @param {int} col The column number. |
|
8025 * @param {Object} rule The rule this message relates to. |
|
8026 * @method info |
|
8027 */ |
|
8028 info: function(message, line, col, rule) { |
|
8029 "use strict"; |
|
8030 this.messages.push({ |
|
8031 type : "info", |
|
8032 line : line, |
|
8033 col : col, |
|
8034 message : message, |
|
8035 evidence: this.lines[line-1], |
|
8036 rule : rule |
|
8037 }); |
|
8038 }, |
|
8039 |
|
8040 /** |
|
8041 * Report some rollup error information. |
|
8042 * @param {String} message The message to store. |
|
8043 * @param {Object} rule The rule this message relates to. |
|
8044 * @method rollupError |
|
8045 */ |
|
8046 rollupError: function(message, rule) { |
|
8047 "use strict"; |
|
8048 this.messages.push({ |
|
8049 type : "error", |
|
8050 rollup : true, |
|
8051 message : message, |
|
8052 rule : rule |
|
8053 }); |
|
8054 }, |
|
8055 |
|
8056 /** |
|
8057 * Report some rollup warning information. |
|
8058 * @param {String} message The message to store. |
|
8059 * @param {Object} rule The rule this message relates to. |
|
8060 * @method rollupWarn |
|
8061 */ |
|
8062 rollupWarn: function(message, rule) { |
|
8063 "use strict"; |
|
8064 this.messages.push({ |
|
8065 type : "warning", |
|
8066 rollup : true, |
|
8067 message : message, |
|
8068 rule : rule |
|
8069 }); |
|
8070 }, |
|
8071 |
|
8072 /** |
|
8073 * Report a statistic. |
|
8074 * @param {String} name The name of the stat to store. |
|
8075 * @param {Variant} value The value of the stat. |
|
8076 * @method stat |
|
8077 */ |
|
8078 stat: function(name, value) { |
|
8079 "use strict"; |
|
8080 this.stats[name] = value; |
|
8081 } |
|
8082 }; |
|
8083 |
|
8084 // expose for testing purposes |
|
8085 CSSLint._Reporter = Reporter; |
|
8086 |
|
8087 /* |
|
8088 * Utility functions that make life easier. |
|
8089 */ |
|
8090 CSSLint.Util = { |
|
8091 /* |
|
8092 * Adds all properties from supplier onto receiver, |
|
8093 * overwriting if the same name already exists on |
|
8094 * receiver. |
|
8095 * @param {Object} The object to receive the properties. |
|
8096 * @param {Object} The object to provide the properties. |
|
8097 * @return {Object} The receiver |
|
8098 */ |
|
8099 mix: function(receiver, supplier) { |
|
8100 "use strict"; |
|
8101 var prop; |
|
8102 |
|
8103 for (prop in supplier) { |
|
8104 if (supplier.hasOwnProperty(prop)) { |
|
8105 receiver[prop] = supplier[prop]; |
|
8106 } |
|
8107 } |
|
8108 |
|
8109 return prop; |
|
8110 }, |
|
8111 |
|
8112 /* |
|
8113 * Polyfill for array indexOf() method. |
|
8114 * @param {Array} values The array to search. |
|
8115 * @param {Variant} value The value to search for. |
|
8116 * @return {int} The index of the value if found, -1 if not. |
|
8117 */ |
|
8118 indexOf: function(values, value) { |
|
8119 "use strict"; |
|
8120 if (values.indexOf) { |
|
8121 return values.indexOf(value); |
|
8122 } else { |
|
8123 for (var i=0, len=values.length; i < len; i++) { |
|
8124 if (values[i] === value) { |
|
8125 return i; |
|
8126 } |
|
8127 } |
|
8128 return -1; |
|
8129 } |
|
8130 }, |
|
8131 |
|
8132 /* |
|
8133 * Polyfill for array forEach() method. |
|
8134 * @param {Array} values The array to operate on. |
|
8135 * @param {Function} func The function to call on each item. |
|
8136 * @return {void} |
|
8137 */ |
|
8138 forEach: function(values, func) { |
|
8139 "use strict"; |
|
8140 if (values.forEach) { |
|
8141 return values.forEach(func); |
|
8142 } else { |
|
8143 for (var i=0, len=values.length; i < len; i++) { |
|
8144 func(values[i], i, values); |
|
8145 } |
|
8146 } |
|
8147 } |
|
8148 }; |
|
8149 |
|
8150 /* |
|
8151 * Rule: Don't use adjoining classes (.foo.bar). |
|
8152 */ |
|
8153 |
|
8154 CSSLint.addRule({ |
|
8155 |
|
8156 // rule information |
|
8157 id: "adjoining-classes", |
|
8158 name: "Disallow adjoining classes", |
|
8159 desc: "Don't use adjoining classes.", |
|
8160 url: "https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes", |
|
8161 browsers: "IE6", |
|
8162 |
|
8163 // initialization |
|
8164 init: function(parser, reporter) { |
|
8165 "use strict"; |
|
8166 var rule = this; |
|
8167 parser.addListener("startrule", function(event) { |
|
8168 var selectors = event.selectors, |
|
8169 selector, |
|
8170 part, |
|
8171 modifier, |
|
8172 classCount, |
|
8173 i, j, k; |
|
8174 |
|
8175 for (i=0; i < selectors.length; i++) { |
|
8176 selector = selectors[i]; |
|
8177 for (j=0; j < selector.parts.length; j++) { |
|
8178 part = selector.parts[j]; |
|
8179 if (part.type === parser.SELECTOR_PART_TYPE) { |
|
8180 classCount = 0; |
|
8181 for (k=0; k < part.modifiers.length; k++) { |
|
8182 modifier = part.modifiers[k]; |
|
8183 if (modifier.type === "class") { |
|
8184 classCount++; |
|
8185 } |
|
8186 if (classCount > 1){ |
|
8187 reporter.report("Adjoining classes: "+selectors[i].text, part.line, part.col, rule); |
|
8188 } |
|
8189 } |
|
8190 } |
|
8191 } |
|
8192 } |
|
8193 }); |
|
8194 } |
|
8195 |
|
8196 }); |
|
8197 |
|
8198 /* |
|
8199 * Rule: Don't use width or height when using padding or border. |
|
8200 */ |
|
8201 CSSLint.addRule({ |
|
8202 |
|
8203 // rule information |
|
8204 id: "box-model", |
|
8205 name: "Beware of broken box size", |
|
8206 desc: "Don't use width or height when using padding or border.", |
|
8207 url: "https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size", |
|
8208 browsers: "All", |
|
8209 |
|
8210 // initialization |
|
8211 init: function(parser, reporter) { |
|
8212 "use strict"; |
|
8213 var rule = this, |
|
8214 widthProperties = { |
|
8215 border: 1, |
|
8216 "border-left": 1, |
|
8217 "border-right": 1, |
|
8218 padding: 1, |
|
8219 "padding-left": 1, |
|
8220 "padding-right": 1 |
|
8221 }, |
|
8222 heightProperties = { |
|
8223 border: 1, |
|
8224 "border-bottom": 1, |
|
8225 "border-top": 1, |
|
8226 padding: 1, |
|
8227 "padding-bottom": 1, |
|
8228 "padding-top": 1 |
|
8229 }, |
|
8230 properties, |
|
8231 boxSizing = false; |
|
8232 |
|
8233 function startRule() { |
|
8234 properties = {}; |
|
8235 boxSizing = false; |
|
8236 } |
|
8237 |
|
8238 function endRule() { |
|
8239 var prop, value; |
|
8240 |
|
8241 if (!boxSizing) { |
|
8242 if (properties.height) { |
|
8243 for (prop in heightProperties) { |
|
8244 if (heightProperties.hasOwnProperty(prop) && properties[prop]) { |
|
8245 value = properties[prop].value; |
|
8246 // special case for padding |
|
8247 if (!(prop === "padding" && value.parts.length === 2 && value.parts[0].value === 0)) { |
|
8248 reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); |
|
8249 } |
|
8250 } |
|
8251 } |
|
8252 } |
|
8253 |
|
8254 if (properties.width) { |
|
8255 for (prop in widthProperties) { |
|
8256 if (widthProperties.hasOwnProperty(prop) && properties[prop]) { |
|
8257 value = properties[prop].value; |
|
8258 |
|
8259 if (!(prop === "padding" && value.parts.length === 2 && value.parts[1].value === 0)) { |
|
8260 reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); |
|
8261 } |
|
8262 } |
|
8263 } |
|
8264 } |
|
8265 } |
|
8266 } |
|
8267 |
|
8268 parser.addListener("startrule", startRule); |
|
8269 parser.addListener("startfontface", startRule); |
|
8270 parser.addListener("startpage", startRule); |
|
8271 parser.addListener("startpagemargin", startRule); |
|
8272 parser.addListener("startkeyframerule", startRule); |
|
8273 parser.addListener("startviewport", startRule); |
|
8274 |
|
8275 parser.addListener("property", function(event) { |
|
8276 var name = event.property.text.toLowerCase(); |
|
8277 |
|
8278 if (heightProperties[name] || widthProperties[name]) { |
|
8279 if (!/^0\S*$/.test(event.value) && !(name === "border" && event.value.toString() === "none")) { |
|
8280 properties[name] = { |
|
8281 line: event.property.line, |
|
8282 col: event.property.col, |
|
8283 value: event.value |
|
8284 }; |
|
8285 } |
|
8286 } else { |
|
8287 if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)) { |
|
8288 properties[name] = 1; |
|
8289 } else if (name === "box-sizing") { |
|
8290 boxSizing = true; |
|
8291 } |
|
8292 } |
|
8293 |
|
8294 }); |
|
8295 |
|
8296 parser.addListener("endrule", endRule); |
|
8297 parser.addListener("endfontface", endRule); |
|
8298 parser.addListener("endpage", endRule); |
|
8299 parser.addListener("endpagemargin", endRule); |
|
8300 parser.addListener("endkeyframerule", endRule); |
|
8301 parser.addListener("endviewport", endRule); |
|
8302 } |
|
8303 |
|
8304 }); |
|
8305 |
|
8306 /* |
|
8307 * Rule: box-sizing doesn't work in IE6 and IE7. |
|
8308 */ |
|
8309 |
|
8310 CSSLint.addRule({ |
|
8311 |
|
8312 // rule information |
|
8313 id: "box-sizing", |
|
8314 name: "Disallow use of box-sizing", |
|
8315 desc: "The box-sizing properties isn't supported in IE6 and IE7.", |
|
8316 url: "https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing", |
|
8317 browsers: "IE6, IE7", |
|
8318 tags: ["Compatibility"], |
|
8319 |
|
8320 // initialization |
|
8321 init: function(parser, reporter) { |
|
8322 "use strict"; |
|
8323 var rule = this; |
|
8324 |
|
8325 parser.addListener("property", function(event) { |
|
8326 var name = event.property.text.toLowerCase(); |
|
8327 |
|
8328 if (name === "box-sizing") { |
|
8329 reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule); |
|
8330 } |
|
8331 }); |
|
8332 } |
|
8333 |
|
8334 }); |
|
8335 |
|
8336 /* |
|
8337 * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE |
|
8338 * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax) |
|
8339 */ |
|
8340 |
|
8341 CSSLint.addRule({ |
|
8342 |
|
8343 // rule information |
|
8344 id: "bulletproof-font-face", |
|
8345 name: "Use the bulletproof @font-face syntax", |
|
8346 desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).", |
|
8347 url: "https://github.com/CSSLint/csslint/wiki/Bulletproof-font-face", |
|
8348 browsers: "All", |
|
8349 |
|
8350 // initialization |
|
8351 init: function(parser, reporter) { |
|
8352 "use strict"; |
|
8353 var rule = this, |
|
8354 fontFaceRule = false, |
|
8355 firstSrc = true, |
|
8356 ruleFailed = false, |
|
8357 line, col; |
|
8358 |
|
8359 // Mark the start of a @font-face declaration so we only test properties inside it |
|
8360 parser.addListener("startfontface", function() { |
|
8361 fontFaceRule = true; |
|
8362 }); |
|
8363 |
|
8364 parser.addListener("property", function(event) { |
|
8365 // If we aren't inside an @font-face declaration then just return |
|
8366 if (!fontFaceRule) { |
|
8367 return; |
|
8368 } |
|
8369 |
|
8370 var propertyName = event.property.toString().toLowerCase(), |
|
8371 value = event.value.toString(); |
|
8372 |
|
8373 // Set the line and col numbers for use in the endfontface listener |
|
8374 line = event.line; |
|
8375 col = event.col; |
|
8376 |
|
8377 // This is the property that we care about, we can ignore the rest |
|
8378 if (propertyName === "src") { |
|
8379 var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i; |
|
8380 |
|
8381 // We need to handle the advanced syntax with two src properties |
|
8382 if (!value.match(regex) && firstSrc) { |
|
8383 ruleFailed = true; |
|
8384 firstSrc = false; |
|
8385 } else if (value.match(regex) && !firstSrc) { |
|
8386 ruleFailed = false; |
|
8387 } |
|
8388 } |
|
8389 |
|
8390 |
|
8391 }); |
|
8392 |
|
8393 // Back to normal rules that we don't need to test |
|
8394 parser.addListener("endfontface", function() { |
|
8395 fontFaceRule = false; |
|
8396 |
|
8397 if (ruleFailed) { |
|
8398 reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule); |
|
8399 } |
|
8400 }); |
|
8401 } |
|
8402 }); |
|
8403 |
|
8404 /* |
|
8405 * Rule: Include all compatible vendor prefixes to reach a wider |
|
8406 * range of users. |
|
8407 */ |
|
8408 |
|
8409 CSSLint.addRule({ |
|
8410 |
|
8411 // rule information |
|
8412 id: "compatible-vendor-prefixes", |
|
8413 name: "Require compatible vendor prefixes", |
|
8414 desc: "Include all compatible vendor prefixes to reach a wider range of users.", |
|
8415 url: "https://github.com/CSSLint/csslint/wiki/Require-compatible-vendor-prefixes", |
|
8416 browsers: "All", |
|
8417 |
|
8418 // initialization |
|
8419 init: function (parser, reporter) { |
|
8420 "use strict"; |
|
8421 var rule = this, |
|
8422 compatiblePrefixes, |
|
8423 properties, |
|
8424 prop, |
|
8425 variations, |
|
8426 prefixed, |
|
8427 i, |
|
8428 len, |
|
8429 inKeyFrame = false, |
|
8430 arrayPush = Array.prototype.push, |
|
8431 applyTo = []; |
|
8432 |
|
8433 // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details |
|
8434 compatiblePrefixes = { |
|
8435 "animation" : "webkit", |
|
8436 "animation-delay" : "webkit", |
|
8437 "animation-direction" : "webkit", |
|
8438 "animation-duration" : "webkit", |
|
8439 "animation-fill-mode" : "webkit", |
|
8440 "animation-iteration-count" : "webkit", |
|
8441 "animation-name" : "webkit", |
|
8442 "animation-play-state" : "webkit", |
|
8443 "animation-timing-function" : "webkit", |
|
8444 "appearance" : "webkit moz", |
|
8445 "border-end" : "webkit moz", |
|
8446 "border-end-color" : "webkit moz", |
|
8447 "border-end-style" : "webkit moz", |
|
8448 "border-end-width" : "webkit moz", |
|
8449 "border-image" : "webkit moz o", |
|
8450 "border-radius" : "webkit", |
|
8451 "border-start" : "webkit moz", |
|
8452 "border-start-color" : "webkit moz", |
|
8453 "border-start-style" : "webkit moz", |
|
8454 "border-start-width" : "webkit moz", |
|
8455 "box-align" : "webkit moz ms", |
|
8456 "box-direction" : "webkit moz ms", |
|
8457 "box-flex" : "webkit moz ms", |
|
8458 "box-lines" : "webkit ms", |
|
8459 "box-ordinal-group" : "webkit moz ms", |
|
8460 "box-orient" : "webkit moz ms", |
|
8461 "box-pack" : "webkit moz ms", |
|
8462 "box-sizing" : "", |
|
8463 "box-shadow" : "", |
|
8464 "column-count" : "webkit moz ms", |
|
8465 "column-gap" : "webkit moz ms", |
|
8466 "column-rule" : "webkit moz ms", |
|
8467 "column-rule-color" : "webkit moz ms", |
|
8468 "column-rule-style" : "webkit moz ms", |
|
8469 "column-rule-width" : "webkit moz ms", |
|
8470 "column-width" : "webkit moz ms", |
|
8471 "hyphens" : "epub moz", |
|
8472 "line-break" : "webkit ms", |
|
8473 "margin-end" : "webkit moz", |
|
8474 "margin-start" : "webkit moz", |
|
8475 "marquee-speed" : "webkit wap", |
|
8476 "marquee-style" : "webkit wap", |
|
8477 "padding-end" : "webkit moz", |
|
8478 "padding-start" : "webkit moz", |
|
8479 "tab-size" : "moz o", |
|
8480 "text-size-adjust" : "webkit ms", |
|
8481 "transform" : "webkit ms", |
|
8482 "transform-origin" : "webkit ms", |
|
8483 "transition" : "", |
|
8484 "transition-delay" : "", |
|
8485 "transition-duration" : "", |
|
8486 "transition-property" : "", |
|
8487 "transition-timing-function" : "", |
|
8488 "user-modify" : "webkit moz", |
|
8489 "user-select" : "webkit moz ms", |
|
8490 "word-break" : "epub ms", |
|
8491 "writing-mode" : "epub ms" |
|
8492 }; |
|
8493 |
|
8494 |
|
8495 for (prop in compatiblePrefixes) { |
|
8496 if (compatiblePrefixes.hasOwnProperty(prop)) { |
|
8497 variations = []; |
|
8498 prefixed = compatiblePrefixes[prop].split(" "); |
|
8499 for (i = 0, len = prefixed.length; i < len; i++) { |
|
8500 variations.push("-" + prefixed[i] + "-" + prop); |
|
8501 } |
|
8502 compatiblePrefixes[prop] = variations; |
|
8503 arrayPush.apply(applyTo, variations); |
|
8504 } |
|
8505 } |
|
8506 |
|
8507 parser.addListener("startrule", function () { |
|
8508 properties = []; |
|
8509 }); |
|
8510 |
|
8511 parser.addListener("startkeyframes", function (event) { |
|
8512 inKeyFrame = event.prefix || true; |
|
8513 }); |
|
8514 |
|
8515 parser.addListener("endkeyframes", function () { |
|
8516 inKeyFrame = false; |
|
8517 }); |
|
8518 |
|
8519 parser.addListener("property", function (event) { |
|
8520 var name = event.property; |
|
8521 if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { |
|
8522 |
|
8523 // e.g., -moz-transform is okay to be alone in @-moz-keyframes |
|
8524 if (!inKeyFrame || typeof inKeyFrame !== "string" || |
|
8525 name.text.indexOf("-" + inKeyFrame + "-") !== 0) { |
|
8526 properties.push(name); |
|
8527 } |
|
8528 } |
|
8529 }); |
|
8530 |
|
8531 parser.addListener("endrule", function () { |
|
8532 if (!properties.length) { |
|
8533 return; |
|
8534 } |
|
8535 |
|
8536 var propertyGroups = {}, |
|
8537 i, |
|
8538 len, |
|
8539 name, |
|
8540 prop, |
|
8541 variations, |
|
8542 value, |
|
8543 full, |
|
8544 actual, |
|
8545 item, |
|
8546 propertiesSpecified; |
|
8547 |
|
8548 for (i = 0, len = properties.length; i < len; i++) { |
|
8549 name = properties[i]; |
|
8550 |
|
8551 for (prop in compatiblePrefixes) { |
|
8552 if (compatiblePrefixes.hasOwnProperty(prop)) { |
|
8553 variations = compatiblePrefixes[prop]; |
|
8554 if (CSSLint.Util.indexOf(variations, name.text) > -1) { |
|
8555 if (!propertyGroups[prop]) { |
|
8556 propertyGroups[prop] = { |
|
8557 full: variations.slice(0), |
|
8558 actual: [], |
|
8559 actualNodes: [] |
|
8560 }; |
|
8561 } |
|
8562 if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) { |
|
8563 propertyGroups[prop].actual.push(name.text); |
|
8564 propertyGroups[prop].actualNodes.push(name); |
|
8565 } |
|
8566 } |
|
8567 } |
|
8568 } |
|
8569 } |
|
8570 |
|
8571 for (prop in propertyGroups) { |
|
8572 if (propertyGroups.hasOwnProperty(prop)) { |
|
8573 value = propertyGroups[prop]; |
|
8574 full = value.full; |
|
8575 actual = value.actual; |
|
8576 |
|
8577 if (full.length > actual.length) { |
|
8578 for (i = 0, len = full.length; i < len; i++) { |
|
8579 item = full[i]; |
|
8580 if (CSSLint.Util.indexOf(actual, item) === -1) { |
|
8581 propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length === 2) ? actual.join(" and ") : actual.join(", "); |
|
8582 reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule); |
|
8583 } |
|
8584 } |
|
8585 |
|
8586 } |
|
8587 } |
|
8588 } |
|
8589 }); |
|
8590 } |
|
8591 }); |
|
8592 |
|
8593 /* |
|
8594 * Rule: Certain properties don't play well with certain display values. |
|
8595 * - float should not be used with inline-block |
|
8596 * - height, width, margin-top, margin-bottom, float should not be used with inline |
|
8597 * - vertical-align should not be used with block |
|
8598 * - margin, float should not be used with table-* |
|
8599 */ |
|
8600 |
|
8601 CSSLint.addRule({ |
|
8602 |
|
8603 // rule information |
|
8604 id: "display-property-grouping", |
|
8605 name: "Require properties appropriate for display", |
|
8606 desc: "Certain properties shouldn't be used with certain display property values.", |
|
8607 url: "https://github.com/CSSLint/csslint/wiki/Require-properties-appropriate-for-display", |
|
8608 browsers: "All", |
|
8609 |
|
8610 // initialization |
|
8611 init: function(parser, reporter) { |
|
8612 "use strict"; |
|
8613 var rule = this; |
|
8614 |
|
8615 var propertiesToCheck = { |
|
8616 display: 1, |
|
8617 "float": "none", |
|
8618 height: 1, |
|
8619 width: 1, |
|
8620 margin: 1, |
|
8621 "margin-left": 1, |
|
8622 "margin-right": 1, |
|
8623 "margin-bottom": 1, |
|
8624 "margin-top": 1, |
|
8625 padding: 1, |
|
8626 "padding-left": 1, |
|
8627 "padding-right": 1, |
|
8628 "padding-bottom": 1, |
|
8629 "padding-top": 1, |
|
8630 "vertical-align": 1 |
|
8631 }, |
|
8632 properties; |
|
8633 |
|
8634 function reportProperty(name, display, msg) { |
|
8635 if (properties[name]) { |
|
8636 if (typeof propertiesToCheck[name] !== "string" || properties[name].value.toLowerCase() !== propertiesToCheck[name]) { |
|
8637 reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule); |
|
8638 } |
|
8639 } |
|
8640 } |
|
8641 |
|
8642 function startRule() { |
|
8643 properties = {}; |
|
8644 } |
|
8645 |
|
8646 function endRule() { |
|
8647 |
|
8648 var display = properties.display ? properties.display.value : null; |
|
8649 if (display) { |
|
8650 switch (display) { |
|
8651 |
|
8652 case "inline": |
|
8653 // height, width, margin-top, margin-bottom, float should not be used with inline |
|
8654 reportProperty("height", display); |
|
8655 reportProperty("width", display); |
|
8656 reportProperty("margin", display); |
|
8657 reportProperty("margin-top", display); |
|
8658 reportProperty("margin-bottom", display); |
|
8659 reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug)."); |
|
8660 break; |
|
8661 |
|
8662 case "block": |
|
8663 // vertical-align should not be used with block |
|
8664 reportProperty("vertical-align", display); |
|
8665 break; |
|
8666 |
|
8667 case "inline-block": |
|
8668 // float should not be used with inline-block |
|
8669 reportProperty("float", display); |
|
8670 break; |
|
8671 |
|
8672 default: |
|
8673 // margin, float should not be used with table |
|
8674 if (display.indexOf("table-") === 0) { |
|
8675 reportProperty("margin", display); |
|
8676 reportProperty("margin-left", display); |
|
8677 reportProperty("margin-right", display); |
|
8678 reportProperty("margin-top", display); |
|
8679 reportProperty("margin-bottom", display); |
|
8680 reportProperty("float", display); |
|
8681 } |
|
8682 |
|
8683 // otherwise do nothing |
|
8684 } |
|
8685 } |
|
8686 |
|
8687 } |
|
8688 |
|
8689 parser.addListener("startrule", startRule); |
|
8690 parser.addListener("startfontface", startRule); |
|
8691 parser.addListener("startkeyframerule", startRule); |
|
8692 parser.addListener("startpagemargin", startRule); |
|
8693 parser.addListener("startpage", startRule); |
|
8694 parser.addListener("startviewport", startRule); |
|
8695 |
|
8696 parser.addListener("property", function(event) { |
|
8697 var name = event.property.text.toLowerCase(); |
|
8698 |
|
8699 if (propertiesToCheck[name]) { |
|
8700 properties[name] = { |
|
8701 value: event.value.text, |
|
8702 line: event.property.line, |
|
8703 col: event.property.col |
|
8704 }; |
|
8705 } |
|
8706 }); |
|
8707 |
|
8708 parser.addListener("endrule", endRule); |
|
8709 parser.addListener("endfontface", endRule); |
|
8710 parser.addListener("endkeyframerule", endRule); |
|
8711 parser.addListener("endpagemargin", endRule); |
|
8712 parser.addListener("endpage", endRule); |
|
8713 parser.addListener("endviewport", endRule); |
|
8714 |
|
8715 } |
|
8716 |
|
8717 }); |
|
8718 |
|
8719 /* |
|
8720 * Rule: Disallow duplicate background-images (using url). |
|
8721 */ |
|
8722 |
|
8723 CSSLint.addRule({ |
|
8724 |
|
8725 // rule information |
|
8726 id: "duplicate-background-images", |
|
8727 name: "Disallow duplicate background images", |
|
8728 desc: "Every background-image should be unique. Use a common class for e.g. sprites.", |
|
8729 url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images", |
|
8730 browsers: "All", |
|
8731 |
|
8732 // initialization |
|
8733 init: function(parser, reporter) { |
|
8734 "use strict"; |
|
8735 var rule = this, |
|
8736 stack = {}; |
|
8737 |
|
8738 parser.addListener("property", function(event) { |
|
8739 var name = event.property.text, |
|
8740 value = event.value, |
|
8741 i, len; |
|
8742 |
|
8743 if (name.match(/background/i)) { |
|
8744 for (i=0, len=value.parts.length; i < len; i++) { |
|
8745 if (value.parts[i].type === "uri") { |
|
8746 if (typeof stack[value.parts[i].uri] === "undefined") { |
|
8747 stack[value.parts[i].uri] = event; |
|
8748 } else { |
|
8749 reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule); |
|
8750 } |
|
8751 } |
|
8752 } |
|
8753 } |
|
8754 }); |
|
8755 } |
|
8756 }); |
|
8757 |
|
8758 /* |
|
8759 * Rule: Duplicate properties must appear one after the other. If an already-defined |
|
8760 * property appears somewhere else in the rule, then it's likely an error. |
|
8761 */ |
|
8762 |
|
8763 CSSLint.addRule({ |
|
8764 |
|
8765 // rule information |
|
8766 id: "duplicate-properties", |
|
8767 name: "Disallow duplicate properties", |
|
8768 desc: "Duplicate properties must appear one after the other.", |
|
8769 url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties", |
|
8770 browsers: "All", |
|
8771 |
|
8772 // initialization |
|
8773 init: function(parser, reporter) { |
|
8774 "use strict"; |
|
8775 var rule = this, |
|
8776 properties, |
|
8777 lastProperty; |
|
8778 |
|
8779 function startRule() { |
|
8780 properties = {}; |
|
8781 } |
|
8782 |
|
8783 parser.addListener("startrule", startRule); |
|
8784 parser.addListener("startfontface", startRule); |
|
8785 parser.addListener("startpage", startRule); |
|
8786 parser.addListener("startpagemargin", startRule); |
|
8787 parser.addListener("startkeyframerule", startRule); |
|
8788 parser.addListener("startviewport", startRule); |
|
8789 |
|
8790 parser.addListener("property", function(event) { |
|
8791 var property = event.property, |
|
8792 name = property.text.toLowerCase(); |
|
8793 |
|
8794 if (properties[name] && (lastProperty !== name || properties[name] === event.value.text)) { |
|
8795 reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); |
|
8796 } |
|
8797 |
|
8798 properties[name] = event.value.text; |
|
8799 lastProperty = name; |
|
8800 |
|
8801 }); |
|
8802 |
|
8803 |
|
8804 } |
|
8805 |
|
8806 }); |
|
8807 |
|
8808 /* |
|
8809 * Rule: Style rules without any properties defined should be removed. |
|
8810 */ |
|
8811 |
|
8812 CSSLint.addRule({ |
|
8813 |
|
8814 // rule information |
|
8815 id: "empty-rules", |
|
8816 name: "Disallow empty rules", |
|
8817 desc: "Rules without any properties specified should be removed.", |
|
8818 url: "https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules", |
|
8819 browsers: "All", |
|
8820 |
|
8821 // initialization |
|
8822 init: function(parser, reporter) { |
|
8823 "use strict"; |
|
8824 var rule = this, |
|
8825 count = 0; |
|
8826 |
|
8827 parser.addListener("startrule", function() { |
|
8828 count=0; |
|
8829 }); |
|
8830 |
|
8831 parser.addListener("property", function() { |
|
8832 count++; |
|
8833 }); |
|
8834 |
|
8835 parser.addListener("endrule", function(event) { |
|
8836 var selectors = event.selectors; |
|
8837 if (count === 0) { |
|
8838 reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule); |
|
8839 } |
|
8840 }); |
|
8841 } |
|
8842 |
|
8843 }); |
|
8844 |
|
8845 /* |
|
8846 * Rule: There should be no syntax errors. (Duh.) |
|
8847 */ |
|
8848 |
|
8849 CSSLint.addRule({ |
|
8850 |
|
8851 // rule information |
|
8852 id: "errors", |
|
8853 name: "Parsing Errors", |
|
8854 desc: "This rule looks for recoverable syntax errors.", |
|
8855 browsers: "All", |
|
8856 |
|
8857 // initialization |
|
8858 init: function(parser, reporter) { |
|
8859 "use strict"; |
|
8860 var rule = this; |
|
8861 |
|
8862 parser.addListener("error", function(event) { |
|
8863 reporter.error(event.message, event.line, event.col, rule); |
|
8864 }); |
|
8865 |
|
8866 } |
|
8867 |
|
8868 }); |
|
8869 |
|
8870 CSSLint.addRule({ |
|
8871 |
|
8872 // rule information |
|
8873 id: "fallback-colors", |
|
8874 name: "Require fallback colors", |
|
8875 desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.", |
|
8876 url: "https://github.com/CSSLint/csslint/wiki/Require-fallback-colors", |
|
8877 browsers: "IE6,IE7,IE8", |
|
8878 |
|
8879 // initialization |
|
8880 init: function(parser, reporter) { |
|
8881 "use strict"; |
|
8882 var rule = this, |
|
8883 lastProperty, |
|
8884 propertiesToCheck = { |
|
8885 color: 1, |
|
8886 background: 1, |
|
8887 "border-color": 1, |
|
8888 "border-top-color": 1, |
|
8889 "border-right-color": 1, |
|
8890 "border-bottom-color": 1, |
|
8891 "border-left-color": 1, |
|
8892 border: 1, |
|
8893 "border-top": 1, |
|
8894 "border-right": 1, |
|
8895 "border-bottom": 1, |
|
8896 "border-left": 1, |
|
8897 "background-color": 1 |
|
8898 }; |
|
8899 |
|
8900 function startRule() { |
|
8901 lastProperty = null; |
|
8902 } |
|
8903 |
|
8904 parser.addListener("startrule", startRule); |
|
8905 parser.addListener("startfontface", startRule); |
|
8906 parser.addListener("startpage", startRule); |
|
8907 parser.addListener("startpagemargin", startRule); |
|
8908 parser.addListener("startkeyframerule", startRule); |
|
8909 parser.addListener("startviewport", startRule); |
|
8910 |
|
8911 parser.addListener("property", function(event) { |
|
8912 var property = event.property, |
|
8913 name = property.text.toLowerCase(), |
|
8914 parts = event.value.parts, |
|
8915 i = 0, |
|
8916 colorType = "", |
|
8917 len = parts.length; |
|
8918 |
|
8919 if (propertiesToCheck[name]) { |
|
8920 while (i < len) { |
|
8921 if (parts[i].type === "color") { |
|
8922 if ("alpha" in parts[i] || "hue" in parts[i]) { |
|
8923 |
|
8924 if (/([^\)]+)\(/.test(parts[i])) { |
|
8925 colorType = RegExp.$1.toUpperCase(); |
|
8926 } |
|
8927 |
|
8928 if (!lastProperty || (lastProperty.property.text.toLowerCase() !== name || lastProperty.colorType !== "compat")) { |
|
8929 reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule); |
|
8930 } |
|
8931 } else { |
|
8932 event.colorType = "compat"; |
|
8933 } |
|
8934 } |
|
8935 |
|
8936 i++; |
|
8937 } |
|
8938 } |
|
8939 |
|
8940 lastProperty = event; |
|
8941 }); |
|
8942 |
|
8943 } |
|
8944 |
|
8945 }); |
|
8946 |
|
8947 /* |
|
8948 * Rule: You shouldn't use more than 10 floats. If you do, there's probably |
|
8949 * room for some abstraction. |
|
8950 */ |
|
8951 |
|
8952 CSSLint.addRule({ |
|
8953 |
|
8954 // rule information |
|
8955 id: "floats", |
|
8956 name: "Disallow too many floats", |
|
8957 desc: "This rule tests if the float property is used too many times", |
|
8958 url: "https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats", |
|
8959 browsers: "All", |
|
8960 |
|
8961 // initialization |
|
8962 init: function(parser, reporter) { |
|
8963 "use strict"; |
|
8964 var rule = this; |
|
8965 var count = 0; |
|
8966 |
|
8967 // count how many times "float" is used |
|
8968 parser.addListener("property", function(event) { |
|
8969 if (event.property.text.toLowerCase() === "float" && |
|
8970 event.value.text.toLowerCase() !== "none") { |
|
8971 count++; |
|
8972 } |
|
8973 }); |
|
8974 |
|
8975 // report the results |
|
8976 parser.addListener("endstylesheet", function() { |
|
8977 reporter.stat("floats", count); |
|
8978 if (count >= 10) { |
|
8979 reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule); |
|
8980 } |
|
8981 }); |
|
8982 } |
|
8983 |
|
8984 }); |
|
8985 |
|
8986 /* |
|
8987 * Rule: Avoid too many @font-face declarations in the same stylesheet. |
|
8988 */ |
|
8989 |
|
8990 CSSLint.addRule({ |
|
8991 |
|
8992 // rule information |
|
8993 id: "font-faces", |
|
8994 name: "Don't use too many web fonts", |
|
8995 desc: "Too many different web fonts in the same stylesheet.", |
|
8996 url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts", |
|
8997 browsers: "All", |
|
8998 |
|
8999 // initialization |
|
9000 init: function(parser, reporter) { |
|
9001 "use strict"; |
|
9002 var rule = this, |
|
9003 count = 0; |
|
9004 |
|
9005 |
|
9006 parser.addListener("startfontface", function() { |
|
9007 count++; |
|
9008 }); |
|
9009 |
|
9010 parser.addListener("endstylesheet", function() { |
|
9011 if (count > 5) { |
|
9012 reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule); |
|
9013 } |
|
9014 }); |
|
9015 } |
|
9016 |
|
9017 }); |
|
9018 |
|
9019 /* |
|
9020 * Rule: You shouldn't need more than 9 font-size declarations. |
|
9021 */ |
|
9022 |
|
9023 CSSLint.addRule({ |
|
9024 |
|
9025 // rule information |
|
9026 id: "font-sizes", |
|
9027 name: "Disallow too many font sizes", |
|
9028 desc: "Checks the number of font-size declarations.", |
|
9029 url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations", |
|
9030 browsers: "All", |
|
9031 |
|
9032 // initialization |
|
9033 init: function(parser, reporter) { |
|
9034 "use strict"; |
|
9035 var rule = this, |
|
9036 count = 0; |
|
9037 |
|
9038 // check for use of "font-size" |
|
9039 parser.addListener("property", function(event) { |
|
9040 if (event.property.toString() === "font-size") { |
|
9041 count++; |
|
9042 } |
|
9043 }); |
|
9044 |
|
9045 // report the results |
|
9046 parser.addListener("endstylesheet", function() { |
|
9047 reporter.stat("font-sizes", count); |
|
9048 if (count >= 10) { |
|
9049 reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule); |
|
9050 } |
|
9051 }); |
|
9052 } |
|
9053 |
|
9054 }); |
|
9055 |
|
9056 /* |
|
9057 * Rule: When using a vendor-prefixed gradient, make sure to use them all. |
|
9058 */ |
|
9059 |
|
9060 CSSLint.addRule({ |
|
9061 |
|
9062 // rule information |
|
9063 id: "gradients", |
|
9064 name: "Require all gradient definitions", |
|
9065 desc: "When using a vendor-prefixed gradient, make sure to use them all.", |
|
9066 url: "https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions", |
|
9067 browsers: "All", |
|
9068 |
|
9069 // initialization |
|
9070 init: function(parser, reporter) { |
|
9071 "use strict"; |
|
9072 var rule = this, |
|
9073 gradients; |
|
9074 |
|
9075 parser.addListener("startrule", function() { |
|
9076 gradients = { |
|
9077 moz: 0, |
|
9078 webkit: 0, |
|
9079 oldWebkit: 0, |
|
9080 o: 0 |
|
9081 }; |
|
9082 }); |
|
9083 |
|
9084 parser.addListener("property", function(event) { |
|
9085 |
|
9086 if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)) { |
|
9087 gradients[RegExp.$1] = 1; |
|
9088 } else if (/\-webkit\-gradient/i.test(event.value)) { |
|
9089 gradients.oldWebkit = 1; |
|
9090 } |
|
9091 |
|
9092 }); |
|
9093 |
|
9094 parser.addListener("endrule", function(event) { |
|
9095 var missing = []; |
|
9096 |
|
9097 if (!gradients.moz) { |
|
9098 missing.push("Firefox 3.6+"); |
|
9099 } |
|
9100 |
|
9101 if (!gradients.webkit) { |
|
9102 missing.push("Webkit (Safari 5+, Chrome)"); |
|
9103 } |
|
9104 |
|
9105 if (!gradients.oldWebkit) { |
|
9106 missing.push("Old Webkit (Safari 4+, Chrome)"); |
|
9107 } |
|
9108 |
|
9109 if (!gradients.o) { |
|
9110 missing.push("Opera 11.1+"); |
|
9111 } |
|
9112 |
|
9113 if (missing.length && missing.length < 4) { |
|
9114 reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); |
|
9115 } |
|
9116 |
|
9117 }); |
|
9118 |
|
9119 } |
|
9120 |
|
9121 }); |
|
9122 |
|
9123 /* |
|
9124 * Rule: Don't use IDs for selectors. |
|
9125 */ |
|
9126 |
|
9127 CSSLint.addRule({ |
|
9128 |
|
9129 // rule information |
|
9130 id: "ids", |
|
9131 name: "Disallow IDs in selectors", |
|
9132 desc: "Selectors should not contain IDs.", |
|
9133 url: "https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors", |
|
9134 browsers: "All", |
|
9135 |
|
9136 // initialization |
|
9137 init: function(parser, reporter) { |
|
9138 "use strict"; |
|
9139 var rule = this; |
|
9140 parser.addListener("startrule", function(event) { |
|
9141 var selectors = event.selectors, |
|
9142 selector, |
|
9143 part, |
|
9144 modifier, |
|
9145 idCount, |
|
9146 i, j, k; |
|
9147 |
|
9148 for (i=0; i < selectors.length; i++) { |
|
9149 selector = selectors[i]; |
|
9150 idCount = 0; |
|
9151 |
|
9152 for (j=0; j < selector.parts.length; j++) { |
|
9153 part = selector.parts[j]; |
|
9154 if (part.type === parser.SELECTOR_PART_TYPE) { |
|
9155 for (k=0; k < part.modifiers.length; k++) { |
|
9156 modifier = part.modifiers[k]; |
|
9157 if (modifier.type === "id") { |
|
9158 idCount++; |
|
9159 } |
|
9160 } |
|
9161 } |
|
9162 } |
|
9163 |
|
9164 if (idCount === 1) { |
|
9165 reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule); |
|
9166 } else if (idCount > 1) { |
|
9167 reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule); |
|
9168 } |
|
9169 } |
|
9170 |
|
9171 }); |
|
9172 } |
|
9173 |
|
9174 }); |
|
9175 |
|
9176 /* |
|
9177 * Rule: IE6-9 supports up to 31 stylesheet import. |
|
9178 * Reference: |
|
9179 * http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx |
|
9180 */ |
|
9181 |
|
9182 CSSLint.addRule({ |
|
9183 |
|
9184 // rule information |
|
9185 id: "import-ie-limit", |
|
9186 name: "@import limit on IE6-IE9", |
|
9187 desc: "IE6-9 supports up to 31 @import per stylesheet", |
|
9188 browsers: "IE6, IE7, IE8, IE9", |
|
9189 |
|
9190 // initialization |
|
9191 init: function(parser, reporter) { |
|
9192 "use strict"; |
|
9193 var rule = this, |
|
9194 MAX_IMPORT_COUNT = 31, |
|
9195 count = 0; |
|
9196 |
|
9197 function startPage() { |
|
9198 count = 0; |
|
9199 } |
|
9200 |
|
9201 parser.addListener("startpage", startPage); |
|
9202 |
|
9203 parser.addListener("import", function() { |
|
9204 count++; |
|
9205 }); |
|
9206 |
|
9207 parser.addListener("endstylesheet", function() { |
|
9208 if (count > MAX_IMPORT_COUNT) { |
|
9209 reporter.rollupError( |
|
9210 "Too many @import rules (" + count + "). IE6-9 supports up to 31 import per stylesheet.", |
|
9211 rule |
|
9212 ); |
|
9213 } |
|
9214 }); |
|
9215 } |
|
9216 |
|
9217 }); |
|
9218 |
|
9219 /* |
|
9220 * Rule: Don't use @import, use <link> instead. |
|
9221 */ |
|
9222 |
|
9223 CSSLint.addRule({ |
|
9224 |
|
9225 // rule information |
|
9226 id: "import", |
|
9227 name: "Disallow @import", |
|
9228 desc: "Don't use @import, use <link> instead.", |
|
9229 url: "https://github.com/CSSLint/csslint/wiki/Disallow-%40import", |
|
9230 browsers: "All", |
|
9231 |
|
9232 // initialization |
|
9233 init: function(parser, reporter) { |
|
9234 "use strict"; |
|
9235 var rule = this; |
|
9236 |
|
9237 parser.addListener("import", function(event) { |
|
9238 reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule); |
|
9239 }); |
|
9240 |
|
9241 } |
|
9242 |
|
9243 }); |
|
9244 |
|
9245 /* |
|
9246 * Rule: Make sure !important is not overused, this could lead to specificity |
|
9247 * war. Display a warning on !important declarations, an error if it's |
|
9248 * used more at least 10 times. |
|
9249 */ |
|
9250 |
|
9251 CSSLint.addRule({ |
|
9252 |
|
9253 // rule information |
|
9254 id: "important", |
|
9255 name: "Disallow !important", |
|
9256 desc: "Be careful when using !important declaration", |
|
9257 url: "https://github.com/CSSLint/csslint/wiki/Disallow-%21important", |
|
9258 browsers: "All", |
|
9259 |
|
9260 // initialization |
|
9261 init: function(parser, reporter) { |
|
9262 "use strict"; |
|
9263 var rule = this, |
|
9264 count = 0; |
|
9265 |
|
9266 // warn that important is used and increment the declaration counter |
|
9267 parser.addListener("property", function(event) { |
|
9268 if (event.important === true) { |
|
9269 count++; |
|
9270 reporter.report("Use of !important", event.line, event.col, rule); |
|
9271 } |
|
9272 }); |
|
9273 |
|
9274 // if there are more than 10, show an error |
|
9275 parser.addListener("endstylesheet", function() { |
|
9276 reporter.stat("important", count); |
|
9277 if (count >= 10) { |
|
9278 reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule); |
|
9279 } |
|
9280 }); |
|
9281 } |
|
9282 |
|
9283 }); |
|
9284 |
|
9285 /* |
|
9286 * Rule: Properties should be known (listed in CSS3 specification) or |
|
9287 * be a vendor-prefixed property. |
|
9288 */ |
|
9289 |
|
9290 CSSLint.addRule({ |
|
9291 |
|
9292 // rule information |
|
9293 id: "known-properties", |
|
9294 name: "Require use of known properties", |
|
9295 desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", |
|
9296 url: "https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties", |
|
9297 browsers: "All", |
|
9298 |
|
9299 // initialization |
|
9300 init: function(parser, reporter) { |
|
9301 "use strict"; |
|
9302 var rule = this; |
|
9303 |
|
9304 parser.addListener("property", function(event) { |
|
9305 |
|
9306 // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) |
|
9307 if (event.invalid) { |
|
9308 reporter.report(event.invalid.message, event.line, event.col, rule); |
|
9309 } |
|
9310 |
|
9311 }); |
|
9312 } |
|
9313 |
|
9314 }); |
|
9315 |
|
9316 /* |
|
9317 * Rule: All properties should be in alphabetical order. |
|
9318 */ |
|
9319 |
|
9320 CSSLint.addRule({ |
|
9321 |
|
9322 // rule information |
|
9323 id: "order-alphabetical", |
|
9324 name: "Alphabetical order", |
|
9325 desc: "Assure properties are in alphabetical order", |
|
9326 browsers: "All", |
|
9327 |
|
9328 // initialization |
|
9329 init: function(parser, reporter) { |
|
9330 "use strict"; |
|
9331 var rule = this, |
|
9332 properties; |
|
9333 |
|
9334 var startRule = function () { |
|
9335 properties = []; |
|
9336 }; |
|
9337 |
|
9338 var endRule = function(event) { |
|
9339 var currentProperties = properties.join(","), |
|
9340 expectedProperties = properties.sort().join(","); |
|
9341 |
|
9342 if (currentProperties !== expectedProperties) { |
|
9343 reporter.report("Rule doesn't have all its properties in alphabetical order.", event.line, event.col, rule); |
|
9344 } |
|
9345 }; |
|
9346 |
|
9347 parser.addListener("startrule", startRule); |
|
9348 parser.addListener("startfontface", startRule); |
|
9349 parser.addListener("startpage", startRule); |
|
9350 parser.addListener("startpagemargin", startRule); |
|
9351 parser.addListener("startkeyframerule", startRule); |
|
9352 parser.addListener("startviewport", startRule); |
|
9353 |
|
9354 parser.addListener("property", function(event) { |
|
9355 var name = event.property.text, |
|
9356 lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, ""); |
|
9357 |
|
9358 properties.push(lowerCasePrefixLessName); |
|
9359 }); |
|
9360 |
|
9361 parser.addListener("endrule", endRule); |
|
9362 parser.addListener("endfontface", endRule); |
|
9363 parser.addListener("endpage", endRule); |
|
9364 parser.addListener("endpagemargin", endRule); |
|
9365 parser.addListener("endkeyframerule", endRule); |
|
9366 parser.addListener("endviewport", endRule); |
|
9367 } |
|
9368 |
|
9369 }); |
|
9370 |
|
9371 /* |
|
9372 * Rule: outline: none or outline: 0 should only be used in a :focus rule |
|
9373 * and only if there are other properties in the same rule. |
|
9374 */ |
|
9375 |
|
9376 CSSLint.addRule({ |
|
9377 |
|
9378 // rule information |
|
9379 id: "outline-none", |
|
9380 name: "Disallow outline: none", |
|
9381 desc: "Use of outline: none or outline: 0 should be limited to :focus rules.", |
|
9382 url: "https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone", |
|
9383 browsers: "All", |
|
9384 tags: ["Accessibility"], |
|
9385 |
|
9386 // initialization |
|
9387 init: function(parser, reporter) { |
|
9388 "use strict"; |
|
9389 var rule = this, |
|
9390 lastRule; |
|
9391 |
|
9392 function startRule(event) { |
|
9393 if (event.selectors) { |
|
9394 lastRule = { |
|
9395 line: event.line, |
|
9396 col: event.col, |
|
9397 selectors: event.selectors, |
|
9398 propCount: 0, |
|
9399 outline: false |
|
9400 }; |
|
9401 } else { |
|
9402 lastRule = null; |
|
9403 } |
|
9404 } |
|
9405 |
|
9406 function endRule() { |
|
9407 if (lastRule) { |
|
9408 if (lastRule.outline) { |
|
9409 if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") === -1) { |
|
9410 reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule); |
|
9411 } else if (lastRule.propCount === 1) { |
|
9412 reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule); |
|
9413 } |
|
9414 } |
|
9415 } |
|
9416 } |
|
9417 |
|
9418 parser.addListener("startrule", startRule); |
|
9419 parser.addListener("startfontface", startRule); |
|
9420 parser.addListener("startpage", startRule); |
|
9421 parser.addListener("startpagemargin", startRule); |
|
9422 parser.addListener("startkeyframerule", startRule); |
|
9423 parser.addListener("startviewport", startRule); |
|
9424 |
|
9425 parser.addListener("property", function(event) { |
|
9426 var name = event.property.text.toLowerCase(), |
|
9427 value = event.value; |
|
9428 |
|
9429 if (lastRule) { |
|
9430 lastRule.propCount++; |
|
9431 if (name === "outline" && (value.toString() === "none" || value.toString() === "0")) { |
|
9432 lastRule.outline = true; |
|
9433 } |
|
9434 } |
|
9435 |
|
9436 }); |
|
9437 |
|
9438 parser.addListener("endrule", endRule); |
|
9439 parser.addListener("endfontface", endRule); |
|
9440 parser.addListener("endpage", endRule); |
|
9441 parser.addListener("endpagemargin", endRule); |
|
9442 parser.addListener("endkeyframerule", endRule); |
|
9443 parser.addListener("endviewport", endRule); |
|
9444 |
|
9445 } |
|
9446 |
|
9447 }); |
|
9448 |
|
9449 /* |
|
9450 * Rule: Don't use classes or IDs with elements (a.foo or a#foo). |
|
9451 */ |
|
9452 |
|
9453 CSSLint.addRule({ |
|
9454 |
|
9455 // rule information |
|
9456 id: "overqualified-elements", |
|
9457 name: "Disallow overqualified elements", |
|
9458 desc: "Don't use classes or IDs with elements (a.foo or a#foo).", |
|
9459 url: "https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements", |
|
9460 browsers: "All", |
|
9461 |
|
9462 // initialization |
|
9463 init: function(parser, reporter) { |
|
9464 "use strict"; |
|
9465 var rule = this, |
|
9466 classes = {}; |
|
9467 |
|
9468 parser.addListener("startrule", function(event) { |
|
9469 var selectors = event.selectors, |
|
9470 selector, |
|
9471 part, |
|
9472 modifier, |
|
9473 i, j, k; |
|
9474 |
|
9475 for (i=0; i < selectors.length; i++) { |
|
9476 selector = selectors[i]; |
|
9477 |
|
9478 for (j=0; j < selector.parts.length; j++) { |
|
9479 part = selector.parts[j]; |
|
9480 if (part.type === parser.SELECTOR_PART_TYPE) { |
|
9481 for (k=0; k < part.modifiers.length; k++) { |
|
9482 modifier = part.modifiers[k]; |
|
9483 if (part.elementName && modifier.type === "id") { |
|
9484 reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule); |
|
9485 } else if (modifier.type === "class") { |
|
9486 |
|
9487 if (!classes[modifier]) { |
|
9488 classes[modifier] = []; |
|
9489 } |
|
9490 classes[modifier].push({ |
|
9491 modifier: modifier, |
|
9492 part: part |
|
9493 }); |
|
9494 } |
|
9495 } |
|
9496 } |
|
9497 } |
|
9498 } |
|
9499 }); |
|
9500 |
|
9501 parser.addListener("endstylesheet", function() { |
|
9502 |
|
9503 var prop; |
|
9504 for (prop in classes) { |
|
9505 if (classes.hasOwnProperty(prop)) { |
|
9506 |
|
9507 // one use means that this is overqualified |
|
9508 if (classes[prop].length === 1 && classes[prop][0].part.elementName) { |
|
9509 reporter.report("Element (" + classes[prop][0].part + ") is overqualified, just use " + classes[prop][0].modifier + " without element name.", classes[prop][0].part.line, classes[prop][0].part.col, rule); |
|
9510 } |
|
9511 } |
|
9512 } |
|
9513 }); |
|
9514 } |
|
9515 |
|
9516 }); |
|
9517 |
|
9518 /* |
|
9519 * Rule: Headings (h1-h6) should not be qualified (namespaced). |
|
9520 */ |
|
9521 |
|
9522 CSSLint.addRule({ |
|
9523 |
|
9524 // rule information |
|
9525 id: "qualified-headings", |
|
9526 name: "Disallow qualified headings", |
|
9527 desc: "Headings should not be qualified (namespaced).", |
|
9528 url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings", |
|
9529 browsers: "All", |
|
9530 |
|
9531 // initialization |
|
9532 init: function(parser, reporter) { |
|
9533 "use strict"; |
|
9534 var rule = this; |
|
9535 |
|
9536 parser.addListener("startrule", function(event) { |
|
9537 var selectors = event.selectors, |
|
9538 selector, |
|
9539 part, |
|
9540 i, j; |
|
9541 |
|
9542 for (i=0; i < selectors.length; i++) { |
|
9543 selector = selectors[i]; |
|
9544 |
|
9545 for (j=0; j < selector.parts.length; j++) { |
|
9546 part = selector.parts[j]; |
|
9547 if (part.type === parser.SELECTOR_PART_TYPE) { |
|
9548 if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0) { |
|
9549 reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule); |
|
9550 } |
|
9551 } |
|
9552 } |
|
9553 } |
|
9554 }); |
|
9555 } |
|
9556 |
|
9557 }); |
|
9558 |
|
9559 /* |
|
9560 * Rule: Selectors that look like regular expressions are slow and should be avoided. |
|
9561 */ |
|
9562 |
|
9563 CSSLint.addRule({ |
|
9564 |
|
9565 // rule information |
|
9566 id: "regex-selectors", |
|
9567 name: "Disallow selectors that look like regexs", |
|
9568 desc: "Selectors that look like regular expressions are slow and should be avoided.", |
|
9569 url: "https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions", |
|
9570 browsers: "All", |
|
9571 |
|
9572 // initialization |
|
9573 init: function(parser, reporter) { |
|
9574 "use strict"; |
|
9575 var rule = this; |
|
9576 |
|
9577 parser.addListener("startrule", function(event) { |
|
9578 var selectors = event.selectors, |
|
9579 selector, |
|
9580 part, |
|
9581 modifier, |
|
9582 i, j, k; |
|
9583 |
|
9584 for (i=0; i < selectors.length; i++) { |
|
9585 selector = selectors[i]; |
|
9586 for (j=0; j < selector.parts.length; j++) { |
|
9587 part = selector.parts[j]; |
|
9588 if (part.type === parser.SELECTOR_PART_TYPE) { |
|
9589 for (k=0; k < part.modifiers.length; k++) { |
|
9590 modifier = part.modifiers[k]; |
|
9591 if (modifier.type === "attribute") { |
|
9592 if (/([~\|\^\$\*]=)/.test(modifier)) { |
|
9593 reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); |
|
9594 } |
|
9595 } |
|
9596 |
|
9597 } |
|
9598 } |
|
9599 } |
|
9600 } |
|
9601 }); |
|
9602 } |
|
9603 |
|
9604 }); |
|
9605 |
|
9606 /* |
|
9607 * Rule: Total number of rules should not exceed x. |
|
9608 */ |
|
9609 |
|
9610 CSSLint.addRule({ |
|
9611 |
|
9612 // rule information |
|
9613 id: "rules-count", |
|
9614 name: "Rules Count", |
|
9615 desc: "Track how many rules there are.", |
|
9616 browsers: "All", |
|
9617 |
|
9618 // initialization |
|
9619 init: function(parser, reporter) { |
|
9620 "use strict"; |
|
9621 var count = 0; |
|
9622 |
|
9623 // count each rule |
|
9624 parser.addListener("startrule", function() { |
|
9625 count++; |
|
9626 }); |
|
9627 |
|
9628 parser.addListener("endstylesheet", function() { |
|
9629 reporter.stat("rule-count", count); |
|
9630 }); |
|
9631 } |
|
9632 |
|
9633 }); |
|
9634 |
|
9635 /* |
|
9636 * Rule: Warn people with approaching the IE 4095 limit |
|
9637 */ |
|
9638 |
|
9639 CSSLint.addRule({ |
|
9640 |
|
9641 // rule information |
|
9642 id: "selector-max-approaching", |
|
9643 name: "Warn when approaching the 4095 selector limit for IE", |
|
9644 desc: "Will warn when selector count is >= 3800 selectors.", |
|
9645 browsers: "IE", |
|
9646 |
|
9647 // initialization |
|
9648 init: function(parser, reporter) { |
|
9649 "use strict"; |
|
9650 var rule = this, count = 0; |
|
9651 |
|
9652 parser.addListener("startrule", function(event) { |
|
9653 count += event.selectors.length; |
|
9654 }); |
|
9655 |
|
9656 parser.addListener("endstylesheet", function() { |
|
9657 if (count >= 3800) { |
|
9658 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule); |
|
9659 } |
|
9660 }); |
|
9661 } |
|
9662 |
|
9663 }); |
|
9664 |
|
9665 /* |
|
9666 * Rule: Warn people past the IE 4095 limit |
|
9667 */ |
|
9668 |
|
9669 CSSLint.addRule({ |
|
9670 |
|
9671 // rule information |
|
9672 id: "selector-max", |
|
9673 name: "Error when past the 4095 selector limit for IE", |
|
9674 desc: "Will error when selector count is > 4095.", |
|
9675 browsers: "IE", |
|
9676 |
|
9677 // initialization |
|
9678 init: function(parser, reporter) { |
|
9679 "use strict"; |
|
9680 var rule = this, count = 0; |
|
9681 |
|
9682 parser.addListener("startrule", function(event) { |
|
9683 count += event.selectors.length; |
|
9684 }); |
|
9685 |
|
9686 parser.addListener("endstylesheet", function() { |
|
9687 if (count > 4095) { |
|
9688 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule); |
|
9689 } |
|
9690 }); |
|
9691 } |
|
9692 |
|
9693 }); |
|
9694 |
|
9695 /* |
|
9696 * Rule: Avoid new-line characters in selectors. |
|
9697 */ |
|
9698 |
|
9699 CSSLint.addRule({ |
|
9700 |
|
9701 // rule information |
|
9702 id: "selector-newline", |
|
9703 name: "Disallow new-line characters in selectors", |
|
9704 desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.", |
|
9705 browsers: "All", |
|
9706 |
|
9707 // initialization |
|
9708 init: function(parser, reporter) { |
|
9709 "use strict"; |
|
9710 var rule = this; |
|
9711 |
|
9712 function startRule(event) { |
|
9713 var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine, |
|
9714 selectors = event.selectors; |
|
9715 |
|
9716 for (i = 0, len = selectors.length; i < len; i++) { |
|
9717 selector = selectors[i]; |
|
9718 for (p = 0, pLen = selector.parts.length; p < pLen; p++) { |
|
9719 for (n = p + 1; n < pLen; n++) { |
|
9720 part = selector.parts[p]; |
|
9721 part2 = selector.parts[n]; |
|
9722 type = part.type; |
|
9723 currentLine = part.line; |
|
9724 nextLine = part2.line; |
|
9725 |
|
9726 if (type === "descendant" && nextLine > currentLine) { |
|
9727 reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule); |
|
9728 } |
|
9729 } |
|
9730 } |
|
9731 |
|
9732 } |
|
9733 } |
|
9734 |
|
9735 parser.addListener("startrule", startRule); |
|
9736 |
|
9737 } |
|
9738 }); |
|
9739 |
|
9740 /* |
|
9741 * Rule: Use shorthand properties where possible. |
|
9742 * |
|
9743 */ |
|
9744 |
|
9745 CSSLint.addRule({ |
|
9746 |
|
9747 // rule information |
|
9748 id: "shorthand", |
|
9749 name: "Require shorthand properties", |
|
9750 desc: "Use shorthand properties where possible.", |
|
9751 url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties", |
|
9752 browsers: "All", |
|
9753 |
|
9754 // initialization |
|
9755 init: function(parser, reporter) { |
|
9756 "use strict"; |
|
9757 var rule = this, |
|
9758 prop, i, len, |
|
9759 propertiesToCheck = {}, |
|
9760 properties, |
|
9761 mapping = { |
|
9762 "margin": [ |
|
9763 "margin-top", |
|
9764 "margin-bottom", |
|
9765 "margin-left", |
|
9766 "margin-right" |
|
9767 ], |
|
9768 "padding": [ |
|
9769 "padding-top", |
|
9770 "padding-bottom", |
|
9771 "padding-left", |
|
9772 "padding-right" |
|
9773 ] |
|
9774 }; |
|
9775 |
|
9776 // initialize propertiesToCheck |
|
9777 for (prop in mapping) { |
|
9778 if (mapping.hasOwnProperty(prop)) { |
|
9779 for (i=0, len=mapping[prop].length; i < len; i++) { |
|
9780 propertiesToCheck[mapping[prop][i]] = prop; |
|
9781 } |
|
9782 } |
|
9783 } |
|
9784 |
|
9785 function startRule() { |
|
9786 properties = {}; |
|
9787 } |
|
9788 |
|
9789 // event handler for end of rules |
|
9790 function endRule(event) { |
|
9791 |
|
9792 var prop, i, len, total; |
|
9793 |
|
9794 // check which properties this rule has |
|
9795 for (prop in mapping) { |
|
9796 if (mapping.hasOwnProperty(prop)) { |
|
9797 total=0; |
|
9798 |
|
9799 for (i=0, len=mapping[prop].length; i < len; i++) { |
|
9800 total += properties[mapping[prop][i]] ? 1 : 0; |
|
9801 } |
|
9802 |
|
9803 if (total === mapping[prop].length) { |
|
9804 reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule); |
|
9805 } |
|
9806 } |
|
9807 } |
|
9808 } |
|
9809 |
|
9810 parser.addListener("startrule", startRule); |
|
9811 parser.addListener("startfontface", startRule); |
|
9812 |
|
9813 // check for use of "font-size" |
|
9814 parser.addListener("property", function(event) { |
|
9815 var name = event.property.toString().toLowerCase(); |
|
9816 |
|
9817 if (propertiesToCheck[name]) { |
|
9818 properties[name] = 1; |
|
9819 } |
|
9820 }); |
|
9821 |
|
9822 parser.addListener("endrule", endRule); |
|
9823 parser.addListener("endfontface", endRule); |
|
9824 |
|
9825 } |
|
9826 |
|
9827 }); |
|
9828 |
|
9829 /* |
|
9830 * Rule: Don't use properties with a star prefix. |
|
9831 * |
|
9832 */ |
|
9833 |
|
9834 CSSLint.addRule({ |
|
9835 |
|
9836 // rule information |
|
9837 id: "star-property-hack", |
|
9838 name: "Disallow properties with a star prefix", |
|
9839 desc: "Checks for the star property hack (targets IE6/7)", |
|
9840 url: "https://github.com/CSSLint/csslint/wiki/Disallow-star-hack", |
|
9841 browsers: "All", |
|
9842 |
|
9843 // initialization |
|
9844 init: function(parser, reporter) { |
|
9845 "use strict"; |
|
9846 var rule = this; |
|
9847 |
|
9848 // check if property name starts with "*" |
|
9849 parser.addListener("property", function(event) { |
|
9850 var property = event.property; |
|
9851 |
|
9852 if (property.hack === "*") { |
|
9853 reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule); |
|
9854 } |
|
9855 }); |
|
9856 } |
|
9857 }); |
|
9858 |
|
9859 /* |
|
9860 * Rule: Don't use text-indent for image replacement if you need to support rtl. |
|
9861 * |
|
9862 */ |
|
9863 |
|
9864 CSSLint.addRule({ |
|
9865 |
|
9866 // rule information |
|
9867 id: "text-indent", |
|
9868 name: "Disallow negative text-indent", |
|
9869 desc: "Checks for text indent less than -99px", |
|
9870 url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent", |
|
9871 browsers: "All", |
|
9872 |
|
9873 // initialization |
|
9874 init: function(parser, reporter) { |
|
9875 "use strict"; |
|
9876 var rule = this, |
|
9877 textIndent, |
|
9878 direction; |
|
9879 |
|
9880 |
|
9881 function startRule() { |
|
9882 textIndent = false; |
|
9883 direction = "inherit"; |
|
9884 } |
|
9885 |
|
9886 // event handler for end of rules |
|
9887 function endRule() { |
|
9888 if (textIndent && direction !== "ltr") { |
|
9889 reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule); |
|
9890 } |
|
9891 } |
|
9892 |
|
9893 parser.addListener("startrule", startRule); |
|
9894 parser.addListener("startfontface", startRule); |
|
9895 |
|
9896 // check for use of "font-size" |
|
9897 parser.addListener("property", function(event) { |
|
9898 var name = event.property.toString().toLowerCase(), |
|
9899 value = event.value; |
|
9900 |
|
9901 if (name === "text-indent" && value.parts[0].value < -99) { |
|
9902 textIndent = event.property; |
|
9903 } else if (name === "direction" && value.toString() === "ltr") { |
|
9904 direction = "ltr"; |
|
9905 } |
|
9906 }); |
|
9907 |
|
9908 parser.addListener("endrule", endRule); |
|
9909 parser.addListener("endfontface", endRule); |
|
9910 |
|
9911 } |
|
9912 |
|
9913 }); |
|
9914 |
|
9915 /* |
|
9916 * Rule: Don't use properties with a underscore prefix. |
|
9917 * |
|
9918 */ |
|
9919 |
|
9920 CSSLint.addRule({ |
|
9921 |
|
9922 // rule information |
|
9923 id: "underscore-property-hack", |
|
9924 name: "Disallow properties with an underscore prefix", |
|
9925 desc: "Checks for the underscore property hack (targets IE6)", |
|
9926 url: "https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack", |
|
9927 browsers: "All", |
|
9928 |
|
9929 // initialization |
|
9930 init: function(parser, reporter) { |
|
9931 "use strict"; |
|
9932 var rule = this; |
|
9933 |
|
9934 // check if property name starts with "_" |
|
9935 parser.addListener("property", function(event) { |
|
9936 var property = event.property; |
|
9937 |
|
9938 if (property.hack === "_") { |
|
9939 reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule); |
|
9940 } |
|
9941 }); |
|
9942 } |
|
9943 }); |
|
9944 |
|
9945 /* |
|
9946 * Rule: Headings (h1-h6) should be defined only once. |
|
9947 */ |
|
9948 |
|
9949 CSSLint.addRule({ |
|
9950 |
|
9951 // rule information |
|
9952 id: "unique-headings", |
|
9953 name: "Headings should only be defined once", |
|
9954 desc: "Headings should be defined only once.", |
|
9955 url: "https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once", |
|
9956 browsers: "All", |
|
9957 |
|
9958 // initialization |
|
9959 init: function(parser, reporter) { |
|
9960 "use strict"; |
|
9961 var rule = this; |
|
9962 |
|
9963 var headings = { |
|
9964 h1: 0, |
|
9965 h2: 0, |
|
9966 h3: 0, |
|
9967 h4: 0, |
|
9968 h5: 0, |
|
9969 h6: 0 |
|
9970 }; |
|
9971 |
|
9972 parser.addListener("startrule", function(event) { |
|
9973 var selectors = event.selectors, |
|
9974 selector, |
|
9975 part, |
|
9976 pseudo, |
|
9977 i, j; |
|
9978 |
|
9979 for (i=0; i < selectors.length; i++) { |
|
9980 selector = selectors[i]; |
|
9981 part = selector.parts[selector.parts.length-1]; |
|
9982 |
|
9983 if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())) { |
|
9984 |
|
9985 for (j=0; j < part.modifiers.length; j++) { |
|
9986 if (part.modifiers[j].type === "pseudo") { |
|
9987 pseudo = true; |
|
9988 break; |
|
9989 } |
|
9990 } |
|
9991 |
|
9992 if (!pseudo) { |
|
9993 headings[RegExp.$1]++; |
|
9994 if (headings[RegExp.$1] > 1) { |
|
9995 reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule); |
|
9996 } |
|
9997 } |
|
9998 } |
|
9999 } |
|
10000 }); |
|
10001 |
|
10002 parser.addListener("endstylesheet", function() { |
|
10003 var prop, |
|
10004 messages = []; |
|
10005 |
|
10006 for (prop in headings) { |
|
10007 if (headings.hasOwnProperty(prop)) { |
|
10008 if (headings[prop] > 1) { |
|
10009 messages.push(headings[prop] + " " + prop + "s"); |
|
10010 } |
|
10011 } |
|
10012 } |
|
10013 |
|
10014 if (messages.length) { |
|
10015 reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule); |
|
10016 } |
|
10017 }); |
|
10018 } |
|
10019 |
|
10020 }); |
|
10021 |
|
10022 /* |
|
10023 * Rule: Don't use universal selector because it's slow. |
|
10024 */ |
|
10025 |
|
10026 CSSLint.addRule({ |
|
10027 |
|
10028 // rule information |
|
10029 id: "universal-selector", |
|
10030 name: "Disallow universal selector", |
|
10031 desc: "The universal selector (*) is known to be slow.", |
|
10032 url: "https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector", |
|
10033 browsers: "All", |
|
10034 |
|
10035 // initialization |
|
10036 init: function(parser, reporter) { |
|
10037 "use strict"; |
|
10038 var rule = this; |
|
10039 |
|
10040 parser.addListener("startrule", function(event) { |
|
10041 var selectors = event.selectors, |
|
10042 selector, |
|
10043 part, |
|
10044 i; |
|
10045 |
|
10046 for (i=0; i < selectors.length; i++) { |
|
10047 selector = selectors[i]; |
|
10048 |
|
10049 part = selector.parts[selector.parts.length-1]; |
|
10050 if (part.elementName === "*") { |
|
10051 reporter.report(rule.desc, part.line, part.col, rule); |
|
10052 } |
|
10053 } |
|
10054 }); |
|
10055 } |
|
10056 |
|
10057 }); |
|
10058 |
|
10059 /* |
|
10060 * Rule: Don't use unqualified attribute selectors because they're just like universal selectors. |
|
10061 */ |
|
10062 |
|
10063 CSSLint.addRule({ |
|
10064 |
|
10065 // rule information |
|
10066 id: "unqualified-attributes", |
|
10067 name: "Disallow unqualified attribute selectors", |
|
10068 desc: "Unqualified attribute selectors are known to be slow.", |
|
10069 url: "https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors", |
|
10070 browsers: "All", |
|
10071 |
|
10072 // initialization |
|
10073 init: function(parser, reporter) { |
|
10074 "use strict"; |
|
10075 |
|
10076 var rule = this; |
|
10077 |
|
10078 parser.addListener("startrule", function(event) { |
|
10079 |
|
10080 var selectors = event.selectors, |
|
10081 selectorContainsClassOrId = false, |
|
10082 selector, |
|
10083 part, |
|
10084 modifier, |
|
10085 i, k; |
|
10086 |
|
10087 for (i=0; i < selectors.length; i++) { |
|
10088 selector = selectors[i]; |
|
10089 |
|
10090 part = selector.parts[selector.parts.length-1]; |
|
10091 if (part.type === parser.SELECTOR_PART_TYPE) { |
|
10092 for (k=0; k < part.modifiers.length; k++) { |
|
10093 modifier = part.modifiers[k]; |
|
10094 |
|
10095 if (modifier.type === "class" || modifier.type === "id") { |
|
10096 selectorContainsClassOrId = true; |
|
10097 break; |
|
10098 } |
|
10099 } |
|
10100 |
|
10101 if (!selectorContainsClassOrId) { |
|
10102 for (k=0; k < part.modifiers.length; k++) { |
|
10103 modifier = part.modifiers[k]; |
|
10104 if (modifier.type === "attribute" && (!part.elementName || part.elementName === "*")) { |
|
10105 reporter.report(rule.desc, part.line, part.col, rule); |
|
10106 } |
|
10107 } |
|
10108 } |
|
10109 } |
|
10110 |
|
10111 } |
|
10112 }); |
|
10113 } |
|
10114 |
|
10115 }); |
|
10116 |
|
10117 /* |
|
10118 * Rule: When using a vendor-prefixed property, make sure to |
|
10119 * include the standard one. |
|
10120 */ |
|
10121 |
|
10122 CSSLint.addRule({ |
|
10123 |
|
10124 // rule information |
|
10125 id: "vendor-prefix", |
|
10126 name: "Require standard property with vendor prefix", |
|
10127 desc: "When using a vendor-prefixed property, make sure to include the standard one.", |
|
10128 url: "https://github.com/CSSLint/csslint/wiki/Require-standard-property-with-vendor-prefix", |
|
10129 browsers: "All", |
|
10130 |
|
10131 // initialization |
|
10132 init: function(parser, reporter) { |
|
10133 "use strict"; |
|
10134 var rule = this, |
|
10135 properties, |
|
10136 num, |
|
10137 propertiesToCheck = { |
|
10138 "-webkit-border-radius": "border-radius", |
|
10139 "-webkit-border-top-left-radius": "border-top-left-radius", |
|
10140 "-webkit-border-top-right-radius": "border-top-right-radius", |
|
10141 "-webkit-border-bottom-left-radius": "border-bottom-left-radius", |
|
10142 "-webkit-border-bottom-right-radius": "border-bottom-right-radius", |
|
10143 |
|
10144 "-o-border-radius": "border-radius", |
|
10145 "-o-border-top-left-radius": "border-top-left-radius", |
|
10146 "-o-border-top-right-radius": "border-top-right-radius", |
|
10147 "-o-border-bottom-left-radius": "border-bottom-left-radius", |
|
10148 "-o-border-bottom-right-radius": "border-bottom-right-radius", |
|
10149 |
|
10150 "-moz-border-radius": "border-radius", |
|
10151 "-moz-border-radius-topleft": "border-top-left-radius", |
|
10152 "-moz-border-radius-topright": "border-top-right-radius", |
|
10153 "-moz-border-radius-bottomleft": "border-bottom-left-radius", |
|
10154 "-moz-border-radius-bottomright": "border-bottom-right-radius", |
|
10155 |
|
10156 "-moz-column-count": "column-count", |
|
10157 "-webkit-column-count": "column-count", |
|
10158 |
|
10159 "-moz-column-gap": "column-gap", |
|
10160 "-webkit-column-gap": "column-gap", |
|
10161 |
|
10162 "-moz-column-rule": "column-rule", |
|
10163 "-webkit-column-rule": "column-rule", |
|
10164 |
|
10165 "-moz-column-rule-style": "column-rule-style", |
|
10166 "-webkit-column-rule-style": "column-rule-style", |
|
10167 |
|
10168 "-moz-column-rule-color": "column-rule-color", |
|
10169 "-webkit-column-rule-color": "column-rule-color", |
|
10170 |
|
10171 "-moz-column-rule-width": "column-rule-width", |
|
10172 "-webkit-column-rule-width": "column-rule-width", |
|
10173 |
|
10174 "-moz-column-width": "column-width", |
|
10175 "-webkit-column-width": "column-width", |
|
10176 |
|
10177 "-webkit-column-span": "column-span", |
|
10178 "-webkit-columns": "columns", |
|
10179 |
|
10180 "-moz-box-shadow": "box-shadow", |
|
10181 "-webkit-box-shadow": "box-shadow", |
|
10182 |
|
10183 "-moz-transform": "transform", |
|
10184 "-webkit-transform": "transform", |
|
10185 "-o-transform": "transform", |
|
10186 "-ms-transform": "transform", |
|
10187 |
|
10188 "-moz-transform-origin": "transform-origin", |
|
10189 "-webkit-transform-origin": "transform-origin", |
|
10190 "-o-transform-origin": "transform-origin", |
|
10191 "-ms-transform-origin": "transform-origin", |
|
10192 |
|
10193 "-moz-box-sizing": "box-sizing", |
|
10194 "-webkit-box-sizing": "box-sizing" |
|
10195 }; |
|
10196 |
|
10197 // event handler for beginning of rules |
|
10198 function startRule() { |
|
10199 properties = {}; |
|
10200 num = 1; |
|
10201 } |
|
10202 |
|
10203 // event handler for end of rules |
|
10204 function endRule() { |
|
10205 var prop, |
|
10206 i, |
|
10207 len, |
|
10208 needed, |
|
10209 actual, |
|
10210 needsStandard = []; |
|
10211 |
|
10212 for (prop in properties) { |
|
10213 if (propertiesToCheck[prop]) { |
|
10214 needsStandard.push({ |
|
10215 actual: prop, |
|
10216 needed: propertiesToCheck[prop] |
|
10217 }); |
|
10218 } |
|
10219 } |
|
10220 |
|
10221 for (i=0, len=needsStandard.length; i < len; i++) { |
|
10222 needed = needsStandard[i].needed; |
|
10223 actual = needsStandard[i].actual; |
|
10224 |
|
10225 if (!properties[needed]) { |
|
10226 reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule); |
|
10227 } else { |
|
10228 // make sure standard property is last |
|
10229 if (properties[needed][0].pos < properties[actual][0].pos) { |
|
10230 reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule); |
|
10231 } |
|
10232 } |
|
10233 } |
|
10234 |
|
10235 } |
|
10236 |
|
10237 parser.addListener("startrule", startRule); |
|
10238 parser.addListener("startfontface", startRule); |
|
10239 parser.addListener("startpage", startRule); |
|
10240 parser.addListener("startpagemargin", startRule); |
|
10241 parser.addListener("startkeyframerule", startRule); |
|
10242 parser.addListener("startviewport", startRule); |
|
10243 |
|
10244 parser.addListener("property", function(event) { |
|
10245 var name = event.property.text.toLowerCase(); |
|
10246 |
|
10247 if (!properties[name]) { |
|
10248 properties[name] = []; |
|
10249 } |
|
10250 |
|
10251 properties[name].push({ |
|
10252 name: event.property, |
|
10253 value: event.value, |
|
10254 pos: num++ |
|
10255 }); |
|
10256 }); |
|
10257 |
|
10258 parser.addListener("endrule", endRule); |
|
10259 parser.addListener("endfontface", endRule); |
|
10260 parser.addListener("endpage", endRule); |
|
10261 parser.addListener("endpagemargin", endRule); |
|
10262 parser.addListener("endkeyframerule", endRule); |
|
10263 parser.addListener("endviewport", endRule); |
|
10264 } |
|
10265 |
|
10266 }); |
|
10267 |
|
10268 /* |
|
10269 * Rule: You don't need to specify units when a value is 0. |
|
10270 */ |
|
10271 |
|
10272 CSSLint.addRule({ |
|
10273 |
|
10274 // rule information |
|
10275 id: "zero-units", |
|
10276 name: "Disallow units for 0 values", |
|
10277 desc: "You don't need to specify units when a value is 0.", |
|
10278 url: "https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values", |
|
10279 browsers: "All", |
|
10280 |
|
10281 // initialization |
|
10282 init: function(parser, reporter) { |
|
10283 "use strict"; |
|
10284 var rule = this; |
|
10285 |
|
10286 // count how many times "float" is used |
|
10287 parser.addListener("property", function(event) { |
|
10288 var parts = event.value.parts, |
|
10289 i = 0, |
|
10290 len = parts.length; |
|
10291 |
|
10292 while (i < len) { |
|
10293 if ((parts[i].units || parts[i].type === "percentage") && parts[i].value === 0 && parts[i].type !== "time") { |
|
10294 reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); |
|
10295 } |
|
10296 i++; |
|
10297 } |
|
10298 |
|
10299 }); |
|
10300 |
|
10301 } |
|
10302 |
|
10303 }); |
|
10304 |
|
10305 (function() { |
|
10306 "use strict"; |
|
10307 |
|
10308 /** |
|
10309 * Replace special characters before write to output. |
|
10310 * |
|
10311 * Rules: |
|
10312 * - single quotes is the escape sequence for double-quotes |
|
10313 * - & is the escape sequence for & |
|
10314 * - < is the escape sequence for < |
|
10315 * - > is the escape sequence for > |
|
10316 * |
|
10317 * @param {String} message to escape |
|
10318 * @return escaped message as {String} |
|
10319 */ |
|
10320 var xmlEscape = function(str) { |
|
10321 if (!str || str.constructor !== String) { |
|
10322 return ""; |
|
10323 } |
|
10324 |
|
10325 return str.replace(/["&><]/g, function(match) { |
|
10326 switch (match) { |
|
10327 case "\"": |
|
10328 return """; |
|
10329 case "&": |
|
10330 return "&"; |
|
10331 case "<": |
|
10332 return "<"; |
|
10333 case ">": |
|
10334 return ">"; |
|
10335 } |
|
10336 }); |
|
10337 }; |
|
10338 |
|
10339 CSSLint.addFormatter({ |
|
10340 // format information |
|
10341 id: "checkstyle-xml", |
|
10342 name: "Checkstyle XML format", |
|
10343 |
|
10344 /** |
|
10345 * Return opening root XML tag. |
|
10346 * @return {String} to prepend before all results |
|
10347 */ |
|
10348 startFormat: function() { |
|
10349 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>"; |
|
10350 }, |
|
10351 |
|
10352 /** |
|
10353 * Return closing root XML tag. |
|
10354 * @return {String} to append after all results |
|
10355 */ |
|
10356 endFormat: function() { |
|
10357 return "</checkstyle>"; |
|
10358 }, |
|
10359 |
|
10360 /** |
|
10361 * Returns message when there is a file read error. |
|
10362 * @param {String} filename The name of the file that caused the error. |
|
10363 * @param {String} message The error message |
|
10364 * @return {String} The error message. |
|
10365 */ |
|
10366 readError: function(filename, message) { |
|
10367 return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>"; |
|
10368 }, |
|
10369 |
|
10370 /** |
|
10371 * Given CSS Lint results for a file, return output for this format. |
|
10372 * @param results {Object} with error and warning messages |
|
10373 * @param filename {String} relative file path |
|
10374 * @param options {Object} (UNUSED for now) specifies special handling of output |
|
10375 * @return {String} output for results |
|
10376 */ |
|
10377 formatResults: function(results, filename/*, options*/) { |
|
10378 var messages = results.messages, |
|
10379 output = []; |
|
10380 |
|
10381 /** |
|
10382 * Generate a source string for a rule. |
|
10383 * Checkstyle source strings usually resemble Java class names e.g |
|
10384 * net.csslint.SomeRuleName |
|
10385 * @param {Object} rule |
|
10386 * @return rule source as {String} |
|
10387 */ |
|
10388 var generateSource = function(rule) { |
|
10389 if (!rule || !("name" in rule)) { |
|
10390 return ""; |
|
10391 } |
|
10392 return "net.csslint." + rule.name.replace(/\s/g, ""); |
|
10393 }; |
|
10394 |
|
10395 |
|
10396 if (messages.length > 0) { |
|
10397 output.push("<file name=\""+filename+"\">"); |
|
10398 CSSLint.Util.forEach(messages, function (message) { |
|
10399 // ignore rollups for now |
|
10400 if (!message.rollup) { |
|
10401 output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" + |
|
10402 " message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>"); |
|
10403 } |
|
10404 }); |
|
10405 output.push("</file>"); |
|
10406 } |
|
10407 |
|
10408 return output.join(""); |
|
10409 } |
|
10410 }); |
|
10411 |
|
10412 }()); |
|
10413 |
|
10414 CSSLint.addFormatter({ |
|
10415 // format information |
|
10416 id: "compact", |
|
10417 name: "Compact, 'porcelain' format", |
|
10418 |
|
10419 /** |
|
10420 * Return content to be printed before all file results. |
|
10421 * @return {String} to prepend before all results |
|
10422 */ |
|
10423 startFormat: function() { |
|
10424 "use strict"; |
|
10425 return ""; |
|
10426 }, |
|
10427 |
|
10428 /** |
|
10429 * Return content to be printed after all file results. |
|
10430 * @return {String} to append after all results |
|
10431 */ |
|
10432 endFormat: function() { |
|
10433 "use strict"; |
|
10434 return ""; |
|
10435 }, |
|
10436 |
|
10437 /** |
|
10438 * Given CSS Lint results for a file, return output for this format. |
|
10439 * @param results {Object} with error and warning messages |
|
10440 * @param filename {String} relative file path |
|
10441 * @param options {Object} (Optional) specifies special handling of output |
|
10442 * @return {String} output for results |
|
10443 */ |
|
10444 formatResults: function(results, filename, options) { |
|
10445 "use strict"; |
|
10446 var messages = results.messages, |
|
10447 output = ""; |
|
10448 options = options || {}; |
|
10449 |
|
10450 /** |
|
10451 * Capitalize and return given string. |
|
10452 * @param str {String} to capitalize |
|
10453 * @return {String} capitalized |
|
10454 */ |
|
10455 var capitalize = function(str) { |
|
10456 return str.charAt(0).toUpperCase() + str.slice(1); |
|
10457 }; |
|
10458 |
|
10459 if (messages.length === 0) { |
|
10460 return options.quiet ? "" : filename + ": Lint Free!"; |
|
10461 } |
|
10462 |
|
10463 CSSLint.Util.forEach(messages, function(message) { |
|
10464 if (message.rollup) { |
|
10465 output += filename + ": " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n"; |
|
10466 } else { |
|
10467 output += filename + ": line " + message.line + |
|
10468 ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n"; |
|
10469 } |
|
10470 }); |
|
10471 |
|
10472 return output; |
|
10473 } |
|
10474 }); |
|
10475 |
|
10476 CSSLint.addFormatter({ |
|
10477 // format information |
|
10478 id: "csslint-xml", |
|
10479 name: "CSSLint XML format", |
|
10480 |
|
10481 /** |
|
10482 * Return opening root XML tag. |
|
10483 * @return {String} to prepend before all results |
|
10484 */ |
|
10485 startFormat: function() { |
|
10486 "use strict"; |
|
10487 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>"; |
|
10488 }, |
|
10489 |
|
10490 /** |
|
10491 * Return closing root XML tag. |
|
10492 * @return {String} to append after all results |
|
10493 */ |
|
10494 endFormat: function() { |
|
10495 "use strict"; |
|
10496 return "</csslint>"; |
|
10497 }, |
|
10498 |
|
10499 /** |
|
10500 * Given CSS Lint results for a file, return output for this format. |
|
10501 * @param results {Object} with error and warning messages |
|
10502 * @param filename {String} relative file path |
|
10503 * @param options {Object} (UNUSED for now) specifies special handling of output |
|
10504 * @return {String} output for results |
|
10505 */ |
|
10506 formatResults: function(results, filename/*, options*/) { |
|
10507 "use strict"; |
|
10508 var messages = results.messages, |
|
10509 output = []; |
|
10510 |
|
10511 /** |
|
10512 * Replace special characters before write to output. |
|
10513 * |
|
10514 * Rules: |
|
10515 * - single quotes is the escape sequence for double-quotes |
|
10516 * - & is the escape sequence for & |
|
10517 * - < is the escape sequence for < |
|
10518 * - > is the escape sequence for > |
|
10519 * |
|
10520 * @param {String} message to escape |
|
10521 * @return escaped message as {String} |
|
10522 */ |
|
10523 var escapeSpecialCharacters = function(str) { |
|
10524 if (!str || str.constructor !== String) { |
|
10525 return ""; |
|
10526 } |
|
10527 return str.replace(/"/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); |
|
10528 }; |
|
10529 |
|
10530 if (messages.length > 0) { |
|
10531 output.push("<file name=\""+filename+"\">"); |
|
10532 CSSLint.Util.forEach(messages, function (message) { |
|
10533 if (message.rollup) { |
|
10534 output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); |
|
10535 } else { |
|
10536 output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" + |
|
10537 " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); |
|
10538 } |
|
10539 }); |
|
10540 output.push("</file>"); |
|
10541 } |
|
10542 |
|
10543 return output.join(""); |
|
10544 } |
|
10545 }); |
|
10546 |
|
10547 /* globals JSON: true */ |
|
10548 |
|
10549 CSSLint.addFormatter({ |
|
10550 // format information |
|
10551 id: "json", |
|
10552 name: "JSON", |
|
10553 |
|
10554 /** |
|
10555 * Return content to be printed before all file results. |
|
10556 * @return {String} to prepend before all results |
|
10557 */ |
|
10558 startFormat: function() { |
|
10559 "use strict"; |
|
10560 this.json = []; |
|
10561 return ""; |
|
10562 }, |
|
10563 |
|
10564 /** |
|
10565 * Return content to be printed after all file results. |
|
10566 * @return {String} to append after all results |
|
10567 */ |
|
10568 endFormat: function() { |
|
10569 "use strict"; |
|
10570 var ret = ""; |
|
10571 if (this.json.length > 0) { |
|
10572 if (this.json.length === 1) { |
|
10573 ret = JSON.stringify(this.json[0]); |
|
10574 } else { |
|
10575 ret = JSON.stringify(this.json); |
|
10576 } |
|
10577 } |
|
10578 return ret; |
|
10579 }, |
|
10580 |
|
10581 /** |
|
10582 * Given CSS Lint results for a file, return output for this format. |
|
10583 * @param results {Object} with error and warning messages |
|
10584 * @param filename {String} relative file path (Unused) |
|
10585 * @return {String} output for results |
|
10586 */ |
|
10587 formatResults: function(results, filename, options) { |
|
10588 "use strict"; |
|
10589 if (results.messages.length > 0 || !options.quiet) { |
|
10590 this.json.push({ |
|
10591 filename: filename, |
|
10592 messages: results.messages, |
|
10593 stats: results.stats |
|
10594 }); |
|
10595 } |
|
10596 return ""; |
|
10597 } |
|
10598 }); |
|
10599 |
|
10600 CSSLint.addFormatter({ |
|
10601 // format information |
|
10602 id: "junit-xml", |
|
10603 name: "JUNIT XML format", |
|
10604 |
|
10605 /** |
|
10606 * Return opening root XML tag. |
|
10607 * @return {String} to prepend before all results |
|
10608 */ |
|
10609 startFormat: function() { |
|
10610 "use strict"; |
|
10611 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>"; |
|
10612 }, |
|
10613 |
|
10614 /** |
|
10615 * Return closing root XML tag. |
|
10616 * @return {String} to append after all results |
|
10617 */ |
|
10618 endFormat: function() { |
|
10619 "use strict"; |
|
10620 return "</testsuites>"; |
|
10621 }, |
|
10622 |
|
10623 /** |
|
10624 * Given CSS Lint results for a file, return output for this format. |
|
10625 * @param results {Object} with error and warning messages |
|
10626 * @param filename {String} relative file path |
|
10627 * @param options {Object} (UNUSED for now) specifies special handling of output |
|
10628 * @return {String} output for results |
|
10629 */ |
|
10630 formatResults: function(results, filename/*, options*/) { |
|
10631 "use strict"; |
|
10632 |
|
10633 var messages = results.messages, |
|
10634 output = [], |
|
10635 tests = { |
|
10636 "error": 0, |
|
10637 "failure": 0 |
|
10638 }; |
|
10639 |
|
10640 /** |
|
10641 * Generate a source string for a rule. |
|
10642 * JUNIT source strings usually resemble Java class names e.g |
|
10643 * net.csslint.SomeRuleName |
|
10644 * @param {Object} rule |
|
10645 * @return rule source as {String} |
|
10646 */ |
|
10647 var generateSource = function(rule) { |
|
10648 if (!rule || !("name" in rule)) { |
|
10649 return ""; |
|
10650 } |
|
10651 return "net.csslint." + rule.name.replace(/\s/g, ""); |
|
10652 }; |
|
10653 |
|
10654 /** |
|
10655 * Replace special characters before write to output. |
|
10656 * |
|
10657 * Rules: |
|
10658 * - single quotes is the escape sequence for double-quotes |
|
10659 * - < is the escape sequence for < |
|
10660 * - > is the escape sequence for > |
|
10661 * |
|
10662 * @param {String} message to escape |
|
10663 * @return escaped message as {String} |
|
10664 */ |
|
10665 var escapeSpecialCharacters = function(str) { |
|
10666 |
|
10667 if (!str || str.constructor !== String) { |
|
10668 return ""; |
|
10669 } |
|
10670 |
|
10671 return str.replace(/"/g, "'").replace(/</g, "<").replace(/>/g, ">"); |
|
10672 |
|
10673 }; |
|
10674 |
|
10675 if (messages.length > 0) { |
|
10676 |
|
10677 messages.forEach(function (message) { |
|
10678 |
|
10679 // since junit has no warning class |
|
10680 // all issues as errors |
|
10681 var type = message.type === "warning" ? "error" : message.type; |
|
10682 |
|
10683 // ignore rollups for now |
|
10684 if (!message.rollup) { |
|
10685 |
|
10686 // build the test case separately, once joined |
|
10687 // we'll add it to a custom array filtered by type |
|
10688 output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">"); |
|
10689 output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ":" + message.col + ":" + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">"); |
|
10690 output.push("</testcase>"); |
|
10691 |
|
10692 tests[type] += 1; |
|
10693 |
|
10694 } |
|
10695 |
|
10696 }); |
|
10697 |
|
10698 output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">"); |
|
10699 output.push("</testsuite>"); |
|
10700 |
|
10701 } |
|
10702 |
|
10703 return output.join(""); |
|
10704 |
|
10705 } |
|
10706 }); |
|
10707 |
|
10708 CSSLint.addFormatter({ |
|
10709 // format information |
|
10710 id: "lint-xml", |
|
10711 name: "Lint XML format", |
|
10712 |
|
10713 /** |
|
10714 * Return opening root XML tag. |
|
10715 * @return {String} to prepend before all results |
|
10716 */ |
|
10717 startFormat: function() { |
|
10718 "use strict"; |
|
10719 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>"; |
|
10720 }, |
|
10721 |
|
10722 /** |
|
10723 * Return closing root XML tag. |
|
10724 * @return {String} to append after all results |
|
10725 */ |
|
10726 endFormat: function() { |
|
10727 "use strict"; |
|
10728 return "</lint>"; |
|
10729 }, |
|
10730 |
|
10731 /** |
|
10732 * Given CSS Lint results for a file, return output for this format. |
|
10733 * @param results {Object} with error and warning messages |
|
10734 * @param filename {String} relative file path |
|
10735 * @param options {Object} (UNUSED for now) specifies special handling of output |
|
10736 * @return {String} output for results |
|
10737 */ |
|
10738 formatResults: function(results, filename/*, options*/) { |
|
10739 "use strict"; |
|
10740 var messages = results.messages, |
|
10741 output = []; |
|
10742 |
|
10743 /** |
|
10744 * Replace special characters before write to output. |
|
10745 * |
|
10746 * Rules: |
|
10747 * - single quotes is the escape sequence for double-quotes |
|
10748 * - & is the escape sequence for & |
|
10749 * - < is the escape sequence for < |
|
10750 * - > is the escape sequence for > |
|
10751 * |
|
10752 * @param {String} message to escape |
|
10753 * @return escaped message as {String} |
|
10754 */ |
|
10755 var escapeSpecialCharacters = function(str) { |
|
10756 if (!str || str.constructor !== String) { |
|
10757 return ""; |
|
10758 } |
|
10759 return str.replace(/"/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); |
|
10760 }; |
|
10761 |
|
10762 if (messages.length > 0) { |
|
10763 |
|
10764 output.push("<file name=\""+filename+"\">"); |
|
10765 CSSLint.Util.forEach(messages, function (message) { |
|
10766 if (message.rollup) { |
|
10767 output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); |
|
10768 } else { |
|
10769 var rule = ""; |
|
10770 if (message.rule && message.rule.id) { |
|
10771 rule = "rule=\"" + escapeSpecialCharacters(message.rule.id) + "\" "; |
|
10772 } |
|
10773 output.push("<issue " + rule + "line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" + |
|
10774 " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); |
|
10775 } |
|
10776 }); |
|
10777 output.push("</file>"); |
|
10778 } |
|
10779 |
|
10780 return output.join(""); |
|
10781 } |
|
10782 }); |
|
10783 |
|
10784 CSSLint.addFormatter({ |
|
10785 // format information |
|
10786 id: "text", |
|
10787 name: "Plain Text", |
|
10788 |
|
10789 /** |
|
10790 * Return content to be printed before all file results. |
|
10791 * @return {String} to prepend before all results |
|
10792 */ |
|
10793 startFormat: function() { |
|
10794 "use strict"; |
|
10795 return ""; |
|
10796 }, |
|
10797 |
|
10798 /** |
|
10799 * Return content to be printed after all file results. |
|
10800 * @return {String} to append after all results |
|
10801 */ |
|
10802 endFormat: function() { |
|
10803 "use strict"; |
|
10804 return ""; |
|
10805 }, |
|
10806 |
|
10807 /** |
|
10808 * Given CSS Lint results for a file, return output for this format. |
|
10809 * @param results {Object} with error and warning messages |
|
10810 * @param filename {String} relative file path |
|
10811 * @param options {Object} (Optional) specifies special handling of output |
|
10812 * @return {String} output for results |
|
10813 */ |
|
10814 formatResults: function(results, filename, options) { |
|
10815 "use strict"; |
|
10816 var messages = results.messages, |
|
10817 output = ""; |
|
10818 options = options || {}; |
|
10819 |
|
10820 if (messages.length === 0) { |
|
10821 return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + "."; |
|
10822 } |
|
10823 |
|
10824 output = "\n\ncsslint: There "; |
|
10825 if (messages.length === 1) { |
|
10826 output += "is 1 problem"; |
|
10827 } else { |
|
10828 output += "are " + messages.length + " problems"; |
|
10829 } |
|
10830 output += " in " + filename + "."; |
|
10831 |
|
10832 var pos = filename.lastIndexOf("/"), |
|
10833 shortFilename = filename; |
|
10834 |
|
10835 if (pos === -1) { |
|
10836 pos = filename.lastIndexOf("\\"); |
|
10837 } |
|
10838 if (pos > -1) { |
|
10839 shortFilename = filename.substring(pos+1); |
|
10840 } |
|
10841 |
|
10842 CSSLint.Util.forEach(messages, function (message, i) { |
|
10843 output = output + "\n\n" + shortFilename; |
|
10844 if (message.rollup) { |
|
10845 output += "\n" + (i+1) + ": " + message.type; |
|
10846 output += "\n" + message.message; |
|
10847 } else { |
|
10848 output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col; |
|
10849 output += "\n" + message.message; |
|
10850 output += "\n" + message.evidence; |
|
10851 } |
|
10852 }); |
|
10853 |
|
10854 return output; |
|
10855 } |
|
10856 }); |
|
10857 |
|
10858 return CSSLint; |
|
10859 })(); |