css_validator 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;