diff --git a/CHANGELOG b/CHANGELOG index c0d961db..e130466d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,29 @@ +September 12, 2012 - v0.9.9 + +* Fix Travis-CI file (Nicholas C. Zakas) +* Merge pull request #296 from travis4all/clean (Nicholas C. Zakas) +* Fix box model rule so there's not a warning when box-sizing is used (fixes #298) (Nicholas C. Zakas) +* Added config file capability to CLI (fixes #297) (Nicholas C. Zakas) +* :gem: Travis CI image/link in readme :gem: (travis4all) +* :gem: Added travis.yml file :gem: (travis4all) +* Fixed behavior of ignores/warnings/errors to preserve old behavior (Nicholas C. Zakas) +* Added CLI option for ignoring rules (fixes #231) (Nicholas C. Zakas) +* Make sure box model rule doesn't warn when width or height are set to auto, etc. (fixes #287) (Nicholas C. Zakas) +* Merge branch 'master' of github.com:stubbornella/csslint (Nicholas C. Zakas) +* Fixes to compatible vendor prefixes rule (fixes #285 and fixes #286) (Nicholas C. Zakas) +* Merge pull request #235 from indieisaconcept/junit-formatter (Nicholas C. Zakas) +* Updated parser (fixes #276) (Nicholas C. Zakas) +* Merge pull request #292 from zachleat/patch-1 (Nicholas C. Zakas) +* Microsoft uses unprefixed CSS3 animations, so this should just rely on the "Require standard property with vendor prefix" rule instead. First noticed on css3please.com, also reported on caniuse.com, and http://msdn.microsoft.com/en-us/library/ie/hh673530(v=vs.85).aspx (Zach Leatherman) +* Merge pull request #284 from mahonnaise/known-properties (Nicholas C. Zakas) +* dead code removal, point at parser-lib as authority for known properties, addressed concern from #283 (Jos Hirth) +* Merge pull request #278 from mahonnaise/all-rules (Nicholas C. Zakas) +* normalize/reset flavored rule (i.e. the blandest thing I could think of) (Jos Hirth) +* Use an element rather than a class for the 'regular rule' test. This avoids conflicts with rules about naming conventions. (Jos Hirth) +* Added CDATA wrapper to evidence output for JUNIT formatter (indieisaconcept) +* Added junit to the available formatters (indieisaconcept) + + May 14, 2012 - v0.9.8 * Merge pull request #272 from mahonnaise/text-indent (Nicholas C. Zakas) @@ -281,6 +307,8 @@ June 15, 2011 - v0.1.0 + + diff --git a/build.xml b/build.xml index 0fcc0e33..5fbcb562 100644 --- a/build.xml +++ b/build.xml @@ -1,7 +1,7 @@ - + diff --git a/release/csslint-node.js b/release/csslint-node.js index 5d4ae2bc..50e499cd 100644 --- a/release/csslint-node.js +++ b/release/csslint-node.js @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 14-May-2012 10:24:48 */ +/* Build time: 12-September-2012 01:46:26 */ /*! Parser-Lib @@ -46,7 +46,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ var parserlib = {}; (function(){ @@ -956,7 +956,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ (function(){ var EventTarget = parserlib.util.EventTarget, TokenStreamBase = parserlib.util.TokenStreamBase, @@ -3024,9 +3024,15 @@ Parser.prototype = function(){ var tokenStream = this._tokenStream, token, tt, - name; + name, + prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + this._readWhitespace(); name = this._keyframe_name(); @@ -3036,8 +3042,9 @@ Parser.prototype = function(){ this.fire({ type: "startkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3053,8 +3060,9 @@ Parser.prototype = function(){ this.fire({ type: "endkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3836,6 +3844,7 @@ var Properties = { "pitch" : 1, "pitch-range" : 1, "play-during" : 1, + "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit", "position" : "static | relative | absolute | fixed | inherit", "presentation-level" : 1, "punctuation-trim" : 1, @@ -5580,7 +5589,7 @@ var Tokens = [ //{ name: "ATKEYWORD"}, //CSS3 animations - { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] }, + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, //important symbol { name: "IMPORTANT_SYM"}, @@ -6332,7 +6341,7 @@ var CSSLint = (function(){ formatters = [], api = new parserlib.util.EventTarget(); - api.version = "0.9.8"; + api.version = "0.9.9"; //------------------------------------------------------------------------- // Rule Management @@ -6366,6 +6375,23 @@ var CSSLint = (function(){ return a.id > b.id ? 1 : 0; }); }; + + /** + * Returns a ruleset configuration object with all current rules. + * @return A ruleset object. + * @method getRuleset + */ + api.getRuleset = function() { + var ruleset = {}, + i = 0, + len = rules.length; + + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + + return ruleset; + }; //------------------------------------------------------------------------- // Formatters @@ -6446,13 +6472,11 @@ var CSSLint = (function(){ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); + // normalize line endings lines = text.replace(/\n\r?/g, "$split$").split('$split$'); if (!ruleset){ - ruleset = {}; - while (i < len){ - ruleset[rules[i++].id] = 1; //by default, everything is a warning - } + ruleset = this.getRuleset(); } reporter = new Reporter(lines, ruleset); @@ -6801,36 +6825,42 @@ CSSLint.addRule({ "padding-bottom": 1, "padding-top": 1 }, - properties; + properties, + boxSizing = false; function startRule(){ properties = {}; + boxSizing = false; } function endRule(){ - var prop; - if (properties.height){ - for (prop in heightProperties){ - if (heightProperties.hasOwnProperty(prop) && properties[prop]){ - - //special case for padding - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){ - reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + var prop, value; + + if (!boxSizing) { + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + //special case for padding + if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } } - } - if (properties.width){ - for (prop in widthProperties){ - if (widthProperties.hasOwnProperty(prop) && properties[prop]){ - - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){ - reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + + if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } - } - } + } + } } parser.addListener("startrule", startRule); @@ -6847,8 +6877,10 @@ CSSLint.addRule({ properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; } } else { - if (name == "width" || name == "height"){ + if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){ properties[name] = 1; + } else if (name == "box-sizing") { + boxSizing = true; } } @@ -6913,20 +6945,21 @@ CSSLint.addRule({ prefixed, i, len, + inKeyFrame = false, arrayPush = Array.prototype.push, applyTo = []; // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details compatiblePrefixes = { - "animation" : "webkit moz ms", - "animation-delay" : "webkit moz ms", - "animation-direction" : "webkit moz ms", - "animation-duration" : "webkit moz ms", - "animation-fill-mode" : "webkit moz ms", - "animation-iteration-count" : "webkit moz ms", - "animation-name" : "webkit moz ms", - "animation-play-state" : "webkit moz ms", - "animation-timing-function" : "webkit moz ms", + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", "appearance" : "webkit moz", "border-end" : "webkit moz", "border-end-color" : "webkit moz", @@ -6966,11 +6999,11 @@ CSSLint.addRule({ "text-size-adjust" : "webkit ms", "transform" : "webkit moz ms o", "transform-origin" : "webkit moz ms o", - "transition" : "webkit moz o ms", - "transition-delay" : "webkit moz o ms", - "transition-duration" : "webkit moz o ms", - "transition-property" : "webkit moz o ms", - "transition-timing-function" : "webkit moz o ms", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", "user-select" : "webkit moz ms", "word-break" : "epub ms", @@ -6989,14 +7022,28 @@ CSSLint.addRule({ arrayPush.apply(applyTo, variations); } } + parser.addListener("startrule", function () { properties = []; }); + parser.addListener("startkeyframes", function (event) { + inKeyFrame = event.prefix || true; + }); + + parser.addListener("endkeyframes", function (event) { + inKeyFrame = false; + }); + parser.addListener("property", function (event) { var name = event.property; if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { - properties.push(name); + + // e.g., -moz-transform is okay to be alone in @-moz-keyframes + if (!inKeyFrame || typeof inKeyFrame != "string" || + name.text.indexOf("-" + inKeyFrame + "-") !== 0) { + properties.push(name); + } } }); @@ -7675,308 +7722,20 @@ CSSLint.addRule({ //rule information id: "known-properties", name: "Require use of known properties", - desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", browsers: "All", //initialization init: function(parser, reporter){ - var rule = this, - properties = { - - "alignment-adjust": 1, - "alignment-baseline": 1, - "animation": 1, - "animation-delay": 1, - "animation-direction": 1, - "animation-duration": 1, - "animation-fill-mode": 1, - "animation-iteration-count": 1, - "animation-name": 1, - "animation-play-state": 1, - "animation-timing-function": 1, - "appearance": 1, - "azimuth": 1, - "backface-visibility": 1, - "background": 1, - "background-attachment": 1, - "background-break": 1, - "background-clip": 1, - "background-color": 1, - "background-image": 1, - "background-origin": 1, - "background-position": 1, - "background-repeat": 1, - "background-size": 1, - "baseline-shift": 1, - "binding": 1, - "bleed": 1, - "bookmark-label": 1, - "bookmark-level": 1, - "bookmark-state": 1, - "bookmark-target": 1, - "border": 1, - "border-bottom": 1, - "border-bottom-color": 1, - "border-bottom-left-radius": 1, - "border-bottom-right-radius": 1, - "border-bottom-style": 1, - "border-bottom-width": 1, - "border-collapse": 1, - "border-color": 1, - "border-image": 1, - "border-image-outset": 1, - "border-image-repeat": 1, - "border-image-slice": 1, - "border-image-source": 1, - "border-image-width": 1, - "border-left": 1, - "border-left-color": 1, - "border-left-style": 1, - "border-left-width": 1, - "border-radius": 1, - "border-right": 1, - "border-right-color": 1, - "border-right-style": 1, - "border-right-width": 1, - "border-spacing": 1, - "border-style": 1, - "border-top": 1, - "border-top-color": 1, - "border-top-left-radius": 1, - "border-top-right-radius": 1, - "border-top-style": 1, - "border-top-width": 1, - "border-width": 1, - "bottom": 1, - "box-align": 1, - "box-decoration-break": 1, - "box-direction": 1, - "box-flex": 1, - "box-flex-group": 1, - "box-lines": 1, - "box-ordinal-group": 1, - "box-orient": 1, - "box-pack": 1, - "box-shadow": 1, - "box-sizing": 1, - "break-after": 1, - "break-before": 1, - "break-inside": 1, - "caption-side": 1, - "clear": 1, - "clip": 1, - "color": 1, - "color-profile": 1, - "column-count": 1, - "column-fill": 1, - "column-gap": 1, - "column-rule": 1, - "column-rule-color": 1, - "column-rule-style": 1, - "column-rule-width": 1, - "column-span": 1, - "column-width": 1, - "columns": 1, - "content": 1, - "counter-increment": 1, - "counter-reset": 1, - "crop": 1, - "cue": 1, - "cue-after": 1, - "cue-before": 1, - "cursor": 1, - "direction": 1, - "display": 1, - "dominant-baseline": 1, - "drop-initial-after-adjust": 1, - "drop-initial-after-align": 1, - "drop-initial-before-adjust": 1, - "drop-initial-before-align": 1, - "drop-initial-size": 1, - "drop-initial-value": 1, - "elevation": 1, - "empty-cells": 1, - "fit": 1, - "fit-position": 1, - "float": 1, - "float-offset": 1, - "font": 1, - "font-family": 1, - "font-size": 1, - "font-size-adjust": 1, - "font-stretch": 1, - "font-style": 1, - "font-variant": 1, - "font-weight": 1, - "grid-columns": 1, - "grid-rows": 1, - "hanging-punctuation": 1, - "height": 1, - "hyphenate-after": 1, - "hyphenate-before": 1, - "hyphenate-character": 1, - "hyphenate-lines": 1, - "hyphenate-resource": 1, - "hyphens": 1, - "icon": 1, - "image-orientation": 1, - "image-rendering": 1, - "image-resolution": 1, - "inline-box-align": 1, - "left": 1, - "letter-spacing": 1, - "line-height": 1, - "line-stacking": 1, - "line-stacking-ruby": 1, - "line-stacking-shift": 1, - "line-stacking-strategy": 1, - "list-style": 1, - "list-style-image": 1, - "list-style-position": 1, - "list-style-type": 1, - "margin": 1, - "margin-bottom": 1, - "margin-left": 1, - "margin-right": 1, - "margin-top": 1, - "mark": 1, - "mark-after": 1, - "mark-before": 1, - "marks": 1, - "marquee-direction": 1, - "marquee-play-count": 1, - "marquee-speed": 1, - "marquee-style": 1, - "max-height": 1, - "max-width": 1, - "min-height": 1, - "min-width": 1, - "move-to": 1, - "nav-down": 1, - "nav-index": 1, - "nav-left": 1, - "nav-right": 1, - "nav-up": 1, - "opacity": 1, - "orphans": 1, - "outline": 1, - "outline-color": 1, - "outline-offset": 1, - "outline-style": 1, - "outline-width": 1, - "overflow": 1, - "overflow-style": 1, - "overflow-x": 1, - "overflow-y": 1, - "padding": 1, - "padding-bottom": 1, - "padding-left": 1, - "padding-right": 1, - "padding-top": 1, - "page": 1, - "page-break-after": 1, - "page-break-before": 1, - "page-break-inside": 1, - "page-policy": 1, - "pause": 1, - "pause-after": 1, - "pause-before": 1, - "perspective": 1, - "perspective-origin": 1, - "phonemes": 1, - "pitch": 1, - "pitch-range": 1, - "play-during": 1, - "position": 1, - "presentation-level": 1, - "punctuation-trim": 1, - "quotes": 1, - "rendering-intent": 1, - "resize": 1, - "rest": 1, - "rest-after": 1, - "rest-before": 1, - "richness": 1, - "right": 1, - "rotation": 1, - "rotation-point": 1, - "ruby-align": 1, - "ruby-overhang": 1, - "ruby-position": 1, - "ruby-span": 1, - "size": 1, - "speak": 1, - "speak-header": 1, - "speak-numeral": 1, - "speak-punctuation": 1, - "speech-rate": 1, - "stress": 1, - "string-set": 1, - "table-layout": 1, - "target": 1, - "target-name": 1, - "target-new": 1, - "target-position": 1, - "text-align": 1, - "text-align-last": 1, - "text-decoration": 1, - "text-emphasis": 1, - "text-height": 1, - "text-indent": 1, - "text-justify": 1, - "text-outline": 1, - "text-shadow": 1, - "text-transform": 1, - "text-wrap": 1, - "top": 1, - "transform": 1, - "transform-origin": 1, - "transform-style": 1, - "transition": 1, - "transition-delay": 1, - "transition-duration": 1, - "transition-property": 1, - "transition-timing-function": 1, - "unicode-bidi": 1, - "user-modify": 1, - "user-select": 1, - "vertical-align": 1, - "visibility": 1, - "voice-balance": 1, - "voice-duration": 1, - "voice-family": 1, - "voice-pitch": 1, - "voice-pitch-range": 1, - "voice-rate": 1, - "voice-stress": 1, - "voice-volume": 1, - "volume": 1, - "white-space": 1, - "white-space-collapse": 1, - "widows": 1, - "width": 1, - "word-break": 1, - "word-spacing": 1, - "word-wrap": 1, - "z-index": 1, - - //IE - "filter": 1, - "zoom": 1, - - //@font-face - "src": 1 - }; + var rule = this; parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); + // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) if (event.invalid) { reporter.report(event.invalid.message, event.line, event.col, rule); } - //if (!properties[name] && name.charAt(0) != "-"){ - // reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); - //} }); } @@ -8987,6 +8746,111 @@ CSSLint.addFormatter({ } }); /*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "junit-xml", + name: "JUNIT XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + + var messages = results.messages, + output = [], + tests = { + 'error': 0, + 'failure': 0 + }; + + /** + * Generate a source string for a rule. + * JUNIT source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/\"/g, "'").replace(//g, ">"); + + }; + + if (messages.length > 0) { + + messages.forEach(function (message, i) { + + // since junit has no warning class + // all issues as errors + var type = message.type === 'warning' ? 'error' : message.type; + + //ignore rollups for now + if (!message.rollup) { + + // build the test case seperately, once joined + // we'll add it to a custom array filtered by type + output.push(""); + output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); + output.push(""); + + tests[type] += 1; + + } + + }); + + output.unshift(""); + output.push(""); + + } + + return output.join(""); + + } +}); +/*global CSSLint*/ CSSLint.addFormatter({ //format information id: "lint-xml", diff --git a/release/csslint-rhino.js b/release/csslint-rhino.js index b51e2724..078fa78b 100644 --- a/release/csslint-rhino.js +++ b/release/csslint-rhino.js @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 14-May-2012 10:24:48 */ +/* Build time: 12-September-2012 01:46:26 */ var CSSLint = (function(){ /*! @@ -47,7 +47,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ var parserlib = {}; (function(){ @@ -957,7 +957,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ (function(){ var EventTarget = parserlib.util.EventTarget, TokenStreamBase = parserlib.util.TokenStreamBase, @@ -3025,9 +3025,15 @@ Parser.prototype = function(){ var tokenStream = this._tokenStream, token, tt, - name; + name, + prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + this._readWhitespace(); name = this._keyframe_name(); @@ -3037,8 +3043,9 @@ Parser.prototype = function(){ this.fire({ type: "startkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3054,8 +3061,9 @@ Parser.prototype = function(){ this.fire({ type: "endkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3837,6 +3845,7 @@ var Properties = { "pitch" : 1, "pitch-range" : 1, "play-during" : 1, + "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit", "position" : "static | relative | absolute | fixed | inherit", "presentation-level" : 1, "punctuation-trim" : 1, @@ -5581,7 +5590,7 @@ var Tokens = [ //{ name: "ATKEYWORD"}, //CSS3 animations - { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] }, + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, //important symbol { name: "IMPORTANT_SYM"}, @@ -6333,7 +6342,7 @@ var CSSLint = (function(){ formatters = [], api = new parserlib.util.EventTarget(); - api.version = "0.9.8"; + api.version = "0.9.9"; //------------------------------------------------------------------------- // Rule Management @@ -6367,6 +6376,23 @@ var CSSLint = (function(){ return a.id > b.id ? 1 : 0; }); }; + + /** + * Returns a ruleset configuration object with all current rules. + * @return A ruleset object. + * @method getRuleset + */ + api.getRuleset = function() { + var ruleset = {}, + i = 0, + len = rules.length; + + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + + return ruleset; + }; //------------------------------------------------------------------------- // Formatters @@ -6447,13 +6473,11 @@ var CSSLint = (function(){ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); + // normalize line endings lines = text.replace(/\n\r?/g, "$split$").split('$split$'); if (!ruleset){ - ruleset = {}; - while (i < len){ - ruleset[rules[i++].id] = 1; //by default, everything is a warning - } + ruleset = this.getRuleset(); } reporter = new Reporter(lines, ruleset); @@ -6802,36 +6826,42 @@ CSSLint.addRule({ "padding-bottom": 1, "padding-top": 1 }, - properties; + properties, + boxSizing = false; function startRule(){ properties = {}; + boxSizing = false; } function endRule(){ - var prop; - if (properties.height){ - for (prop in heightProperties){ - if (heightProperties.hasOwnProperty(prop) && properties[prop]){ - - //special case for padding - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){ - reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + var prop, value; + + if (!boxSizing) { + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + //special case for padding + if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } } - } - - if (properties.width){ - for (prop in widthProperties){ - if (widthProperties.hasOwnProperty(prop) && properties[prop]){ - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){ - reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + + if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } - } - } + } + } } parser.addListener("startrule", startRule); @@ -6848,8 +6878,10 @@ CSSLint.addRule({ properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; } } else { - if (name == "width" || name == "height"){ + if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){ properties[name] = 1; + } else if (name == "box-sizing") { + boxSizing = true; } } @@ -6914,20 +6946,21 @@ CSSLint.addRule({ prefixed, i, len, + inKeyFrame = false, arrayPush = Array.prototype.push, applyTo = []; // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details compatiblePrefixes = { - "animation" : "webkit moz ms", - "animation-delay" : "webkit moz ms", - "animation-direction" : "webkit moz ms", - "animation-duration" : "webkit moz ms", - "animation-fill-mode" : "webkit moz ms", - "animation-iteration-count" : "webkit moz ms", - "animation-name" : "webkit moz ms", - "animation-play-state" : "webkit moz ms", - "animation-timing-function" : "webkit moz ms", + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", "appearance" : "webkit moz", "border-end" : "webkit moz", "border-end-color" : "webkit moz", @@ -6967,11 +7000,11 @@ CSSLint.addRule({ "text-size-adjust" : "webkit ms", "transform" : "webkit moz ms o", "transform-origin" : "webkit moz ms o", - "transition" : "webkit moz o ms", - "transition-delay" : "webkit moz o ms", - "transition-duration" : "webkit moz o ms", - "transition-property" : "webkit moz o ms", - "transition-timing-function" : "webkit moz o ms", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", "user-select" : "webkit moz ms", "word-break" : "epub ms", @@ -6990,14 +7023,28 @@ CSSLint.addRule({ arrayPush.apply(applyTo, variations); } } + parser.addListener("startrule", function () { properties = []; }); + parser.addListener("startkeyframes", function (event) { + inKeyFrame = event.prefix || true; + }); + + parser.addListener("endkeyframes", function (event) { + inKeyFrame = false; + }); + parser.addListener("property", function (event) { var name = event.property; if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { - properties.push(name); + + // e.g., -moz-transform is okay to be alone in @-moz-keyframes + if (!inKeyFrame || typeof inKeyFrame != "string" || + name.text.indexOf("-" + inKeyFrame + "-") !== 0) { + properties.push(name); + } } }); @@ -7676,308 +7723,20 @@ CSSLint.addRule({ //rule information id: "known-properties", name: "Require use of known properties", - desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", browsers: "All", //initialization init: function(parser, reporter){ - var rule = this, - properties = { - - "alignment-adjust": 1, - "alignment-baseline": 1, - "animation": 1, - "animation-delay": 1, - "animation-direction": 1, - "animation-duration": 1, - "animation-fill-mode": 1, - "animation-iteration-count": 1, - "animation-name": 1, - "animation-play-state": 1, - "animation-timing-function": 1, - "appearance": 1, - "azimuth": 1, - "backface-visibility": 1, - "background": 1, - "background-attachment": 1, - "background-break": 1, - "background-clip": 1, - "background-color": 1, - "background-image": 1, - "background-origin": 1, - "background-position": 1, - "background-repeat": 1, - "background-size": 1, - "baseline-shift": 1, - "binding": 1, - "bleed": 1, - "bookmark-label": 1, - "bookmark-level": 1, - "bookmark-state": 1, - "bookmark-target": 1, - "border": 1, - "border-bottom": 1, - "border-bottom-color": 1, - "border-bottom-left-radius": 1, - "border-bottom-right-radius": 1, - "border-bottom-style": 1, - "border-bottom-width": 1, - "border-collapse": 1, - "border-color": 1, - "border-image": 1, - "border-image-outset": 1, - "border-image-repeat": 1, - "border-image-slice": 1, - "border-image-source": 1, - "border-image-width": 1, - "border-left": 1, - "border-left-color": 1, - "border-left-style": 1, - "border-left-width": 1, - "border-radius": 1, - "border-right": 1, - "border-right-color": 1, - "border-right-style": 1, - "border-right-width": 1, - "border-spacing": 1, - "border-style": 1, - "border-top": 1, - "border-top-color": 1, - "border-top-left-radius": 1, - "border-top-right-radius": 1, - "border-top-style": 1, - "border-top-width": 1, - "border-width": 1, - "bottom": 1, - "box-align": 1, - "box-decoration-break": 1, - "box-direction": 1, - "box-flex": 1, - "box-flex-group": 1, - "box-lines": 1, - "box-ordinal-group": 1, - "box-orient": 1, - "box-pack": 1, - "box-shadow": 1, - "box-sizing": 1, - "break-after": 1, - "break-before": 1, - "break-inside": 1, - "caption-side": 1, - "clear": 1, - "clip": 1, - "color": 1, - "color-profile": 1, - "column-count": 1, - "column-fill": 1, - "column-gap": 1, - "column-rule": 1, - "column-rule-color": 1, - "column-rule-style": 1, - "column-rule-width": 1, - "column-span": 1, - "column-width": 1, - "columns": 1, - "content": 1, - "counter-increment": 1, - "counter-reset": 1, - "crop": 1, - "cue": 1, - "cue-after": 1, - "cue-before": 1, - "cursor": 1, - "direction": 1, - "display": 1, - "dominant-baseline": 1, - "drop-initial-after-adjust": 1, - "drop-initial-after-align": 1, - "drop-initial-before-adjust": 1, - "drop-initial-before-align": 1, - "drop-initial-size": 1, - "drop-initial-value": 1, - "elevation": 1, - "empty-cells": 1, - "fit": 1, - "fit-position": 1, - "float": 1, - "float-offset": 1, - "font": 1, - "font-family": 1, - "font-size": 1, - "font-size-adjust": 1, - "font-stretch": 1, - "font-style": 1, - "font-variant": 1, - "font-weight": 1, - "grid-columns": 1, - "grid-rows": 1, - "hanging-punctuation": 1, - "height": 1, - "hyphenate-after": 1, - "hyphenate-before": 1, - "hyphenate-character": 1, - "hyphenate-lines": 1, - "hyphenate-resource": 1, - "hyphens": 1, - "icon": 1, - "image-orientation": 1, - "image-rendering": 1, - "image-resolution": 1, - "inline-box-align": 1, - "left": 1, - "letter-spacing": 1, - "line-height": 1, - "line-stacking": 1, - "line-stacking-ruby": 1, - "line-stacking-shift": 1, - "line-stacking-strategy": 1, - "list-style": 1, - "list-style-image": 1, - "list-style-position": 1, - "list-style-type": 1, - "margin": 1, - "margin-bottom": 1, - "margin-left": 1, - "margin-right": 1, - "margin-top": 1, - "mark": 1, - "mark-after": 1, - "mark-before": 1, - "marks": 1, - "marquee-direction": 1, - "marquee-play-count": 1, - "marquee-speed": 1, - "marquee-style": 1, - "max-height": 1, - "max-width": 1, - "min-height": 1, - "min-width": 1, - "move-to": 1, - "nav-down": 1, - "nav-index": 1, - "nav-left": 1, - "nav-right": 1, - "nav-up": 1, - "opacity": 1, - "orphans": 1, - "outline": 1, - "outline-color": 1, - "outline-offset": 1, - "outline-style": 1, - "outline-width": 1, - "overflow": 1, - "overflow-style": 1, - "overflow-x": 1, - "overflow-y": 1, - "padding": 1, - "padding-bottom": 1, - "padding-left": 1, - "padding-right": 1, - "padding-top": 1, - "page": 1, - "page-break-after": 1, - "page-break-before": 1, - "page-break-inside": 1, - "page-policy": 1, - "pause": 1, - "pause-after": 1, - "pause-before": 1, - "perspective": 1, - "perspective-origin": 1, - "phonemes": 1, - "pitch": 1, - "pitch-range": 1, - "play-during": 1, - "position": 1, - "presentation-level": 1, - "punctuation-trim": 1, - "quotes": 1, - "rendering-intent": 1, - "resize": 1, - "rest": 1, - "rest-after": 1, - "rest-before": 1, - "richness": 1, - "right": 1, - "rotation": 1, - "rotation-point": 1, - "ruby-align": 1, - "ruby-overhang": 1, - "ruby-position": 1, - "ruby-span": 1, - "size": 1, - "speak": 1, - "speak-header": 1, - "speak-numeral": 1, - "speak-punctuation": 1, - "speech-rate": 1, - "stress": 1, - "string-set": 1, - "table-layout": 1, - "target": 1, - "target-name": 1, - "target-new": 1, - "target-position": 1, - "text-align": 1, - "text-align-last": 1, - "text-decoration": 1, - "text-emphasis": 1, - "text-height": 1, - "text-indent": 1, - "text-justify": 1, - "text-outline": 1, - "text-shadow": 1, - "text-transform": 1, - "text-wrap": 1, - "top": 1, - "transform": 1, - "transform-origin": 1, - "transform-style": 1, - "transition": 1, - "transition-delay": 1, - "transition-duration": 1, - "transition-property": 1, - "transition-timing-function": 1, - "unicode-bidi": 1, - "user-modify": 1, - "user-select": 1, - "vertical-align": 1, - "visibility": 1, - "voice-balance": 1, - "voice-duration": 1, - "voice-family": 1, - "voice-pitch": 1, - "voice-pitch-range": 1, - "voice-rate": 1, - "voice-stress": 1, - "voice-volume": 1, - "volume": 1, - "white-space": 1, - "white-space-collapse": 1, - "widows": 1, - "width": 1, - "word-break": 1, - "word-spacing": 1, - "word-wrap": 1, - "z-index": 1, - - //IE - "filter": 1, - "zoom": 1, - - //@font-face - "src": 1 - }; + var rule = this; parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); + // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) if (event.invalid) { reporter.report(event.invalid.message, event.line, event.col, rule); } - //if (!properties[name] && name.charAt(0) != "-"){ - // reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); - //} }); } @@ -8988,6 +8747,111 @@ CSSLint.addFormatter({ } }); /*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "junit-xml", + name: "JUNIT XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + + var messages = results.messages, + output = [], + tests = { + 'error': 0, + 'failure': 0 + }; + + /** + * Generate a source string for a rule. + * JUNIT source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/\"/g, "'").replace(//g, ">"); + + }; + + if (messages.length > 0) { + + messages.forEach(function (message, i) { + + // since junit has no warning class + // all issues as errors + var type = message.type === 'warning' ? 'error' : message.type; + + //ignore rollups for now + if (!message.rollup) { + + // build the test case seperately, once joined + // we'll add it to a custom array filtered by type + output.push(""); + output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); + output.push(""); + + tests[type] += 1; + + } + + }); + + output.unshift(""); + output.push(""); + + } + + return output.join(""); + + } +}); +/*global CSSLint*/ CSSLint.addFormatter({ //format information id: "lint-xml", @@ -9154,9 +9018,8 @@ function cli(api){ * @param options {Object} The CLI options. * @return {Object} A ruleset object. */ - function gatherRules(options){ - var ruleset, - warnings = options.rules || options.warnings, + function gatherRules(options, ruleset){ + var warnings = options.rules || options.warnings, errors = options.errors; if (warnings){ @@ -9172,6 +9035,25 @@ function cli(api){ ruleset[value] = 2; }); } + + return ruleset; + } + + /** + * Filters out rules using the ignore command line option. + * @param options {Object} the CLI options + * @return {Object} A ruleset object. + */ + function filterRules(options) { + var ignore = options.ignore, + ruleset = null; + + if (ignore) { + ruleset = CSSLint.getRuleset(); + ignore.split(",").forEach(function(value){ + delete ruleset[value]; + }); + } return ruleset; } @@ -9196,7 +9078,8 @@ function cli(api){ */ function processFile(relativeFilePath, options) { var input = api.readFile(relativeFilePath), - result = CSSLint.verify(input, gatherRules(options)), + ruleset = filterRules(options), + result = CSSLint.verify(input, gatherRules(options, ruleset)), formatter = CSSLint.getFormatter(options.format || "text"), messages = result.messages || [], output, @@ -9241,6 +9124,7 @@ function cli(api){ " --quiet Only output when errors are present.", " --errors= Indicate which rules to include as errors.", " --warnings= Indicate which rules to include as warnings.", + " --ignore= Indicate which rules to ignore completely.", " --version Outputs the current version number." ].join("\n") + "\n"); } @@ -9287,44 +9171,72 @@ function cli(api){ } } return exitCode; - } - - //----------------------------------------------------------------------------- - // Process command line - //----------------------------------------------------------------------------- - - var args = api.args, - argName, - parts, - arg = args.shift(), - options = {}, - files = []; - - while(arg){ - if (arg.indexOf("--") === 0){ - argName = arg.substring(2); - options[argName] = true; - - if (argName.indexOf("=") > -1){ - parts = argName.split("="); - options[parts[0]] = parts[1]; - } else { + } + + + function processArguments(args, options) { + var arg = args.shift(), + argName, + parts, + files = []; + + while(arg){ + if (arg.indexOf("--") === 0){ + argName = arg.substring(2); options[argName] = true; - } + + if (argName.indexOf("=") > -1){ + parts = argName.split("="); + options[parts[0]] = parts[1]; + } else { + options[argName] = true; + } - } else { - - //see if it's a directory or a file - if (api.isDirectory(arg)){ - files = files.concat(api.getFiles(arg)); } else { - files.push(arg); + + //see if it's a directory or a file + if (api.isDirectory(arg)){ + files = files.concat(api.getFiles(arg)); + } else { + files.push(arg); + } } + arg = args.shift(); } - arg = args.shift(); + + options.files = files; + return options; + } + + function readConfigFile(options) { + var data = api.readFile(api.getFullPath(".csslintrc")); + if (data) { + options = processArguments(data.split(/[\s\n\r]+/m), options); + api.print("ignore = " + options.ignore); + api.print("errors = " + options.errors); + api.print("warnings = " + options.warnings); + } + + return options; } + + + + //----------------------------------------------------------------------------- + // Process command line + //----------------------------------------------------------------------------- + + var args = api.args, + argCount = args.length, + options = {}; + + // first look for config file .csslintrc + options = readConfigFile(options); + + // Command line arguments override config file + options = processArguments(args, options); - if (options.help || arguments.length === 0){ + if (options.help || argCount === 0){ outputHelp(); api.quit(0); } @@ -9339,7 +9251,7 @@ function cli(api){ api.quit(0); } - api.quit(processFiles(files,options)); + api.quit(processFiles(options.files,options)); } /* * CSSLint Rhino Command Line Interface @@ -9350,7 +9262,7 @@ function cli(api){ importPackage(java.io); cli({ - args: arguments, + args: Array.prototype.concat.call(arguments), print: print, quit: quit, @@ -9386,5 +9298,11 @@ cli({ return (new File(filename)).getCanonicalPath(); }, - readFile: readFile + readFile: function(filename) { + try { + return readFile(filename); + } catch (ex) { + return ""; + } + } }); diff --git a/release/csslint-tests.js b/release/csslint-tests.js index 10ed488d..39c012e3 100644 --- a/release/csslint-tests.js +++ b/release/csslint-tests.js @@ -194,6 +194,61 @@ })); })(); +(function(){ + + /*global YUITest, CSSLint*/ + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "JUNIT XML formatter test", + + "File with no problems should say so": function(){ + + var result = { messages: [], stats: [] }, + expected = ""; + Assert.areEqual(expected, CSSLint.format(result, "FILE", "junit-xml")); + + }, + + "File with problems should list them": function(){ + + var result = { messages: [ + { type: "warning", line: 1, col: 1, message: "BOGUS", evidence: "ALSO BOGUS", rule: { name: "A Rule"} }, + { type: "error", line: 2, col: 1, message: "BOGUS", evidence: "ALSO BOGUS", rule: { name: "Some Other Rule"} } + ], stats: [] }, + + file = "", + error1 = "", + error2 = "", + expected = "" + file + error1 + error2 + "", + actual = CSSLint.format(result, "FILE", "junit-xml"); + + Assert.areEqual(expected, actual); + + }, + + "Formatter should escape special characters": function() { + + var specialCharsSting = 'sneaky, "sneaky", ', + result = { messages: [ + { type: "warning", line: 1, col: 1, message: specialCharsSting, evidence: "ALSO BOGUS", rule: [] }, + { type: "error", line: 2, col: 1, message: specialCharsSting, evidence: "ALSO BOGUS", rule: [] } + ], stats: [] }, + + file = "", + error1 = "", + error2 = "", + expected = "" + file + error1 + error2 + "", + actual = CSSLint.format(result, "FILE", "junit-xml"); + + Assert.areEqual(expected, actual); + + } + + })); +})(); + (function(){ /*global YUITest, CSSLint*/ @@ -327,7 +382,22 @@ var result = CSSLint.verify(".foo { width: 100px; padding: 0; }", { "box-model": 1 }); Assert.areEqual(0, result.messages.length); }, + + "Using width:auto with padding should not result in a warning": function(){ + var result = CSSLint.verify(".foo { width: auto; padding: 10px; }", { "box-model": 1 }); + Assert.areEqual(0, result.messages.length); + }, + + "Using width:available with padding should not result in a warning": function(){ + var result = CSSLint.verify(".foo { width: available; padding: 10px; }", { "box-model": 1 }); + Assert.areEqual(0, result.messages.length); + }, + "Using height:auto with padding should not result in a warning": function(){ + var result = CSSLint.verify(".foo { height: auto; padding: 10px; }", { "box-model": 1 }); + Assert.areEqual(0, result.messages.length); + }, + "Using width and padding-left should result in a warning": function(){ var result = CSSLint.verify(".foo { width: 100px; padding-left: 10px; }", { "box-model": 1 }); Assert.areEqual(1, result.messages.length); @@ -374,6 +444,11 @@ Assert.areEqual("Using width with border can sometimes make elements larger than you expect.", result.messages[0].message); }, + "Using width and border with box-sizing should not result in a warning": function(){ + var result = CSSLint.verify(".foo { box-sizing: border-box; width: 100px; border: 10px; }", { "box-model": 1 }); + Assert.areEqual(0, result.messages.length); + }, + "Using width and border-left should result in a warning": function(){ var result = CSSLint.verify(".foo { width: 100px; border-left: 10px; }", { "box-model": 1 }); Assert.areEqual(1, result.messages.length); @@ -561,21 +636,16 @@ Assert.areEqual(1, result.messages[0].line); }, - "Using -webkit-transition and -moz-transition should warn to also include -o-transition and -ms-transition.": function(){ + "Using -webkit-transition and -moz-transition should warn to also include -o-transition.": function() { var result = CSSLint.verify("h1 { -webkit-transition: height 20px 1s; -moz-transition: height 20px 1s; }", { "compatible-vendor-prefixes": 1 }); - Assert.areEqual(2, result.messages.length); + Assert.areEqual(1, result.messages.length); Assert.areEqual("warning", result.messages[0].type); Assert.areEqual("The property -o-transition is compatible with -webkit-transition and -moz-transition and should be included as well.", result.messages[0].message); Assert.areEqual(6, result.messages[0].col); - Assert.areEqual(1, result.messages[0].line); - Assert.areEqual("warning", result.messages[1].type); - Assert.areEqual("The property -ms-transition is compatible with -webkit-transition and -moz-transition and should be included as well.", result.messages[1].message); - Assert.areEqual(6, result.messages[1].col); - Assert.areEqual(1, result.messages[1].line); - + Assert.areEqual(1, result.messages[0].line); }, - "Using -webkit-transform should warn to also include -moz-transform, -ms-transform, and -o-transform.": function(){ + "Using -webkit-transform should warn to also include -moz-transform, -ms-transform, and -o-transform.": function() { var result = CSSLint.verify("div.box { -webkit-transform: translate(50px, 100px); }", { "compatible-vendor-prefixes": 3 }); Assert.areEqual(3, result.messages.length); Assert.areEqual("warning", result.messages[0].type); @@ -586,13 +656,18 @@ Assert.areEqual("The property -o-transform is compatible with -webkit-transform and should be included as well.", result.messages[2].message); }, + "Using -webkit-transform inside of an @-webkit- block shouldn't cause a warning": function(){ + var result = CSSLint.verify("@-webkit-keyframes spin {0%{ -webkit-transform: rotateX(-10deg) rotateY(0deg); } 100%{ -webkit-transform: rotateX(-10deg) rotateY(-360deg); } }", { "compatible-vendor-prefixes": 1 }); + Assert.areEqual(0, result.messages.length); + }, + "Using all compatible vendor prefixes for animation should be allowed with no warnings.": function(){ - var result = CSSLint.verify(".next:focus { -moz-animation: 'diagonal-slide' 5s 10; -webkit-animation: 'diagonal-slide' 5s 10; -ms-animation: 'diagonal-slide' 5s 10; }", { "compatible-vendor-prefixes": 0 }); + var result = CSSLint.verify(".next:focus { -moz-animation: 'diagonal-slide' 5s 10; -webkit-animation: 'diagonal-slide' 5s 10; -ms-animation: 'diagonal-slide' 5s 10; }", { "compatible-vendor-prefixes": 1 }); Assert.areEqual(0, result.messages.length); }, "Using box-shadow with no vendor prefixes should be allowed with no warnings.": function(){ - var result = CSSLint.verify("h1 { box-shadow: 5px 5px 5px #ccc; }", { "compatible-vendor-prefixes": 0 }); + var result = CSSLint.verify("h1 { box-shadow: 5px 5px 5px #ccc; }", { "compatible-vendor-prefixes": 1 }); Assert.areEqual(0, result.messages.length); } diff --git a/release/csslint-worker.js b/release/csslint-worker.js index 2dfef8df..843b049c 100644 --- a/release/csslint-worker.js +++ b/release/csslint-worker.js @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 14-May-2012 10:24:48 */ +/* Build time: 12-September-2012 01:46:26 */ /*! Parser-Lib @@ -46,7 +46,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ var parserlib = {}; (function(){ @@ -956,7 +956,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ (function(){ var EventTarget = parserlib.util.EventTarget, TokenStreamBase = parserlib.util.TokenStreamBase, @@ -3024,9 +3024,15 @@ Parser.prototype = function(){ var tokenStream = this._tokenStream, token, tt, - name; + name, + prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + this._readWhitespace(); name = this._keyframe_name(); @@ -3036,8 +3042,9 @@ Parser.prototype = function(){ this.fire({ type: "startkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3053,8 +3060,9 @@ Parser.prototype = function(){ this.fire({ type: "endkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3836,6 +3844,7 @@ var Properties = { "pitch" : 1, "pitch-range" : 1, "play-during" : 1, + "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit", "position" : "static | relative | absolute | fixed | inherit", "presentation-level" : 1, "punctuation-trim" : 1, @@ -5580,7 +5589,7 @@ var Tokens = [ //{ name: "ATKEYWORD"}, //CSS3 animations - { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] }, + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, //important symbol { name: "IMPORTANT_SYM"}, @@ -6332,7 +6341,7 @@ var CSSLint = (function(){ formatters = [], api = new parserlib.util.EventTarget(); - api.version = "0.9.8"; + api.version = "0.9.9"; //------------------------------------------------------------------------- // Rule Management @@ -6366,6 +6375,23 @@ var CSSLint = (function(){ return a.id > b.id ? 1 : 0; }); }; + + /** + * Returns a ruleset configuration object with all current rules. + * @return A ruleset object. + * @method getRuleset + */ + api.getRuleset = function() { + var ruleset = {}, + i = 0, + len = rules.length; + + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + + return ruleset; + }; //------------------------------------------------------------------------- // Formatters @@ -6446,13 +6472,11 @@ var CSSLint = (function(){ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); + // normalize line endings lines = text.replace(/\n\r?/g, "$split$").split('$split$'); if (!ruleset){ - ruleset = {}; - while (i < len){ - ruleset[rules[i++].id] = 1; //by default, everything is a warning - } + ruleset = this.getRuleset(); } reporter = new Reporter(lines, ruleset); @@ -6801,36 +6825,42 @@ CSSLint.addRule({ "padding-bottom": 1, "padding-top": 1 }, - properties; + properties, + boxSizing = false; function startRule(){ properties = {}; + boxSizing = false; } function endRule(){ - var prop; - if (properties.height){ - for (prop in heightProperties){ - if (heightProperties.hasOwnProperty(prop) && properties[prop]){ - - //special case for padding - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){ - reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + var prop, value; + + if (!boxSizing) { + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + //special case for padding + if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } } - } - if (properties.width){ - for (prop in widthProperties){ - if (widthProperties.hasOwnProperty(prop) && properties[prop]){ - - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){ - reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + + if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } - } - } + } + } } parser.addListener("startrule", startRule); @@ -6847,8 +6877,10 @@ CSSLint.addRule({ properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; } } else { - if (name == "width" || name == "height"){ + if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){ properties[name] = 1; + } else if (name == "box-sizing") { + boxSizing = true; } } @@ -6913,20 +6945,21 @@ CSSLint.addRule({ prefixed, i, len, + inKeyFrame = false, arrayPush = Array.prototype.push, applyTo = []; // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details compatiblePrefixes = { - "animation" : "webkit moz ms", - "animation-delay" : "webkit moz ms", - "animation-direction" : "webkit moz ms", - "animation-duration" : "webkit moz ms", - "animation-fill-mode" : "webkit moz ms", - "animation-iteration-count" : "webkit moz ms", - "animation-name" : "webkit moz ms", - "animation-play-state" : "webkit moz ms", - "animation-timing-function" : "webkit moz ms", + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", "appearance" : "webkit moz", "border-end" : "webkit moz", "border-end-color" : "webkit moz", @@ -6966,11 +6999,11 @@ CSSLint.addRule({ "text-size-adjust" : "webkit ms", "transform" : "webkit moz ms o", "transform-origin" : "webkit moz ms o", - "transition" : "webkit moz o ms", - "transition-delay" : "webkit moz o ms", - "transition-duration" : "webkit moz o ms", - "transition-property" : "webkit moz o ms", - "transition-timing-function" : "webkit moz o ms", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", "user-select" : "webkit moz ms", "word-break" : "epub ms", @@ -6989,14 +7022,28 @@ CSSLint.addRule({ arrayPush.apply(applyTo, variations); } } + parser.addListener("startrule", function () { properties = []; }); + parser.addListener("startkeyframes", function (event) { + inKeyFrame = event.prefix || true; + }); + + parser.addListener("endkeyframes", function (event) { + inKeyFrame = false; + }); + parser.addListener("property", function (event) { var name = event.property; if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { - properties.push(name); + + // e.g., -moz-transform is okay to be alone in @-moz-keyframes + if (!inKeyFrame || typeof inKeyFrame != "string" || + name.text.indexOf("-" + inKeyFrame + "-") !== 0) { + properties.push(name); + } } }); @@ -7675,308 +7722,20 @@ CSSLint.addRule({ //rule information id: "known-properties", name: "Require use of known properties", - desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", browsers: "All", //initialization init: function(parser, reporter){ - var rule = this, - properties = { - - "alignment-adjust": 1, - "alignment-baseline": 1, - "animation": 1, - "animation-delay": 1, - "animation-direction": 1, - "animation-duration": 1, - "animation-fill-mode": 1, - "animation-iteration-count": 1, - "animation-name": 1, - "animation-play-state": 1, - "animation-timing-function": 1, - "appearance": 1, - "azimuth": 1, - "backface-visibility": 1, - "background": 1, - "background-attachment": 1, - "background-break": 1, - "background-clip": 1, - "background-color": 1, - "background-image": 1, - "background-origin": 1, - "background-position": 1, - "background-repeat": 1, - "background-size": 1, - "baseline-shift": 1, - "binding": 1, - "bleed": 1, - "bookmark-label": 1, - "bookmark-level": 1, - "bookmark-state": 1, - "bookmark-target": 1, - "border": 1, - "border-bottom": 1, - "border-bottom-color": 1, - "border-bottom-left-radius": 1, - "border-bottom-right-radius": 1, - "border-bottom-style": 1, - "border-bottom-width": 1, - "border-collapse": 1, - "border-color": 1, - "border-image": 1, - "border-image-outset": 1, - "border-image-repeat": 1, - "border-image-slice": 1, - "border-image-source": 1, - "border-image-width": 1, - "border-left": 1, - "border-left-color": 1, - "border-left-style": 1, - "border-left-width": 1, - "border-radius": 1, - "border-right": 1, - "border-right-color": 1, - "border-right-style": 1, - "border-right-width": 1, - "border-spacing": 1, - "border-style": 1, - "border-top": 1, - "border-top-color": 1, - "border-top-left-radius": 1, - "border-top-right-radius": 1, - "border-top-style": 1, - "border-top-width": 1, - "border-width": 1, - "bottom": 1, - "box-align": 1, - "box-decoration-break": 1, - "box-direction": 1, - "box-flex": 1, - "box-flex-group": 1, - "box-lines": 1, - "box-ordinal-group": 1, - "box-orient": 1, - "box-pack": 1, - "box-shadow": 1, - "box-sizing": 1, - "break-after": 1, - "break-before": 1, - "break-inside": 1, - "caption-side": 1, - "clear": 1, - "clip": 1, - "color": 1, - "color-profile": 1, - "column-count": 1, - "column-fill": 1, - "column-gap": 1, - "column-rule": 1, - "column-rule-color": 1, - "column-rule-style": 1, - "column-rule-width": 1, - "column-span": 1, - "column-width": 1, - "columns": 1, - "content": 1, - "counter-increment": 1, - "counter-reset": 1, - "crop": 1, - "cue": 1, - "cue-after": 1, - "cue-before": 1, - "cursor": 1, - "direction": 1, - "display": 1, - "dominant-baseline": 1, - "drop-initial-after-adjust": 1, - "drop-initial-after-align": 1, - "drop-initial-before-adjust": 1, - "drop-initial-before-align": 1, - "drop-initial-size": 1, - "drop-initial-value": 1, - "elevation": 1, - "empty-cells": 1, - "fit": 1, - "fit-position": 1, - "float": 1, - "float-offset": 1, - "font": 1, - "font-family": 1, - "font-size": 1, - "font-size-adjust": 1, - "font-stretch": 1, - "font-style": 1, - "font-variant": 1, - "font-weight": 1, - "grid-columns": 1, - "grid-rows": 1, - "hanging-punctuation": 1, - "height": 1, - "hyphenate-after": 1, - "hyphenate-before": 1, - "hyphenate-character": 1, - "hyphenate-lines": 1, - "hyphenate-resource": 1, - "hyphens": 1, - "icon": 1, - "image-orientation": 1, - "image-rendering": 1, - "image-resolution": 1, - "inline-box-align": 1, - "left": 1, - "letter-spacing": 1, - "line-height": 1, - "line-stacking": 1, - "line-stacking-ruby": 1, - "line-stacking-shift": 1, - "line-stacking-strategy": 1, - "list-style": 1, - "list-style-image": 1, - "list-style-position": 1, - "list-style-type": 1, - "margin": 1, - "margin-bottom": 1, - "margin-left": 1, - "margin-right": 1, - "margin-top": 1, - "mark": 1, - "mark-after": 1, - "mark-before": 1, - "marks": 1, - "marquee-direction": 1, - "marquee-play-count": 1, - "marquee-speed": 1, - "marquee-style": 1, - "max-height": 1, - "max-width": 1, - "min-height": 1, - "min-width": 1, - "move-to": 1, - "nav-down": 1, - "nav-index": 1, - "nav-left": 1, - "nav-right": 1, - "nav-up": 1, - "opacity": 1, - "orphans": 1, - "outline": 1, - "outline-color": 1, - "outline-offset": 1, - "outline-style": 1, - "outline-width": 1, - "overflow": 1, - "overflow-style": 1, - "overflow-x": 1, - "overflow-y": 1, - "padding": 1, - "padding-bottom": 1, - "padding-left": 1, - "padding-right": 1, - "padding-top": 1, - "page": 1, - "page-break-after": 1, - "page-break-before": 1, - "page-break-inside": 1, - "page-policy": 1, - "pause": 1, - "pause-after": 1, - "pause-before": 1, - "perspective": 1, - "perspective-origin": 1, - "phonemes": 1, - "pitch": 1, - "pitch-range": 1, - "play-during": 1, - "position": 1, - "presentation-level": 1, - "punctuation-trim": 1, - "quotes": 1, - "rendering-intent": 1, - "resize": 1, - "rest": 1, - "rest-after": 1, - "rest-before": 1, - "richness": 1, - "right": 1, - "rotation": 1, - "rotation-point": 1, - "ruby-align": 1, - "ruby-overhang": 1, - "ruby-position": 1, - "ruby-span": 1, - "size": 1, - "speak": 1, - "speak-header": 1, - "speak-numeral": 1, - "speak-punctuation": 1, - "speech-rate": 1, - "stress": 1, - "string-set": 1, - "table-layout": 1, - "target": 1, - "target-name": 1, - "target-new": 1, - "target-position": 1, - "text-align": 1, - "text-align-last": 1, - "text-decoration": 1, - "text-emphasis": 1, - "text-height": 1, - "text-indent": 1, - "text-justify": 1, - "text-outline": 1, - "text-shadow": 1, - "text-transform": 1, - "text-wrap": 1, - "top": 1, - "transform": 1, - "transform-origin": 1, - "transform-style": 1, - "transition": 1, - "transition-delay": 1, - "transition-duration": 1, - "transition-property": 1, - "transition-timing-function": 1, - "unicode-bidi": 1, - "user-modify": 1, - "user-select": 1, - "vertical-align": 1, - "visibility": 1, - "voice-balance": 1, - "voice-duration": 1, - "voice-family": 1, - "voice-pitch": 1, - "voice-pitch-range": 1, - "voice-rate": 1, - "voice-stress": 1, - "voice-volume": 1, - "volume": 1, - "white-space": 1, - "white-space-collapse": 1, - "widows": 1, - "width": 1, - "word-break": 1, - "word-spacing": 1, - "word-wrap": 1, - "z-index": 1, - - //IE - "filter": 1, - "zoom": 1, - - //@font-face - "src": 1 - }; + var rule = this; parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); + // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) if (event.invalid) { reporter.report(event.invalid.message, event.line, event.col, rule); } - //if (!properties[name] && name.charAt(0) != "-"){ - // reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); - //} }); } @@ -8987,6 +8746,111 @@ CSSLint.addFormatter({ } }); /*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "junit-xml", + name: "JUNIT XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + + var messages = results.messages, + output = [], + tests = { + 'error': 0, + 'failure': 0 + }; + + /** + * Generate a source string for a rule. + * JUNIT source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/\"/g, "'").replace(//g, ">"); + + }; + + if (messages.length > 0) { + + messages.forEach(function (message, i) { + + // since junit has no warning class + // all issues as errors + var type = message.type === 'warning' ? 'error' : message.type; + + //ignore rollups for now + if (!message.rollup) { + + // build the test case seperately, once joined + // we'll add it to a custom array filtered by type + output.push(""); + output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); + output.push(""); + + tests[type] += 1; + + } + + }); + + output.unshift(""); + output.push(""); + + } + + return output.join(""); + + } +}); +/*global CSSLint*/ CSSLint.addFormatter({ //format information id: "lint-xml", diff --git a/release/csslint-wsh.js b/release/csslint-wsh.js index 54576bb5..3a966058 100644 --- a/release/csslint-wsh.js +++ b/release/csslint-wsh.js @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 14-May-2012 10:24:48 */ +/* Build time: 12-September-2012 01:46:26 */ var CSSLint = (function(){ /*! @@ -47,7 +47,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ var parserlib = {}; (function(){ @@ -957,7 +957,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ (function(){ var EventTarget = parserlib.util.EventTarget, TokenStreamBase = parserlib.util.TokenStreamBase, @@ -3025,9 +3025,15 @@ Parser.prototype = function(){ var tokenStream = this._tokenStream, token, tt, - name; + name, + prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + this._readWhitespace(); name = this._keyframe_name(); @@ -3037,8 +3043,9 @@ Parser.prototype = function(){ this.fire({ type: "startkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3054,8 +3061,9 @@ Parser.prototype = function(){ this.fire({ type: "endkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3837,6 +3845,7 @@ var Properties = { "pitch" : 1, "pitch-range" : 1, "play-during" : 1, + "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit", "position" : "static | relative | absolute | fixed | inherit", "presentation-level" : 1, "punctuation-trim" : 1, @@ -5581,7 +5590,7 @@ var Tokens = [ //{ name: "ATKEYWORD"}, //CSS3 animations - { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] }, + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, //important symbol { name: "IMPORTANT_SYM"}, @@ -6333,7 +6342,7 @@ var CSSLint = (function(){ formatters = [], api = new parserlib.util.EventTarget(); - api.version = "0.9.8"; + api.version = "0.9.9"; //------------------------------------------------------------------------- // Rule Management @@ -6367,6 +6376,23 @@ var CSSLint = (function(){ return a.id > b.id ? 1 : 0; }); }; + + /** + * Returns a ruleset configuration object with all current rules. + * @return A ruleset object. + * @method getRuleset + */ + api.getRuleset = function() { + var ruleset = {}, + i = 0, + len = rules.length; + + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + + return ruleset; + }; //------------------------------------------------------------------------- // Formatters @@ -6447,13 +6473,11 @@ var CSSLint = (function(){ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); + // normalize line endings lines = text.replace(/\n\r?/g, "$split$").split('$split$'); if (!ruleset){ - ruleset = {}; - while (i < len){ - ruleset[rules[i++].id] = 1; //by default, everything is a warning - } + ruleset = this.getRuleset(); } reporter = new Reporter(lines, ruleset); @@ -6802,36 +6826,42 @@ CSSLint.addRule({ "padding-bottom": 1, "padding-top": 1 }, - properties; + properties, + boxSizing = false; function startRule(){ properties = {}; + boxSizing = false; } function endRule(){ - var prop; - if (properties.height){ - for (prop in heightProperties){ - if (heightProperties.hasOwnProperty(prop) && properties[prop]){ - - //special case for padding - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){ - reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + var prop, value; + + if (!boxSizing) { + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + //special case for padding + if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } } - } - - if (properties.width){ - for (prop in widthProperties){ - if (widthProperties.hasOwnProperty(prop) && properties[prop]){ - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){ - reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + + if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } - } - } + } + } } parser.addListener("startrule", startRule); @@ -6848,8 +6878,10 @@ CSSLint.addRule({ properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; } } else { - if (name == "width" || name == "height"){ + if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){ properties[name] = 1; + } else if (name == "box-sizing") { + boxSizing = true; } } @@ -6914,20 +6946,21 @@ CSSLint.addRule({ prefixed, i, len, + inKeyFrame = false, arrayPush = Array.prototype.push, applyTo = []; // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details compatiblePrefixes = { - "animation" : "webkit moz ms", - "animation-delay" : "webkit moz ms", - "animation-direction" : "webkit moz ms", - "animation-duration" : "webkit moz ms", - "animation-fill-mode" : "webkit moz ms", - "animation-iteration-count" : "webkit moz ms", - "animation-name" : "webkit moz ms", - "animation-play-state" : "webkit moz ms", - "animation-timing-function" : "webkit moz ms", + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", "appearance" : "webkit moz", "border-end" : "webkit moz", "border-end-color" : "webkit moz", @@ -6967,11 +7000,11 @@ CSSLint.addRule({ "text-size-adjust" : "webkit ms", "transform" : "webkit moz ms o", "transform-origin" : "webkit moz ms o", - "transition" : "webkit moz o ms", - "transition-delay" : "webkit moz o ms", - "transition-duration" : "webkit moz o ms", - "transition-property" : "webkit moz o ms", - "transition-timing-function" : "webkit moz o ms", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", "user-select" : "webkit moz ms", "word-break" : "epub ms", @@ -6990,14 +7023,28 @@ CSSLint.addRule({ arrayPush.apply(applyTo, variations); } } + parser.addListener("startrule", function () { properties = []; }); + parser.addListener("startkeyframes", function (event) { + inKeyFrame = event.prefix || true; + }); + + parser.addListener("endkeyframes", function (event) { + inKeyFrame = false; + }); + parser.addListener("property", function (event) { var name = event.property; if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { - properties.push(name); + + // e.g., -moz-transform is okay to be alone in @-moz-keyframes + if (!inKeyFrame || typeof inKeyFrame != "string" || + name.text.indexOf("-" + inKeyFrame + "-") !== 0) { + properties.push(name); + } } }); @@ -7676,308 +7723,20 @@ CSSLint.addRule({ //rule information id: "known-properties", name: "Require use of known properties", - desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", browsers: "All", //initialization init: function(parser, reporter){ - var rule = this, - properties = { - - "alignment-adjust": 1, - "alignment-baseline": 1, - "animation": 1, - "animation-delay": 1, - "animation-direction": 1, - "animation-duration": 1, - "animation-fill-mode": 1, - "animation-iteration-count": 1, - "animation-name": 1, - "animation-play-state": 1, - "animation-timing-function": 1, - "appearance": 1, - "azimuth": 1, - "backface-visibility": 1, - "background": 1, - "background-attachment": 1, - "background-break": 1, - "background-clip": 1, - "background-color": 1, - "background-image": 1, - "background-origin": 1, - "background-position": 1, - "background-repeat": 1, - "background-size": 1, - "baseline-shift": 1, - "binding": 1, - "bleed": 1, - "bookmark-label": 1, - "bookmark-level": 1, - "bookmark-state": 1, - "bookmark-target": 1, - "border": 1, - "border-bottom": 1, - "border-bottom-color": 1, - "border-bottom-left-radius": 1, - "border-bottom-right-radius": 1, - "border-bottom-style": 1, - "border-bottom-width": 1, - "border-collapse": 1, - "border-color": 1, - "border-image": 1, - "border-image-outset": 1, - "border-image-repeat": 1, - "border-image-slice": 1, - "border-image-source": 1, - "border-image-width": 1, - "border-left": 1, - "border-left-color": 1, - "border-left-style": 1, - "border-left-width": 1, - "border-radius": 1, - "border-right": 1, - "border-right-color": 1, - "border-right-style": 1, - "border-right-width": 1, - "border-spacing": 1, - "border-style": 1, - "border-top": 1, - "border-top-color": 1, - "border-top-left-radius": 1, - "border-top-right-radius": 1, - "border-top-style": 1, - "border-top-width": 1, - "border-width": 1, - "bottom": 1, - "box-align": 1, - "box-decoration-break": 1, - "box-direction": 1, - "box-flex": 1, - "box-flex-group": 1, - "box-lines": 1, - "box-ordinal-group": 1, - "box-orient": 1, - "box-pack": 1, - "box-shadow": 1, - "box-sizing": 1, - "break-after": 1, - "break-before": 1, - "break-inside": 1, - "caption-side": 1, - "clear": 1, - "clip": 1, - "color": 1, - "color-profile": 1, - "column-count": 1, - "column-fill": 1, - "column-gap": 1, - "column-rule": 1, - "column-rule-color": 1, - "column-rule-style": 1, - "column-rule-width": 1, - "column-span": 1, - "column-width": 1, - "columns": 1, - "content": 1, - "counter-increment": 1, - "counter-reset": 1, - "crop": 1, - "cue": 1, - "cue-after": 1, - "cue-before": 1, - "cursor": 1, - "direction": 1, - "display": 1, - "dominant-baseline": 1, - "drop-initial-after-adjust": 1, - "drop-initial-after-align": 1, - "drop-initial-before-adjust": 1, - "drop-initial-before-align": 1, - "drop-initial-size": 1, - "drop-initial-value": 1, - "elevation": 1, - "empty-cells": 1, - "fit": 1, - "fit-position": 1, - "float": 1, - "float-offset": 1, - "font": 1, - "font-family": 1, - "font-size": 1, - "font-size-adjust": 1, - "font-stretch": 1, - "font-style": 1, - "font-variant": 1, - "font-weight": 1, - "grid-columns": 1, - "grid-rows": 1, - "hanging-punctuation": 1, - "height": 1, - "hyphenate-after": 1, - "hyphenate-before": 1, - "hyphenate-character": 1, - "hyphenate-lines": 1, - "hyphenate-resource": 1, - "hyphens": 1, - "icon": 1, - "image-orientation": 1, - "image-rendering": 1, - "image-resolution": 1, - "inline-box-align": 1, - "left": 1, - "letter-spacing": 1, - "line-height": 1, - "line-stacking": 1, - "line-stacking-ruby": 1, - "line-stacking-shift": 1, - "line-stacking-strategy": 1, - "list-style": 1, - "list-style-image": 1, - "list-style-position": 1, - "list-style-type": 1, - "margin": 1, - "margin-bottom": 1, - "margin-left": 1, - "margin-right": 1, - "margin-top": 1, - "mark": 1, - "mark-after": 1, - "mark-before": 1, - "marks": 1, - "marquee-direction": 1, - "marquee-play-count": 1, - "marquee-speed": 1, - "marquee-style": 1, - "max-height": 1, - "max-width": 1, - "min-height": 1, - "min-width": 1, - "move-to": 1, - "nav-down": 1, - "nav-index": 1, - "nav-left": 1, - "nav-right": 1, - "nav-up": 1, - "opacity": 1, - "orphans": 1, - "outline": 1, - "outline-color": 1, - "outline-offset": 1, - "outline-style": 1, - "outline-width": 1, - "overflow": 1, - "overflow-style": 1, - "overflow-x": 1, - "overflow-y": 1, - "padding": 1, - "padding-bottom": 1, - "padding-left": 1, - "padding-right": 1, - "padding-top": 1, - "page": 1, - "page-break-after": 1, - "page-break-before": 1, - "page-break-inside": 1, - "page-policy": 1, - "pause": 1, - "pause-after": 1, - "pause-before": 1, - "perspective": 1, - "perspective-origin": 1, - "phonemes": 1, - "pitch": 1, - "pitch-range": 1, - "play-during": 1, - "position": 1, - "presentation-level": 1, - "punctuation-trim": 1, - "quotes": 1, - "rendering-intent": 1, - "resize": 1, - "rest": 1, - "rest-after": 1, - "rest-before": 1, - "richness": 1, - "right": 1, - "rotation": 1, - "rotation-point": 1, - "ruby-align": 1, - "ruby-overhang": 1, - "ruby-position": 1, - "ruby-span": 1, - "size": 1, - "speak": 1, - "speak-header": 1, - "speak-numeral": 1, - "speak-punctuation": 1, - "speech-rate": 1, - "stress": 1, - "string-set": 1, - "table-layout": 1, - "target": 1, - "target-name": 1, - "target-new": 1, - "target-position": 1, - "text-align": 1, - "text-align-last": 1, - "text-decoration": 1, - "text-emphasis": 1, - "text-height": 1, - "text-indent": 1, - "text-justify": 1, - "text-outline": 1, - "text-shadow": 1, - "text-transform": 1, - "text-wrap": 1, - "top": 1, - "transform": 1, - "transform-origin": 1, - "transform-style": 1, - "transition": 1, - "transition-delay": 1, - "transition-duration": 1, - "transition-property": 1, - "transition-timing-function": 1, - "unicode-bidi": 1, - "user-modify": 1, - "user-select": 1, - "vertical-align": 1, - "visibility": 1, - "voice-balance": 1, - "voice-duration": 1, - "voice-family": 1, - "voice-pitch": 1, - "voice-pitch-range": 1, - "voice-rate": 1, - "voice-stress": 1, - "voice-volume": 1, - "volume": 1, - "white-space": 1, - "white-space-collapse": 1, - "widows": 1, - "width": 1, - "word-break": 1, - "word-spacing": 1, - "word-wrap": 1, - "z-index": 1, - - //IE - "filter": 1, - "zoom": 1, - - //@font-face - "src": 1 - }; + var rule = this; parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); + // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) if (event.invalid) { reporter.report(event.invalid.message, event.line, event.col, rule); } - //if (!properties[name] && name.charAt(0) != "-"){ - // reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); - //} }); } @@ -8988,6 +8747,111 @@ CSSLint.addFormatter({ } }); /*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "junit-xml", + name: "JUNIT XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + + var messages = results.messages, + output = [], + tests = { + 'error': 0, + 'failure': 0 + }; + + /** + * Generate a source string for a rule. + * JUNIT source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/\"/g, "'").replace(//g, ">"); + + }; + + if (messages.length > 0) { + + messages.forEach(function (message, i) { + + // since junit has no warning class + // all issues as errors + var type = message.type === 'warning' ? 'error' : message.type; + + //ignore rollups for now + if (!message.rollup) { + + // build the test case seperately, once joined + // we'll add it to a custom array filtered by type + output.push(""); + output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); + output.push(""); + + tests[type] += 1; + + } + + }); + + output.unshift(""); + output.push(""); + + } + + return output.join(""); + + } +}); +/*global CSSLint*/ CSSLint.addFormatter({ //format information id: "lint-xml", @@ -9154,9 +9018,8 @@ function cli(api){ * @param options {Object} The CLI options. * @return {Object} A ruleset object. */ - function gatherRules(options){ - var ruleset, - warnings = options.rules || options.warnings, + function gatherRules(options, ruleset){ + var warnings = options.rules || options.warnings, errors = options.errors; if (warnings){ @@ -9172,6 +9035,25 @@ function cli(api){ ruleset[value] = 2; }); } + + return ruleset; + } + + /** + * Filters out rules using the ignore command line option. + * @param options {Object} the CLI options + * @return {Object} A ruleset object. + */ + function filterRules(options) { + var ignore = options.ignore, + ruleset = null; + + if (ignore) { + ruleset = CSSLint.getRuleset(); + ignore.split(",").forEach(function(value){ + delete ruleset[value]; + }); + } return ruleset; } @@ -9196,7 +9078,8 @@ function cli(api){ */ function processFile(relativeFilePath, options) { var input = api.readFile(relativeFilePath), - result = CSSLint.verify(input, gatherRules(options)), + ruleset = filterRules(options), + result = CSSLint.verify(input, gatherRules(options, ruleset)), formatter = CSSLint.getFormatter(options.format || "text"), messages = result.messages || [], output, @@ -9241,6 +9124,7 @@ function cli(api){ " --quiet Only output when errors are present.", " --errors= Indicate which rules to include as errors.", " --warnings= Indicate which rules to include as warnings.", + " --ignore= Indicate which rules to ignore completely.", " --version Outputs the current version number." ].join("\n") + "\n"); } @@ -9287,44 +9171,72 @@ function cli(api){ } } return exitCode; - } - - //----------------------------------------------------------------------------- - // Process command line - //----------------------------------------------------------------------------- - - var args = api.args, - argName, - parts, - arg = args.shift(), - options = {}, - files = []; - - while(arg){ - if (arg.indexOf("--") === 0){ - argName = arg.substring(2); - options[argName] = true; - - if (argName.indexOf("=") > -1){ - parts = argName.split("="); - options[parts[0]] = parts[1]; - } else { + } + + + function processArguments(args, options) { + var arg = args.shift(), + argName, + parts, + files = []; + + while(arg){ + if (arg.indexOf("--") === 0){ + argName = arg.substring(2); options[argName] = true; - } + + if (argName.indexOf("=") > -1){ + parts = argName.split("="); + options[parts[0]] = parts[1]; + } else { + options[argName] = true; + } - } else { - - //see if it's a directory or a file - if (api.isDirectory(arg)){ - files = files.concat(api.getFiles(arg)); } else { - files.push(arg); + + //see if it's a directory or a file + if (api.isDirectory(arg)){ + files = files.concat(api.getFiles(arg)); + } else { + files.push(arg); + } } + arg = args.shift(); } - arg = args.shift(); + + options.files = files; + return options; + } + + function readConfigFile(options) { + var data = api.readFile(api.getFullPath(".csslintrc")); + if (data) { + options = processArguments(data.split(/[\s\n\r]+/m), options); + api.print("ignore = " + options.ignore); + api.print("errors = " + options.errors); + api.print("warnings = " + options.warnings); + } + + return options; } + + + + //----------------------------------------------------------------------------- + // Process command line + //----------------------------------------------------------------------------- + + var args = api.args, + argCount = args.length, + options = {}; + + // first look for config file .csslintrc + options = readConfigFile(options); + + // Command line arguments override config file + options = processArguments(args, options); - if (options.help || arguments.length === 0){ + if (options.help || argCount === 0){ outputHelp(); api.quit(0); } @@ -9339,7 +9251,7 @@ function cli(api){ api.quit(0); } - api.quit(processFiles(files,options)); + api.quit(processFiles(options.files,options)); } /* * Windows Script Host Command Line Interface diff --git a/release/csslint.js b/release/csslint.js index 25f4150d..b62d6f25 100644 --- a/release/csslint.js +++ b/release/csslint.js @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 14-May-2012 10:24:48 */ +/* Build time: 12-September-2012 01:46:26 */ var CSSLint = (function(){ /*! @@ -47,7 +47,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ var parserlib = {}; (function(){ @@ -957,7 +957,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ (function(){ var EventTarget = parserlib.util.EventTarget, TokenStreamBase = parserlib.util.TokenStreamBase, @@ -3025,9 +3025,15 @@ Parser.prototype = function(){ var tokenStream = this._tokenStream, token, tt, - name; + name, + prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + this._readWhitespace(); name = this._keyframe_name(); @@ -3037,8 +3043,9 @@ Parser.prototype = function(){ this.fire({ type: "startkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3054,8 +3061,9 @@ Parser.prototype = function(){ this.fire({ type: "endkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3837,6 +3845,7 @@ var Properties = { "pitch" : 1, "pitch-range" : 1, "play-during" : 1, + "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit", "position" : "static | relative | absolute | fixed | inherit", "presentation-level" : 1, "punctuation-trim" : 1, @@ -5581,7 +5590,7 @@ var Tokens = [ //{ name: "ATKEYWORD"}, //CSS3 animations - { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] }, + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, //important symbol { name: "IMPORTANT_SYM"}, @@ -6333,7 +6342,7 @@ var CSSLint = (function(){ formatters = [], api = new parserlib.util.EventTarget(); - api.version = "0.9.8"; + api.version = "0.9.9"; //------------------------------------------------------------------------- // Rule Management @@ -6367,6 +6376,23 @@ var CSSLint = (function(){ return a.id > b.id ? 1 : 0; }); }; + + /** + * Returns a ruleset configuration object with all current rules. + * @return A ruleset object. + * @method getRuleset + */ + api.getRuleset = function() { + var ruleset = {}, + i = 0, + len = rules.length; + + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + + return ruleset; + }; //------------------------------------------------------------------------- // Formatters @@ -6447,13 +6473,11 @@ var CSSLint = (function(){ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); + // normalize line endings lines = text.replace(/\n\r?/g, "$split$").split('$split$'); if (!ruleset){ - ruleset = {}; - while (i < len){ - ruleset[rules[i++].id] = 1; //by default, everything is a warning - } + ruleset = this.getRuleset(); } reporter = new Reporter(lines, ruleset); @@ -6802,36 +6826,42 @@ CSSLint.addRule({ "padding-bottom": 1, "padding-top": 1 }, - properties; + properties, + boxSizing = false; function startRule(){ properties = {}; + boxSizing = false; } function endRule(){ - var prop; - if (properties.height){ - for (prop in heightProperties){ - if (heightProperties.hasOwnProperty(prop) && properties[prop]){ - - //special case for padding - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){ - reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + var prop, value; + + if (!boxSizing) { + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + //special case for padding + if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } } - } - if (properties.width){ - for (prop in widthProperties){ - if (widthProperties.hasOwnProperty(prop) && properties[prop]){ - - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){ - reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + + if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } - } - } + } + } } parser.addListener("startrule", startRule); @@ -6848,8 +6878,10 @@ CSSLint.addRule({ properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; } } else { - if (name == "width" || name == "height"){ + if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){ properties[name] = 1; + } else if (name == "box-sizing") { + boxSizing = true; } } @@ -6914,20 +6946,21 @@ CSSLint.addRule({ prefixed, i, len, + inKeyFrame = false, arrayPush = Array.prototype.push, applyTo = []; // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details compatiblePrefixes = { - "animation" : "webkit moz ms", - "animation-delay" : "webkit moz ms", - "animation-direction" : "webkit moz ms", - "animation-duration" : "webkit moz ms", - "animation-fill-mode" : "webkit moz ms", - "animation-iteration-count" : "webkit moz ms", - "animation-name" : "webkit moz ms", - "animation-play-state" : "webkit moz ms", - "animation-timing-function" : "webkit moz ms", + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", "appearance" : "webkit moz", "border-end" : "webkit moz", "border-end-color" : "webkit moz", @@ -6967,11 +7000,11 @@ CSSLint.addRule({ "text-size-adjust" : "webkit ms", "transform" : "webkit moz ms o", "transform-origin" : "webkit moz ms o", - "transition" : "webkit moz o ms", - "transition-delay" : "webkit moz o ms", - "transition-duration" : "webkit moz o ms", - "transition-property" : "webkit moz o ms", - "transition-timing-function" : "webkit moz o ms", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", "user-select" : "webkit moz ms", "word-break" : "epub ms", @@ -6990,14 +7023,28 @@ CSSLint.addRule({ arrayPush.apply(applyTo, variations); } } + parser.addListener("startrule", function () { properties = []; }); + parser.addListener("startkeyframes", function (event) { + inKeyFrame = event.prefix || true; + }); + + parser.addListener("endkeyframes", function (event) { + inKeyFrame = false; + }); + parser.addListener("property", function (event) { var name = event.property; if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { - properties.push(name); + + // e.g., -moz-transform is okay to be alone in @-moz-keyframes + if (!inKeyFrame || typeof inKeyFrame != "string" || + name.text.indexOf("-" + inKeyFrame + "-") !== 0) { + properties.push(name); + } } }); @@ -7676,308 +7723,20 @@ CSSLint.addRule({ //rule information id: "known-properties", name: "Require use of known properties", - desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", browsers: "All", //initialization init: function(parser, reporter){ - var rule = this, - properties = { - - "alignment-adjust": 1, - "alignment-baseline": 1, - "animation": 1, - "animation-delay": 1, - "animation-direction": 1, - "animation-duration": 1, - "animation-fill-mode": 1, - "animation-iteration-count": 1, - "animation-name": 1, - "animation-play-state": 1, - "animation-timing-function": 1, - "appearance": 1, - "azimuth": 1, - "backface-visibility": 1, - "background": 1, - "background-attachment": 1, - "background-break": 1, - "background-clip": 1, - "background-color": 1, - "background-image": 1, - "background-origin": 1, - "background-position": 1, - "background-repeat": 1, - "background-size": 1, - "baseline-shift": 1, - "binding": 1, - "bleed": 1, - "bookmark-label": 1, - "bookmark-level": 1, - "bookmark-state": 1, - "bookmark-target": 1, - "border": 1, - "border-bottom": 1, - "border-bottom-color": 1, - "border-bottom-left-radius": 1, - "border-bottom-right-radius": 1, - "border-bottom-style": 1, - "border-bottom-width": 1, - "border-collapse": 1, - "border-color": 1, - "border-image": 1, - "border-image-outset": 1, - "border-image-repeat": 1, - "border-image-slice": 1, - "border-image-source": 1, - "border-image-width": 1, - "border-left": 1, - "border-left-color": 1, - "border-left-style": 1, - "border-left-width": 1, - "border-radius": 1, - "border-right": 1, - "border-right-color": 1, - "border-right-style": 1, - "border-right-width": 1, - "border-spacing": 1, - "border-style": 1, - "border-top": 1, - "border-top-color": 1, - "border-top-left-radius": 1, - "border-top-right-radius": 1, - "border-top-style": 1, - "border-top-width": 1, - "border-width": 1, - "bottom": 1, - "box-align": 1, - "box-decoration-break": 1, - "box-direction": 1, - "box-flex": 1, - "box-flex-group": 1, - "box-lines": 1, - "box-ordinal-group": 1, - "box-orient": 1, - "box-pack": 1, - "box-shadow": 1, - "box-sizing": 1, - "break-after": 1, - "break-before": 1, - "break-inside": 1, - "caption-side": 1, - "clear": 1, - "clip": 1, - "color": 1, - "color-profile": 1, - "column-count": 1, - "column-fill": 1, - "column-gap": 1, - "column-rule": 1, - "column-rule-color": 1, - "column-rule-style": 1, - "column-rule-width": 1, - "column-span": 1, - "column-width": 1, - "columns": 1, - "content": 1, - "counter-increment": 1, - "counter-reset": 1, - "crop": 1, - "cue": 1, - "cue-after": 1, - "cue-before": 1, - "cursor": 1, - "direction": 1, - "display": 1, - "dominant-baseline": 1, - "drop-initial-after-adjust": 1, - "drop-initial-after-align": 1, - "drop-initial-before-adjust": 1, - "drop-initial-before-align": 1, - "drop-initial-size": 1, - "drop-initial-value": 1, - "elevation": 1, - "empty-cells": 1, - "fit": 1, - "fit-position": 1, - "float": 1, - "float-offset": 1, - "font": 1, - "font-family": 1, - "font-size": 1, - "font-size-adjust": 1, - "font-stretch": 1, - "font-style": 1, - "font-variant": 1, - "font-weight": 1, - "grid-columns": 1, - "grid-rows": 1, - "hanging-punctuation": 1, - "height": 1, - "hyphenate-after": 1, - "hyphenate-before": 1, - "hyphenate-character": 1, - "hyphenate-lines": 1, - "hyphenate-resource": 1, - "hyphens": 1, - "icon": 1, - "image-orientation": 1, - "image-rendering": 1, - "image-resolution": 1, - "inline-box-align": 1, - "left": 1, - "letter-spacing": 1, - "line-height": 1, - "line-stacking": 1, - "line-stacking-ruby": 1, - "line-stacking-shift": 1, - "line-stacking-strategy": 1, - "list-style": 1, - "list-style-image": 1, - "list-style-position": 1, - "list-style-type": 1, - "margin": 1, - "margin-bottom": 1, - "margin-left": 1, - "margin-right": 1, - "margin-top": 1, - "mark": 1, - "mark-after": 1, - "mark-before": 1, - "marks": 1, - "marquee-direction": 1, - "marquee-play-count": 1, - "marquee-speed": 1, - "marquee-style": 1, - "max-height": 1, - "max-width": 1, - "min-height": 1, - "min-width": 1, - "move-to": 1, - "nav-down": 1, - "nav-index": 1, - "nav-left": 1, - "nav-right": 1, - "nav-up": 1, - "opacity": 1, - "orphans": 1, - "outline": 1, - "outline-color": 1, - "outline-offset": 1, - "outline-style": 1, - "outline-width": 1, - "overflow": 1, - "overflow-style": 1, - "overflow-x": 1, - "overflow-y": 1, - "padding": 1, - "padding-bottom": 1, - "padding-left": 1, - "padding-right": 1, - "padding-top": 1, - "page": 1, - "page-break-after": 1, - "page-break-before": 1, - "page-break-inside": 1, - "page-policy": 1, - "pause": 1, - "pause-after": 1, - "pause-before": 1, - "perspective": 1, - "perspective-origin": 1, - "phonemes": 1, - "pitch": 1, - "pitch-range": 1, - "play-during": 1, - "position": 1, - "presentation-level": 1, - "punctuation-trim": 1, - "quotes": 1, - "rendering-intent": 1, - "resize": 1, - "rest": 1, - "rest-after": 1, - "rest-before": 1, - "richness": 1, - "right": 1, - "rotation": 1, - "rotation-point": 1, - "ruby-align": 1, - "ruby-overhang": 1, - "ruby-position": 1, - "ruby-span": 1, - "size": 1, - "speak": 1, - "speak-header": 1, - "speak-numeral": 1, - "speak-punctuation": 1, - "speech-rate": 1, - "stress": 1, - "string-set": 1, - "table-layout": 1, - "target": 1, - "target-name": 1, - "target-new": 1, - "target-position": 1, - "text-align": 1, - "text-align-last": 1, - "text-decoration": 1, - "text-emphasis": 1, - "text-height": 1, - "text-indent": 1, - "text-justify": 1, - "text-outline": 1, - "text-shadow": 1, - "text-transform": 1, - "text-wrap": 1, - "top": 1, - "transform": 1, - "transform-origin": 1, - "transform-style": 1, - "transition": 1, - "transition-delay": 1, - "transition-duration": 1, - "transition-property": 1, - "transition-timing-function": 1, - "unicode-bidi": 1, - "user-modify": 1, - "user-select": 1, - "vertical-align": 1, - "visibility": 1, - "voice-balance": 1, - "voice-duration": 1, - "voice-family": 1, - "voice-pitch": 1, - "voice-pitch-range": 1, - "voice-rate": 1, - "voice-stress": 1, - "voice-volume": 1, - "volume": 1, - "white-space": 1, - "white-space-collapse": 1, - "widows": 1, - "width": 1, - "word-break": 1, - "word-spacing": 1, - "word-wrap": 1, - "z-index": 1, - - //IE - "filter": 1, - "zoom": 1, - - //@font-face - "src": 1 - }; + var rule = this; parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); + // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) if (event.invalid) { reporter.report(event.invalid.message, event.line, event.col, rule); } - //if (!properties[name] && name.charAt(0) != "-"){ - // reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); - //} }); } @@ -8988,6 +8747,111 @@ CSSLint.addFormatter({ } }); /*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "junit-xml", + name: "JUNIT XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + + var messages = results.messages, + output = [], + tests = { + 'error': 0, + 'failure': 0 + }; + + /** + * Generate a source string for a rule. + * JUNIT source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/\"/g, "'").replace(//g, ">"); + + }; + + if (messages.length > 0) { + + messages.forEach(function (message, i) { + + // since junit has no warning class + // all issues as errors + var type = message.type === 'warning' ? 'error' : message.type; + + //ignore rollups for now + if (!message.rollup) { + + // build the test case seperately, once joined + // we'll add it to a custom array filtered by type + output.push(""); + output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); + output.push(""); + + tests[type] += 1; + + } + + }); + + output.unshift(""); + output.push(""); + + } + + return output.join(""); + + } +}); +/*global CSSLint*/ CSSLint.addFormatter({ //format information id: "lint-xml", diff --git a/release/npm/cli.js b/release/npm/cli.js index 5ee4e968..0ed0019a 100644 --- a/release/npm/cli.js +++ b/release/npm/cli.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -/* Build time: 14-May-2012 10:24:48 */ +/* Build time: 12-September-2012 01:46:26 */ /* * Encapsulates all of the CLI functionality. The api argument simply @@ -29,9 +29,8 @@ function cli(api){ * @param options {Object} The CLI options. * @return {Object} A ruleset object. */ - function gatherRules(options){ - var ruleset, - warnings = options.rules || options.warnings, + function gatherRules(options, ruleset){ + var warnings = options.rules || options.warnings, errors = options.errors; if (warnings){ @@ -47,6 +46,25 @@ function cli(api){ ruleset[value] = 2; }); } + + return ruleset; + } + + /** + * Filters out rules using the ignore command line option. + * @param options {Object} the CLI options + * @return {Object} A ruleset object. + */ + function filterRules(options) { + var ignore = options.ignore, + ruleset = null; + + if (ignore) { + ruleset = CSSLint.getRuleset(); + ignore.split(",").forEach(function(value){ + delete ruleset[value]; + }); + } return ruleset; } @@ -71,7 +89,8 @@ function cli(api){ */ function processFile(relativeFilePath, options) { var input = api.readFile(relativeFilePath), - result = CSSLint.verify(input, gatherRules(options)), + ruleset = filterRules(options), + result = CSSLint.verify(input, gatherRules(options, ruleset)), formatter = CSSLint.getFormatter(options.format || "text"), messages = result.messages || [], output, @@ -116,6 +135,7 @@ function cli(api){ " --quiet Only output when errors are present.", " --errors= Indicate which rules to include as errors.", " --warnings= Indicate which rules to include as warnings.", + " --ignore= Indicate which rules to ignore completely.", " --version Outputs the current version number." ].join("\n") + "\n"); } @@ -162,44 +182,72 @@ function cli(api){ } } return exitCode; - } - - //----------------------------------------------------------------------------- - // Process command line - //----------------------------------------------------------------------------- - - var args = api.args, - argName, - parts, - arg = args.shift(), - options = {}, - files = []; - - while(arg){ - if (arg.indexOf("--") === 0){ - argName = arg.substring(2); - options[argName] = true; - - if (argName.indexOf("=") > -1){ - parts = argName.split("="); - options[parts[0]] = parts[1]; - } else { + } + + + function processArguments(args, options) { + var arg = args.shift(), + argName, + parts, + files = []; + + while(arg){ + if (arg.indexOf("--") === 0){ + argName = arg.substring(2); options[argName] = true; - } + + if (argName.indexOf("=") > -1){ + parts = argName.split("="); + options[parts[0]] = parts[1]; + } else { + options[argName] = true; + } - } else { - - //see if it's a directory or a file - if (api.isDirectory(arg)){ - files = files.concat(api.getFiles(arg)); } else { - files.push(arg); + + //see if it's a directory or a file + if (api.isDirectory(arg)){ + files = files.concat(api.getFiles(arg)); + } else { + files.push(arg); + } } + arg = args.shift(); } - arg = args.shift(); + + options.files = files; + return options; + } + + function readConfigFile(options) { + var data = api.readFile(api.getFullPath(".csslintrc")); + if (data) { + options = processArguments(data.split(/[\s\n\r]+/m), options); + api.print("ignore = " + options.ignore); + api.print("errors = " + options.errors); + api.print("warnings = " + options.warnings); + } + + return options; } + + + + //----------------------------------------------------------------------------- + // Process command line + //----------------------------------------------------------------------------- + + var args = api.args, + argCount = args.length, + options = {}; + + // first look for config file .csslintrc + options = readConfigFile(options); + + // Command line arguments override config file + options = processArguments(args, options); - if (options.help || arguments.length === 0){ + if (options.help || argCount === 0){ outputHelp(); api.quit(0); } @@ -214,7 +262,7 @@ function cli(api){ api.quit(0); } - api.quit(processFiles(files,options)); + api.quit(processFiles(options.files,options)); } /* * CSSLint Node.js Command Line Interface diff --git a/release/npm/lib/csslint-node.js b/release/npm/lib/csslint-node.js index 5d4ae2bc..50e499cd 100644 --- a/release/npm/lib/csslint-node.js +++ b/release/npm/lib/csslint-node.js @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 14-May-2012 10:24:48 */ +/* Build time: 12-September-2012 01:46:26 */ /*! Parser-Lib @@ -46,7 +46,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ var parserlib = {}; (function(){ @@ -956,7 +956,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.7, Build time: 4-May-2012 03:57:04 */ +/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ (function(){ var EventTarget = parserlib.util.EventTarget, TokenStreamBase = parserlib.util.TokenStreamBase, @@ -3024,9 +3024,15 @@ Parser.prototype = function(){ var tokenStream = this._tokenStream, token, tt, - name; + name, + prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + this._readWhitespace(); name = this._keyframe_name(); @@ -3036,8 +3042,9 @@ Parser.prototype = function(){ this.fire({ type: "startkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3053,8 +3060,9 @@ Parser.prototype = function(){ this.fire({ type: "endkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -3836,6 +3844,7 @@ var Properties = { "pitch" : 1, "pitch-range" : 1, "play-during" : 1, + "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit", "position" : "static | relative | absolute | fixed | inherit", "presentation-level" : 1, "punctuation-trim" : 1, @@ -5580,7 +5589,7 @@ var Tokens = [ //{ name: "ATKEYWORD"}, //CSS3 animations - { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] }, + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, //important symbol { name: "IMPORTANT_SYM"}, @@ -6332,7 +6341,7 @@ var CSSLint = (function(){ formatters = [], api = new parserlib.util.EventTarget(); - api.version = "0.9.8"; + api.version = "0.9.9"; //------------------------------------------------------------------------- // Rule Management @@ -6366,6 +6375,23 @@ var CSSLint = (function(){ return a.id > b.id ? 1 : 0; }); }; + + /** + * Returns a ruleset configuration object with all current rules. + * @return A ruleset object. + * @method getRuleset + */ + api.getRuleset = function() { + var ruleset = {}, + i = 0, + len = rules.length; + + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + + return ruleset; + }; //------------------------------------------------------------------------- // Formatters @@ -6446,13 +6472,11 @@ var CSSLint = (function(){ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); + // normalize line endings lines = text.replace(/\n\r?/g, "$split$").split('$split$'); if (!ruleset){ - ruleset = {}; - while (i < len){ - ruleset[rules[i++].id] = 1; //by default, everything is a warning - } + ruleset = this.getRuleset(); } reporter = new Reporter(lines, ruleset); @@ -6801,36 +6825,42 @@ CSSLint.addRule({ "padding-bottom": 1, "padding-top": 1 }, - properties; + properties, + boxSizing = false; function startRule(){ properties = {}; + boxSizing = false; } function endRule(){ - var prop; - if (properties.height){ - for (prop in heightProperties){ - if (heightProperties.hasOwnProperty(prop) && properties[prop]){ - - //special case for padding - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){ - reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + var prop, value; + + if (!boxSizing) { + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + //special case for padding + if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } } - } - if (properties.width){ - for (prop in widthProperties){ - if (widthProperties.hasOwnProperty(prop) && properties[prop]){ - - if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){ - reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + + if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } } } - } - } + } + } } parser.addListener("startrule", startRule); @@ -6847,8 +6877,10 @@ CSSLint.addRule({ properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; } } else { - if (name == "width" || name == "height"){ + if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){ properties[name] = 1; + } else if (name == "box-sizing") { + boxSizing = true; } } @@ -6913,20 +6945,21 @@ CSSLint.addRule({ prefixed, i, len, + inKeyFrame = false, arrayPush = Array.prototype.push, applyTo = []; // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details compatiblePrefixes = { - "animation" : "webkit moz ms", - "animation-delay" : "webkit moz ms", - "animation-direction" : "webkit moz ms", - "animation-duration" : "webkit moz ms", - "animation-fill-mode" : "webkit moz ms", - "animation-iteration-count" : "webkit moz ms", - "animation-name" : "webkit moz ms", - "animation-play-state" : "webkit moz ms", - "animation-timing-function" : "webkit moz ms", + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", "appearance" : "webkit moz", "border-end" : "webkit moz", "border-end-color" : "webkit moz", @@ -6966,11 +6999,11 @@ CSSLint.addRule({ "text-size-adjust" : "webkit ms", "transform" : "webkit moz ms o", "transform-origin" : "webkit moz ms o", - "transition" : "webkit moz o ms", - "transition-delay" : "webkit moz o ms", - "transition-duration" : "webkit moz o ms", - "transition-property" : "webkit moz o ms", - "transition-timing-function" : "webkit moz o ms", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", "user-select" : "webkit moz ms", "word-break" : "epub ms", @@ -6989,14 +7022,28 @@ CSSLint.addRule({ arrayPush.apply(applyTo, variations); } } + parser.addListener("startrule", function () { properties = []; }); + parser.addListener("startkeyframes", function (event) { + inKeyFrame = event.prefix || true; + }); + + parser.addListener("endkeyframes", function (event) { + inKeyFrame = false; + }); + parser.addListener("property", function (event) { var name = event.property; if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { - properties.push(name); + + // e.g., -moz-transform is okay to be alone in @-moz-keyframes + if (!inKeyFrame || typeof inKeyFrame != "string" || + name.text.indexOf("-" + inKeyFrame + "-") !== 0) { + properties.push(name); + } } }); @@ -7675,308 +7722,20 @@ CSSLint.addRule({ //rule information id: "known-properties", name: "Require use of known properties", - desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", browsers: "All", //initialization init: function(parser, reporter){ - var rule = this, - properties = { - - "alignment-adjust": 1, - "alignment-baseline": 1, - "animation": 1, - "animation-delay": 1, - "animation-direction": 1, - "animation-duration": 1, - "animation-fill-mode": 1, - "animation-iteration-count": 1, - "animation-name": 1, - "animation-play-state": 1, - "animation-timing-function": 1, - "appearance": 1, - "azimuth": 1, - "backface-visibility": 1, - "background": 1, - "background-attachment": 1, - "background-break": 1, - "background-clip": 1, - "background-color": 1, - "background-image": 1, - "background-origin": 1, - "background-position": 1, - "background-repeat": 1, - "background-size": 1, - "baseline-shift": 1, - "binding": 1, - "bleed": 1, - "bookmark-label": 1, - "bookmark-level": 1, - "bookmark-state": 1, - "bookmark-target": 1, - "border": 1, - "border-bottom": 1, - "border-bottom-color": 1, - "border-bottom-left-radius": 1, - "border-bottom-right-radius": 1, - "border-bottom-style": 1, - "border-bottom-width": 1, - "border-collapse": 1, - "border-color": 1, - "border-image": 1, - "border-image-outset": 1, - "border-image-repeat": 1, - "border-image-slice": 1, - "border-image-source": 1, - "border-image-width": 1, - "border-left": 1, - "border-left-color": 1, - "border-left-style": 1, - "border-left-width": 1, - "border-radius": 1, - "border-right": 1, - "border-right-color": 1, - "border-right-style": 1, - "border-right-width": 1, - "border-spacing": 1, - "border-style": 1, - "border-top": 1, - "border-top-color": 1, - "border-top-left-radius": 1, - "border-top-right-radius": 1, - "border-top-style": 1, - "border-top-width": 1, - "border-width": 1, - "bottom": 1, - "box-align": 1, - "box-decoration-break": 1, - "box-direction": 1, - "box-flex": 1, - "box-flex-group": 1, - "box-lines": 1, - "box-ordinal-group": 1, - "box-orient": 1, - "box-pack": 1, - "box-shadow": 1, - "box-sizing": 1, - "break-after": 1, - "break-before": 1, - "break-inside": 1, - "caption-side": 1, - "clear": 1, - "clip": 1, - "color": 1, - "color-profile": 1, - "column-count": 1, - "column-fill": 1, - "column-gap": 1, - "column-rule": 1, - "column-rule-color": 1, - "column-rule-style": 1, - "column-rule-width": 1, - "column-span": 1, - "column-width": 1, - "columns": 1, - "content": 1, - "counter-increment": 1, - "counter-reset": 1, - "crop": 1, - "cue": 1, - "cue-after": 1, - "cue-before": 1, - "cursor": 1, - "direction": 1, - "display": 1, - "dominant-baseline": 1, - "drop-initial-after-adjust": 1, - "drop-initial-after-align": 1, - "drop-initial-before-adjust": 1, - "drop-initial-before-align": 1, - "drop-initial-size": 1, - "drop-initial-value": 1, - "elevation": 1, - "empty-cells": 1, - "fit": 1, - "fit-position": 1, - "float": 1, - "float-offset": 1, - "font": 1, - "font-family": 1, - "font-size": 1, - "font-size-adjust": 1, - "font-stretch": 1, - "font-style": 1, - "font-variant": 1, - "font-weight": 1, - "grid-columns": 1, - "grid-rows": 1, - "hanging-punctuation": 1, - "height": 1, - "hyphenate-after": 1, - "hyphenate-before": 1, - "hyphenate-character": 1, - "hyphenate-lines": 1, - "hyphenate-resource": 1, - "hyphens": 1, - "icon": 1, - "image-orientation": 1, - "image-rendering": 1, - "image-resolution": 1, - "inline-box-align": 1, - "left": 1, - "letter-spacing": 1, - "line-height": 1, - "line-stacking": 1, - "line-stacking-ruby": 1, - "line-stacking-shift": 1, - "line-stacking-strategy": 1, - "list-style": 1, - "list-style-image": 1, - "list-style-position": 1, - "list-style-type": 1, - "margin": 1, - "margin-bottom": 1, - "margin-left": 1, - "margin-right": 1, - "margin-top": 1, - "mark": 1, - "mark-after": 1, - "mark-before": 1, - "marks": 1, - "marquee-direction": 1, - "marquee-play-count": 1, - "marquee-speed": 1, - "marquee-style": 1, - "max-height": 1, - "max-width": 1, - "min-height": 1, - "min-width": 1, - "move-to": 1, - "nav-down": 1, - "nav-index": 1, - "nav-left": 1, - "nav-right": 1, - "nav-up": 1, - "opacity": 1, - "orphans": 1, - "outline": 1, - "outline-color": 1, - "outline-offset": 1, - "outline-style": 1, - "outline-width": 1, - "overflow": 1, - "overflow-style": 1, - "overflow-x": 1, - "overflow-y": 1, - "padding": 1, - "padding-bottom": 1, - "padding-left": 1, - "padding-right": 1, - "padding-top": 1, - "page": 1, - "page-break-after": 1, - "page-break-before": 1, - "page-break-inside": 1, - "page-policy": 1, - "pause": 1, - "pause-after": 1, - "pause-before": 1, - "perspective": 1, - "perspective-origin": 1, - "phonemes": 1, - "pitch": 1, - "pitch-range": 1, - "play-during": 1, - "position": 1, - "presentation-level": 1, - "punctuation-trim": 1, - "quotes": 1, - "rendering-intent": 1, - "resize": 1, - "rest": 1, - "rest-after": 1, - "rest-before": 1, - "richness": 1, - "right": 1, - "rotation": 1, - "rotation-point": 1, - "ruby-align": 1, - "ruby-overhang": 1, - "ruby-position": 1, - "ruby-span": 1, - "size": 1, - "speak": 1, - "speak-header": 1, - "speak-numeral": 1, - "speak-punctuation": 1, - "speech-rate": 1, - "stress": 1, - "string-set": 1, - "table-layout": 1, - "target": 1, - "target-name": 1, - "target-new": 1, - "target-position": 1, - "text-align": 1, - "text-align-last": 1, - "text-decoration": 1, - "text-emphasis": 1, - "text-height": 1, - "text-indent": 1, - "text-justify": 1, - "text-outline": 1, - "text-shadow": 1, - "text-transform": 1, - "text-wrap": 1, - "top": 1, - "transform": 1, - "transform-origin": 1, - "transform-style": 1, - "transition": 1, - "transition-delay": 1, - "transition-duration": 1, - "transition-property": 1, - "transition-timing-function": 1, - "unicode-bidi": 1, - "user-modify": 1, - "user-select": 1, - "vertical-align": 1, - "visibility": 1, - "voice-balance": 1, - "voice-duration": 1, - "voice-family": 1, - "voice-pitch": 1, - "voice-pitch-range": 1, - "voice-rate": 1, - "voice-stress": 1, - "voice-volume": 1, - "volume": 1, - "white-space": 1, - "white-space-collapse": 1, - "widows": 1, - "width": 1, - "word-break": 1, - "word-spacing": 1, - "word-wrap": 1, - "z-index": 1, - - //IE - "filter": 1, - "zoom": 1, - - //@font-face - "src": 1 - }; + var rule = this; parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); + // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) if (event.invalid) { reporter.report(event.invalid.message, event.line, event.col, rule); } - //if (!properties[name] && name.charAt(0) != "-"){ - // reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); - //} }); } @@ -8987,6 +8746,111 @@ CSSLint.addFormatter({ } }); /*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "junit-xml", + name: "JUNIT XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + + var messages = results.messages, + output = [], + tests = { + 'error': 0, + 'failure': 0 + }; + + /** + * Generate a source string for a rule. + * JUNIT source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/\"/g, "'").replace(//g, ">"); + + }; + + if (messages.length > 0) { + + messages.forEach(function (message, i) { + + // since junit has no warning class + // all issues as errors + var type = message.type === 'warning' ? 'error' : message.type; + + //ignore rollups for now + if (!message.rollup) { + + // build the test case seperately, once joined + // we'll add it to a custom array filtered by type + output.push(""); + output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); + output.push(""); + + tests[type] += 1; + + } + + }); + + output.unshift(""); + output.push(""); + + } + + return output.join(""); + + } +}); +/*global CSSLint*/ CSSLint.addFormatter({ //format information id: "lint-xml", diff --git a/release/npm/package.json b/release/npm/package.json index 3e2513cc..dae82dda 100644 --- a/release/npm/package.json +++ b/release/npm/package.json @@ -1,6 +1,6 @@ { "name": "csslint", - "version": "0.9.8", + "version": "0.9.9", "description": "CSSLint", "author": "Nicholas C. Zakas", "os": ["darwin", "linux", "win32"],