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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/README.md +2 -2
- data/benchmark.rb +27 -0
- data/css_validator.gemspec +1 -1
- data/lib/css_validator.rb +3 -3
- data/vendor/cli.js +441 -0
- data/vendor/lib/csslint-node.js +2851 -0
- data/vendor/{csslint-rhino.js → lib/node-parserlib.js} +81 -3276
- metadata +7 -5
- data/vendor/js.jar +0 -0
@@ -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
|
+
* - & is the escape sequence for &
|
2386
|
+
* - < is the escape sequence for <
|
2387
|
+
* - > 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 """;
|
2401
|
+
case "&":
|
2402
|
+
return "&";
|
2403
|
+
case "<":
|
2404
|
+
return "<";
|
2405
|
+
case ">":
|
2406
|
+
return ">";
|
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
|
+
* - & is the escape sequence for &
|
2584
|
+
* - < is the escape sequence for <
|
2585
|
+
* - > 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, "&").replace(/</g, "<").replace(/>/g, ">");
|
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
|
+
* - < is the escape sequence for <
|
2671
|
+
* - > 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, "<").replace(/>/g, ">");
|
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
|
+
* - & is the escape sequence for &
|
2757
|
+
* - < is the escape sequence for <
|
2758
|
+
* - > 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, "&").replace(/</g, "<").replace(/>/g, ">");
|
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;
|