css_validator 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2851 @@
1
+ /*!
2
+ CSSLint
3
+ Copyright (c) 2013 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
+ /* Build: v0.10.0 15-August-2013 01:07:22 */
25
+ var parserlib = require("./node-parserlib");
26
+ /**
27
+ * Main CSSLint object.
28
+ * @class CSSLint
29
+ * @static
30
+ * @extends parserlib.util.EventTarget
31
+ */
32
+ /*global parserlib, Reporter*/
33
+ var CSSLint = (function(){
34
+
35
+ var rules = [],
36
+ formatters = [],
37
+ embeddedRuleset = /\/\*csslint([^\*]*)\*\//,
38
+ api = new parserlib.util.EventTarget();
39
+
40
+ api.version = "0.10.0";
41
+
42
+ //-------------------------------------------------------------------------
43
+ // Rule Management
44
+ //-------------------------------------------------------------------------
45
+
46
+ /**
47
+ * Adds a new rule to the engine.
48
+ * @param {Object} rule The rule to add.
49
+ * @method addRule
50
+ */
51
+ api.addRule = function(rule){
52
+ rules.push(rule);
53
+ rules[rule.id] = rule;
54
+ };
55
+
56
+ /**
57
+ * Clears all rule from the engine.
58
+ * @method clearRules
59
+ */
60
+ api.clearRules = function(){
61
+ rules = [];
62
+ };
63
+
64
+ /**
65
+ * Returns the rule objects.
66
+ * @return An array of rule objects.
67
+ * @method getRules
68
+ */
69
+ api.getRules = function(){
70
+ return [].concat(rules).sort(function(a,b){
71
+ return a.id > b.id ? 1 : 0;
72
+ });
73
+ };
74
+
75
+ /**
76
+ * Returns a ruleset configuration object with all current rules.
77
+ * @return A ruleset object.
78
+ * @method getRuleset
79
+ */
80
+ api.getRuleset = function() {
81
+ var ruleset = {},
82
+ i = 0,
83
+ len = rules.length;
84
+
85
+ while (i < len){
86
+ ruleset[rules[i++].id] = 1; //by default, everything is a warning
87
+ }
88
+
89
+ return ruleset;
90
+ };
91
+
92
+ /**
93
+ * Returns a ruleset object based on embedded rules.
94
+ * @param {String} text A string of css containing embedded rules.
95
+ * @param {Object} ruleset A ruleset object to modify.
96
+ * @return {Object} A ruleset object.
97
+ * @method getEmbeddedRuleset
98
+ */
99
+ function applyEmbeddedRuleset(text, ruleset){
100
+ var valueMap,
101
+ embedded = text && text.match(embeddedRuleset),
102
+ rules = embedded && embedded[1];
103
+
104
+ if (rules) {
105
+ valueMap = {
106
+ "true": 2, // true is error
107
+ "": 1, // blank is warning
108
+ "false": 0, // false is ignore
109
+
110
+ "2": 2, // explicit error
111
+ "1": 1, // explicit warning
112
+ "0": 0 // explicit ignore
113
+ };
114
+
115
+ rules.toLowerCase().split(",").forEach(function(rule){
116
+ var pair = rule.split(":"),
117
+ property = pair[0] || "",
118
+ value = pair[1] || "";
119
+
120
+ ruleset[property.trim()] = valueMap[value.trim()];
121
+ });
122
+ }
123
+
124
+ return ruleset;
125
+ }
126
+
127
+ //-------------------------------------------------------------------------
128
+ // Formatters
129
+ //-------------------------------------------------------------------------
130
+
131
+ /**
132
+ * Adds a new formatter to the engine.
133
+ * @param {Object} formatter The formatter to add.
134
+ * @method addFormatter
135
+ */
136
+ api.addFormatter = function(formatter) {
137
+ // formatters.push(formatter);
138
+ formatters[formatter.id] = formatter;
139
+ };
140
+
141
+ /**
142
+ * Retrieves a formatter for use.
143
+ * @param {String} formatId The name of the format to retrieve.
144
+ * @return {Object} The formatter or undefined.
145
+ * @method getFormatter
146
+ */
147
+ api.getFormatter = function(formatId){
148
+ return formatters[formatId];
149
+ };
150
+
151
+ /**
152
+ * Formats the results in a particular format for a single file.
153
+ * @param {Object} result The results returned from CSSLint.verify().
154
+ * @param {String} filename The filename for which the results apply.
155
+ * @param {String} formatId The name of the formatter to use.
156
+ * @param {Object} options (Optional) for special output handling.
157
+ * @return {String} A formatted string for the results.
158
+ * @method format
159
+ */
160
+ api.format = function(results, filename, formatId, options) {
161
+ var formatter = this.getFormatter(formatId),
162
+ result = null;
163
+
164
+ if (formatter){
165
+ result = formatter.startFormat();
166
+ result += formatter.formatResults(results, filename, options || {});
167
+ result += formatter.endFormat();
168
+ }
169
+
170
+ return result;
171
+ };
172
+
173
+ /**
174
+ * Indicates if the given format is supported.
175
+ * @param {String} formatId The ID of the format to check.
176
+ * @return {Boolean} True if the format exists, false if not.
177
+ * @method hasFormat
178
+ */
179
+ api.hasFormat = function(formatId){
180
+ return formatters.hasOwnProperty(formatId);
181
+ };
182
+
183
+ //-------------------------------------------------------------------------
184
+ // Verification
185
+ //-------------------------------------------------------------------------
186
+
187
+ /**
188
+ * Starts the verification process for the given CSS text.
189
+ * @param {String} text The CSS text to verify.
190
+ * @param {Object} ruleset (Optional) List of rules to apply. If null, then
191
+ * all rules are used. If a rule has a value of 1 then it's a warning,
192
+ * a value of 2 means it's an error.
193
+ * @return {Object} Results of the verification.
194
+ * @method verify
195
+ */
196
+ api.verify = function(text, ruleset){
197
+
198
+ var i = 0,
199
+ len = rules.length,
200
+ reporter,
201
+ lines,
202
+ report,
203
+ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true,
204
+ underscoreHack: true, strict: false });
205
+
206
+ // normalize line endings
207
+ lines = text.replace(/\n\r?/g, "$split$").split('$split$');
208
+
209
+ if (!ruleset){
210
+ ruleset = this.getRuleset();
211
+ }
212
+
213
+ if (embeddedRuleset.test(text)){
214
+ ruleset = applyEmbeddedRuleset(text, ruleset);
215
+ }
216
+
217
+ reporter = new Reporter(lines, ruleset);
218
+
219
+ ruleset.errors = 2; //always report parsing errors as errors
220
+ for (i in ruleset){
221
+ if(ruleset.hasOwnProperty(i) && ruleset[i]){
222
+ if (rules[i]){
223
+ rules[i].init(parser, reporter);
224
+ }
225
+ }
226
+ }
227
+
228
+
229
+ //capture most horrible error type
230
+ try {
231
+ parser.parse(text);
232
+ } catch (ex) {
233
+ reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {});
234
+ }
235
+
236
+ report = {
237
+ messages : reporter.messages,
238
+ stats : reporter.stats,
239
+ ruleset : reporter.ruleset
240
+ };
241
+
242
+ //sort by line numbers, rollups at the bottom
243
+ report.messages.sort(function (a, b){
244
+ if (a.rollup && !b.rollup){
245
+ return 1;
246
+ } else if (!a.rollup && b.rollup){
247
+ return -1;
248
+ } else {
249
+ return a.line - b.line;
250
+ }
251
+ });
252
+
253
+ return report;
254
+ };
255
+
256
+ //-------------------------------------------------------------------------
257
+ // Publish the API
258
+ //-------------------------------------------------------------------------
259
+
260
+ return api;
261
+
262
+ })();
263
+
264
+ /*global CSSLint*/
265
+ /**
266
+ * An instance of Report is used to report results of the
267
+ * verification back to the main API.
268
+ * @class Reporter
269
+ * @constructor
270
+ * @param {String[]} lines The text lines of the source.
271
+ * @param {Object} ruleset The set of rules to work with, including if
272
+ * they are errors or warnings.
273
+ */
274
+ function Reporter(lines, ruleset){
275
+
276
+ /**
277
+ * List of messages being reported.
278
+ * @property messages
279
+ * @type String[]
280
+ */
281
+ this.messages = [];
282
+
283
+ /**
284
+ * List of statistics being reported.
285
+ * @property stats
286
+ * @type String[]
287
+ */
288
+ this.stats = [];
289
+
290
+ /**
291
+ * Lines of code being reported on. Used to provide contextual information
292
+ * for messages.
293
+ * @property lines
294
+ * @type String[]
295
+ */
296
+ this.lines = lines;
297
+
298
+ /**
299
+ * Information about the rules. Used to determine whether an issue is an
300
+ * error or warning.
301
+ * @property ruleset
302
+ * @type Object
303
+ */
304
+ this.ruleset = ruleset;
305
+ }
306
+
307
+ Reporter.prototype = {
308
+
309
+ //restore constructor
310
+ constructor: Reporter,
311
+
312
+ /**
313
+ * Report an error.
314
+ * @param {String} message The message to store.
315
+ * @param {int} line The line number.
316
+ * @param {int} col The column number.
317
+ * @param {Object} rule The rule this message relates to.
318
+ * @method error
319
+ */
320
+ error: function(message, line, col, rule){
321
+ this.messages.push({
322
+ type : "error",
323
+ line : line,
324
+ col : col,
325
+ message : message,
326
+ evidence: this.lines[line-1],
327
+ rule : rule || {}
328
+ });
329
+ },
330
+
331
+ /**
332
+ * Report an warning.
333
+ * @param {String} message The message to store.
334
+ * @param {int} line The line number.
335
+ * @param {int} col The column number.
336
+ * @param {Object} rule The rule this message relates to.
337
+ * @method warn
338
+ * @deprecated Use report instead.
339
+ */
340
+ warn: function(message, line, col, rule){
341
+ this.report(message, line, col, rule);
342
+ },
343
+
344
+ /**
345
+ * Report an issue.
346
+ * @param {String} message The message to store.
347
+ * @param {int} line The line number.
348
+ * @param {int} col The column number.
349
+ * @param {Object} rule The rule this message relates to.
350
+ * @method report
351
+ */
352
+ report: function(message, line, col, rule){
353
+ this.messages.push({
354
+ type : this.ruleset[rule.id] == 2 ? "error" : "warning",
355
+ line : line,
356
+ col : col,
357
+ message : message,
358
+ evidence: this.lines[line-1],
359
+ rule : rule
360
+ });
361
+ },
362
+
363
+ /**
364
+ * Report some informational text.
365
+ * @param {String} message The message to store.
366
+ * @param {int} line The line number.
367
+ * @param {int} col The column number.
368
+ * @param {Object} rule The rule this message relates to.
369
+ * @method info
370
+ */
371
+ info: function(message, line, col, rule){
372
+ this.messages.push({
373
+ type : "info",
374
+ line : line,
375
+ col : col,
376
+ message : message,
377
+ evidence: this.lines[line-1],
378
+ rule : rule
379
+ });
380
+ },
381
+
382
+ /**
383
+ * Report some rollup error information.
384
+ * @param {String} message The message to store.
385
+ * @param {Object} rule The rule this message relates to.
386
+ * @method rollupError
387
+ */
388
+ rollupError: function(message, rule){
389
+ this.messages.push({
390
+ type : "error",
391
+ rollup : true,
392
+ message : message,
393
+ rule : rule
394
+ });
395
+ },
396
+
397
+ /**
398
+ * Report some rollup warning information.
399
+ * @param {String} message The message to store.
400
+ * @param {Object} rule The rule this message relates to.
401
+ * @method rollupWarn
402
+ */
403
+ rollupWarn: function(message, rule){
404
+ this.messages.push({
405
+ type : "warning",
406
+ rollup : true,
407
+ message : message,
408
+ rule : rule
409
+ });
410
+ },
411
+
412
+ /**
413
+ * Report a statistic.
414
+ * @param {String} name The name of the stat to store.
415
+ * @param {Variant} value The value of the stat.
416
+ * @method stat
417
+ */
418
+ stat: function(name, value){
419
+ this.stats[name] = value;
420
+ }
421
+ };
422
+
423
+ //expose for testing purposes
424
+ CSSLint._Reporter = Reporter;
425
+
426
+ /*global CSSLint*/
427
+
428
+ /*
429
+ * Utility functions that make life easier.
430
+ */
431
+ CSSLint.Util = {
432
+ /*
433
+ * Adds all properties from supplier onto receiver,
434
+ * overwriting if the same name already exists on
435
+ * reciever.
436
+ * @param {Object} The object to receive the properties.
437
+ * @param {Object} The object to provide the properties.
438
+ * @return {Object} The receiver
439
+ */
440
+ mix: function(receiver, supplier){
441
+ var prop;
442
+
443
+ for (prop in supplier){
444
+ if (supplier.hasOwnProperty(prop)){
445
+ receiver[prop] = supplier[prop];
446
+ }
447
+ }
448
+
449
+ return prop;
450
+ },
451
+
452
+ /*
453
+ * Polyfill for array indexOf() method.
454
+ * @param {Array} values The array to search.
455
+ * @param {Variant} value The value to search for.
456
+ * @return {int} The index of the value if found, -1 if not.
457
+ */
458
+ indexOf: function(values, value){
459
+ if (values.indexOf){
460
+ return values.indexOf(value);
461
+ } else {
462
+ for (var i=0, len=values.length; i < len; i++){
463
+ if (values[i] === value){
464
+ return i;
465
+ }
466
+ }
467
+ return -1;
468
+ }
469
+ },
470
+
471
+ /*
472
+ * Polyfill for array forEach() method.
473
+ * @param {Array} values The array to operate on.
474
+ * @param {Function} func The function to call on each item.
475
+ * @return {void}
476
+ */
477
+ forEach: function(values, func) {
478
+ if (values.forEach){
479
+ return values.forEach(func);
480
+ } else {
481
+ for (var i=0, len=values.length; i < len; i++){
482
+ func(values[i], i, values);
483
+ }
484
+ }
485
+ }
486
+ };
487
+ /*global CSSLint*/
488
+ /*
489
+ * Rule: Don't use adjoining classes (.foo.bar).
490
+ */
491
+ CSSLint.addRule({
492
+
493
+ //rule information
494
+ id: "adjoining-classes",
495
+ name: "Disallow adjoining classes",
496
+ desc: "Don't use adjoining classes.",
497
+ browsers: "IE6",
498
+
499
+ //initialization
500
+ init: function(parser, reporter){
501
+ var rule = this;
502
+ parser.addListener("startrule", function(event){
503
+ var selectors = event.selectors,
504
+ selector,
505
+ part,
506
+ modifier,
507
+ classCount,
508
+ i, j, k;
509
+
510
+ for (i=0; i < selectors.length; i++){
511
+ selector = selectors[i];
512
+ for (j=0; j < selector.parts.length; j++){
513
+ part = selector.parts[j];
514
+ if (part.type == parser.SELECTOR_PART_TYPE){
515
+ classCount = 0;
516
+ for (k=0; k < part.modifiers.length; k++){
517
+ modifier = part.modifiers[k];
518
+ if (modifier.type == "class"){
519
+ classCount++;
520
+ }
521
+ if (classCount > 1){
522
+ reporter.report("Don't use adjoining classes.", part.line, part.col, rule);
523
+ }
524
+ }
525
+ }
526
+ }
527
+ }
528
+ });
529
+ }
530
+
531
+ });
532
+ /*global CSSLint*/
533
+
534
+ /*
535
+ * Rule: Don't use width or height when using padding or border.
536
+ */
537
+ CSSLint.addRule({
538
+
539
+ //rule information
540
+ id: "box-model",
541
+ name: "Beware of broken box size",
542
+ desc: "Don't use width or height when using padding or border.",
543
+ browsers: "All",
544
+
545
+ //initialization
546
+ init: function(parser, reporter){
547
+ var rule = this,
548
+ widthProperties = {
549
+ border: 1,
550
+ "border-left": 1,
551
+ "border-right": 1,
552
+ padding: 1,
553
+ "padding-left": 1,
554
+ "padding-right": 1
555
+ },
556
+ heightProperties = {
557
+ border: 1,
558
+ "border-bottom": 1,
559
+ "border-top": 1,
560
+ padding: 1,
561
+ "padding-bottom": 1,
562
+ "padding-top": 1
563
+ },
564
+ properties,
565
+ boxSizing = false;
566
+
567
+ function startRule(){
568
+ properties = {};
569
+ boxSizing = false;
570
+ }
571
+
572
+ function endRule(){
573
+ var prop, value;
574
+
575
+ if (!boxSizing) {
576
+ if (properties.height){
577
+ for (prop in heightProperties){
578
+ if (heightProperties.hasOwnProperty(prop) && properties[prop]){
579
+ value = properties[prop].value;
580
+ //special case for padding
581
+ if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){
582
+ reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ if (properties.width){
589
+ for (prop in widthProperties){
590
+ if (widthProperties.hasOwnProperty(prop) && properties[prop]){
591
+ value = properties[prop].value;
592
+
593
+ if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){
594
+ reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
595
+ }
596
+ }
597
+ }
598
+ }
599
+ }
600
+ }
601
+
602
+ parser.addListener("startrule", startRule);
603
+ parser.addListener("startfontface", startRule);
604
+ parser.addListener("startpage", startRule);
605
+ parser.addListener("startpagemargin", startRule);
606
+ parser.addListener("startkeyframerule", startRule);
607
+
608
+ parser.addListener("property", function(event){
609
+ var name = event.property.text.toLowerCase();
610
+
611
+ if (heightProperties[name] || widthProperties[name]){
612
+ if (!/^0\S*$/.test(event.value) && !(name == "border" && event.value == "none")){
613
+ properties[name] = { line: event.property.line, col: event.property.col, value: event.value };
614
+ }
615
+ } else {
616
+ if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){
617
+ properties[name] = 1;
618
+ } else if (name == "box-sizing") {
619
+ boxSizing = true;
620
+ }
621
+ }
622
+
623
+ });
624
+
625
+ parser.addListener("endrule", endRule);
626
+ parser.addListener("endfontface", endRule);
627
+ parser.addListener("endpage", endRule);
628
+ parser.addListener("endpagemargin", endRule);
629
+ parser.addListener("endkeyframerule", endRule);
630
+ }
631
+
632
+ });
633
+ /*global CSSLint*/
634
+
635
+ /*
636
+ * Rule: box-sizing doesn't work in IE6 and IE7.
637
+ */
638
+ CSSLint.addRule({
639
+
640
+ //rule information
641
+ id: "box-sizing",
642
+ name: "Disallow use of box-sizing",
643
+ desc: "The box-sizing properties isn't supported in IE6 and IE7.",
644
+ browsers: "IE6, IE7",
645
+ tags: ["Compatibility"],
646
+
647
+ //initialization
648
+ init: function(parser, reporter){
649
+ var rule = this;
650
+
651
+ parser.addListener("property", function(event){
652
+ var name = event.property.text.toLowerCase();
653
+
654
+ if (name == "box-sizing"){
655
+ reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule);
656
+ }
657
+ });
658
+ }
659
+
660
+ });
661
+ /*
662
+ * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE
663
+ * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax)
664
+ */
665
+ /*global CSSLint*/
666
+ CSSLint.addRule({
667
+
668
+ //rule information
669
+ id: "bulletproof-font-face",
670
+ name: "Use the bulletproof @font-face syntax",
671
+ 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).",
672
+ browsers: "All",
673
+
674
+ //initialization
675
+ init: function(parser, reporter){
676
+ var rule = this,
677
+ count = 0,
678
+ fontFaceRule = false,
679
+ firstSrc = true,
680
+ ruleFailed = false,
681
+ line, col;
682
+
683
+ // Mark the start of a @font-face declaration so we only test properties inside it
684
+ parser.addListener("startfontface", function(event){
685
+ fontFaceRule = true;
686
+ });
687
+
688
+ parser.addListener("property", function(event){
689
+ // If we aren't inside an @font-face declaration then just return
690
+ if (!fontFaceRule) {
691
+ return;
692
+ }
693
+
694
+ var propertyName = event.property.toString().toLowerCase(),
695
+ value = event.value.toString();
696
+
697
+ // Set the line and col numbers for use in the endfontface listener
698
+ line = event.line;
699
+ col = event.col;
700
+
701
+ // This is the property that we care about, we can ignore the rest
702
+ if (propertyName === 'src') {
703
+ var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
704
+
705
+ // We need to handle the advanced syntax with two src properties
706
+ if (!value.match(regex) && firstSrc) {
707
+ ruleFailed = true;
708
+ firstSrc = false;
709
+ } else if (value.match(regex) && !firstSrc) {
710
+ ruleFailed = false;
711
+ }
712
+ }
713
+
714
+
715
+ });
716
+
717
+ // Back to normal rules that we don't need to test
718
+ parser.addListener("endfontface", function(event){
719
+ fontFaceRule = false;
720
+
721
+ if (ruleFailed) {
722
+ reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule);
723
+ }
724
+ });
725
+ }
726
+ });
727
+ /*
728
+ * Rule: Include all compatible vendor prefixes to reach a wider
729
+ * range of users.
730
+ */
731
+ /*global CSSLint*/
732
+ CSSLint.addRule({
733
+
734
+ //rule information
735
+ id: "compatible-vendor-prefixes",
736
+ name: "Require compatible vendor prefixes",
737
+ desc: "Include all compatible vendor prefixes to reach a wider range of users.",
738
+ browsers: "All",
739
+
740
+ //initialization
741
+ init: function (parser, reporter) {
742
+ var rule = this,
743
+ compatiblePrefixes,
744
+ properties,
745
+ prop,
746
+ variations,
747
+ prefixed,
748
+ i,
749
+ len,
750
+ inKeyFrame = false,
751
+ arrayPush = Array.prototype.push,
752
+ applyTo = [];
753
+
754
+ // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
755
+ compatiblePrefixes = {
756
+ "animation" : "webkit moz",
757
+ "animation-delay" : "webkit moz",
758
+ "animation-direction" : "webkit moz",
759
+ "animation-duration" : "webkit moz",
760
+ "animation-fill-mode" : "webkit moz",
761
+ "animation-iteration-count" : "webkit moz",
762
+ "animation-name" : "webkit moz",
763
+ "animation-play-state" : "webkit moz",
764
+ "animation-timing-function" : "webkit moz",
765
+ "appearance" : "webkit moz",
766
+ "border-end" : "webkit moz",
767
+ "border-end-color" : "webkit moz",
768
+ "border-end-style" : "webkit moz",
769
+ "border-end-width" : "webkit moz",
770
+ "border-image" : "webkit moz o",
771
+ "border-radius" : "webkit",
772
+ "border-start" : "webkit moz",
773
+ "border-start-color" : "webkit moz",
774
+ "border-start-style" : "webkit moz",
775
+ "border-start-width" : "webkit moz",
776
+ "box-align" : "webkit moz ms",
777
+ "box-direction" : "webkit moz ms",
778
+ "box-flex" : "webkit moz ms",
779
+ "box-lines" : "webkit ms",
780
+ "box-ordinal-group" : "webkit moz ms",
781
+ "box-orient" : "webkit moz ms",
782
+ "box-pack" : "webkit moz ms",
783
+ "box-sizing" : "webkit moz",
784
+ "box-shadow" : "webkit moz",
785
+ "column-count" : "webkit moz ms",
786
+ "column-gap" : "webkit moz ms",
787
+ "column-rule" : "webkit moz ms",
788
+ "column-rule-color" : "webkit moz ms",
789
+ "column-rule-style" : "webkit moz ms",
790
+ "column-rule-width" : "webkit moz ms",
791
+ "column-width" : "webkit moz ms",
792
+ "hyphens" : "epub moz",
793
+ "line-break" : "webkit ms",
794
+ "margin-end" : "webkit moz",
795
+ "margin-start" : "webkit moz",
796
+ "marquee-speed" : "webkit wap",
797
+ "marquee-style" : "webkit wap",
798
+ "padding-end" : "webkit moz",
799
+ "padding-start" : "webkit moz",
800
+ "tab-size" : "moz o",
801
+ "text-size-adjust" : "webkit ms",
802
+ "transform" : "webkit moz ms o",
803
+ "transform-origin" : "webkit moz ms o",
804
+ "transition" : "webkit moz o",
805
+ "transition-delay" : "webkit moz o",
806
+ "transition-duration" : "webkit moz o",
807
+ "transition-property" : "webkit moz o",
808
+ "transition-timing-function" : "webkit moz o",
809
+ "user-modify" : "webkit moz",
810
+ "user-select" : "webkit moz ms",
811
+ "word-break" : "epub ms",
812
+ "writing-mode" : "epub ms"
813
+ };
814
+
815
+
816
+ for (prop in compatiblePrefixes) {
817
+ if (compatiblePrefixes.hasOwnProperty(prop)) {
818
+ variations = [];
819
+ prefixed = compatiblePrefixes[prop].split(' ');
820
+ for (i = 0, len = prefixed.length; i < len; i++) {
821
+ variations.push('-' + prefixed[i] + '-' + prop);
822
+ }
823
+ compatiblePrefixes[prop] = variations;
824
+ arrayPush.apply(applyTo, variations);
825
+ }
826
+ }
827
+
828
+ parser.addListener("startrule", function () {
829
+ properties = [];
830
+ });
831
+
832
+ parser.addListener("startkeyframes", function (event) {
833
+ inKeyFrame = event.prefix || true;
834
+ });
835
+
836
+ parser.addListener("endkeyframes", function (event) {
837
+ inKeyFrame = false;
838
+ });
839
+
840
+ parser.addListener("property", function (event) {
841
+ var name = event.property;
842
+ if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
843
+
844
+ // e.g., -moz-transform is okay to be alone in @-moz-keyframes
845
+ if (!inKeyFrame || typeof inKeyFrame != "string" ||
846
+ name.text.indexOf("-" + inKeyFrame + "-") !== 0) {
847
+ properties.push(name);
848
+ }
849
+ }
850
+ });
851
+
852
+ parser.addListener("endrule", function (event) {
853
+ if (!properties.length) {
854
+ return;
855
+ }
856
+
857
+ var propertyGroups = {},
858
+ i,
859
+ len,
860
+ name,
861
+ prop,
862
+ variations,
863
+ value,
864
+ full,
865
+ actual,
866
+ item,
867
+ propertiesSpecified;
868
+
869
+ for (i = 0, len = properties.length; i < len; i++) {
870
+ name = properties[i];
871
+
872
+ for (prop in compatiblePrefixes) {
873
+ if (compatiblePrefixes.hasOwnProperty(prop)) {
874
+ variations = compatiblePrefixes[prop];
875
+ if (CSSLint.Util.indexOf(variations, name.text) > -1) {
876
+ if (!propertyGroups[prop]) {
877
+ propertyGroups[prop] = {
878
+ full : variations.slice(0),
879
+ actual : [],
880
+ actualNodes: []
881
+ };
882
+ }
883
+ if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) {
884
+ propertyGroups[prop].actual.push(name.text);
885
+ propertyGroups[prop].actualNodes.push(name);
886
+ }
887
+ }
888
+ }
889
+ }
890
+ }
891
+
892
+ for (prop in propertyGroups) {
893
+ if (propertyGroups.hasOwnProperty(prop)) {
894
+ value = propertyGroups[prop];
895
+ full = value.full;
896
+ actual = value.actual;
897
+
898
+ if (full.length > actual.length) {
899
+ for (i = 0, len = full.length; i < len; i++) {
900
+ item = full[i];
901
+ if (CSSLint.Util.indexOf(actual, item) === -1) {
902
+ propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length == 2) ? actual.join(" and ") : actual.join(", ");
903
+ reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule);
904
+ }
905
+ }
906
+
907
+ }
908
+ }
909
+ }
910
+ });
911
+ }
912
+ });
913
+ /*
914
+ * Rule: Certain properties don't play well with certain display values.
915
+ * - float should not be used with inline-block
916
+ * - height, width, margin-top, margin-bottom, float should not be used with inline
917
+ * - vertical-align should not be used with block
918
+ * - margin, float should not be used with table-*
919
+ */
920
+ /*global CSSLint*/
921
+ CSSLint.addRule({
922
+
923
+ //rule information
924
+ id: "display-property-grouping",
925
+ name: "Require properties appropriate for display",
926
+ desc: "Certain properties shouldn't be used with certain display property values.",
927
+ browsers: "All",
928
+
929
+ //initialization
930
+ init: function(parser, reporter){
931
+ var rule = this;
932
+
933
+ var propertiesToCheck = {
934
+ display: 1,
935
+ "float": "none",
936
+ height: 1,
937
+ width: 1,
938
+ margin: 1,
939
+ "margin-left": 1,
940
+ "margin-right": 1,
941
+ "margin-bottom": 1,
942
+ "margin-top": 1,
943
+ padding: 1,
944
+ "padding-left": 1,
945
+ "padding-right": 1,
946
+ "padding-bottom": 1,
947
+ "padding-top": 1,
948
+ "vertical-align": 1
949
+ },
950
+ properties;
951
+
952
+ function reportProperty(name, display, msg){
953
+ if (properties[name]){
954
+ if (typeof propertiesToCheck[name] != "string" || properties[name].value.toLowerCase() != propertiesToCheck[name]){
955
+ reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
956
+ }
957
+ }
958
+ }
959
+
960
+ function startRule(){
961
+ properties = {};
962
+ }
963
+
964
+ function endRule(){
965
+
966
+ var display = properties.display ? properties.display.value : null;
967
+ if (display){
968
+ switch(display){
969
+
970
+ case "inline":
971
+ //height, width, margin-top, margin-bottom, float should not be used with inline
972
+ reportProperty("height", display);
973
+ reportProperty("width", display);
974
+ reportProperty("margin", display);
975
+ reportProperty("margin-top", display);
976
+ reportProperty("margin-bottom", display);
977
+ reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");
978
+ break;
979
+
980
+ case "block":
981
+ //vertical-align should not be used with block
982
+ reportProperty("vertical-align", display);
983
+ break;
984
+
985
+ case "inline-block":
986
+ //float should not be used with inline-block
987
+ reportProperty("float", display);
988
+ break;
989
+
990
+ default:
991
+ //margin, float should not be used with table
992
+ if (display.indexOf("table-") === 0){
993
+ reportProperty("margin", display);
994
+ reportProperty("margin-left", display);
995
+ reportProperty("margin-right", display);
996
+ reportProperty("margin-top", display);
997
+ reportProperty("margin-bottom", display);
998
+ reportProperty("float", display);
999
+ }
1000
+
1001
+ //otherwise do nothing
1002
+ }
1003
+ }
1004
+
1005
+ }
1006
+
1007
+ parser.addListener("startrule", startRule);
1008
+ parser.addListener("startfontface", startRule);
1009
+ parser.addListener("startkeyframerule", startRule);
1010
+ parser.addListener("startpagemargin", startRule);
1011
+ parser.addListener("startpage", startRule);
1012
+
1013
+ parser.addListener("property", function(event){
1014
+ var name = event.property.text.toLowerCase();
1015
+
1016
+ if (propertiesToCheck[name]){
1017
+ properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col };
1018
+ }
1019
+ });
1020
+
1021
+ parser.addListener("endrule", endRule);
1022
+ parser.addListener("endfontface", endRule);
1023
+ parser.addListener("endkeyframerule", endRule);
1024
+ parser.addListener("endpagemargin", endRule);
1025
+ parser.addListener("endpage", endRule);
1026
+
1027
+ }
1028
+
1029
+ });
1030
+ /*
1031
+ * Rule: Disallow duplicate background-images (using url).
1032
+ */
1033
+ /*global CSSLint*/
1034
+ CSSLint.addRule({
1035
+
1036
+ //rule information
1037
+ id: "duplicate-background-images",
1038
+ name: "Disallow duplicate background images",
1039
+ desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
1040
+ browsers: "All",
1041
+
1042
+ //initialization
1043
+ init: function(parser, reporter){
1044
+ var rule = this,
1045
+ stack = {};
1046
+
1047
+ parser.addListener("property", function(event){
1048
+ var name = event.property.text,
1049
+ value = event.value,
1050
+ i, len;
1051
+
1052
+ if (name.match(/background/i)) {
1053
+ for (i=0, len=value.parts.length; i < len; i++) {
1054
+ if (value.parts[i].type == 'uri') {
1055
+ if (typeof stack[value.parts[i].uri] === 'undefined') {
1056
+ stack[value.parts[i].uri] = event;
1057
+ }
1058
+ else {
1059
+ 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);
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+ });
1065
+ }
1066
+ });
1067
+ /*
1068
+ * Rule: Duplicate properties must appear one after the other. If an already-defined
1069
+ * property appears somewhere else in the rule, then it's likely an error.
1070
+ */
1071
+ /*global CSSLint*/
1072
+ CSSLint.addRule({
1073
+
1074
+ //rule information
1075
+ id: "duplicate-properties",
1076
+ name: "Disallow duplicate properties",
1077
+ desc: "Duplicate properties must appear one after the other.",
1078
+ browsers: "All",
1079
+
1080
+ //initialization
1081
+ init: function(parser, reporter){
1082
+ var rule = this,
1083
+ properties,
1084
+ lastProperty;
1085
+
1086
+ function startRule(event){
1087
+ properties = {};
1088
+ }
1089
+
1090
+ parser.addListener("startrule", startRule);
1091
+ parser.addListener("startfontface", startRule);
1092
+ parser.addListener("startpage", startRule);
1093
+ parser.addListener("startpagemargin", startRule);
1094
+ parser.addListener("startkeyframerule", startRule);
1095
+
1096
+ parser.addListener("property", function(event){
1097
+ var property = event.property,
1098
+ name = property.text.toLowerCase();
1099
+
1100
+ if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){
1101
+ reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule);
1102
+ }
1103
+
1104
+ properties[name] = event.value.text;
1105
+ lastProperty = name;
1106
+
1107
+ });
1108
+
1109
+
1110
+ }
1111
+
1112
+ });
1113
+ /*
1114
+ * Rule: Style rules without any properties defined should be removed.
1115
+ */
1116
+ /*global CSSLint*/
1117
+ CSSLint.addRule({
1118
+
1119
+ //rule information
1120
+ id: "empty-rules",
1121
+ name: "Disallow empty rules",
1122
+ desc: "Rules without any properties specified should be removed.",
1123
+ browsers: "All",
1124
+
1125
+ //initialization
1126
+ init: function(parser, reporter){
1127
+ var rule = this,
1128
+ count = 0;
1129
+
1130
+ parser.addListener("startrule", function(){
1131
+ count=0;
1132
+ });
1133
+
1134
+ parser.addListener("property", function(){
1135
+ count++;
1136
+ });
1137
+
1138
+ parser.addListener("endrule", function(event){
1139
+ var selectors = event.selectors;
1140
+ if (count === 0){
1141
+ reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule);
1142
+ }
1143
+ });
1144
+ }
1145
+
1146
+ });
1147
+ /*
1148
+ * Rule: There should be no syntax errors. (Duh.)
1149
+ */
1150
+ /*global CSSLint*/
1151
+ CSSLint.addRule({
1152
+
1153
+ //rule information
1154
+ id: "errors",
1155
+ name: "Parsing Errors",
1156
+ desc: "This rule looks for recoverable syntax errors.",
1157
+ browsers: "All",
1158
+
1159
+ //initialization
1160
+ init: function(parser, reporter){
1161
+ var rule = this;
1162
+
1163
+ parser.addListener("error", function(event){
1164
+ reporter.error(event.message, event.line, event.col, rule);
1165
+ });
1166
+
1167
+ }
1168
+
1169
+ });
1170
+
1171
+ /*global CSSLint*/
1172
+ CSSLint.addRule({
1173
+
1174
+ //rule information
1175
+ id: "fallback-colors",
1176
+ name: "Require fallback colors",
1177
+ desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
1178
+ browsers: "IE6,IE7,IE8",
1179
+
1180
+ //initialization
1181
+ init: function(parser, reporter){
1182
+ var rule = this,
1183
+ lastProperty,
1184
+ propertiesToCheck = {
1185
+ color: 1,
1186
+ background: 1,
1187
+ "border-color": 1,
1188
+ "border-top-color": 1,
1189
+ "border-right-color": 1,
1190
+ "border-bottom-color": 1,
1191
+ "border-left-color": 1,
1192
+ border: 1,
1193
+ "border-top": 1,
1194
+ "border-right": 1,
1195
+ "border-bottom": 1,
1196
+ "border-left": 1,
1197
+ "background-color": 1
1198
+ },
1199
+ properties;
1200
+
1201
+ function startRule(event){
1202
+ properties = {};
1203
+ lastProperty = null;
1204
+ }
1205
+
1206
+ parser.addListener("startrule", startRule);
1207
+ parser.addListener("startfontface", startRule);
1208
+ parser.addListener("startpage", startRule);
1209
+ parser.addListener("startpagemargin", startRule);
1210
+ parser.addListener("startkeyframerule", startRule);
1211
+
1212
+ parser.addListener("property", function(event){
1213
+ var property = event.property,
1214
+ name = property.text.toLowerCase(),
1215
+ parts = event.value.parts,
1216
+ i = 0,
1217
+ colorType = "",
1218
+ len = parts.length;
1219
+
1220
+ if(propertiesToCheck[name]){
1221
+ while(i < len){
1222
+ if (parts[i].type == "color"){
1223
+ if ("alpha" in parts[i] || "hue" in parts[i]){
1224
+
1225
+ if (/([^\)]+)\(/.test(parts[i])){
1226
+ colorType = RegExp.$1.toUpperCase();
1227
+ }
1228
+
1229
+ if (!lastProperty || (lastProperty.property.text.toLowerCase() != name || lastProperty.colorType != "compat")){
1230
+ reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule);
1231
+ }
1232
+ } else {
1233
+ event.colorType = "compat";
1234
+ }
1235
+ }
1236
+
1237
+ i++;
1238
+ }
1239
+ }
1240
+
1241
+ lastProperty = event;
1242
+ });
1243
+
1244
+ }
1245
+
1246
+ });
1247
+ /*
1248
+ * Rule: You shouldn't use more than 10 floats. If you do, there's probably
1249
+ * room for some abstraction.
1250
+ */
1251
+ /*global CSSLint*/
1252
+ CSSLint.addRule({
1253
+
1254
+ //rule information
1255
+ id: "floats",
1256
+ name: "Disallow too many floats",
1257
+ desc: "This rule tests if the float property is used too many times",
1258
+ browsers: "All",
1259
+
1260
+ //initialization
1261
+ init: function(parser, reporter){
1262
+ var rule = this;
1263
+ var count = 0;
1264
+
1265
+ //count how many times "float" is used
1266
+ parser.addListener("property", function(event){
1267
+ if (event.property.text.toLowerCase() == "float" &&
1268
+ event.value.text.toLowerCase() != "none"){
1269
+ count++;
1270
+ }
1271
+ });
1272
+
1273
+ //report the results
1274
+ parser.addListener("endstylesheet", function(){
1275
+ reporter.stat("floats", count);
1276
+ if (count >= 10){
1277
+ reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
1278
+ }
1279
+ });
1280
+ }
1281
+
1282
+ });
1283
+ /*
1284
+ * Rule: Avoid too many @font-face declarations in the same stylesheet.
1285
+ */
1286
+ /*global CSSLint*/
1287
+ CSSLint.addRule({
1288
+
1289
+ //rule information
1290
+ id: "font-faces",
1291
+ name: "Don't use too many web fonts",
1292
+ desc: "Too many different web fonts in the same stylesheet.",
1293
+ browsers: "All",
1294
+
1295
+ //initialization
1296
+ init: function(parser, reporter){
1297
+ var rule = this,
1298
+ count = 0;
1299
+
1300
+
1301
+ parser.addListener("startfontface", function(){
1302
+ count++;
1303
+ });
1304
+
1305
+ parser.addListener("endstylesheet", function(){
1306
+ if (count > 5){
1307
+ reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
1308
+ }
1309
+ });
1310
+ }
1311
+
1312
+ });
1313
+ /*
1314
+ * Rule: You shouldn't need more than 9 font-size declarations.
1315
+ */
1316
+
1317
+ /*global CSSLint*/
1318
+ CSSLint.addRule({
1319
+
1320
+ //rule information
1321
+ id: "font-sizes",
1322
+ name: "Disallow too many font sizes",
1323
+ desc: "Checks the number of font-size declarations.",
1324
+ browsers: "All",
1325
+
1326
+ //initialization
1327
+ init: function(parser, reporter){
1328
+ var rule = this,
1329
+ count = 0;
1330
+
1331
+ //check for use of "font-size"
1332
+ parser.addListener("property", function(event){
1333
+ if (event.property == "font-size"){
1334
+ count++;
1335
+ }
1336
+ });
1337
+
1338
+ //report the results
1339
+ parser.addListener("endstylesheet", function(){
1340
+ reporter.stat("font-sizes", count);
1341
+ if (count >= 10){
1342
+ reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
1343
+ }
1344
+ });
1345
+ }
1346
+
1347
+ });
1348
+ /*
1349
+ * Rule: When using a vendor-prefixed gradient, make sure to use them all.
1350
+ */
1351
+ /*global CSSLint*/
1352
+ CSSLint.addRule({
1353
+
1354
+ //rule information
1355
+ id: "gradients",
1356
+ name: "Require all gradient definitions",
1357
+ desc: "When using a vendor-prefixed gradient, make sure to use them all.",
1358
+ browsers: "All",
1359
+
1360
+ //initialization
1361
+ init: function(parser, reporter){
1362
+ var rule = this,
1363
+ gradients;
1364
+
1365
+ parser.addListener("startrule", function(){
1366
+ gradients = {
1367
+ moz: 0,
1368
+ webkit: 0,
1369
+ oldWebkit: 0,
1370
+ o: 0
1371
+ };
1372
+ });
1373
+
1374
+ parser.addListener("property", function(event){
1375
+
1376
+ if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){
1377
+ gradients[RegExp.$1] = 1;
1378
+ } else if (/\-webkit\-gradient/i.test(event.value)){
1379
+ gradients.oldWebkit = 1;
1380
+ }
1381
+
1382
+ });
1383
+
1384
+ parser.addListener("endrule", function(event){
1385
+ var missing = [];
1386
+
1387
+ if (!gradients.moz){
1388
+ missing.push("Firefox 3.6+");
1389
+ }
1390
+
1391
+ if (!gradients.webkit){
1392
+ missing.push("Webkit (Safari 5+, Chrome)");
1393
+ }
1394
+
1395
+ if (!gradients.oldWebkit){
1396
+ missing.push("Old Webkit (Safari 4+, Chrome)");
1397
+ }
1398
+
1399
+ if (!gradients.o){
1400
+ missing.push("Opera 11.1+");
1401
+ }
1402
+
1403
+ if (missing.length && missing.length < 4){
1404
+ reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
1405
+ }
1406
+
1407
+ });
1408
+
1409
+ }
1410
+
1411
+ });
1412
+
1413
+ /*
1414
+ * Rule: Don't use IDs for selectors.
1415
+ */
1416
+ /*global CSSLint*/
1417
+ CSSLint.addRule({
1418
+
1419
+ //rule information
1420
+ id: "ids",
1421
+ name: "Disallow IDs in selectors",
1422
+ desc: "Selectors should not contain IDs.",
1423
+ browsers: "All",
1424
+
1425
+ //initialization
1426
+ init: function(parser, reporter){
1427
+ var rule = this;
1428
+ parser.addListener("startrule", function(event){
1429
+ var selectors = event.selectors,
1430
+ selector,
1431
+ part,
1432
+ modifier,
1433
+ idCount,
1434
+ i, j, k;
1435
+
1436
+ for (i=0; i < selectors.length; i++){
1437
+ selector = selectors[i];
1438
+ idCount = 0;
1439
+
1440
+ for (j=0; j < selector.parts.length; j++){
1441
+ part = selector.parts[j];
1442
+ if (part.type == parser.SELECTOR_PART_TYPE){
1443
+ for (k=0; k < part.modifiers.length; k++){
1444
+ modifier = part.modifiers[k];
1445
+ if (modifier.type == "id"){
1446
+ idCount++;
1447
+ }
1448
+ }
1449
+ }
1450
+ }
1451
+
1452
+ if (idCount == 1){
1453
+ reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule);
1454
+ } else if (idCount > 1){
1455
+ reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule);
1456
+ }
1457
+ }
1458
+
1459
+ });
1460
+ }
1461
+
1462
+ });
1463
+ /*
1464
+ * Rule: Don't use @import, use <link> instead.
1465
+ */
1466
+ /*global CSSLint*/
1467
+ CSSLint.addRule({
1468
+
1469
+ //rule information
1470
+ id: "import",
1471
+ name: "Disallow @import",
1472
+ desc: "Don't use @import, use <link> instead.",
1473
+ browsers: "All",
1474
+
1475
+ //initialization
1476
+ init: function(parser, reporter){
1477
+ var rule = this;
1478
+
1479
+ parser.addListener("import", function(event){
1480
+ reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule);
1481
+ });
1482
+
1483
+ }
1484
+
1485
+ });
1486
+ /*
1487
+ * Rule: Make sure !important is not overused, this could lead to specificity
1488
+ * war. Display a warning on !important declarations, an error if it's
1489
+ * used more at least 10 times.
1490
+ */
1491
+ /*global CSSLint*/
1492
+ CSSLint.addRule({
1493
+
1494
+ //rule information
1495
+ id: "important",
1496
+ name: "Disallow !important",
1497
+ desc: "Be careful when using !important declaration",
1498
+ browsers: "All",
1499
+
1500
+ //initialization
1501
+ init: function(parser, reporter){
1502
+ var rule = this,
1503
+ count = 0;
1504
+
1505
+ //warn that important is used and increment the declaration counter
1506
+ parser.addListener("property", function(event){
1507
+ if (event.important === true){
1508
+ count++;
1509
+ reporter.report("Use of !important", event.line, event.col, rule);
1510
+ }
1511
+ });
1512
+
1513
+ //if there are more than 10, show an error
1514
+ parser.addListener("endstylesheet", function(){
1515
+ reporter.stat("important", count);
1516
+ if (count >= 10){
1517
+ reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule);
1518
+ }
1519
+ });
1520
+ }
1521
+
1522
+ });
1523
+ /*
1524
+ * Rule: Properties should be known (listed in CSS3 specification) or
1525
+ * be a vendor-prefixed property.
1526
+ */
1527
+ /*global CSSLint*/
1528
+ CSSLint.addRule({
1529
+
1530
+ //rule information
1531
+ id: "known-properties",
1532
+ name: "Require use of known properties",
1533
+ desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
1534
+ browsers: "All",
1535
+
1536
+ //initialization
1537
+ init: function(parser, reporter){
1538
+ var rule = this;
1539
+
1540
+ parser.addListener("property", function(event){
1541
+ var name = event.property.text.toLowerCase();
1542
+
1543
+ // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib)
1544
+ if (event.invalid) {
1545
+ reporter.report(event.invalid.message, event.line, event.col, rule);
1546
+ }
1547
+
1548
+ });
1549
+ }
1550
+
1551
+ });
1552
+ /*
1553
+ * Rule: outline: none or outline: 0 should only be used in a :focus rule
1554
+ * and only if there are other properties in the same rule.
1555
+ */
1556
+ /*global CSSLint*/
1557
+ CSSLint.addRule({
1558
+
1559
+ //rule information
1560
+ id: "outline-none",
1561
+ name: "Disallow outline: none",
1562
+ desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
1563
+ browsers: "All",
1564
+ tags: ["Accessibility"],
1565
+
1566
+ //initialization
1567
+ init: function(parser, reporter){
1568
+ var rule = this,
1569
+ lastRule;
1570
+
1571
+ function startRule(event){
1572
+ if (event.selectors){
1573
+ lastRule = {
1574
+ line: event.line,
1575
+ col: event.col,
1576
+ selectors: event.selectors,
1577
+ propCount: 0,
1578
+ outline: false
1579
+ };
1580
+ } else {
1581
+ lastRule = null;
1582
+ }
1583
+ }
1584
+
1585
+ function endRule(event){
1586
+ if (lastRule){
1587
+ if (lastRule.outline){
1588
+ if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") == -1){
1589
+ reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule);
1590
+ } else if (lastRule.propCount == 1) {
1591
+ reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
1592
+ }
1593
+ }
1594
+ }
1595
+ }
1596
+
1597
+ parser.addListener("startrule", startRule);
1598
+ parser.addListener("startfontface", startRule);
1599
+ parser.addListener("startpage", startRule);
1600
+ parser.addListener("startpagemargin", startRule);
1601
+ parser.addListener("startkeyframerule", startRule);
1602
+
1603
+ parser.addListener("property", function(event){
1604
+ var name = event.property.text.toLowerCase(),
1605
+ value = event.value;
1606
+
1607
+ if (lastRule){
1608
+ lastRule.propCount++;
1609
+ if (name == "outline" && (value == "none" || value == "0")){
1610
+ lastRule.outline = true;
1611
+ }
1612
+ }
1613
+
1614
+ });
1615
+
1616
+ parser.addListener("endrule", endRule);
1617
+ parser.addListener("endfontface", endRule);
1618
+ parser.addListener("endpage", endRule);
1619
+ parser.addListener("endpagemargin", endRule);
1620
+ parser.addListener("endkeyframerule", endRule);
1621
+
1622
+ }
1623
+
1624
+ });
1625
+ /*
1626
+ * Rule: Don't use classes or IDs with elements (a.foo or a#foo).
1627
+ */
1628
+ /*global CSSLint*/
1629
+ CSSLint.addRule({
1630
+
1631
+ //rule information
1632
+ id: "overqualified-elements",
1633
+ name: "Disallow overqualified elements",
1634
+ desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
1635
+ browsers: "All",
1636
+
1637
+ //initialization
1638
+ init: function(parser, reporter){
1639
+ var rule = this,
1640
+ classes = {};
1641
+
1642
+ parser.addListener("startrule", function(event){
1643
+ var selectors = event.selectors,
1644
+ selector,
1645
+ part,
1646
+ modifier,
1647
+ i, j, k;
1648
+
1649
+ for (i=0; i < selectors.length; i++){
1650
+ selector = selectors[i];
1651
+
1652
+ for (j=0; j < selector.parts.length; j++){
1653
+ part = selector.parts[j];
1654
+ if (part.type == parser.SELECTOR_PART_TYPE){
1655
+ for (k=0; k < part.modifiers.length; k++){
1656
+ modifier = part.modifiers[k];
1657
+ if (part.elementName && modifier.type == "id"){
1658
+ reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
1659
+ } else if (modifier.type == "class"){
1660
+
1661
+ if (!classes[modifier]){
1662
+ classes[modifier] = [];
1663
+ }
1664
+ classes[modifier].push({ modifier: modifier, part: part });
1665
+ }
1666
+ }
1667
+ }
1668
+ }
1669
+ }
1670
+ });
1671
+
1672
+ parser.addListener("endstylesheet", function(){
1673
+
1674
+ var prop;
1675
+ for (prop in classes){
1676
+ if (classes.hasOwnProperty(prop)){
1677
+
1678
+ //one use means that this is overqualified
1679
+ if (classes[prop].length == 1 && classes[prop][0].part.elementName){
1680
+ 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);
1681
+ }
1682
+ }
1683
+ }
1684
+ });
1685
+ }
1686
+
1687
+ });
1688
+ /*
1689
+ * Rule: Headings (h1-h6) should not be qualified (namespaced).
1690
+ */
1691
+ /*global CSSLint*/
1692
+ CSSLint.addRule({
1693
+
1694
+ //rule information
1695
+ id: "qualified-headings",
1696
+ name: "Disallow qualified headings",
1697
+ desc: "Headings should not be qualified (namespaced).",
1698
+ browsers: "All",
1699
+
1700
+ //initialization
1701
+ init: function(parser, reporter){
1702
+ var rule = this;
1703
+
1704
+ parser.addListener("startrule", function(event){
1705
+ var selectors = event.selectors,
1706
+ selector,
1707
+ part,
1708
+ i, j;
1709
+
1710
+ for (i=0; i < selectors.length; i++){
1711
+ selector = selectors[i];
1712
+
1713
+ for (j=0; j < selector.parts.length; j++){
1714
+ part = selector.parts[j];
1715
+ if (part.type == parser.SELECTOR_PART_TYPE){
1716
+ if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){
1717
+ reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
1718
+ }
1719
+ }
1720
+ }
1721
+ }
1722
+ });
1723
+ }
1724
+
1725
+ });
1726
+ /*
1727
+ * Rule: Selectors that look like regular expressions are slow and should be avoided.
1728
+ */
1729
+ /*global CSSLint*/
1730
+ CSSLint.addRule({
1731
+
1732
+ //rule information
1733
+ id: "regex-selectors",
1734
+ name: "Disallow selectors that look like regexs",
1735
+ desc: "Selectors that look like regular expressions are slow and should be avoided.",
1736
+ browsers: "All",
1737
+
1738
+ //initialization
1739
+ init: function(parser, reporter){
1740
+ var rule = this;
1741
+
1742
+ parser.addListener("startrule", function(event){
1743
+ var selectors = event.selectors,
1744
+ selector,
1745
+ part,
1746
+ modifier,
1747
+ i, j, k;
1748
+
1749
+ for (i=0; i < selectors.length; i++){
1750
+ selector = selectors[i];
1751
+ for (j=0; j < selector.parts.length; j++){
1752
+ part = selector.parts[j];
1753
+ if (part.type == parser.SELECTOR_PART_TYPE){
1754
+ for (k=0; k < part.modifiers.length; k++){
1755
+ modifier = part.modifiers[k];
1756
+ if (modifier.type == "attribute"){
1757
+ if (/([\~\|\^\$\*]=)/.test(modifier)){
1758
+ reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
1759
+ }
1760
+ }
1761
+
1762
+ }
1763
+ }
1764
+ }
1765
+ }
1766
+ });
1767
+ }
1768
+
1769
+ });
1770
+ /*
1771
+ * Rule: Total number of rules should not exceed x.
1772
+ */
1773
+ /*global CSSLint*/
1774
+ CSSLint.addRule({
1775
+
1776
+ //rule information
1777
+ id: "rules-count",
1778
+ name: "Rules Count",
1779
+ desc: "Track how many rules there are.",
1780
+ browsers: "All",
1781
+
1782
+ //initialization
1783
+ init: function(parser, reporter){
1784
+ var rule = this,
1785
+ count = 0;
1786
+
1787
+ //count each rule
1788
+ parser.addListener("startrule", function(){
1789
+ count++;
1790
+ });
1791
+
1792
+ parser.addListener("endstylesheet", function(){
1793
+ reporter.stat("rule-count", count);
1794
+ });
1795
+ }
1796
+
1797
+ });
1798
+ /*
1799
+ * Rule: Warn people with approaching the IE 4095 limit
1800
+ */
1801
+ /*global CSSLint*/
1802
+ CSSLint.addRule({
1803
+
1804
+ //rule information
1805
+ id: "selector-max-approaching",
1806
+ name: "Warn when approaching the 4095 selector limit for IE",
1807
+ desc: "Will warn when selector count is >= 3800 selectors.",
1808
+ browsers: "IE",
1809
+
1810
+ //initialization
1811
+ init: function(parser, reporter) {
1812
+ var rule = this, count = 0;
1813
+
1814
+ parser.addListener('startrule', function(event) {
1815
+ count += event.selectors.length;
1816
+ });
1817
+
1818
+ parser.addListener("endstylesheet", function() {
1819
+ if (count >= 3800) {
1820
+ reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
1821
+ }
1822
+ });
1823
+ }
1824
+
1825
+ });
1826
+
1827
+ /*
1828
+ * Rule: Warn people past the IE 4095 limit
1829
+ */
1830
+ /*global CSSLint*/
1831
+ CSSLint.addRule({
1832
+
1833
+ //rule information
1834
+ id: "selector-max",
1835
+ name: "Error when past the 4095 selector limit for IE",
1836
+ desc: "Will error when selector count is > 4095.",
1837
+ browsers: "IE",
1838
+
1839
+ //initialization
1840
+ init: function(parser, reporter){
1841
+ var rule = this, count = 0;
1842
+
1843
+ parser.addListener('startrule',function(event) {
1844
+ count += event.selectors.length;
1845
+ });
1846
+
1847
+ parser.addListener("endstylesheet", function() {
1848
+ if (count > 4095) {
1849
+ reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
1850
+ }
1851
+ });
1852
+ }
1853
+
1854
+ });
1855
+ /*
1856
+ * Rule: Use shorthand properties where possible.
1857
+ *
1858
+ */
1859
+ /*global CSSLint*/
1860
+ CSSLint.addRule({
1861
+
1862
+ //rule information
1863
+ id: "shorthand",
1864
+ name: "Require shorthand properties",
1865
+ desc: "Use shorthand properties where possible.",
1866
+ browsers: "All",
1867
+
1868
+ //initialization
1869
+ init: function(parser, reporter){
1870
+ var rule = this,
1871
+ prop, i, len,
1872
+ propertiesToCheck = {},
1873
+ properties,
1874
+ mapping = {
1875
+ "margin": [
1876
+ "margin-top",
1877
+ "margin-bottom",
1878
+ "margin-left",
1879
+ "margin-right"
1880
+ ],
1881
+ "padding": [
1882
+ "padding-top",
1883
+ "padding-bottom",
1884
+ "padding-left",
1885
+ "padding-right"
1886
+ ]
1887
+ };
1888
+
1889
+ //initialize propertiesToCheck
1890
+ for (prop in mapping){
1891
+ if (mapping.hasOwnProperty(prop)){
1892
+ for (i=0, len=mapping[prop].length; i < len; i++){
1893
+ propertiesToCheck[mapping[prop][i]] = prop;
1894
+ }
1895
+ }
1896
+ }
1897
+
1898
+ function startRule(event){
1899
+ properties = {};
1900
+ }
1901
+
1902
+ //event handler for end of rules
1903
+ function endRule(event){
1904
+
1905
+ var prop, i, len, total;
1906
+
1907
+ //check which properties this rule has
1908
+ for (prop in mapping){
1909
+ if (mapping.hasOwnProperty(prop)){
1910
+ total=0;
1911
+
1912
+ for (i=0, len=mapping[prop].length; i < len; i++){
1913
+ total += properties[mapping[prop][i]] ? 1 : 0;
1914
+ }
1915
+
1916
+ if (total == mapping[prop].length){
1917
+ reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
1918
+ }
1919
+ }
1920
+ }
1921
+ }
1922
+
1923
+ parser.addListener("startrule", startRule);
1924
+ parser.addListener("startfontface", startRule);
1925
+
1926
+ //check for use of "font-size"
1927
+ parser.addListener("property", function(event){
1928
+ var name = event.property.toString().toLowerCase(),
1929
+ value = event.value.parts[0].value;
1930
+
1931
+ if (propertiesToCheck[name]){
1932
+ properties[name] = 1;
1933
+ }
1934
+ });
1935
+
1936
+ parser.addListener("endrule", endRule);
1937
+ parser.addListener("endfontface", endRule);
1938
+
1939
+ }
1940
+
1941
+ });
1942
+ /*
1943
+ * Rule: Don't use properties with a star prefix.
1944
+ *
1945
+ */
1946
+ /*global CSSLint*/
1947
+ CSSLint.addRule({
1948
+
1949
+ //rule information
1950
+ id: "star-property-hack",
1951
+ name: "Disallow properties with a star prefix",
1952
+ desc: "Checks for the star property hack (targets IE6/7)",
1953
+ browsers: "All",
1954
+
1955
+ //initialization
1956
+ init: function(parser, reporter){
1957
+ var rule = this;
1958
+
1959
+ //check if property name starts with "*"
1960
+ parser.addListener("property", function(event){
1961
+ var property = event.property;
1962
+
1963
+ if (property.hack == "*") {
1964
+ reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
1965
+ }
1966
+ });
1967
+ }
1968
+ });
1969
+ /*
1970
+ * Rule: Don't use text-indent for image replacement if you need to support rtl.
1971
+ *
1972
+ */
1973
+ /*global CSSLint*/
1974
+ CSSLint.addRule({
1975
+
1976
+ //rule information
1977
+ id: "text-indent",
1978
+ name: "Disallow negative text-indent",
1979
+ desc: "Checks for text indent less than -99px",
1980
+ browsers: "All",
1981
+
1982
+ //initialization
1983
+ init: function(parser, reporter){
1984
+ var rule = this,
1985
+ textIndent,
1986
+ direction;
1987
+
1988
+
1989
+ function startRule(event){
1990
+ textIndent = false;
1991
+ direction = "inherit";
1992
+ }
1993
+
1994
+ //event handler for end of rules
1995
+ function endRule(event){
1996
+ if (textIndent && direction != "ltr"){
1997
+ 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);
1998
+ }
1999
+ }
2000
+
2001
+ parser.addListener("startrule", startRule);
2002
+ parser.addListener("startfontface", startRule);
2003
+
2004
+ //check for use of "font-size"
2005
+ parser.addListener("property", function(event){
2006
+ var name = event.property.toString().toLowerCase(),
2007
+ value = event.value;
2008
+
2009
+ if (name == "text-indent" && value.parts[0].value < -99){
2010
+ textIndent = event.property;
2011
+ } else if (name == "direction" && value == "ltr"){
2012
+ direction = "ltr";
2013
+ }
2014
+ });
2015
+
2016
+ parser.addListener("endrule", endRule);
2017
+ parser.addListener("endfontface", endRule);
2018
+
2019
+ }
2020
+
2021
+ });
2022
+ /*
2023
+ * Rule: Don't use properties with a underscore prefix.
2024
+ *
2025
+ */
2026
+ /*global CSSLint*/
2027
+ CSSLint.addRule({
2028
+
2029
+ //rule information
2030
+ id: "underscore-property-hack",
2031
+ name: "Disallow properties with an underscore prefix",
2032
+ desc: "Checks for the underscore property hack (targets IE6)",
2033
+ browsers: "All",
2034
+
2035
+ //initialization
2036
+ init: function(parser, reporter){
2037
+ var rule = this;
2038
+
2039
+ //check if property name starts with "_"
2040
+ parser.addListener("property", function(event){
2041
+ var property = event.property;
2042
+
2043
+ if (property.hack == "_") {
2044
+ reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
2045
+ }
2046
+ });
2047
+ }
2048
+ });
2049
+ /*
2050
+ * Rule: Headings (h1-h6) should be defined only once.
2051
+ */
2052
+ /*global CSSLint*/
2053
+ CSSLint.addRule({
2054
+
2055
+ //rule information
2056
+ id: "unique-headings",
2057
+ name: "Headings should only be defined once",
2058
+ desc: "Headings should be defined only once.",
2059
+ browsers: "All",
2060
+
2061
+ //initialization
2062
+ init: function(parser, reporter){
2063
+ var rule = this;
2064
+
2065
+ var headings = {
2066
+ h1: 0,
2067
+ h2: 0,
2068
+ h3: 0,
2069
+ h4: 0,
2070
+ h5: 0,
2071
+ h6: 0
2072
+ };
2073
+
2074
+ parser.addListener("startrule", function(event){
2075
+ var selectors = event.selectors,
2076
+ selector,
2077
+ part,
2078
+ pseudo,
2079
+ i, j;
2080
+
2081
+ for (i=0; i < selectors.length; i++){
2082
+ selector = selectors[i];
2083
+ part = selector.parts[selector.parts.length-1];
2084
+
2085
+ if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){
2086
+
2087
+ for (j=0; j < part.modifiers.length; j++){
2088
+ if (part.modifiers[j].type == "pseudo"){
2089
+ pseudo = true;
2090
+ break;
2091
+ }
2092
+ }
2093
+
2094
+ if (!pseudo){
2095
+ headings[RegExp.$1]++;
2096
+ if (headings[RegExp.$1] > 1) {
2097
+ reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
2098
+ }
2099
+ }
2100
+ }
2101
+ }
2102
+ });
2103
+
2104
+ parser.addListener("endstylesheet", function(event){
2105
+ var prop,
2106
+ messages = [];
2107
+
2108
+ for (prop in headings){
2109
+ if (headings.hasOwnProperty(prop)){
2110
+ if (headings[prop] > 1){
2111
+ messages.push(headings[prop] + " " + prop + "s");
2112
+ }
2113
+ }
2114
+ }
2115
+
2116
+ if (messages.length){
2117
+ reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
2118
+ }
2119
+ });
2120
+ }
2121
+
2122
+ });
2123
+ /*
2124
+ * Rule: Don't use universal selector because it's slow.
2125
+ */
2126
+ /*global CSSLint*/
2127
+ CSSLint.addRule({
2128
+
2129
+ //rule information
2130
+ id: "universal-selector",
2131
+ name: "Disallow universal selector",
2132
+ desc: "The universal selector (*) is known to be slow.",
2133
+ browsers: "All",
2134
+
2135
+ //initialization
2136
+ init: function(parser, reporter){
2137
+ var rule = this;
2138
+
2139
+ parser.addListener("startrule", function(event){
2140
+ var selectors = event.selectors,
2141
+ selector,
2142
+ part,
2143
+ modifier,
2144
+ i, j, k;
2145
+
2146
+ for (i=0; i < selectors.length; i++){
2147
+ selector = selectors[i];
2148
+
2149
+ part = selector.parts[selector.parts.length-1];
2150
+ if (part.elementName == "*"){
2151
+ reporter.report(rule.desc, part.line, part.col, rule);
2152
+ }
2153
+ }
2154
+ });
2155
+ }
2156
+
2157
+ });
2158
+ /*
2159
+ * Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
2160
+ */
2161
+ /*global CSSLint*/
2162
+ CSSLint.addRule({
2163
+
2164
+ //rule information
2165
+ id: "unqualified-attributes",
2166
+ name: "Disallow unqualified attribute selectors",
2167
+ desc: "Unqualified attribute selectors are known to be slow.",
2168
+ browsers: "All",
2169
+
2170
+ //initialization
2171
+ init: function(parser, reporter){
2172
+ var rule = this;
2173
+
2174
+ parser.addListener("startrule", function(event){
2175
+
2176
+ var selectors = event.selectors,
2177
+ selector,
2178
+ part,
2179
+ modifier,
2180
+ i, j, k;
2181
+
2182
+ for (i=0; i < selectors.length; i++){
2183
+ selector = selectors[i];
2184
+
2185
+ part = selector.parts[selector.parts.length-1];
2186
+ if (part.type == parser.SELECTOR_PART_TYPE){
2187
+ for (k=0; k < part.modifiers.length; k++){
2188
+ modifier = part.modifiers[k];
2189
+ if (modifier.type == "attribute" && (!part.elementName || part.elementName == "*")){
2190
+ reporter.report(rule.desc, part.line, part.col, rule);
2191
+ }
2192
+ }
2193
+ }
2194
+
2195
+ }
2196
+ });
2197
+ }
2198
+
2199
+ });
2200
+ /*
2201
+ * Rule: When using a vendor-prefixed property, make sure to
2202
+ * include the standard one.
2203
+ */
2204
+ /*global CSSLint*/
2205
+ CSSLint.addRule({
2206
+
2207
+ //rule information
2208
+ id: "vendor-prefix",
2209
+ name: "Require standard property with vendor prefix",
2210
+ desc: "When using a vendor-prefixed property, make sure to include the standard one.",
2211
+ browsers: "All",
2212
+
2213
+ //initialization
2214
+ init: function(parser, reporter){
2215
+ var rule = this,
2216
+ properties,
2217
+ num,
2218
+ propertiesToCheck = {
2219
+ "-webkit-border-radius": "border-radius",
2220
+ "-webkit-border-top-left-radius": "border-top-left-radius",
2221
+ "-webkit-border-top-right-radius": "border-top-right-radius",
2222
+ "-webkit-border-bottom-left-radius": "border-bottom-left-radius",
2223
+ "-webkit-border-bottom-right-radius": "border-bottom-right-radius",
2224
+
2225
+ "-o-border-radius": "border-radius",
2226
+ "-o-border-top-left-radius": "border-top-left-radius",
2227
+ "-o-border-top-right-radius": "border-top-right-radius",
2228
+ "-o-border-bottom-left-radius": "border-bottom-left-radius",
2229
+ "-o-border-bottom-right-radius": "border-bottom-right-radius",
2230
+
2231
+ "-moz-border-radius": "border-radius",
2232
+ "-moz-border-radius-topleft": "border-top-left-radius",
2233
+ "-moz-border-radius-topright": "border-top-right-radius",
2234
+ "-moz-border-radius-bottomleft": "border-bottom-left-radius",
2235
+ "-moz-border-radius-bottomright": "border-bottom-right-radius",
2236
+
2237
+ "-moz-column-count": "column-count",
2238
+ "-webkit-column-count": "column-count",
2239
+
2240
+ "-moz-column-gap": "column-gap",
2241
+ "-webkit-column-gap": "column-gap",
2242
+
2243
+ "-moz-column-rule": "column-rule",
2244
+ "-webkit-column-rule": "column-rule",
2245
+
2246
+ "-moz-column-rule-style": "column-rule-style",
2247
+ "-webkit-column-rule-style": "column-rule-style",
2248
+
2249
+ "-moz-column-rule-color": "column-rule-color",
2250
+ "-webkit-column-rule-color": "column-rule-color",
2251
+
2252
+ "-moz-column-rule-width": "column-rule-width",
2253
+ "-webkit-column-rule-width": "column-rule-width",
2254
+
2255
+ "-moz-column-width": "column-width",
2256
+ "-webkit-column-width": "column-width",
2257
+
2258
+ "-webkit-column-span": "column-span",
2259
+ "-webkit-columns": "columns",
2260
+
2261
+ "-moz-box-shadow": "box-shadow",
2262
+ "-webkit-box-shadow": "box-shadow",
2263
+
2264
+ "-moz-transform" : "transform",
2265
+ "-webkit-transform" : "transform",
2266
+ "-o-transform" : "transform",
2267
+ "-ms-transform" : "transform",
2268
+
2269
+ "-moz-transform-origin" : "transform-origin",
2270
+ "-webkit-transform-origin" : "transform-origin",
2271
+ "-o-transform-origin" : "transform-origin",
2272
+ "-ms-transform-origin" : "transform-origin",
2273
+
2274
+ "-moz-box-sizing" : "box-sizing",
2275
+ "-webkit-box-sizing" : "box-sizing",
2276
+
2277
+ "-moz-user-select" : "user-select",
2278
+ "-khtml-user-select" : "user-select",
2279
+ "-webkit-user-select" : "user-select"
2280
+ };
2281
+
2282
+ //event handler for beginning of rules
2283
+ function startRule(){
2284
+ properties = {};
2285
+ num=1;
2286
+ }
2287
+
2288
+ //event handler for end of rules
2289
+ function endRule(event){
2290
+ var prop,
2291
+ i, len,
2292
+ standard,
2293
+ needed,
2294
+ actual,
2295
+ needsStandard = [];
2296
+
2297
+ for (prop in properties){
2298
+ if (propertiesToCheck[prop]){
2299
+ needsStandard.push({ actual: prop, needed: propertiesToCheck[prop]});
2300
+ }
2301
+ }
2302
+
2303
+ for (i=0, len=needsStandard.length; i < len; i++){
2304
+ needed = needsStandard[i].needed;
2305
+ actual = needsStandard[i].actual;
2306
+
2307
+ if (!properties[needed]){
2308
+ reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
2309
+ } else {
2310
+ //make sure standard property is last
2311
+ if (properties[needed][0].pos < properties[actual][0].pos){
2312
+ reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
2313
+ }
2314
+ }
2315
+ }
2316
+
2317
+ }
2318
+
2319
+ parser.addListener("startrule", startRule);
2320
+ parser.addListener("startfontface", startRule);
2321
+ parser.addListener("startpage", startRule);
2322
+ parser.addListener("startpagemargin", startRule);
2323
+ parser.addListener("startkeyframerule", startRule);
2324
+
2325
+ parser.addListener("property", function(event){
2326
+ var name = event.property.text.toLowerCase();
2327
+
2328
+ if (!properties[name]){
2329
+ properties[name] = [];
2330
+ }
2331
+
2332
+ properties[name].push({ name: event.property, value : event.value, pos:num++ });
2333
+ });
2334
+
2335
+ parser.addListener("endrule", endRule);
2336
+ parser.addListener("endfontface", endRule);
2337
+ parser.addListener("endpage", endRule);
2338
+ parser.addListener("endpagemargin", endRule);
2339
+ parser.addListener("endkeyframerule", endRule);
2340
+ }
2341
+
2342
+ });
2343
+ /*
2344
+ * Rule: You don't need to specify units when a value is 0.
2345
+ */
2346
+ /*global CSSLint*/
2347
+ CSSLint.addRule({
2348
+
2349
+ //rule information
2350
+ id: "zero-units",
2351
+ name: "Disallow units for 0 values",
2352
+ desc: "You don't need to specify units when a value is 0.",
2353
+ browsers: "All",
2354
+
2355
+ //initialization
2356
+ init: function(parser, reporter){
2357
+ var rule = this;
2358
+
2359
+ //count how many times "float" is used
2360
+ parser.addListener("property", function(event){
2361
+ var parts = event.value.parts,
2362
+ i = 0,
2363
+ len = parts.length;
2364
+
2365
+ while(i < len){
2366
+ if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0 && parts[i].type != "time"){
2367
+ reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule);
2368
+ }
2369
+ i++;
2370
+ }
2371
+
2372
+ });
2373
+
2374
+ }
2375
+
2376
+ });
2377
+ /*global CSSLint*/
2378
+ (function() {
2379
+
2380
+ /**
2381
+ * Replace special characters before write to output.
2382
+ *
2383
+ * Rules:
2384
+ * - single quotes is the escape sequence for double-quotes
2385
+ * - &amp; is the escape sequence for &
2386
+ * - &lt; is the escape sequence for <
2387
+ * - &gt; is the escape sequence for >
2388
+ *
2389
+ * @param {String} message to escape
2390
+ * @return escaped message as {String}
2391
+ */
2392
+ var xmlEscape = function(str) {
2393
+ if (!str || str.constructor !== String) {
2394
+ return "";
2395
+ }
2396
+
2397
+ return str.replace(/[\"&><]/g, function(match) {
2398
+ switch (match) {
2399
+ case "\"":
2400
+ return "&quot;";
2401
+ case "&":
2402
+ return "&amp;";
2403
+ case "<":
2404
+ return "&lt;";
2405
+ case ">":
2406
+ return "&gt;";
2407
+ }
2408
+ });
2409
+ };
2410
+
2411
+ CSSLint.addFormatter({
2412
+ //format information
2413
+ id: "checkstyle-xml",
2414
+ name: "Checkstyle XML format",
2415
+
2416
+ /**
2417
+ * Return opening root XML tag.
2418
+ * @return {String} to prepend before all results
2419
+ */
2420
+ startFormat: function(){
2421
+ return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
2422
+ },
2423
+
2424
+ /**
2425
+ * Return closing root XML tag.
2426
+ * @return {String} to append after all results
2427
+ */
2428
+ endFormat: function(){
2429
+ return "</checkstyle>";
2430
+ },
2431
+
2432
+ /**
2433
+ * Returns message when there is a file read error.
2434
+ * @param {String} filename The name of the file that caused the error.
2435
+ * @param {String} message The error message
2436
+ * @return {String} The error message.
2437
+ */
2438
+ readError: function(filename, message) {
2439
+ return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
2440
+ },
2441
+
2442
+ /**
2443
+ * Given CSS Lint results for a file, return output for this format.
2444
+ * @param results {Object} with error and warning messages
2445
+ * @param filename {String} relative file path
2446
+ * @param options {Object} (UNUSED for now) specifies special handling of output
2447
+ * @return {String} output for results
2448
+ */
2449
+ formatResults: function(results, filename, options) {
2450
+ var messages = results.messages,
2451
+ output = [];
2452
+
2453
+ /**
2454
+ * Generate a source string for a rule.
2455
+ * Checkstyle source strings usually resemble Java class names e.g
2456
+ * net.csslint.SomeRuleName
2457
+ * @param {Object} rule
2458
+ * @return rule source as {String}
2459
+ */
2460
+ var generateSource = function(rule) {
2461
+ if (!rule || !('name' in rule)) {
2462
+ return "";
2463
+ }
2464
+ return 'net.csslint.' + rule.name.replace(/\s/g,'');
2465
+ };
2466
+
2467
+
2468
+
2469
+ if (messages.length > 0) {
2470
+ output.push("<file name=\""+filename+"\">");
2471
+ CSSLint.Util.forEach(messages, function (message, i) {
2472
+ //ignore rollups for now
2473
+ if (!message.rollup) {
2474
+ output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
2475
+ " message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
2476
+ }
2477
+ });
2478
+ output.push("</file>");
2479
+ }
2480
+
2481
+ return output.join("");
2482
+ }
2483
+ });
2484
+
2485
+ }());
2486
+ /*global CSSLint*/
2487
+ CSSLint.addFormatter({
2488
+ //format information
2489
+ id: "compact",
2490
+ name: "Compact, 'porcelain' format",
2491
+
2492
+ /**
2493
+ * Return content to be printed before all file results.
2494
+ * @return {String} to prepend before all results
2495
+ */
2496
+ startFormat: function() {
2497
+ return "";
2498
+ },
2499
+
2500
+ /**
2501
+ * Return content to be printed after all file results.
2502
+ * @return {String} to append after all results
2503
+ */
2504
+ endFormat: function() {
2505
+ return "";
2506
+ },
2507
+
2508
+ /**
2509
+ * Given CSS Lint results for a file, return output for this format.
2510
+ * @param results {Object} with error and warning messages
2511
+ * @param filename {String} relative file path
2512
+ * @param options {Object} (Optional) specifies special handling of output
2513
+ * @return {String} output for results
2514
+ */
2515
+ formatResults: function(results, filename, options) {
2516
+ var messages = results.messages,
2517
+ output = "";
2518
+ options = options || {};
2519
+
2520
+ /**
2521
+ * Capitalize and return given string.
2522
+ * @param str {String} to capitalize
2523
+ * @return {String} capitalized
2524
+ */
2525
+ var capitalize = function(str) {
2526
+ return str.charAt(0).toUpperCase() + str.slice(1);
2527
+ };
2528
+
2529
+ if (messages.length === 0) {
2530
+ return options.quiet ? "" : filename + ": Lint Free!";
2531
+ }
2532
+
2533
+ CSSLint.Util.forEach(messages, function(message, i) {
2534
+ if (message.rollup) {
2535
+ output += filename + ": " + capitalize(message.type) + " - " + message.message + "\n";
2536
+ } else {
2537
+ output += filename + ": " + "line " + message.line +
2538
+ ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + "\n";
2539
+ }
2540
+ });
2541
+
2542
+ return output;
2543
+ }
2544
+ });
2545
+ /*global CSSLint*/
2546
+ CSSLint.addFormatter({
2547
+ //format information
2548
+ id: "csslint-xml",
2549
+ name: "CSSLint XML format",
2550
+
2551
+ /**
2552
+ * Return opening root XML tag.
2553
+ * @return {String} to prepend before all results
2554
+ */
2555
+ startFormat: function(){
2556
+ return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
2557
+ },
2558
+
2559
+ /**
2560
+ * Return closing root XML tag.
2561
+ * @return {String} to append after all results
2562
+ */
2563
+ endFormat: function(){
2564
+ return "</csslint>";
2565
+ },
2566
+
2567
+ /**
2568
+ * Given CSS Lint results for a file, return output for this format.
2569
+ * @param results {Object} with error and warning messages
2570
+ * @param filename {String} relative file path
2571
+ * @param options {Object} (UNUSED for now) specifies special handling of output
2572
+ * @return {String} output for results
2573
+ */
2574
+ formatResults: function(results, filename, options) {
2575
+ var messages = results.messages,
2576
+ output = [];
2577
+
2578
+ /**
2579
+ * Replace special characters before write to output.
2580
+ *
2581
+ * Rules:
2582
+ * - single quotes is the escape sequence for double-quotes
2583
+ * - &amp; is the escape sequence for &
2584
+ * - &lt; is the escape sequence for <
2585
+ * - &gt; is the escape sequence for >
2586
+ *
2587
+ * @param {String} message to escape
2588
+ * @return escaped message as {String}
2589
+ */
2590
+ var escapeSpecialCharacters = function(str) {
2591
+ if (!str || str.constructor !== String) {
2592
+ return "";
2593
+ }
2594
+ return str.replace(/\"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2595
+ };
2596
+
2597
+ if (messages.length > 0) {
2598
+ output.push("<file name=\""+filename+"\">");
2599
+ CSSLint.Util.forEach(messages, function (message, i) {
2600
+ if (message.rollup) {
2601
+ output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
2602
+ } else {
2603
+ output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
2604
+ " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
2605
+ }
2606
+ });
2607
+ output.push("</file>");
2608
+ }
2609
+
2610
+ return output.join("");
2611
+ }
2612
+ });
2613
+ /*global CSSLint*/
2614
+ CSSLint.addFormatter({
2615
+ //format information
2616
+ id: "junit-xml",
2617
+ name: "JUNIT XML format",
2618
+
2619
+ /**
2620
+ * Return opening root XML tag.
2621
+ * @return {String} to prepend before all results
2622
+ */
2623
+ startFormat: function(){
2624
+ return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>";
2625
+ },
2626
+
2627
+ /**
2628
+ * Return closing root XML tag.
2629
+ * @return {String} to append after all results
2630
+ */
2631
+ endFormat: function() {
2632
+ return "</testsuites>";
2633
+ },
2634
+
2635
+ /**
2636
+ * Given CSS Lint results for a file, return output for this format.
2637
+ * @param results {Object} with error and warning messages
2638
+ * @param filename {String} relative file path
2639
+ * @param options {Object} (UNUSED for now) specifies special handling of output
2640
+ * @return {String} output for results
2641
+ */
2642
+ formatResults: function(results, filename, options) {
2643
+
2644
+ var messages = results.messages,
2645
+ output = [],
2646
+ tests = {
2647
+ 'error': 0,
2648
+ 'failure': 0
2649
+ };
2650
+
2651
+ /**
2652
+ * Generate a source string for a rule.
2653
+ * JUNIT source strings usually resemble Java class names e.g
2654
+ * net.csslint.SomeRuleName
2655
+ * @param {Object} rule
2656
+ * @return rule source as {String}
2657
+ */
2658
+ var generateSource = function(rule) {
2659
+ if (!rule || !('name' in rule)) {
2660
+ return "";
2661
+ }
2662
+ return 'net.csslint.' + rule.name.replace(/\s/g,'');
2663
+ };
2664
+
2665
+ /**
2666
+ * Replace special characters before write to output.
2667
+ *
2668
+ * Rules:
2669
+ * - single quotes is the escape sequence for double-quotes
2670
+ * - &lt; is the escape sequence for <
2671
+ * - &gt; is the escape sequence for >
2672
+ *
2673
+ * @param {String} message to escape
2674
+ * @return escaped message as {String}
2675
+ */
2676
+ var escapeSpecialCharacters = function(str) {
2677
+
2678
+ if (!str || str.constructor !== String) {
2679
+ return "";
2680
+ }
2681
+
2682
+ return str.replace(/\"/g, "'").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2683
+
2684
+ };
2685
+
2686
+ if (messages.length > 0) {
2687
+
2688
+ messages.forEach(function (message, i) {
2689
+
2690
+ // since junit has no warning class
2691
+ // all issues as errors
2692
+ var type = message.type === 'warning' ? 'error' : message.type;
2693
+
2694
+ //ignore rollups for now
2695
+ if (!message.rollup) {
2696
+
2697
+ // build the test case seperately, once joined
2698
+ // we'll add it to a custom array filtered by type
2699
+ output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">");
2700
+ output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ':' + message.col + ':' + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">");
2701
+ output.push("</testcase>");
2702
+
2703
+ tests[type] += 1;
2704
+
2705
+ }
2706
+
2707
+ });
2708
+
2709
+ output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">");
2710
+ output.push("</testsuite>");
2711
+
2712
+ }
2713
+
2714
+ return output.join("");
2715
+
2716
+ }
2717
+ });
2718
+ /*global CSSLint*/
2719
+ CSSLint.addFormatter({
2720
+ //format information
2721
+ id: "lint-xml",
2722
+ name: "Lint XML format",
2723
+
2724
+ /**
2725
+ * Return opening root XML tag.
2726
+ * @return {String} to prepend before all results
2727
+ */
2728
+ startFormat: function(){
2729
+ return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
2730
+ },
2731
+
2732
+ /**
2733
+ * Return closing root XML tag.
2734
+ * @return {String} to append after all results
2735
+ */
2736
+ endFormat: function(){
2737
+ return "</lint>";
2738
+ },
2739
+
2740
+ /**
2741
+ * Given CSS Lint results for a file, return output for this format.
2742
+ * @param results {Object} with error and warning messages
2743
+ * @param filename {String} relative file path
2744
+ * @param options {Object} (UNUSED for now) specifies special handling of output
2745
+ * @return {String} output for results
2746
+ */
2747
+ formatResults: function(results, filename, options) {
2748
+ var messages = results.messages,
2749
+ output = [];
2750
+
2751
+ /**
2752
+ * Replace special characters before write to output.
2753
+ *
2754
+ * Rules:
2755
+ * - single quotes is the escape sequence for double-quotes
2756
+ * - &amp; is the escape sequence for &
2757
+ * - &lt; is the escape sequence for <
2758
+ * - &gt; is the escape sequence for >
2759
+ *
2760
+ * @param {String} message to escape
2761
+ * @return escaped message as {String}
2762
+ */
2763
+ var escapeSpecialCharacters = function(str) {
2764
+ if (!str || str.constructor !== String) {
2765
+ return "";
2766
+ }
2767
+ return str.replace(/\"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2768
+ };
2769
+
2770
+ if (messages.length > 0) {
2771
+
2772
+ output.push("<file name=\""+filename+"\">");
2773
+ CSSLint.Util.forEach(messages, function (message, i) {
2774
+ if (message.rollup) {
2775
+ output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
2776
+ } else {
2777
+ output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
2778
+ " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
2779
+ }
2780
+ });
2781
+ output.push("</file>");
2782
+ }
2783
+
2784
+ return output.join("");
2785
+ }
2786
+ });
2787
+ /*global CSSLint*/
2788
+ CSSLint.addFormatter({
2789
+ //format information
2790
+ id: "text",
2791
+ name: "Plain Text",
2792
+
2793
+ /**
2794
+ * Return content to be printed before all file results.
2795
+ * @return {String} to prepend before all results
2796
+ */
2797
+ startFormat: function() {
2798
+ return "";
2799
+ },
2800
+
2801
+ /**
2802
+ * Return content to be printed after all file results.
2803
+ * @return {String} to append after all results
2804
+ */
2805
+ endFormat: function() {
2806
+ return "";
2807
+ },
2808
+
2809
+ /**
2810
+ * Given CSS Lint results for a file, return output for this format.
2811
+ * @param results {Object} with error and warning messages
2812
+ * @param filename {String} relative file path
2813
+ * @param options {Object} (Optional) specifies special handling of output
2814
+ * @return {String} output for results
2815
+ */
2816
+ formatResults: function(results, filename, options) {
2817
+ var messages = results.messages,
2818
+ output = "";
2819
+ options = options || {};
2820
+
2821
+ if (messages.length === 0) {
2822
+ return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + ".";
2823
+ }
2824
+
2825
+ output = "\n\ncsslint: There are " + messages.length + " problems in " + filename + ".";
2826
+ var pos = filename.lastIndexOf("/"),
2827
+ shortFilename = filename;
2828
+
2829
+ if (pos === -1){
2830
+ pos = filename.lastIndexOf("\\");
2831
+ }
2832
+ if (pos > -1){
2833
+ shortFilename = filename.substring(pos+1);
2834
+ }
2835
+
2836
+ CSSLint.Util.forEach(messages, function (message, i) {
2837
+ output = output + "\n\n" + shortFilename;
2838
+ if (message.rollup) {
2839
+ output += "\n" + (i+1) + ": " + message.type;
2840
+ output += "\n" + message.message;
2841
+ } else {
2842
+ output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col;
2843
+ output += "\n" + message.message;
2844
+ output += "\n" + message.evidence;
2845
+ }
2846
+ });
2847
+
2848
+ return output;
2849
+ }
2850
+ });
2851
+ exports.CSSLint = CSSLint;