├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── demos ├── .csslintrc ├── CSSLintDemo.htm ├── demo.css └── demo.js ├── dist ├── cli.js ├── csslint-node.js ├── csslint-rhino.js ├── csslint-tests.js ├── csslint-worker.js ├── csslint-wsh.js └── csslint.js ├── lib ├── js.jar ├── yuitest-rhino-cli.js └── yuitest.js ├── package.json ├── src ├── cli │ ├── common.js │ ├── node.js │ ├── rhino.js │ └── wsh.js ├── core │ ├── CSSLint.js │ ├── Reporter.js │ └── Util.js ├── formatters │ ├── checkstyle-xml.js │ ├── compact.js │ ├── csslint-xml.js │ ├── json.js │ ├── junit-xml.js │ ├── lint-xml.js │ └── text.js ├── rules │ ├── adjoining-classes.js │ ├── box-model.js │ ├── box-sizing.js │ ├── bulletproof-font-face.js │ ├── compatible-vendor-prefixes.js │ ├── display-property-grouping.js │ ├── duplicate-background-images.js │ ├── duplicate-properties.js │ ├── empty-rules.js │ ├── errors.js │ ├── fallback-colors.js │ ├── floats.js │ ├── font-faces.js │ ├── font-sizes.js │ ├── gradients.js │ ├── ids.js │ ├── import-ie-limit.js │ ├── import.js │ ├── important.js │ ├── known-properties.js │ ├── order-alphabetical.js │ ├── outline-none.js │ ├── overqualified-elements.js │ ├── performant-transitions.js │ ├── qualified-headings.js │ ├── regex-selectors.js │ ├── rules-count.js │ ├── selector-max-approaching.js │ ├── selector-max.js │ ├── selector-newline.js │ ├── shorthand.js │ ├── star-property-hack.js │ ├── text-indent.js │ ├── underscore-property-hack.js │ ├── unique-headings.js │ ├── universal-selector.js │ ├── unqualified-attributes.js │ ├── vendor-prefix.js │ └── zero-units.js └── worker │ └── Worker.js ├── tasks ├── changelog.js ├── test_rhino.js └── yuitest.js └── tests ├── .jshintrc ├── all-rules.js ├── cli ├── assets │ ├── apiStub.js │ └── data.js └── cli-common.js ├── core ├── CSSLint.js └── Reporter.js ├── css └── width-100.html ├── formatters ├── checkstyle-xml.js ├── compact.js ├── csslint-xml.js ├── json.js ├── junit-xml.js ├── lint-xml.js └── text.js ├── rules ├── adjoining-classes.js ├── box-model.js ├── box-sizing.js ├── bulletproof-font-face.js ├── compatible-vendor-prefixes.js ├── display-property-grouping.js ├── duplicate-background-images.js ├── duplicate-properties.js ├── empty-rules.js ├── errors.js ├── fallback-colors.js ├── floats.js ├── font-faces.js ├── font-sizes.js ├── gradients.js ├── ids.js ├── import-ie-limit.js ├── import.js ├── important.js ├── known-properties.js ├── order-alphabetical.js ├── outline-none.js ├── overqualified-elements.js ├── performant-transitions.js ├── qualified-headings.js ├── regex-selectors.js ├── selector-max-approaching.js ├── selector-max.js ├── selector-newline.js ├── shorthand.js ├── star-property-hack.js ├── text-indent.js ├── underscore-property-hack.js ├── unique-headings.js ├── universal-selector.js ├── unqualified-attributes.js ├── vendor-prefix.js └── zero-units.js ├── testrunner.htm └── testrunner.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text eol=lf 3 | *.jar binary 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /npm-debug.log 3 | csslint.pnproj 4 | 5 | # Diff files 6 | *.orig 7 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "es3": true, 6 | "forin": true, 7 | "immed": true, 8 | "indent": 4, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "noempty": true, 13 | "nonbsp": true, 14 | "quotmark": "double", 15 | "strict": true, 16 | "undef": true, 17 | "unused": true, 18 | "globals": { 19 | "CSSLint": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "0.10" 7 | - "4" 8 | - "6" 9 | - "7" 10 | 11 | matrix: 12 | fast_finish: true 13 | 14 | cache: 15 | directories: 16 | - node_modules 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor guidelines - CSSLint 2 | 3 | Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. 4 | 5 | # Contributor License Agreement 6 | 7 | A CLA is a document that specifies how a project is allowed to use your code. We've put a lot of work into creating a CLA that is simple, effective, and as clear as possible so that it doesn't disrupt contributions to CSS Lint. 8 | 9 | When you make a contribution to the CSS Lint project, you agree: 10 | 11 | * The code you wrote is your original work (you own the copyright) or you otherwise have the right to submit the work. 12 | * To grant the CSS Lint project a nonexclusive, irrevocable license to use your submitted code in any way. 13 | * You are capable of granting these rights for the contribution. 14 | 15 | By submitting a fix to CSS Lint you agree to the above statements. 16 | 17 | # Contributor Code of Conduct 18 | 19 | As contributors and maintainers of this project, and in the interest of 20 | fostering an open and welcoming community, we pledge to respect all people who 21 | contribute through reporting issues, posting feature requests, updating 22 | documentation, submitting pull requests or patches, and other activities. 23 | 24 | We are committed to making participation in this project a harassment-free 25 | experience for everyone, regardless of level of experience, gender, gender 26 | identity and expression, sexual orientation, disability, personal appearance, 27 | body size, race, ethnicity, age, religion, or nationality. 28 | 29 | Examples of unacceptable behavior by participants include: 30 | 31 | * The use of sexualized language or imagery 32 | * Personal attacks 33 | * Trolling or insulting/derogatory comments 34 | * Public or private harassment 35 | * Publishing other's private information, such as physical or electronic 36 | addresses, without explicit permission 37 | * Other unethical or unprofessional conduct 38 | 39 | Project maintainers have the right and responsibility to remove, edit, or 40 | reject comments, commits, code, wiki edits, issues, and other contributions 41 | that are not aligned to this Code of Conduct, or to ban temporarily or 42 | permanently any contributor for other behaviors that they deem inappropriate, 43 | threatening, offensive, or harmful. 44 | 45 | By adopting this Code of Conduct, project maintainers commit themselves to 46 | fairly and consistently applying these principles to every aspect of managing 47 | this project. Project maintainers who do not follow or enforce the Code of 48 | Conduct may be permanently removed from the project team. 49 | 50 | This Code of Conduct applies both within project spaces and in public spaces 51 | when an individual is representing the project or its community. 52 | 53 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 54 | reported by contacting a project maintainer at nicole AT stubbornella DOT org. All 55 | complaints will be reviewed and investigated and will result in a response that 56 | is deemed necessary and appropriate to the circumstances. Maintainers are 57 | obligated to maintain confidentiality with regard to the reporter of an 58 | incident. 59 | 60 | 61 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 62 | version 1.3.0, available at 63 | [http://contributor-covenant.org/version/1/3/0/][version] 64 | 65 | [homepage]: http://contributor-covenant.org 66 | [version]: http://contributor-covenant.org/version/1/3/0/ 67 | 68 | # Contributing technically 69 | 70 | * Check out the [Contributing wiki](https://github.com/CSSLint/csslint/wiki/Contributing) and [Developer Guidelines](https://github.com/CSSLint/csslint/wiki/Developer-Guide) first 71 | 72 | * To add properties that CSSLint recognizes, submit a patch to [CSSLint/parser-lib](https://github.com/CSSLint/parser-lib) 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CSSLint 2 | Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/csslint.svg)](https://www.npmjs.com/package/csslint) 2 | [![Build Status](https://img.shields.io/travis/CSSLint/csslint/master.svg)](https://travis-ci.org/CSSLint/csslint) 3 | [![Dependency Status](https://img.shields.io/david/CSSLint/csslint.svg)](https://david-dm.org/CSSLint/csslint) 4 | [![devDependency Status](https://img.shields.io/david/dev/CSSLint/csslint.svg)](https://david-dm.org/CSSLint/csslint#info=devDependencies) 5 | 6 | # CSSLint 7 | 8 | CSSLint is an open source CSS code quality tool originally written by 9 | [Nicholas C. Zakas](http://www.nczonline.net/) and 10 | [Nicole Sullivan](http://www.stubbornella.org/). It was released in June 2011 at 11 | the Velocity conference. 12 | 13 | A [lint](http://en.wikipedia.org/wiki/Lint_programming_tool) tool performs 14 | [static analysis](http://en.wikipedia.org/wiki/Static_code_analysis) of source 15 | code and flags patterns that might be errors or otherwise cause problems for the 16 | developer. 17 | 18 | CSSLint is a tool to help point out problems with your CSS code. It does basic 19 | syntax checking as well as applying a set of rules to the code that look for 20 | problematic patterns or signs of inefficiency. The rules are all pluggable, so 21 | you can easily write your own or omit ones you don't want. 22 | 23 | ## Integration 24 | 25 | ### Command Line Interface 26 | 27 | All about the command line interface for CSSLint. If you'd rather use a CLI 28 | program to verify your CSS instead of using the web site, then this guide is 29 | your best friend. 30 | https://github.com/CSSLint/csslint/wiki/Command-line-interface 31 | 32 | ### Build System 33 | 34 | Once you're familiar with the CSSLint command line interface, the next step is 35 | to integrate it into your build system. This guide walks through using CSSLint 36 | as part of your build. 37 | https://github.com/CSSLint/csslint/wiki/Build-System-Integration 38 | 39 | ### IDE 40 | 41 | You can integrate CSSLint into your favorite IDE to make checking your CSS code 42 | quality easy. In fact, some IDEs already have CSSLint built in. 43 | https://github.com/CSSLint/csslint/wiki/IDE-integration 44 | 45 | ## Rules 46 | 47 | Not sure why a rule is important? This guide talks about each of the CSSLint 48 | rules and explains how the rule is designed to improve your CSS. 49 | https://github.com/CSSLint/csslint/wiki/Rules 50 | 51 | ## Developer Guide 52 | 53 | If you want to contribute to the project, or even just tinker on your own, 54 | this guide explains how to get the source and work with it. 55 | https://github.com/CSSLint/csslint/wiki/Developer-Guide 56 | 57 | ## Contributors 58 | 59 | 1. Samori Gorse, https://twitter.com/shinuza (Rules, Non-zero Exit Code for CLI) 60 | 1. Eitan Konigsburg, https://twitter.com/eitanmk (Rhino CLI) 61 | 1. Ben Barber (Compatible Vendor Prefix Rule) 62 | 1. Eric Wendelin, http://eriwen.com (Output formatters) 63 | 1. Kasper Garnaes, http://reload.dk (Checkstyle XML format) 64 | 1. Gord Tanner, http://www.tinyhippos.com (CLI quiet option) 65 | 1. Hans-Peter Buniat, https://github.com/hpbuniat (Duplicate background image rule) 66 | 1. Dino Chiesa, https://github.com/DinoChiesa (Windows Script Host CLI) 67 | 1. Tomasz Oponowicz, https://github.com/tomasz-oponowicz (XML format and CLI fixes) 68 | 1. Julien Kernec'h, https://github.com/parallel (Fixed a rule) 69 | 1. Cillian de Róiste, https://plus.google.com/116480676247600483573/posts (Node CLI fixes) 70 | 1. Damien Sennm, https://github.com/topaxi (README fixes) 71 | 1. Jonathan Barnett, http://twitter.com/indieisaconcept (JUnit formatter) 72 | 1. Zach Leatherman, http://www.zachleat.com/ (bug fixes) 73 | 1. Philip Walton, http://philipwalton.com (Rules fixes, bug fixes) 74 | 1. Jeff Beck, http://www.jeffbeck.info (Rules fixes, bug fixes) 75 | 1. Jay Merrifield, https://github.com/fracmak (Rules fixes) 76 | 1. Michael Mattiacci, http://mattiacci.com (Rules fixes) 77 | 1. Jonathan Klein, http://www.jonathanklein.net (Bulletproof font-face rule) 78 | 1. Shannon Moeller, http://shannonmoeller.com (Embedded rulesets) 79 | 1. Nick Schonning, https://github.com/nschonni (Contributing.md, grunt build) 80 | 1. Jared Wyles, https://github.com/jaredwy (Managing pull requests, endless advice) 81 | 1. Scott Gonzalez, https://github.com/scottgonzalez (JSON config) 82 | -------------------------------------------------------------------------------- /demos/.csslintrc: -------------------------------------------------------------------------------- 1 | --errors=important 2 | --warnings=import -------------------------------------------------------------------------------- /demos/CSSLintDemo.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CSSLint Demo 6 | 11 | 12 | 13 | 14 | 15 |

CSSLint Demo

16 | 77 |
78 | 79 |

(You may want to keep the CSS kinda small, this could take a while.)

80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /demos/demo.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import url("booya.css") print,screen; 4 | @import "whatup.css" screen; 5 | @import "wicked.css"; 6 | 7 | @namespace "http://www.w3.org/1999/xhtml"; 8 | @namespace svg "http://www.w3.org/2000/svg"; 9 | 10 | li.inline #foo { 11 | background: rgba(234, 212, 200, 0.5) url("something.png"); 12 | display: inline; 13 | padding-left: 3px; 14 | padding-right: 7px; 15 | border-right: 1px dotted #066; 16 | } 17 | 18 | li.last.first { 19 | display: inline; 20 | padding-left: 3px !important; 21 | padding-right: 3px; 22 | border-right: 0px; 23 | } 24 | 25 | @media print { 26 | li.inline { 27 | color: black; 28 | } 29 | 30 | 31 | @charset "UTF-8"; 32 | 33 | @page { 34 | margin: 10%; 35 | counter-increment: page; 36 | 37 | @top-center { 38 | font-family: sans-serif; 39 | font-weight: bold; 40 | font-size: 2em; 41 | content: counter(page); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /demos/demo.js: -------------------------------------------------------------------------------- 1 | /* jshint browser:true */ 2 | 3 | (function() { 4 | "use strict"; 5 | 6 | window.onload = function() { 7 | document.body.onclick = function(event) { 8 | event = event || window.event; 9 | var target = event.target || event.srcElement, 10 | results, 11 | messages, 12 | i, 13 | len; 14 | 15 | function log(value, level) { 16 | var output = document.getElementById("output"); 17 | output.innerHTML += "" + value.replace(/ /g, " ") + "
"; 18 | } 19 | 20 | if (target.id === "lint-btn") { 21 | document.getElementById("output").innerHTML = ""; 22 | results = CSSLint.verify(document.getElementById("input").value); 23 | messages = results.messages; 24 | for (i = 0, len = messages.length; i < len; i++) { 25 | log(messages[i].message + " (line " + messages[i].line + ", col " + messages[i].col + ")", messages[i].type); 26 | } 27 | 28 | } 29 | 30 | }; 31 | }; 32 | })(); 33 | -------------------------------------------------------------------------------- /lib/js.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSSLint/csslint/49a748126ec0a6e4f6905a03c0eaece47ece04d3/lib/js.jar -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csslint", 3 | "version": "1.0.5", 4 | "description": "CSSLint", 5 | "author": "Nicole Sullivan", 6 | "contributors": [ 7 | "Nicholas C. Zakas" 8 | ], 9 | "homepage": "http://csslint.net/", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/CSSLint/csslint.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/CSSLint/csslint/issues" 16 | }, 17 | "license": "MIT", 18 | "main": "./dist/csslint-node.js", 19 | "bin": { 20 | "csslint": "./dist/cli.js" 21 | }, 22 | "scripts": { 23 | "test": "grunt test" 24 | }, 25 | "dependencies": { 26 | "clone": "~2.1.0", 27 | "parserlib": "~1.1.1" 28 | }, 29 | "devDependencies": { 30 | "grunt": "~1.0.1", 31 | "grunt-contrib-clean": "~1.0.0", 32 | "grunt-contrib-concat": "~1.0.0", 33 | "grunt-contrib-jshint": "~1.1.0", 34 | "grunt-contrib-watch": "~1.0.0", 35 | "grunt-include-replace": "~5.0.0", 36 | "load-grunt-tasks": "~3.5.0", 37 | "time-grunt": "~1.4.0", 38 | "yuitest": "~0.7.9" 39 | }, 40 | "engines": { 41 | "node": ">=0.10.0" 42 | }, 43 | "files": [ 44 | "dist/cli.js", 45 | "dist/csslint-node.js", 46 | "dist/csslint.js", 47 | "CHANGELOG", 48 | "LICENSE" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /src/cli/node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CSSLint Node.js Command Line Interface 3 | */ 4 | 5 | /* jshint node:true */ 6 | /* global cli */ 7 | /* exported CSSLint */ 8 | "use strict"; 9 | 10 | var fs = require("fs"), 11 | path = require("path"), 12 | CSSLint = require("./csslint-node").CSSLint; 13 | 14 | cli({ 15 | args: process.argv.slice(2), 16 | 17 | print: function(message) { 18 | fs.writeSync(1, message + "\n"); 19 | }, 20 | 21 | quit: function(code) { 22 | process.exit(code || 0); 23 | }, 24 | 25 | isDirectory: function(name) { 26 | try { 27 | return fs.statSync(name).isDirectory(); 28 | } catch (ex) { 29 | return false; 30 | } 31 | }, 32 | 33 | getFiles: function(dir) { 34 | var files = []; 35 | 36 | try { 37 | fs.statSync(dir); 38 | } catch (ex) { 39 | return []; 40 | } 41 | 42 | function traverse(dir, stack) { 43 | stack.push(dir); 44 | fs.readdirSync(stack.join("/")).forEach(function(file) { 45 | var path = stack.concat([file]).join("/"), 46 | stat = fs.statSync(path); 47 | 48 | if (file[0] === ".") { 49 | return; 50 | } else if (stat.isFile() && /\.css$/.test(file)) { 51 | files.push(path); 52 | } else if (stat.isDirectory()) { 53 | traverse(file, stack); 54 | } 55 | }); 56 | stack.pop(); 57 | } 58 | 59 | traverse(dir, []); 60 | 61 | return files; 62 | }, 63 | 64 | getWorkingDirectory: function() { 65 | return process.cwd(); 66 | }, 67 | 68 | getFullPath: function(filename) { 69 | return path.resolve(process.cwd(), filename); 70 | }, 71 | 72 | readFile: function(filename) { 73 | try { 74 | return fs.readFileSync(filename, "utf-8"); 75 | } catch (ex) { 76 | return ""; 77 | } 78 | } 79 | }); 80 | -------------------------------------------------------------------------------- /src/cli/rhino.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CSSLint Rhino Command Line Interface 3 | */ 4 | 5 | /* jshint rhino:true */ 6 | /* global cli, File */ 7 | 8 | importPackage(java.io); 9 | 10 | cli({ 11 | 12 | args: Array.prototype.concat.call(arguments), 13 | print: print, 14 | quit: quit, 15 | 16 | isDirectory: function(name) { 17 | "use strict"; 18 | var dir = new File(name); 19 | return dir.isDirectory(); 20 | }, 21 | 22 | getFiles: function(dir) { 23 | "use strict"; 24 | var files = []; 25 | 26 | function traverse(dir) { 27 | var dirList = dir.listFiles(); 28 | dirList.forEach(function (file) { 29 | if (/\.css$/.test(file)) { 30 | files.push(file.toString()); 31 | } else if (file.isDirectory()) { 32 | traverse(file); 33 | } 34 | }); 35 | } 36 | 37 | traverse(new File(dir)); 38 | 39 | return files; 40 | }, 41 | 42 | getWorkingDirectory: function() { 43 | "use strict"; 44 | return (new File(".")).getCanonicalPath(); 45 | }, 46 | 47 | getFullPath: function(filename) { 48 | "use strict"; 49 | return (new File(filename)).getCanonicalPath(); 50 | }, 51 | 52 | readFile: function(filename) { 53 | "use strict"; 54 | try { 55 | return readFile(filename); 56 | } catch (ex) { 57 | return ""; 58 | } 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /src/core/Util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility functions that make life easier. 3 | */ 4 | CSSLint.Util = { 5 | /* 6 | * Adds all properties from supplier onto receiver, 7 | * overwriting if the same name already exists on 8 | * receiver. 9 | * @param {Object} The object to receive the properties. 10 | * @param {Object} The object to provide the properties. 11 | * @return {Object} The receiver 12 | */ 13 | mix: function(receiver, supplier) { 14 | "use strict"; 15 | var prop; 16 | 17 | for (prop in supplier) { 18 | if (supplier.hasOwnProperty(prop)) { 19 | receiver[prop] = supplier[prop]; 20 | } 21 | } 22 | 23 | return prop; 24 | }, 25 | 26 | /* 27 | * Polyfill for array indexOf() method. 28 | * @param {Array} values The array to search. 29 | * @param {Variant} value The value to search for. 30 | * @return {int} The index of the value if found, -1 if not. 31 | */ 32 | indexOf: function(values, value) { 33 | "use strict"; 34 | if (values.indexOf) { 35 | return values.indexOf(value); 36 | } else { 37 | for (var i=0, len=values.length; i < len; i++) { 38 | if (values[i] === value) { 39 | return i; 40 | } 41 | } 42 | return -1; 43 | } 44 | }, 45 | 46 | /* 47 | * Polyfill for array forEach() method. 48 | * @param {Array} values The array to operate on. 49 | * @param {Function} func The function to call on each item. 50 | * @return {void} 51 | */ 52 | forEach: function(values, func) { 53 | "use strict"; 54 | if (values.forEach) { 55 | return values.forEach(func); 56 | } else { 57 | for (var i=0, len=values.length; i < len; i++) { 58 | func(values[i], i, values); 59 | } 60 | } 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/formatters/checkstyle-xml.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | 4 | /** 5 | * Replace special characters before write to output. 6 | * 7 | * Rules: 8 | * - single quotes is the escape sequence for double-quotes 9 | * - & is the escape sequence for & 10 | * - < is the escape sequence for < 11 | * - > is the escape sequence for > 12 | * 13 | * @param {String} message to escape 14 | * @return escaped message as {String} 15 | */ 16 | var xmlEscape = function(str) { 17 | if (!str || str.constructor !== String) { 18 | return ""; 19 | } 20 | 21 | return str.replace(/["&><]/g, function(match) { 22 | switch (match) { 23 | case "\"": 24 | return """; 25 | case "&": 26 | return "&"; 27 | case "<": 28 | return "<"; 29 | case ">": 30 | return ">"; 31 | } 32 | }); 33 | }; 34 | 35 | CSSLint.addFormatter({ 36 | // format information 37 | id: "checkstyle-xml", 38 | name: "Checkstyle XML format", 39 | 40 | /** 41 | * Return opening root XML tag. 42 | * @return {String} to prepend before all results 43 | */ 44 | startFormat: function() { 45 | return ""; 46 | }, 47 | 48 | /** 49 | * Return closing root XML tag. 50 | * @return {String} to append after all results 51 | */ 52 | endFormat: function() { 53 | return ""; 54 | }, 55 | 56 | /** 57 | * Returns message when there is a file read error. 58 | * @param {String} filename The name of the file that caused the error. 59 | * @param {String} message The error message 60 | * @return {String} The error message. 61 | */ 62 | readError: function(filename, message) { 63 | return ""; 64 | }, 65 | 66 | /** 67 | * Given CSS Lint results for a file, return output for this format. 68 | * @param results {Object} with error and warning messages 69 | * @param filename {String} relative file path 70 | * @param options {Object} (UNUSED for now) specifies special handling of output 71 | * @return {String} output for results 72 | */ 73 | formatResults: function(results, filename/*, options*/) { 74 | var messages = results.messages, 75 | output = []; 76 | 77 | /** 78 | * Generate a source string for a rule. 79 | * Checkstyle source strings usually resemble Java class names e.g 80 | * net.csslint.SomeRuleName 81 | * @param {Object} rule 82 | * @return rule source as {String} 83 | */ 84 | var generateSource = function(rule) { 85 | if (!rule || !("name" in rule)) { 86 | return ""; 87 | } 88 | return "net.csslint." + rule.name.replace(/\s/g, ""); 89 | }; 90 | 91 | 92 | if (messages.length > 0) { 93 | output.push(""); 94 | CSSLint.Util.forEach(messages, function (message) { 95 | // ignore rollups for now 96 | if (!message.rollup) { 97 | output.push(""); 99 | } 100 | }); 101 | output.push(""); 102 | } 103 | 104 | return output.join(""); 105 | } 106 | }); 107 | 108 | }()); 109 | -------------------------------------------------------------------------------- /src/formatters/compact.js: -------------------------------------------------------------------------------- 1 | CSSLint.addFormatter({ 2 | // format information 3 | id: "compact", 4 | name: "Compact, 'porcelain' format", 5 | 6 | /** 7 | * Return content to be printed before all file results. 8 | * @return {String} to prepend before all results 9 | */ 10 | startFormat: function() { 11 | "use strict"; 12 | return ""; 13 | }, 14 | 15 | /** 16 | * Return content to be printed after all file results. 17 | * @return {String} to append after all results 18 | */ 19 | endFormat: function() { 20 | "use strict"; 21 | return ""; 22 | }, 23 | 24 | /** 25 | * Given CSS Lint results for a file, return output for this format. 26 | * @param results {Object} with error and warning messages 27 | * @param filename {String} relative file path 28 | * @param options {Object} (Optional) specifies special handling of output 29 | * @return {String} output for results 30 | */ 31 | formatResults: function(results, filename, options) { 32 | "use strict"; 33 | var messages = results.messages, 34 | output = ""; 35 | options = options || {}; 36 | 37 | /** 38 | * Capitalize and return given string. 39 | * @param str {String} to capitalize 40 | * @return {String} capitalized 41 | */ 42 | var capitalize = function(str) { 43 | return str.charAt(0).toUpperCase() + str.slice(1); 44 | }; 45 | 46 | if (messages.length === 0) { 47 | return options.quiet ? "" : filename + ": Lint Free!"; 48 | } 49 | 50 | CSSLint.Util.forEach(messages, function(message) { 51 | if (message.rollup) { 52 | output += filename + ": " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n"; 53 | } else { 54 | output += filename + ": line " + message.line + 55 | ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n"; 56 | } 57 | }); 58 | 59 | return output; 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /src/formatters/csslint-xml.js: -------------------------------------------------------------------------------- 1 | CSSLint.addFormatter({ 2 | // format information 3 | id: "csslint-xml", 4 | name: "CSSLint XML format", 5 | 6 | /** 7 | * Return opening root XML tag. 8 | * @return {String} to prepend before all results 9 | */ 10 | startFormat: function() { 11 | "use strict"; 12 | return ""; 13 | }, 14 | 15 | /** 16 | * Return closing root XML tag. 17 | * @return {String} to append after all results 18 | */ 19 | endFormat: function() { 20 | "use strict"; 21 | return ""; 22 | }, 23 | 24 | /** 25 | * Given CSS Lint results for a file, return output for this format. 26 | * @param results {Object} with error and warning messages 27 | * @param filename {String} relative file path 28 | * @param options {Object} (UNUSED for now) specifies special handling of output 29 | * @return {String} output for results 30 | */ 31 | formatResults: function(results, filename/*, options*/) { 32 | "use strict"; 33 | var messages = results.messages, 34 | output = []; 35 | 36 | /** 37 | * Replace special characters before write to output. 38 | * 39 | * Rules: 40 | * - single quotes is the escape sequence for double-quotes 41 | * - & is the escape sequence for & 42 | * - < is the escape sequence for < 43 | * - > is the escape sequence for > 44 | * 45 | * @param {String} message to escape 46 | * @return escaped message as {String} 47 | */ 48 | var escapeSpecialCharacters = function(str) { 49 | if (!str || str.constructor !== String) { 50 | return ""; 51 | } 52 | return str.replace(/"/g, "'").replace(/&/g, "&").replace(//g, ">"); 53 | }; 54 | 55 | if (messages.length > 0) { 56 | output.push(""); 57 | CSSLint.Util.forEach(messages, function (message) { 58 | if (message.rollup) { 59 | output.push(""); 60 | } else { 61 | output.push(""); 63 | } 64 | }); 65 | output.push(""); 66 | } 67 | 68 | return output.join(""); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /src/formatters/json.js: -------------------------------------------------------------------------------- 1 | /* globals JSON: true */ 2 | 3 | CSSLint.addFormatter({ 4 | // format information 5 | id: "json", 6 | name: "JSON", 7 | 8 | /** 9 | * Return content to be printed before all file results. 10 | * @return {String} to prepend before all results 11 | */ 12 | startFormat: function() { 13 | "use strict"; 14 | this.json = []; 15 | return ""; 16 | }, 17 | 18 | /** 19 | * Return content to be printed after all file results. 20 | * @return {String} to append after all results 21 | */ 22 | endFormat: function() { 23 | "use strict"; 24 | var ret = ""; 25 | if (this.json.length > 0) { 26 | if (this.json.length === 1) { 27 | ret = JSON.stringify(this.json[0]); 28 | } else { 29 | ret = JSON.stringify(this.json); 30 | } 31 | } 32 | return ret; 33 | }, 34 | 35 | /** 36 | * Given CSS Lint results for a file, return output for this format. 37 | * @param results {Object} with error and warning messages 38 | * @param filename {String} relative file path (Unused) 39 | * @return {String} output for results 40 | */ 41 | formatResults: function(results, filename, options) { 42 | "use strict"; 43 | if (results.messages.length > 0 || !options.quiet) { 44 | this.json.push({ 45 | filename: filename, 46 | messages: results.messages, 47 | stats: results.stats 48 | }); 49 | } 50 | return ""; 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /src/formatters/junit-xml.js: -------------------------------------------------------------------------------- 1 | CSSLint.addFormatter({ 2 | // format information 3 | id: "junit-xml", 4 | name: "JUNIT XML format", 5 | 6 | /** 7 | * Return opening root XML tag. 8 | * @return {String} to prepend before all results 9 | */ 10 | startFormat: function() { 11 | "use strict"; 12 | return ""; 13 | }, 14 | 15 | /** 16 | * Return closing root XML tag. 17 | * @return {String} to append after all results 18 | */ 19 | endFormat: function() { 20 | "use strict"; 21 | return ""; 22 | }, 23 | 24 | /** 25 | * Given CSS Lint results for a file, return output for this format. 26 | * @param results {Object} with error and warning messages 27 | * @param filename {String} relative file path 28 | * @param options {Object} (UNUSED for now) specifies special handling of output 29 | * @return {String} output for results 30 | */ 31 | formatResults: function(results, filename/*, options*/) { 32 | "use strict"; 33 | 34 | var messages = results.messages, 35 | output = [], 36 | tests = { 37 | "error": 0, 38 | "failure": 0 39 | }; 40 | 41 | /** 42 | * Generate a source string for a rule. 43 | * JUNIT source strings usually resemble Java class names e.g 44 | * net.csslint.SomeRuleName 45 | * @param {Object} rule 46 | * @return rule source as {String} 47 | */ 48 | var generateSource = function(rule) { 49 | if (!rule || !("name" in rule)) { 50 | return ""; 51 | } 52 | return "net.csslint." + rule.name.replace(/\s/g, ""); 53 | }; 54 | 55 | /** 56 | * Replace special characters before write to output. 57 | * 58 | * Rules: 59 | * - single quotes is the escape sequence for double-quotes 60 | * - < is the escape sequence for < 61 | * - > is the escape sequence for > 62 | * 63 | * @param {String} message to escape 64 | * @return escaped message as {String} 65 | */ 66 | var escapeSpecialCharacters = function(str) { 67 | 68 | if (!str || str.constructor !== String) { 69 | return ""; 70 | } 71 | 72 | return str.replace(/"/g, "'").replace(//g, ">"); 73 | 74 | }; 75 | 76 | if (messages.length > 0) { 77 | 78 | messages.forEach(function (message) { 79 | 80 | // since junit has no warning class 81 | // all issues as errors 82 | var type = message.type === "warning" ? "error" : message.type; 83 | 84 | // ignore rollups for now 85 | if (!message.rollup) { 86 | 87 | // build the test case separately, once joined 88 | // we'll add it to a custom array filtered by type 89 | output.push(""); 90 | output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); 91 | output.push(""); 92 | 93 | tests[type] += 1; 94 | 95 | } 96 | 97 | }); 98 | 99 | output.unshift(""); 100 | output.push(""); 101 | 102 | } 103 | 104 | return output.join(""); 105 | 106 | } 107 | }); 108 | -------------------------------------------------------------------------------- /src/formatters/lint-xml.js: -------------------------------------------------------------------------------- 1 | CSSLint.addFormatter({ 2 | // format information 3 | id: "lint-xml", 4 | name: "Lint XML format", 5 | 6 | /** 7 | * Return opening root XML tag. 8 | * @return {String} to prepend before all results 9 | */ 10 | startFormat: function() { 11 | "use strict"; 12 | return ""; 13 | }, 14 | 15 | /** 16 | * Return closing root XML tag. 17 | * @return {String} to append after all results 18 | */ 19 | endFormat: function() { 20 | "use strict"; 21 | return ""; 22 | }, 23 | 24 | /** 25 | * Given CSS Lint results for a file, return output for this format. 26 | * @param results {Object} with error and warning messages 27 | * @param filename {String} relative file path 28 | * @param options {Object} (UNUSED for now) specifies special handling of output 29 | * @return {String} output for results 30 | */ 31 | formatResults: function(results, filename/*, options*/) { 32 | "use strict"; 33 | var messages = results.messages, 34 | output = []; 35 | 36 | /** 37 | * Replace special characters before write to output. 38 | * 39 | * Rules: 40 | * - single quotes is the escape sequence for double-quotes 41 | * - & is the escape sequence for & 42 | * - < is the escape sequence for < 43 | * - > is the escape sequence for > 44 | * 45 | * @param {String} message to escape 46 | * @return escaped message as {String} 47 | */ 48 | var escapeSpecialCharacters = function(str) { 49 | if (!str || str.constructor !== String) { 50 | return ""; 51 | } 52 | return str.replace(/"/g, "'").replace(/&/g, "&").replace(//g, ">"); 53 | }; 54 | 55 | if (messages.length > 0) { 56 | 57 | output.push(""); 58 | CSSLint.Util.forEach(messages, function (message) { 59 | if (message.rollup) { 60 | output.push(""); 61 | } else { 62 | var rule = ""; 63 | if (message.rule && message.rule.id) { 64 | rule = "rule=\"" + escapeSpecialCharacters(message.rule.id) + "\" "; 65 | } 66 | output.push(""); 68 | } 69 | }); 70 | output.push(""); 71 | } 72 | 73 | return output.join(""); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /src/formatters/text.js: -------------------------------------------------------------------------------- 1 | CSSLint.addFormatter({ 2 | // format information 3 | id: "text", 4 | name: "Plain Text", 5 | 6 | /** 7 | * Return content to be printed before all file results. 8 | * @return {String} to prepend before all results 9 | */ 10 | startFormat: function() { 11 | "use strict"; 12 | return ""; 13 | }, 14 | 15 | /** 16 | * Return content to be printed after all file results. 17 | * @return {String} to append after all results 18 | */ 19 | endFormat: function() { 20 | "use strict"; 21 | return ""; 22 | }, 23 | 24 | /** 25 | * Given CSS Lint results for a file, return output for this format. 26 | * @param results {Object} with error and warning messages 27 | * @param filename {String} relative file path 28 | * @param options {Object} (Optional) specifies special handling of output 29 | * @return {String} output for results 30 | */ 31 | formatResults: function(results, filename, options) { 32 | "use strict"; 33 | var messages = results.messages, 34 | output = ""; 35 | options = options || {}; 36 | 37 | if (messages.length === 0) { 38 | return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + "."; 39 | } 40 | 41 | output = "\n\ncsslint: There "; 42 | if (messages.length === 1) { 43 | output += "is 1 problem"; 44 | } else { 45 | output += "are " + messages.length + " problems"; 46 | } 47 | output += " in " + filename + "."; 48 | 49 | var pos = filename.lastIndexOf("/"), 50 | shortFilename = filename; 51 | 52 | if (pos === -1) { 53 | pos = filename.lastIndexOf("\\"); 54 | } 55 | if (pos > -1) { 56 | shortFilename = filename.substring(pos+1); 57 | } 58 | 59 | CSSLint.Util.forEach(messages, function (message, i) { 60 | output = output + "\n\n" + shortFilename; 61 | if (message.rollup) { 62 | output += "\n" + (i+1) + ": " + message.type; 63 | output += "\n" + message.message; 64 | } else { 65 | output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col; 66 | output += "\n" + message.message; 67 | output += "\n" + message.evidence; 68 | } 69 | }); 70 | 71 | return output; 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /src/rules/adjoining-classes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use adjoining classes (.foo.bar). 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "adjoining-classes", 9 | name: "Disallow adjoining classes", 10 | desc: "Don't use adjoining classes.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes", 12 | browsers: "IE6", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | parser.addListener("startrule", function(event) { 19 | var selectors = event.selectors, 20 | selector, 21 | part, 22 | modifier, 23 | classCount, 24 | i, j, k; 25 | 26 | for (i=0; i < selectors.length; i++) { 27 | selector = selectors[i]; 28 | for (j=0; j < selector.parts.length; j++) { 29 | part = selector.parts[j]; 30 | if (part.type === parser.SELECTOR_PART_TYPE) { 31 | classCount = 0; 32 | for (k=0; k < part.modifiers.length; k++) { 33 | modifier = part.modifiers[k]; 34 | if (modifier.type === "class") { 35 | classCount++; 36 | } 37 | if (classCount > 1){ 38 | reporter.report("Adjoining classes: "+selectors[i].text, part.line, part.col, rule); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | }); 45 | } 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /src/rules/box-model.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use width or height when using padding or border. 3 | */ 4 | CSSLint.addRule({ 5 | 6 | // rule information 7 | id: "box-model", 8 | name: "Beware of broken box size", 9 | desc: "Don't use width or height when using padding or border.", 10 | url: "https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size", 11 | browsers: "All", 12 | 13 | // initialization 14 | init: function(parser, reporter) { 15 | "use strict"; 16 | var rule = this, 17 | widthProperties = { 18 | border: 1, 19 | "border-left": 1, 20 | "border-right": 1, 21 | padding: 1, 22 | "padding-left": 1, 23 | "padding-right": 1 24 | }, 25 | heightProperties = { 26 | border: 1, 27 | "border-bottom": 1, 28 | "border-top": 1, 29 | padding: 1, 30 | "padding-bottom": 1, 31 | "padding-top": 1 32 | }, 33 | properties, 34 | boxSizing = false; 35 | 36 | function startRule() { 37 | properties = {}; 38 | boxSizing = false; 39 | } 40 | 41 | function endRule() { 42 | var prop, value; 43 | 44 | if (!boxSizing) { 45 | if (properties.height) { 46 | for (prop in heightProperties) { 47 | if (heightProperties.hasOwnProperty(prop) && properties[prop]) { 48 | value = properties[prop].value; 49 | // special case for padding 50 | if (!(prop === "padding" && value.parts.length === 2 && value.parts[0].value === 0)) { 51 | reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); 52 | } 53 | } 54 | } 55 | } 56 | 57 | if (properties.width) { 58 | for (prop in widthProperties) { 59 | if (widthProperties.hasOwnProperty(prop) && properties[prop]) { 60 | value = properties[prop].value; 61 | 62 | if (!(prop === "padding" && value.parts.length === 2 && value.parts[1].value === 0)) { 63 | reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | parser.addListener("startrule", startRule); 72 | parser.addListener("startfontface", startRule); 73 | parser.addListener("startpage", startRule); 74 | parser.addListener("startpagemargin", startRule); 75 | parser.addListener("startkeyframerule", startRule); 76 | parser.addListener("startviewport", startRule); 77 | 78 | parser.addListener("property", function(event) { 79 | var name = event.property.text.toLowerCase(); 80 | 81 | if (heightProperties[name] || widthProperties[name]) { 82 | if (!/^0\S*$/.test(event.value) && !(name === "border" && event.value.toString() === "none")) { 83 | properties[name] = { 84 | line: event.property.line, 85 | col: event.property.col, 86 | value: event.value 87 | }; 88 | } 89 | } else { 90 | if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)) { 91 | properties[name] = 1; 92 | } else if (name === "box-sizing") { 93 | boxSizing = true; 94 | } 95 | } 96 | 97 | }); 98 | 99 | parser.addListener("endrule", endRule); 100 | parser.addListener("endfontface", endRule); 101 | parser.addListener("endpage", endRule); 102 | parser.addListener("endpagemargin", endRule); 103 | parser.addListener("endkeyframerule", endRule); 104 | parser.addListener("endviewport", endRule); 105 | } 106 | 107 | }); 108 | -------------------------------------------------------------------------------- /src/rules/box-sizing.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: box-sizing doesn't work in IE6 and IE7. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "box-sizing", 9 | name: "Disallow use of box-sizing", 10 | desc: "The box-sizing properties isn't supported in IE6 and IE7.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing", 12 | browsers: "IE6, IE7", 13 | tags: ["Compatibility"], 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this; 19 | 20 | parser.addListener("property", function(event) { 21 | var name = event.property.text.toLowerCase(); 22 | 23 | if (name === "box-sizing") { 24 | reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule); 25 | } 26 | }); 27 | } 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/rules/bulletproof-font-face.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE 3 | * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax) 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "bulletproof-font-face", 10 | name: "Use the bulletproof @font-face syntax", 11 | desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).", 12 | url: "https://github.com/CSSLint/csslint/wiki/Bulletproof-font-face", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this, 19 | fontFaceRule = false, 20 | firstSrc = true, 21 | ruleFailed = false, 22 | line, col; 23 | 24 | // Mark the start of a @font-face declaration so we only test properties inside it 25 | parser.addListener("startfontface", function() { 26 | fontFaceRule = true; 27 | }); 28 | 29 | parser.addListener("property", function(event) { 30 | // If we aren't inside an @font-face declaration then just return 31 | if (!fontFaceRule) { 32 | return; 33 | } 34 | 35 | var propertyName = event.property.toString().toLowerCase(), 36 | value = event.value.toString(); 37 | 38 | // Set the line and col numbers for use in the endfontface listener 39 | line = event.line; 40 | col = event.col; 41 | 42 | // This is the property that we care about, we can ignore the rest 43 | if (propertyName === "src") { 44 | var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i; 45 | 46 | // We need to handle the advanced syntax with two src properties 47 | if (!value.match(regex) && firstSrc) { 48 | ruleFailed = true; 49 | firstSrc = false; 50 | } else if (value.match(regex) && !firstSrc) { 51 | ruleFailed = false; 52 | } 53 | } 54 | 55 | 56 | }); 57 | 58 | // Back to normal rules that we don't need to test 59 | parser.addListener("endfontface", function() { 60 | fontFaceRule = false; 61 | 62 | if (ruleFailed) { 63 | reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule); 64 | } 65 | }); 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /src/rules/duplicate-background-images.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Disallow duplicate background-images (using url). 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "duplicate-background-images", 9 | name: "Disallow duplicate background images", 10 | desc: "Every background-image should be unique. Use a common class for e.g. sprites.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this, 18 | stack = {}; 19 | 20 | parser.addListener("property", function(event) { 21 | var name = event.property.text, 22 | value = event.value, 23 | i, len; 24 | 25 | if (name.match(/background/i)) { 26 | for (i=0, len=value.parts.length; i < len; i++) { 27 | if (value.parts[i].type === "uri") { 28 | if (typeof stack[value.parts[i].uri] === "undefined") { 29 | stack[value.parts[i].uri] = event; 30 | } else { 31 | reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule); 32 | } 33 | } 34 | } 35 | } 36 | }); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /src/rules/duplicate-properties.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Duplicate properties must appear one after the other. If an already-defined 3 | * property appears somewhere else in the rule, then it's likely an error. 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "duplicate-properties", 10 | name: "Disallow duplicate properties", 11 | desc: "Duplicate properties must appear one after the other.", 12 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this, 19 | properties, 20 | lastProperty; 21 | 22 | function startRule() { 23 | properties = {}; 24 | } 25 | 26 | parser.addListener("startrule", startRule); 27 | parser.addListener("startfontface", startRule); 28 | parser.addListener("startpage", startRule); 29 | parser.addListener("startpagemargin", startRule); 30 | parser.addListener("startkeyframerule", startRule); 31 | parser.addListener("startviewport", startRule); 32 | 33 | parser.addListener("property", function(event) { 34 | var property = event.property, 35 | name = property.text.toLowerCase(); 36 | 37 | if (properties[name] && (lastProperty !== name || properties[name] === event.value.text)) { 38 | reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); 39 | } 40 | 41 | properties[name] = event.value.text; 42 | lastProperty = name; 43 | 44 | }); 45 | 46 | 47 | } 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /src/rules/empty-rules.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Style rules without any properties defined should be removed. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "empty-rules", 9 | name: "Disallow empty rules", 10 | desc: "Rules without any properties specified should be removed.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this, 18 | count = 0; 19 | 20 | parser.addListener("startrule", function() { 21 | count=0; 22 | }); 23 | 24 | parser.addListener("property", function() { 25 | count++; 26 | }); 27 | 28 | parser.addListener("endrule", function(event) { 29 | var selectors = event.selectors; 30 | 31 | if (count === 0) { 32 | reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule); 33 | } 34 | }); 35 | } 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /src/rules/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: There should be no syntax errors. (Duh.) 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "errors", 9 | name: "Parsing Errors", 10 | desc: "This rule looks for recoverable syntax errors.", 11 | browsers: "All", 12 | 13 | // initialization 14 | init: function(parser, reporter) { 15 | "use strict"; 16 | var rule = this; 17 | 18 | parser.addListener("error", function(event) { 19 | reporter.error(event.message, event.line, event.col, rule); 20 | }); 21 | 22 | } 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/rules/fallback-colors.js: -------------------------------------------------------------------------------- 1 | CSSLint.addRule({ 2 | 3 | // rule information 4 | id: "fallback-colors", 5 | name: "Require fallback colors", 6 | desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.", 7 | url: "https://github.com/CSSLint/csslint/wiki/Require-fallback-colors", 8 | browsers: "IE6,IE7,IE8", 9 | 10 | // initialization 11 | init: function(parser, reporter) { 12 | "use strict"; 13 | var rule = this, 14 | lastProperty, 15 | propertiesToCheck = { 16 | color: 1, 17 | background: 1, 18 | "border-color": 1, 19 | "border-top-color": 1, 20 | "border-right-color": 1, 21 | "border-bottom-color": 1, 22 | "border-left-color": 1, 23 | border: 1, 24 | "border-top": 1, 25 | "border-right": 1, 26 | "border-bottom": 1, 27 | "border-left": 1, 28 | "background-color": 1 29 | }; 30 | 31 | function startRule() { 32 | lastProperty = null; 33 | } 34 | 35 | parser.addListener("startrule", startRule); 36 | parser.addListener("startfontface", startRule); 37 | parser.addListener("startpage", startRule); 38 | parser.addListener("startpagemargin", startRule); 39 | parser.addListener("startkeyframerule", startRule); 40 | parser.addListener("startviewport", startRule); 41 | 42 | parser.addListener("property", function(event) { 43 | var property = event.property, 44 | name = property.text.toLowerCase(), 45 | parts = event.value.parts, 46 | i = 0, 47 | colorType = "", 48 | len = parts.length; 49 | 50 | if (propertiesToCheck[name]) { 51 | while (i < len) { 52 | if (parts[i].type === "color") { 53 | if ("alpha" in parts[i] || "hue" in parts[i]) { 54 | 55 | if (/([^\)]+)\(/.test(parts[i])) { 56 | colorType = RegExp.$1.toUpperCase(); 57 | } 58 | 59 | if (!lastProperty || (lastProperty.property.text.toLowerCase() !== name || lastProperty.colorType !== "compat")) { 60 | reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule); 61 | } 62 | } else { 63 | event.colorType = "compat"; 64 | } 65 | } 66 | 67 | i++; 68 | } 69 | } 70 | 71 | lastProperty = event; 72 | }); 73 | 74 | } 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /src/rules/floats.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: You shouldn't use more than 10 floats. If you do, there's probably 3 | * room for some abstraction. 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "floats", 10 | name: "Disallow too many floats", 11 | desc: "This rule tests if the float property is used too many times", 12 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this; 19 | var count = 0; 20 | 21 | // count how many times "float" is used 22 | parser.addListener("property", function(event) { 23 | if (!reporter.isIgnored(event.property.line)) { 24 | if (event.property.text.toLowerCase() === "float" && 25 | event.value.text.toLowerCase() !== "none") { 26 | count++; 27 | } 28 | } 29 | }); 30 | 31 | // report the results 32 | parser.addListener("endstylesheet", function() { 33 | reporter.stat("floats", count); 34 | if (count >= 10) { 35 | reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule); 36 | } 37 | }); 38 | } 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /src/rules/font-faces.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Avoid too many @font-face declarations in the same stylesheet. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "font-faces", 9 | name: "Don't use too many web fonts", 10 | desc: "Too many different web fonts in the same stylesheet.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this, 18 | count = 0; 19 | 20 | 21 | parser.addListener("startfontface", function(event) { 22 | if (!reporter.isIgnored(event.line)) { 23 | count++; 24 | } 25 | }); 26 | 27 | parser.addListener("endstylesheet", function() { 28 | if (count > 5) { 29 | reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule); 30 | } 31 | }); 32 | } 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /src/rules/font-sizes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: You shouldn't need more than 9 font-size declarations. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "font-sizes", 9 | name: "Disallow too many font sizes", 10 | desc: "Checks the number of font-size declarations.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this, 18 | count = 0; 19 | 20 | // check for use of "font-size" 21 | parser.addListener("property", function(event) { 22 | if (!reporter.isIgnored(event.property.line)) { 23 | if (event.property.toString() === "font-size") { 24 | count++; 25 | } 26 | } 27 | }); 28 | 29 | // report the results 30 | parser.addListener("endstylesheet", function() { 31 | reporter.stat("font-sizes", count); 32 | if (count >= 10) { 33 | reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule); 34 | } 35 | }); 36 | } 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /src/rules/gradients.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: When using a vendor-prefixed gradient, make sure to use them all. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "gradients", 9 | name: "Require all gradient definitions", 10 | desc: "When using a vendor-prefixed gradient, make sure to use them all.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this, 18 | gradients; 19 | 20 | parser.addListener("startrule", function() { 21 | gradients = { 22 | moz: 0, 23 | webkit: 0, 24 | oldWebkit: 0, 25 | o: 0 26 | }; 27 | }); 28 | 29 | parser.addListener("property", function(event) { 30 | 31 | if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)) { 32 | gradients[RegExp.$1] = 1; 33 | } else if (/\-webkit\-gradient/i.test(event.value)) { 34 | gradients.oldWebkit = 1; 35 | } 36 | 37 | }); 38 | 39 | parser.addListener("endrule", function(event) { 40 | var missing = []; 41 | 42 | if (!gradients.moz) { 43 | missing.push("Firefox 3.6+"); 44 | } 45 | 46 | if (!gradients.webkit) { 47 | missing.push("Webkit (Safari 5+, Chrome)"); 48 | } 49 | 50 | if (!gradients.oldWebkit) { 51 | missing.push("Old Webkit (Safari 4+, Chrome)"); 52 | } 53 | 54 | if (!gradients.o) { 55 | missing.push("Opera 11.1+"); 56 | } 57 | 58 | if (missing.length && missing.length < 4) { 59 | reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); 60 | } 61 | 62 | }); 63 | 64 | } 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /src/rules/ids.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use IDs for selectors. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "ids", 9 | name: "Disallow IDs in selectors", 10 | desc: "Selectors should not contain IDs.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | parser.addListener("startrule", function(event) { 19 | var selectors = event.selectors, 20 | selector, 21 | part, 22 | modifier, 23 | idCount, 24 | i, j, k; 25 | 26 | for (i=0; i < selectors.length; i++) { 27 | selector = selectors[i]; 28 | idCount = 0; 29 | 30 | for (j=0; j < selector.parts.length; j++) { 31 | part = selector.parts[j]; 32 | if (part.type === parser.SELECTOR_PART_TYPE) { 33 | for (k=0; k < part.modifiers.length; k++) { 34 | modifier = part.modifiers[k]; 35 | if (modifier.type === "id") { 36 | idCount++; 37 | } 38 | } 39 | } 40 | } 41 | 42 | if (idCount === 1) { 43 | reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule); 44 | } else if (idCount > 1) { 45 | reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule); 46 | } 47 | } 48 | 49 | }); 50 | } 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /src/rules/import-ie-limit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: IE6-9 supports up to 31 stylesheet import. 3 | * Reference: 4 | * http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx 5 | */ 6 | 7 | CSSLint.addRule({ 8 | 9 | // rule information 10 | id: "import-ie-limit", 11 | name: "@import limit on IE6-IE9", 12 | desc: "IE6-9 supports up to 31 @import per stylesheet", 13 | browsers: "IE6, IE7, IE8, IE9", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this, 19 | MAX_IMPORT_COUNT = 31, 20 | count = 0; 21 | 22 | function startPage() { 23 | count = 0; 24 | } 25 | 26 | parser.addListener("startpage", startPage); 27 | 28 | parser.addListener("import", function() { 29 | count++; 30 | }); 31 | 32 | parser.addListener("endstylesheet", function() { 33 | if (count > MAX_IMPORT_COUNT) { 34 | reporter.rollupError( 35 | "Too many @import rules (" + count + "). IE6-9 supports up to 31 import per stylesheet.", 36 | rule 37 | ); 38 | } 39 | }); 40 | } 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /src/rules/import.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use @import, use instead. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "import", 9 | name: "Disallow @import", 10 | desc: "Don't use @import, use instead.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-%40import", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | 19 | parser.addListener("import", function(event) { 20 | reporter.report("@import prevents parallel downloads, use instead.", event.line, event.col, rule); 21 | }); 22 | 23 | } 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /src/rules/important.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Make sure !important is not overused, this could lead to specificity 3 | * war. Display a warning on !important declarations, an error if it's 4 | * used more at least 10 times. 5 | */ 6 | 7 | CSSLint.addRule({ 8 | 9 | // rule information 10 | id: "important", 11 | name: "Disallow !important", 12 | desc: "Be careful when using !important declaration", 13 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-%21important", 14 | browsers: "All", 15 | 16 | // initialization 17 | init: function(parser, reporter) { 18 | "use strict"; 19 | var rule = this, 20 | count = 0; 21 | 22 | // warn that important is used and increment the declaration counter 23 | parser.addListener("property", function(event) { 24 | if (!reporter.isIgnored(event.line)) { 25 | if (event.important === true) { 26 | count++; 27 | reporter.report("Use of !important", event.line, event.col, rule); 28 | } 29 | } 30 | }); 31 | 32 | // if there are more than 10, show an error 33 | parser.addListener("endstylesheet", function() { 34 | reporter.stat("important", count); 35 | if (count >= 10) { 36 | reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule); 37 | } 38 | }); 39 | } 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /src/rules/known-properties.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Properties should be known (listed in CSS3 specification) or 3 | * be a vendor-prefixed property. 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "known-properties", 10 | name: "Require use of known properties", 11 | desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.", 12 | url: "https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this; 19 | 20 | parser.addListener("property", function(event) { 21 | 22 | // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib) 23 | if (event.invalid) { 24 | reporter.report(event.invalid.message, event.line, event.col, rule); 25 | } 26 | 27 | }); 28 | } 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /src/rules/order-alphabetical.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: All properties should be in alphabetical order. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "order-alphabetical", 9 | name: "Alphabetical order", 10 | desc: "Assure properties are in alphabetical order", 11 | browsers: "All", 12 | 13 | // initialization 14 | init: function(parser, reporter) { 15 | "use strict"; 16 | var rule = this, 17 | properties; 18 | 19 | var startRule = function () { 20 | properties = []; 21 | }; 22 | 23 | var endRule = function(event) { 24 | var currentProperties = properties.join(","), 25 | expectedProperties = properties.sort().join(","); 26 | 27 | if (currentProperties !== expectedProperties) { 28 | reporter.report("Rule doesn't have all its properties in alphabetical order.", event.line, event.col, rule); 29 | } 30 | }; 31 | 32 | parser.addListener("startrule", startRule); 33 | parser.addListener("startfontface", startRule); 34 | parser.addListener("startpage", startRule); 35 | parser.addListener("startpagemargin", startRule); 36 | parser.addListener("startkeyframerule", startRule); 37 | parser.addListener("startviewport", startRule); 38 | 39 | parser.addListener("property", function(event) { 40 | var name = event.property.text, 41 | lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, ""); 42 | 43 | properties.push(lowerCasePrefixLessName); 44 | }); 45 | 46 | parser.addListener("endrule", endRule); 47 | parser.addListener("endfontface", endRule); 48 | parser.addListener("endpage", endRule); 49 | parser.addListener("endpagemargin", endRule); 50 | parser.addListener("endkeyframerule", endRule); 51 | parser.addListener("endviewport", endRule); 52 | } 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /src/rules/outline-none.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: outline: none or outline: 0 should only be used in a :focus rule 3 | * and only if there are other properties in the same rule. 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "outline-none", 10 | name: "Disallow outline: none", 11 | desc: "Use of outline: none or outline: 0 should be limited to :focus rules.", 12 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone", 13 | browsers: "All", 14 | tags: ["Accessibility"], 15 | 16 | // initialization 17 | init: function(parser, reporter) { 18 | "use strict"; 19 | var rule = this, 20 | lastRule; 21 | 22 | function startRule(event) { 23 | if (event.selectors) { 24 | lastRule = { 25 | line: event.line, 26 | col: event.col, 27 | selectors: event.selectors, 28 | propCount: 0, 29 | outline: false 30 | }; 31 | } else { 32 | lastRule = null; 33 | } 34 | } 35 | 36 | function endRule() { 37 | if (lastRule) { 38 | if (lastRule.outline) { 39 | if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") === -1) { 40 | reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule); 41 | } else if (lastRule.propCount === 1) { 42 | reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule); 43 | } 44 | } 45 | } 46 | } 47 | 48 | parser.addListener("startrule", startRule); 49 | parser.addListener("startfontface", startRule); 50 | parser.addListener("startpage", startRule); 51 | parser.addListener("startpagemargin", startRule); 52 | parser.addListener("startkeyframerule", startRule); 53 | parser.addListener("startviewport", startRule); 54 | 55 | parser.addListener("property", function(event) { 56 | var name = event.property.text.toLowerCase(), 57 | value = event.value; 58 | 59 | if (lastRule) { 60 | lastRule.propCount++; 61 | if (name === "outline" && (value.toString() === "none" || value.toString() === "0")) { 62 | lastRule.outline = true; 63 | } 64 | } 65 | 66 | }); 67 | 68 | parser.addListener("endrule", endRule); 69 | parser.addListener("endfontface", endRule); 70 | parser.addListener("endpage", endRule); 71 | parser.addListener("endpagemargin", endRule); 72 | parser.addListener("endkeyframerule", endRule); 73 | parser.addListener("endviewport", endRule); 74 | 75 | } 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /src/rules/overqualified-elements.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use classes or IDs with elements (a.foo or a#foo). 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "overqualified-elements", 9 | name: "Disallow overqualified elements", 10 | desc: "Don't use classes or IDs with elements (a.foo or a#foo).", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this, 18 | classes = {}; 19 | 20 | parser.addListener("startrule", function(event) { 21 | var selectors = event.selectors, 22 | selector, 23 | part, 24 | modifier, 25 | i, j, k; 26 | 27 | for (i=0; i < selectors.length; i++) { 28 | selector = selectors[i]; 29 | 30 | for (j=0; j < selector.parts.length; j++) { 31 | part = selector.parts[j]; 32 | if (part.type === parser.SELECTOR_PART_TYPE) { 33 | for (k=0; k < part.modifiers.length; k++) { 34 | modifier = part.modifiers[k]; 35 | if (part.elementName && modifier.type === "id") { 36 | reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule); 37 | } else if (modifier.type === "class") { 38 | 39 | if (!classes[modifier]) { 40 | classes[modifier] = []; 41 | } 42 | classes[modifier].push({ 43 | modifier: modifier, 44 | part: part 45 | }); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | }); 52 | 53 | parser.addListener("endstylesheet", function() { 54 | 55 | var prop; 56 | for (prop in classes) { 57 | if (classes.hasOwnProperty(prop)) { 58 | 59 | // one use means that this is overqualified 60 | if (classes[prop].length === 1 && classes[prop][0].part.elementName) { 61 | reporter.report("Element (" + classes[prop][0].part + ") is overqualified, just use " + classes[prop][0].modifier + " without element name.", classes[prop][0].part.line, classes[prop][0].part.col, rule); 62 | } 63 | } 64 | } 65 | }); 66 | } 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /src/rules/performant-transitions.js: -------------------------------------------------------------------------------- 1 | CSSLint.addRule({ 2 | id: "performant-transitions", 3 | name: "Allow only performant transisitons", 4 | desc: "Only allow transitions that trigger compositing for performant, 60fps transformations.", 5 | url: "", 6 | browsers: "All", 7 | 8 | init: function(parser, reporter){ 9 | "use strict"; 10 | var rule = this; 11 | 12 | var transitionProperties = ["transition-property", "transition", "-webkit-transition", "-o-transition"]; 13 | var allowedTransitions = [/-webkit-transform/g, /-ms-transform/g, /transform/g, /opacity/g]; 14 | 15 | parser.addListener("property", function(event) { 16 | var propertyName = event.property.toString().toLowerCase(), 17 | propertyValue = event.value.toString(), 18 | line = event.line, 19 | col = event.col; 20 | 21 | var values = propertyValue.split(","); 22 | if (transitionProperties.indexOf(propertyName) !== -1) { 23 | var reportValues = values.filter(function(value) { 24 | var didMatch = []; 25 | for (var i = 0; i < allowedTransitions.length; i++) { 26 | if(value.match(allowedTransitions[i])) { 27 | didMatch.push(i); 28 | } 29 | } 30 | return didMatch.length === 0; 31 | }); 32 | if(reportValues.length > 0) { 33 | reporter.report("Unexpected transition property '"+reportValues.join(",").trim()+"'", line, col, rule); 34 | } 35 | } 36 | }); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /src/rules/qualified-headings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Headings (h1-h6) should not be qualified (namespaced). 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "qualified-headings", 9 | name: "Disallow qualified headings", 10 | desc: "Headings should not be qualified (namespaced).", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | 19 | parser.addListener("startrule", function(event) { 20 | var selectors = event.selectors, 21 | selector, 22 | part, 23 | i, j; 24 | 25 | for (i=0; i < selectors.length; i++) { 26 | selector = selectors[i]; 27 | 28 | for (j=0; j < selector.parts.length; j++) { 29 | part = selector.parts[j]; 30 | if (part.type === parser.SELECTOR_PART_TYPE) { 31 | if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0) { 32 | reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule); 33 | } 34 | } 35 | } 36 | } 37 | }); 38 | } 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /src/rules/regex-selectors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Selectors that look like regular expressions are slow and should be avoided. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "regex-selectors", 9 | name: "Disallow selectors that look like regexs", 10 | desc: "Selectors that look like regular expressions are slow and should be avoided.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | 19 | parser.addListener("startrule", function(event) { 20 | var selectors = event.selectors, 21 | selector, 22 | part, 23 | modifier, 24 | i, j, k; 25 | 26 | for (i=0; i < selectors.length; i++) { 27 | selector = selectors[i]; 28 | for (j=0; j < selector.parts.length; j++) { 29 | part = selector.parts[j]; 30 | if (part.type === parser.SELECTOR_PART_TYPE) { 31 | for (k=0; k < part.modifiers.length; k++) { 32 | modifier = part.modifiers[k]; 33 | if (modifier.type === "attribute") { 34 | if (/([~\|\^\$\*]=)/.test(modifier)) { 35 | reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); 36 | } 37 | } 38 | 39 | } 40 | } 41 | } 42 | } 43 | }); 44 | } 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /src/rules/rules-count.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Total number of rules should not exceed x. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "rules-count", 9 | name: "Rules Count", 10 | desc: "Track how many rules there are.", 11 | browsers: "All", 12 | 13 | // initialization 14 | init: function(parser, reporter) { 15 | "use strict"; 16 | var count = 0; 17 | 18 | // count each rule 19 | parser.addListener("startrule", function() { 20 | count++; 21 | }); 22 | 23 | parser.addListener("endstylesheet", function() { 24 | reporter.stat("rule-count", count); 25 | }); 26 | } 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /src/rules/selector-max-approaching.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Warn people with approaching the IE 4095 limit 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "selector-max-approaching", 9 | name: "Warn when approaching the 4095 selector limit for IE", 10 | desc: "Will warn when selector count is >= 3800 selectors.", 11 | browsers: "IE", 12 | 13 | // initialization 14 | init: function(parser, reporter) { 15 | "use strict"; 16 | var rule = this, count = 0; 17 | 18 | parser.addListener("startrule", function(event) { 19 | count += event.selectors.length; 20 | }); 21 | 22 | parser.addListener("endstylesheet", function() { 23 | if (count >= 3800) { 24 | reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule); 25 | } 26 | }); 27 | } 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/rules/selector-max.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Warn people past the IE 4095 limit 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "selector-max", 9 | name: "Error when past the 4095 selector limit for IE", 10 | desc: "Will error when selector count is > 4095.", 11 | browsers: "IE", 12 | 13 | // initialization 14 | init: function(parser, reporter) { 15 | "use strict"; 16 | var rule = this, count = 0; 17 | 18 | parser.addListener("startrule", function(event) { 19 | count += event.selectors.length; 20 | }); 21 | 22 | parser.addListener("endstylesheet", function() { 23 | if (count > 4095) { 24 | reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule); 25 | } 26 | }); 27 | } 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/rules/selector-newline.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Avoid new-line characters in selectors. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "selector-newline", 9 | name: "Disallow new-line characters in selectors", 10 | desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.", 11 | browsers: "All", 12 | 13 | // initialization 14 | init: function(parser, reporter) { 15 | "use strict"; 16 | var rule = this; 17 | 18 | function startRule(event) { 19 | var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine, 20 | selectors = event.selectors; 21 | 22 | for (i = 0, len = selectors.length; i < len; i++) { 23 | selector = selectors[i]; 24 | for (p = 0, pLen = selector.parts.length; p < pLen; p++) { 25 | for (n = p + 1; n < pLen; n++) { 26 | part = selector.parts[p]; 27 | part2 = selector.parts[n]; 28 | type = part.type; 29 | currentLine = part.line; 30 | nextLine = part2.line; 31 | 32 | if (type === "descendant" && nextLine > currentLine) { 33 | reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule); 34 | } 35 | } 36 | } 37 | 38 | } 39 | } 40 | 41 | parser.addListener("startrule", startRule); 42 | 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/rules/shorthand.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Use shorthand properties where possible. 3 | * 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "shorthand", 10 | name: "Require shorthand properties", 11 | desc: "Use shorthand properties where possible.", 12 | url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this, 19 | prop, i, len, 20 | propertiesToCheck = {}, 21 | properties, 22 | mapping = { 23 | "margin": [ 24 | "margin-top", 25 | "margin-bottom", 26 | "margin-left", 27 | "margin-right" 28 | ], 29 | "padding": [ 30 | "padding-top", 31 | "padding-bottom", 32 | "padding-left", 33 | "padding-right" 34 | ] 35 | }; 36 | 37 | // initialize propertiesToCheck 38 | for (prop in mapping) { 39 | if (mapping.hasOwnProperty(prop)) { 40 | for (i=0, len=mapping[prop].length; i < len; i++) { 41 | propertiesToCheck[mapping[prop][i]] = prop; 42 | } 43 | } 44 | } 45 | 46 | function startRule() { 47 | properties = {}; 48 | } 49 | 50 | // event handler for end of rules 51 | function endRule(event) { 52 | 53 | var prop, i, len, total; 54 | 55 | // check which properties this rule has 56 | for (prop in mapping) { 57 | if (mapping.hasOwnProperty(prop)) { 58 | total=0; 59 | 60 | for (i=0, len=mapping[prop].length; i < len; i++) { 61 | total += properties[mapping[prop][i]] ? 1 : 0; 62 | } 63 | 64 | if (total === mapping[prop].length) { 65 | reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule); 66 | } 67 | } 68 | } 69 | } 70 | 71 | parser.addListener("startrule", startRule); 72 | parser.addListener("startfontface", startRule); 73 | 74 | // check for use of "font-size" 75 | parser.addListener("property", function(event) { 76 | var name = event.property.toString().toLowerCase(); 77 | 78 | if (propertiesToCheck[name]) { 79 | properties[name] = 1; 80 | } 81 | }); 82 | 83 | parser.addListener("endrule", endRule); 84 | parser.addListener("endfontface", endRule); 85 | 86 | } 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /src/rules/star-property-hack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use properties with a star prefix. 3 | * 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "star-property-hack", 10 | name: "Disallow properties with a star prefix", 11 | desc: "Checks for the star property hack (targets IE6/7)", 12 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-star-hack", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this; 19 | 20 | // check if property name starts with "*" 21 | parser.addListener("property", function(event) { 22 | var property = event.property; 23 | 24 | if (property.hack === "*") { 25 | reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule); 26 | } 27 | }); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/rules/text-indent.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use text-indent for image replacement if you need to support rtl. 3 | * 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "text-indent", 10 | name: "Disallow negative text-indent", 11 | desc: "Checks for text indent less than -99px", 12 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this, 19 | textIndent, 20 | direction; 21 | 22 | 23 | function startRule() { 24 | textIndent = false; 25 | direction = "inherit"; 26 | } 27 | 28 | // event handler for end of rules 29 | function endRule() { 30 | if (textIndent && direction !== "ltr") { 31 | reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule); 32 | } 33 | } 34 | 35 | parser.addListener("startrule", startRule); 36 | parser.addListener("startfontface", startRule); 37 | 38 | // check for use of "font-size" 39 | parser.addListener("property", function(event) { 40 | var name = event.property.toString().toLowerCase(), 41 | value = event.value; 42 | 43 | if (name === "text-indent" && value.parts[0].value < -99) { 44 | textIndent = event.property; 45 | } else if (name === "direction" && value.toString() === "ltr") { 46 | direction = "ltr"; 47 | } 48 | }); 49 | 50 | parser.addListener("endrule", endRule); 51 | parser.addListener("endfontface", endRule); 52 | 53 | } 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /src/rules/underscore-property-hack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use properties with a underscore prefix. 3 | * 4 | */ 5 | 6 | CSSLint.addRule({ 7 | 8 | // rule information 9 | id: "underscore-property-hack", 10 | name: "Disallow properties with an underscore prefix", 11 | desc: "Checks for the underscore property hack (targets IE6)", 12 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack", 13 | browsers: "All", 14 | 15 | // initialization 16 | init: function(parser, reporter) { 17 | "use strict"; 18 | var rule = this; 19 | 20 | // check if property name starts with "_" 21 | parser.addListener("property", function(event) { 22 | var property = event.property; 23 | 24 | if (property.hack === "_") { 25 | reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule); 26 | } 27 | }); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/rules/unique-headings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Headings (h1-h6) should be defined only once. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "unique-headings", 9 | name: "Headings should only be defined once", 10 | desc: "Headings should be defined only once.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | 19 | var headings = { 20 | h1: 0, 21 | h2: 0, 22 | h3: 0, 23 | h4: 0, 24 | h5: 0, 25 | h6: 0 26 | }; 27 | 28 | parser.addListener("startrule", function(event) { 29 | var selectors = event.selectors, 30 | selector, 31 | part, 32 | pseudo, 33 | i, j; 34 | 35 | for (i=0; i < selectors.length; i++) { 36 | selector = selectors[i]; 37 | part = selector.parts[selector.parts.length-1]; 38 | 39 | if (reporter.isIgnored(part.line)) { 40 | continue; 41 | } 42 | 43 | if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())) { 44 | 45 | for (j=0; j < part.modifiers.length; j++) { 46 | if (part.modifiers[j].type === "pseudo") { 47 | pseudo = true; 48 | break; 49 | } 50 | } 51 | 52 | if (!pseudo) { 53 | headings[RegExp.$1]++; 54 | if (headings[RegExp.$1] > 1) { 55 | reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule); 56 | } 57 | } 58 | } 59 | } 60 | }); 61 | 62 | parser.addListener("endstylesheet", function() { 63 | var prop, 64 | messages = []; 65 | 66 | for (prop in headings) { 67 | if (headings.hasOwnProperty(prop)) { 68 | if (headings[prop] > 1) { 69 | messages.push(headings[prop] + " " + prop + "s"); 70 | } 71 | } 72 | } 73 | 74 | if (messages.length) { 75 | reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule); 76 | } 77 | }); 78 | } 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /src/rules/universal-selector.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use universal selector because it's slow. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "universal-selector", 9 | name: "Disallow universal selector", 10 | desc: "The universal selector (*) is known to be slow.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | 19 | parser.addListener("startrule", function(event) { 20 | var selectors = event.selectors, 21 | selector, 22 | part, 23 | i; 24 | 25 | for (i=0; i < selectors.length; i++) { 26 | selector = selectors[i]; 27 | 28 | part = selector.parts[selector.parts.length-1]; 29 | if (part.elementName === "*") { 30 | reporter.report(rule.desc, part.line, part.col, rule); 31 | } 32 | } 33 | }); 34 | } 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /src/rules/unqualified-attributes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: Don't use unqualified attribute selectors because they're just like universal selectors. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "unqualified-attributes", 9 | name: "Disallow unqualified attribute selectors", 10 | desc: "Unqualified attribute selectors are known to be slow.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | 18 | var rule = this; 19 | 20 | parser.addListener("startrule", function(event) { 21 | 22 | var selectors = event.selectors, 23 | selectorContainsClassOrId = false, 24 | selector, 25 | part, 26 | modifier, 27 | i, k; 28 | 29 | for (i=0; i < selectors.length; i++) { 30 | selector = selectors[i]; 31 | 32 | part = selector.parts[selector.parts.length-1]; 33 | if (part.type === parser.SELECTOR_PART_TYPE) { 34 | for (k=0; k < part.modifiers.length; k++) { 35 | modifier = part.modifiers[k]; 36 | 37 | if (modifier.type === "class" || modifier.type === "id") { 38 | selectorContainsClassOrId = true; 39 | break; 40 | } 41 | } 42 | 43 | if (!selectorContainsClassOrId) { 44 | for (k=0; k < part.modifiers.length; k++) { 45 | modifier = part.modifiers[k]; 46 | if (modifier.type === "attribute" && (!part.elementName || part.elementName === "*")) { 47 | reporter.report(rule.desc, part.line, part.col, rule); 48 | } 49 | } 50 | } 51 | } 52 | 53 | } 54 | }); 55 | } 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /src/rules/zero-units.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Rule: You don't need to specify units when a value is 0. 3 | */ 4 | 5 | CSSLint.addRule({ 6 | 7 | // rule information 8 | id: "zero-units", 9 | name: "Disallow units for 0 values", 10 | desc: "You don't need to specify units when a value is 0.", 11 | url: "https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values", 12 | browsers: "All", 13 | 14 | // initialization 15 | init: function(parser, reporter) { 16 | "use strict"; 17 | var rule = this; 18 | 19 | // count how many times "float" is used 20 | parser.addListener("property", function(event) { 21 | var parts = event.value.parts, 22 | i = 0, 23 | len = parts.length; 24 | 25 | while (i < len) { 26 | if ((parts[i].units || parts[i].type === "percentage") && parts[i].value === 0 && parts[i].type !== "time") { 27 | reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); 28 | } 29 | i++; 30 | } 31 | 32 | }); 33 | 34 | } 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /src/worker/Worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Web worker for CSSLint 3 | */ 4 | 5 | /* global self, JSON */ 6 | 7 | // message indicates to start linting 8 | self.onmessage = function(event) { 9 | "use strict"; 10 | var data = event.data, 11 | message, 12 | text, 13 | ruleset, 14 | results; 15 | 16 | try { 17 | message = JSON.parse(data); 18 | text = message.text; 19 | ruleset = message.ruleset; 20 | } catch (ex) { 21 | text = data; 22 | } 23 | 24 | results = CSSLint.verify(text, ruleset); 25 | 26 | // Not all browsers support structured clone, so JSON stringify results 27 | self.postMessage(JSON.stringify(results)); 28 | }; 29 | -------------------------------------------------------------------------------- /tasks/changelog.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | "use strict"; 3 | 4 | module.exports = function(grunt) { 5 | grunt.registerMultiTask("changelog", "Write the changelog file", function() { 6 | var done = this.async(); 7 | var lastTag; 8 | var files = this.filesSrc; 9 | 10 | 11 | grunt.util.spawn({ 12 | cmd: "git", 13 | args: ["tag"] 14 | }, function(error, result) { 15 | // Find the latest git tag 16 | var tags = result.stdout.split("\n"), 17 | semver = tags[0].replace("v", "").split("."), 18 | major = parseInt(semver[0], 10), 19 | minor = parseInt(semver[1], 10), 20 | patch = parseInt(semver[2], 10); 21 | 22 | // A simple array sort can't be used because of the comparison of 23 | // the strings "0.9.9" > "0.9.10" 24 | for (var i = 1, len = tags.length; i < len; i++) { 25 | semver = tags[i].replace("v", "").split("."); 26 | 27 | var currentMajor = parseInt(semver[0], 10); 28 | if (currentMajor < major) { 29 | continue; 30 | } else if (currentMajor > major) { 31 | major = currentMajor; 32 | } 33 | 34 | var currentMinor = parseInt(semver[1], 10); 35 | if (currentMinor < minor) { 36 | continue; 37 | } else if (currentMinor > minor) { 38 | minor = currentMinor; 39 | } 40 | 41 | var currentPatch = parseInt(semver[2], 10); 42 | if (currentPatch < patch) { 43 | continue; 44 | } else if (currentPatch > patch) { 45 | patch = currentPatch; 46 | } 47 | } 48 | 49 | lastTag = "v" + major + "." + minor + "." + patch; 50 | 51 | grunt.verbose.write("Last tag: " + lastTag).writeln(); 52 | 53 | grunt.util.spawn({ 54 | cmd: "git", 55 | args: ["log", "--pretty=format:'* %s (%an)'", lastTag + "..HEAD"] 56 | }, function(error, result) { 57 | var prettyPrint = result.stdout.split("'\n'") 58 | .join("\n") 59 | .replace(/"$/, "") 60 | .replace(/^"/, "") 61 | .replace(/^'/, "") 62 | .replace(/'$/, ""); 63 | 64 | grunt.verbose.writeln().write(prettyPrint).writeln(); 65 | 66 | var template = "<%= grunt.template.today('mmmm d, yyyy') %> - v<%= pkg.version %>\n\n" + 67 | prettyPrint + "\n\n" + 68 | grunt.file.read(files[0]); 69 | 70 | grunt.file.write(files[0], grunt.template.process(template)); 71 | 72 | done(); 73 | }); 74 | }); 75 | 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /tasks/test_rhino.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | "use strict"; 3 | 4 | module.exports = function(grunt) { 5 | // Run test suite through rhino 6 | grunt.registerMultiTask("test_rhino", "Run the test suite through rhino", function() { 7 | var done = this.async(); 8 | var files = this.filesSrc; 9 | var progress = files.length; 10 | 11 | files.forEach(function(filepath) { 12 | grunt.util.spawn({ 13 | cmd: "java", 14 | args: ["-jar", "lib/js.jar", "lib/yuitest-rhino-cli.js", "dist/csslint.js", filepath], 15 | opts: { stdio: "inherit" } 16 | }, function() { 17 | progress--; 18 | if (progress === 0) { 19 | done(); 20 | } 21 | }); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /tasks/yuitest.js: -------------------------------------------------------------------------------- 1 | /* jshint evil:true, node:true */ 2 | "use strict"; 3 | 4 | module.exports = function(grunt) { 5 | grunt.registerMultiTask("yuitest", "Run the YUITests for the project", function() { 6 | 7 | var YUITest = require("yuitest"); 8 | var CSSLint = require("../dist/csslint-node").CSSLint; // jshint ignore:line 9 | var files = this.filesSrc; 10 | var TestRunner = YUITest.TestRunner; 11 | var done = this.async(); 12 | var failures = [], 13 | stack = []; 14 | 15 | // Eval each file so the tests are brought into this scope where CSSLint and YUITest are loaded already 16 | files.forEach(function(filepath) { 17 | eval(grunt.file.read(filepath)); 18 | }); 19 | 20 | // From YUITest Node CLI with minor colourization changes 21 | function handleEvent(event) { 22 | 23 | var message = "", 24 | results = event.results, 25 | i, len, gruntFailMessage; 26 | 27 | switch (event.type) { 28 | case TestRunner.BEGIN_EVENT: 29 | grunt.verbose.subhead("YUITest for Node.js"); 30 | 31 | if (TestRunner._groups) { 32 | grunt.verbose.writeln("Filtering on groups '" + TestRunner._groups.slice(1, -1) + "'"); 33 | } 34 | break; 35 | 36 | case TestRunner.COMPLETE_EVENT: 37 | grunt.log.writeln("Total tests: " + results.total + ", " + 38 | ("Failures: " + results.failed).red + ", " + 39 | ("Skipped: " + results.ignored).yellow + 40 | ", Time: " + results.duration / 1000 + " seconds\n"); 41 | 42 | if (failures.length) { 43 | grunt.log.writeln("Tests failed:"); 44 | 45 | for (i=0, len=failures.length; i < len; i++) { 46 | gruntFailMessage += failures[i].name + "\n" + failures[i].error; 47 | } 48 | grunt.fail.warn(gruntFailMessage); 49 | } 50 | 51 | // Tell grunt we're done the async operation 52 | done(); 53 | break; 54 | 55 | case TestRunner.TEST_FAIL_EVENT: 56 | message = "F".red; 57 | failures.push({ 58 | name: stack.concat([event.testName]).join(" > "), 59 | error: event.error 60 | }); 61 | 62 | break; 63 | 64 | case TestRunner.ERROR_EVENT: 65 | grunt.fail.fatal(event.error, stack); 66 | break; 67 | 68 | case TestRunner.TEST_IGNORE_EVENT: 69 | message = "S".yellow; 70 | break; 71 | 72 | case TestRunner.TEST_PASS_EVENT: 73 | message = ".".green; 74 | break; 75 | 76 | case TestRunner.TEST_SUITE_BEGIN_EVENT: 77 | stack.push(event.testSuite.name); 78 | break; 79 | 80 | case TestRunner.TEST_CASE_COMPLETE_EVENT: 81 | case TestRunner.TEST_SUITE_COMPLETE_EVENT: 82 | stack.pop(); 83 | break; 84 | 85 | case TestRunner.TEST_CASE_BEGIN_EVENT: 86 | stack.push(event.testCase.name); 87 | break; 88 | 89 | // no default 90 | } 91 | 92 | grunt.log.write(message); 93 | } 94 | // Add event listeners 95 | TestRunner.subscribe(TestRunner.BEGIN_EVENT, handleEvent); 96 | TestRunner.subscribe(TestRunner.TEST_FAIL_EVENT, handleEvent); 97 | TestRunner.subscribe(TestRunner.TEST_PASS_EVENT, handleEvent); 98 | TestRunner.subscribe(TestRunner.ERROR_EVENT, handleEvent); 99 | TestRunner.subscribe(TestRunner.TEST_IGNORE_EVENT, handleEvent); 100 | TestRunner.subscribe(TestRunner.TEST_CASE_BEGIN_EVENT, handleEvent); 101 | TestRunner.subscribe(TestRunner.TEST_CASE_COMPLETE_EVENT, handleEvent); 102 | TestRunner.subscribe(TestRunner.TEST_SUITE_BEGIN_EVENT, handleEvent); 103 | TestRunner.subscribe(TestRunner.TEST_SUITE_COMPLETE_EVENT, handleEvent); 104 | TestRunner.subscribe(TestRunner.COMPLETE_EVENT, handleEvent); 105 | TestRunner.run(); 106 | }); 107 | }; 108 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : "../.jshintrc", 3 | "globals": { 4 | "YUITest": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/all-rules.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file contains generic tests that are run against every rule. Early on, 3 | * we found some common rule patterns that would cause errors under certain 4 | * conditions. Instead of tracking them down individually, this file runs 5 | * the same tests on every defined rule to track down these patterns. 6 | * 7 | * When run in addition to the other tests, this causes the Rhino CLI test 8 | * to fail due to Java stack overflow. This must be run separate from other tests. 9 | */ 10 | 11 | (function() { 12 | "use strict"; 13 | var Assert = YUITest.Assert, 14 | suite = new YUITest.TestSuite("General Tests for all Rules"), 15 | rules = CSSLint.getRules(), 16 | len = rules.length, 17 | i; 18 | 19 | function testAll(i, rules) { 20 | 21 | suite.add(new YUITest.TestCase({ 22 | 23 | name: "General Tests for " + rules[i].id, 24 | 25 | setUp: function() { 26 | this.options = {}; 27 | this.options[rules[i].id] = 1; 28 | }, 29 | 30 | "Using @viewport should not result in an error": function() { 31 | var result = CSSLint.verify("@viewport { width: auto; }", this.options); 32 | Assert.areEqual(0, result.messages.length); 33 | }, 34 | 35 | "Using @keyframes should not result in an error": function() { 36 | var result = CSSLint.verify("@keyframes resize { 0% {padding: 0;} 50% {padding: 0;} 100% {padding: 0;}}", this.options); 37 | Assert.areEqual(0, result.messages.length); 38 | }, 39 | 40 | "Using @page should not result in an error": function() { 41 | var result = CSSLint.verify("@page { width: 100px; }", this.options); 42 | Assert.areEqual(0, result.messages.length); 43 | }, 44 | 45 | "Using @page @top-left should not result in an error": function() { 46 | var result = CSSLint.verify("@page { @top-left { content: ''; } }", this.options); 47 | Assert.areEqual(0, result.messages.length); 48 | }, 49 | 50 | "Using a regular rule should not result in an error": function() { 51 | var result = CSSLint.verify("body { margin: 0; }", this.options); 52 | Assert.areEqual(0, result.messages.length); 53 | } 54 | 55 | })); 56 | 57 | } 58 | 59 | for (i = 0; i < len; i++) { 60 | testAll(i, rules); 61 | } 62 | 63 | YUITest.TestRunner.add(suite); 64 | 65 | })(); 66 | 67 | -------------------------------------------------------------------------------- /tests/cli/assets/apiStub.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | 3 | "use strict"; 4 | 5 | var stub = { 6 | logbook: function(log) { 7 | this.logs.push(log); 8 | }, 9 | readLogs: function() { 10 | return this.logs.slice(); 11 | }, 12 | 13 | getFullPath: function(path) { 14 | return path; 15 | }, 16 | getFiles: function(dir) { 17 | var filesobj = this.fakedFs[dir], 18 | fileix, 19 | out = []; 20 | for (fileix in filesobj) { 21 | if (filesobj.hasOwnProperty(fileix) && /\.css$/.test(fileix)) { 22 | out.push(dir + "/" + fileix); 23 | } 24 | } 25 | return out; 26 | }, 27 | readFile: function(path) { 28 | var spath = path.split("/"), 29 | spathLen = spath.length, 30 | i, 31 | out = this.fakedFs; 32 | 33 | for (i = 0; i < spathLen; i += 1) { 34 | out = out[spath[i]]; 35 | } 36 | 37 | return out; 38 | }, 39 | isDirectory: function(checkit) { 40 | var result = this.fakedFs[checkit]; 41 | return typeof result === "object"; 42 | }, 43 | print: function(msg) { 44 | this.logbook(msg); 45 | }, 46 | quit: function(signal) { 47 | this.logbook(signal); 48 | } 49 | }; 50 | 51 | module.exports = function(setup) { 52 | var api, 53 | setix; 54 | 55 | api = Object.create(stub); 56 | 57 | for (setix in setup) { 58 | if (setup.hasOwnProperty(setix)) { 59 | api[setix] = setup[setix]; 60 | } 61 | } 62 | 63 | api.logs = []; 64 | return api; 65 | }; 66 | -------------------------------------------------------------------------------- /tests/cli/assets/data.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | 3 | module.exports = { 4 | "suites": { 5 | "config csslintrc override": { 6 | "args": [ 7 | "--config=.rc1", 8 | "dir" 9 | ], 10 | "expecting": [ 11 | "csslint: No errors in dir/a.css.", 12 | "csslint: No errors in dir/b.css.", 13 | 0 14 | ] 15 | }, 16 | "straight linting": { 17 | "args": [ 18 | "dir" 19 | ], 20 | "expecting": [ 21 | "csslint: There is 1 problem in dir/a.css.", 22 | "csslint: There is 1 problem in dir/b.css.", 23 | 0 24 | ] 25 | }, 26 | "mix of cli options": { 27 | "args": [ 28 | "--config=.rc1", 29 | "--ignore=important", 30 | "dir" 31 | ], 32 | "expecting": [ 33 | "csslint: No errors in dir/a.css.", 34 | "csslint: There is 1 problem in dir/b.css.", 35 | 0 36 | ] 37 | }, 38 | "more mixes of cli options": { 39 | "args": [ 40 | "--config=.rc1", 41 | "--errors=important", 42 | "dir" 43 | ], 44 | "expecting": [ 45 | "csslint: There is 1 problem in dir/a.css.", 46 | "csslint: No errors in dir/b.css.", 47 | 1 48 | ] 49 | } 50 | }, 51 | 52 | "fakedFs": { 53 | ".rc1": "--ignore=important,ids", 54 | "dir": { 55 | "a.css": ".a {color: red!important;}", 56 | "b.css": "#a {color: red;}" 57 | } 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /tests/cli/cli-common.js: -------------------------------------------------------------------------------- 1 | /* jshint loopfunc:true, node:true */ 2 | 3 | "use strict"; 4 | function include(path, sandbox) { 5 | var vm = require("vm"), 6 | fs = require("fs"), 7 | file; 8 | 9 | file = fs.readFileSync(path); 10 | vm.runInNewContext(file, sandbox); 11 | } 12 | 13 | 14 | (function() { 15 | 16 | var Assert = YUITest.Assert, 17 | suite = new YUITest.TestSuite("General Tests for CLI"), 18 | apiStub = require("../tests/cli/assets/apiStub.js"), 19 | data = require("../tests/cli/assets/data.js"), 20 | suites = data.suites, 21 | suiteix, 22 | sandbox = { 23 | CSSLint: CSSLint 24 | }; 25 | 26 | include("./src/cli/common.js", sandbox); /* expose sandbox.cli */ 27 | 28 | for (suiteix in suites) { 29 | if (suites.hasOwnProperty(suiteix)) { 30 | (function (suiteix) { 31 | 32 | suite.add(new YUITest.TestCase({ 33 | 34 | name: "Test " + suiteix, 35 | 36 | "Outcome logs should match expected": function () { 37 | var it = suites[suiteix], 38 | expecting = it.expecting, 39 | expectingLen = expecting.length, 40 | outcome, 41 | api, 42 | exp, 43 | out, 44 | i = 0; 45 | 46 | data.args = it.args.slice(); 47 | api = apiStub(data); 48 | sandbox.cli(api); 49 | outcome = api.readLogs(); 50 | 51 | for (i; i < expectingLen; i += 1) { 52 | exp = expecting[i]; 53 | out = outcome[i]; 54 | 55 | if (typeof out === "string") { 56 | out = /^.*/.exec(out.trim())[0]; 57 | } 58 | if (exp !== out) { 59 | Assert.fail("Expecting: " + exp + " Got: " + out); 60 | } 61 | } 62 | Assert.pass(); 63 | 64 | } 65 | })); 66 | })(suiteix); 67 | } 68 | } 69 | 70 | YUITest.TestRunner.add(suite); 71 | })(); 72 | -------------------------------------------------------------------------------- /tests/core/Reporter.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Reporter Object Tests", 8 | 9 | "Report should cause a warning": function() { 10 | var reporter = new CSSLint._Reporter([], { 11 | "fake-rule": 1 12 | }); 13 | reporter.report("Foo", 1, 1, { 14 | id: "fake-rule" 15 | }); 16 | 17 | Assert.areEqual(1, reporter.messages.length); 18 | Assert.areEqual("warning", reporter.messages[0].type); 19 | }, 20 | 21 | "Report should cause an error": function() { 22 | var reporter = new CSSLint._Reporter([], { 23 | "fake-rule": 2 24 | }); 25 | reporter.report("Foo", 1, 1, { 26 | id: "fake-rule" 27 | }); 28 | 29 | Assert.areEqual(1, reporter.messages.length); 30 | Assert.areEqual("error", reporter.messages[0].type); 31 | }, 32 | 33 | "Calling error() should cause an error": function() { 34 | var reporter = new CSSLint._Reporter([], { 35 | "fake-rule": 1 36 | }); 37 | reporter.error("Foo", 1, 1, { 38 | id: "fake-rule" 39 | }); 40 | 41 | Assert.areEqual(1, reporter.messages.length); 42 | Assert.areEqual("error", reporter.messages[0].type); 43 | }, 44 | 45 | "Allow statement should drop message about specific rule on specific line but not other lines": function() { 46 | var reporter = new CSSLint._Reporter([], { 47 | "fake-rule": 1 48 | }, { 49 | "3": { 50 | "fake-rule": true 51 | } 52 | }); 53 | reporter.report("Foo", 2, 1, { 54 | id: "fake-rule" 55 | }); 56 | reporter.report("Bar", 3, 1, { 57 | id: "fake-rule" 58 | }); 59 | 60 | Assert.areEqual(1, reporter.messages.length); 61 | }, 62 | 63 | "Allow statement should drop message about specific rule on specific line but not other rules": function() { 64 | var reporter = new CSSLint._Reporter([], { 65 | "fake-rule": 1, 66 | "fake-rule2": 1 67 | }, { 68 | "3": { 69 | "fake-rule": true 70 | } 71 | }); 72 | reporter.report("Foo", 3, 1, { 73 | id: "fake-rule" 74 | }); 75 | reporter.report("Bar", 3, 1, { 76 | id: "fake-rule2" 77 | }); 78 | 79 | Assert.areEqual(1, reporter.messages.length); 80 | }, 81 | 82 | "Allow statement should drop messages about multiple rules on specific line": function() { 83 | var reporter = new CSSLint._Reporter([], { 84 | "fake-rule": 1, 85 | "fake-rule2": 1 86 | }, { 87 | "3": { 88 | "fake-rule": true, 89 | "fake-rule2": true 90 | } 91 | }); 92 | reporter.report("Foo", 3, 1, { 93 | id: "fake-rule" 94 | }); 95 | reporter.report("Bar", 3, 1, { 96 | id: "fake-rule2" 97 | }); 98 | 99 | Assert.areEqual(0, reporter.messages.length); 100 | }, 101 | 102 | "Ignores should step over a report in their range": function() { 103 | var reporter = new CSSLint._Reporter([], { 104 | "fake-rule": 1 105 | }, {}, [ 106 | [1, 3] 107 | ]); 108 | reporter.report("Foo", 2, 1, { 109 | id: "fake-rule" 110 | }); 111 | reporter.report("Bar", 5, 1, { 112 | id: "fake-rule" 113 | }); 114 | 115 | Assert.areEqual(1, reporter.messages.length); 116 | } 117 | 118 | })); 119 | 120 | })(); 121 | -------------------------------------------------------------------------------- /tests/css/width-100.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Untitled Document 6 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | width 100% child in a parent with padding. 18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
fillerfillerfillerfillerfillerfiller
fillerfillerfillerfillerfillerfiller
fillerfillerfillerfillerfillerfiller
fillerfillerfillerfillerfillerfiller
fillerfillerfillerfillerfillerfiller
fillerfillerfillerfillerfillerfiller
72 | 73 |
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tests/formatters/checkstyle-xml.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Checkstyle XML formatter test", 8 | 9 | "File with no problems should say so": function() { 10 | var result = { 11 | messages: [], 12 | stats: [] 13 | }, 14 | expected = ""; 15 | Assert.areEqual(expected, CSSLint.format(result, "FILE", "checkstyle-xml")); 16 | }, 17 | 18 | "File with problems should list them": function() { 19 | var result = { 20 | messages: [{ 21 | type: "warning", 22 | line: 1, 23 | col: 1, 24 | message: "BOGUS", 25 | evidence: "ALSO BOGUS", 26 | rule: { 27 | name: "A Rule" 28 | } 29 | }, { 30 | type: "error", 31 | line: 2, 32 | col: 1, 33 | message: "BOGUS", 34 | evidence: "ALSO BOGUS", 35 | rule: { 36 | name: "Some Other Rule" 37 | } 38 | }], 39 | stats: [] 40 | }, 41 | file = "", 42 | error1 = "", 43 | error2 = "", 44 | expected = "" + file + error1 + error2 + "", 45 | actual = CSSLint.format(result, "FILE", "checkstyle-xml"); 46 | Assert.areEqual(expected, actual); 47 | }, 48 | 49 | "Formatter should escape special characters": function() { 50 | var specialCharsSting = "sneaky, 'sneaky', , sneak & sneaky", 51 | result = { 52 | messages: [{ 53 | type: "warning", 54 | line: 1, 55 | col: 1, 56 | message: specialCharsSting, 57 | evidence: "ALSO BOGUS", 58 | rule: [] 59 | }, { 60 | type: "error", 61 | line: 2, 62 | col: 1, 63 | message: specialCharsSting, 64 | evidence: "ALSO BOGUS", 65 | rule: [] 66 | }], 67 | stats: [] 68 | }, 69 | file = "", 70 | error1 = "", 71 | error2 = "", 72 | expected = "" + file + error1 + error2 + "", 73 | actual = CSSLint.format(result, "FILE", "checkstyle-xml"); 74 | Assert.areEqual(expected, actual); 75 | } 76 | 77 | })); 78 | })(); 79 | -------------------------------------------------------------------------------- /tests/formatters/csslint-xml.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | name: "CSSLint XML formatter test", 7 | 8 | "File with no problems should say so": function() { 9 | var result = { 10 | messages: [], 11 | stats: [] 12 | }, 13 | expected = ""; 14 | Assert.areEqual(expected, CSSLint.format(result, "FILE", "csslint-xml")); 15 | }, 16 | 17 | "File with problems should list them": function() { 18 | var result = { 19 | messages: [{ 20 | type: "warning", 21 | line: 1, 22 | col: 1, 23 | message: "BOGUS", 24 | evidence: "ALSO BOGUS", 25 | rule: [] 26 | }, { 27 | type: "error", 28 | line: 2, 29 | col: 1, 30 | message: "BOGUS", 31 | evidence: "ALSO BOGUS", 32 | rule: [] 33 | }], 34 | stats: [] 35 | }, 36 | file = "", 37 | error1 = "", 38 | error2 = "", 39 | expected = "" + file + error1 + error2 + "", 40 | actual = CSSLint.format(result, "FILE", "csslint-xml"); 41 | Assert.areEqual(expected, actual); 42 | }, 43 | 44 | "Formatter should escape double quotes": function() { 45 | var doubleQuotedEvidence = "sneaky, \"sneaky\", , sneak & sneaky", 46 | result = { 47 | messages: [{ 48 | type: "warning", 49 | line: 1, 50 | col: 1, 51 | message: "BOGUS", 52 | evidence: doubleQuotedEvidence, 53 | rule: [] 54 | }, { 55 | type: "error", 56 | line: 2, 57 | col: 1, 58 | message: "BOGUS", 59 | evidence: doubleQuotedEvidence, 60 | rule: [] 61 | }], 62 | stats: [] 63 | }, 64 | file = "", 65 | error1 = "", 66 | error2 = "", 67 | expected = "" + file + error1 + error2 + "", 68 | actual = CSSLint.format(result, "FILE", "csslint-xml"); 69 | Assert.areEqual(expected, actual); 70 | } 71 | })); 72 | })(); 73 | -------------------------------------------------------------------------------- /tests/formatters/junit-xml.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "JUNIT XML formatter test", 8 | 9 | "File with no problems should say so": function() { 10 | 11 | var result = { 12 | messages: [], 13 | stats: [] 14 | }, 15 | expected = ""; 16 | Assert.areEqual(expected, CSSLint.format(result, "FILE", "junit-xml")); 17 | 18 | }, 19 | 20 | "File with problems should list them": function() { 21 | 22 | var result = { 23 | messages: [{ 24 | type: "warning", 25 | line: 1, 26 | col: 1, 27 | message: "BOGUS", 28 | evidence: "ALSO BOGUS", 29 | rule: { 30 | name: "A Rule" 31 | } 32 | }, { 33 | type: "error", 34 | line: 2, 35 | col: 1, 36 | message: "BOGUS", 37 | evidence: "ALSO BOGUS", 38 | rule: { 39 | name: "Some Other Rule" 40 | } 41 | }], 42 | stats: [] 43 | }, 44 | 45 | file = "", 46 | error1 = "", 47 | error2 = "", 48 | expected = "" + file + error1 + error2 + "", 49 | actual = CSSLint.format(result, "FILE", "junit-xml"); 50 | 51 | Assert.areEqual(expected, actual); 52 | 53 | }, 54 | 55 | "Formatter should escape special characters": function() { 56 | 57 | var specialCharsSting = "sneaky, 'sneaky', ", 58 | result = { 59 | messages: [{ 60 | type: "warning", 61 | line: 1, 62 | col: 1, 63 | message: specialCharsSting, 64 | evidence: "ALSO BOGUS", 65 | rule: [] 66 | }, { 67 | type: "error", 68 | line: 2, 69 | col: 1, 70 | message: specialCharsSting, 71 | evidence: "ALSO BOGUS", 72 | rule: [] 73 | }], 74 | stats: [] 75 | }, 76 | 77 | file = "", 78 | error1 = "", 79 | error2 = "", 80 | expected = "" + file + error1 + error2 + "", 81 | actual = CSSLint.format(result, "FILE", "junit-xml"); 82 | 83 | Assert.areEqual(expected, actual); 84 | 85 | } 86 | 87 | })); 88 | })(); 89 | -------------------------------------------------------------------------------- /tests/formatters/lint-xml.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Lint XML formatter test", 8 | 9 | "File with no problems should say so": function() { 10 | var result = { 11 | messages: [], 12 | stats: [] 13 | }, 14 | expected = ""; 15 | Assert.areEqual(expected, CSSLint.format(result, "FILE", "lint-xml")); 16 | }, 17 | 18 | "File with problems should list them": function() { 19 | var result = { 20 | messages: [{ 21 | type: "warning", 22 | line: 1, 23 | col: 1, 24 | message: "BOGUS", 25 | evidence: "ALSO BOGUS", 26 | rule: [] 27 | }, { 28 | type: "error", 29 | line: 2, 30 | col: 1, 31 | message: "BOGUS", 32 | evidence: "ALSO BOGUS", 33 | rule: [] 34 | }], 35 | stats: [] 36 | }, 37 | file = "", 38 | error1 = "", 39 | error2 = "", 40 | expected = "" + file + error1 + error2 + "", 41 | actual = CSSLint.format(result, "FILE", "lint-xml"); 42 | Assert.areEqual(expected, actual); 43 | }, 44 | 45 | "Formatter should escape double quotes": function() { 46 | var doubleQuotedEvidence = "sneaky, \"sneaky\", , sneak & sneaky", 47 | result = { 48 | messages: [{ 49 | type: "warning", 50 | line: 1, 51 | col: 1, 52 | message: "BOGUS", 53 | evidence: doubleQuotedEvidence, 54 | rule: [] 55 | }, { 56 | type: "error", 57 | line: 2, 58 | col: 1, 59 | message: "BOGUS", 60 | evidence: doubleQuotedEvidence, 61 | rule: [] 62 | }], 63 | stats: [] 64 | }, 65 | file = "", 66 | error1 = "", 67 | error2 = "", 68 | expected = "" + file + error1 + error2 + "", 69 | actual = CSSLint.format(result, "FILE", "lint-xml"); 70 | Assert.areEqual(expected, actual); 71 | }, 72 | 73 | "Messages should include rule IDs": function() { 74 | var result = { 75 | messages: [{ 76 | type: "error", 77 | line: 1, 78 | col: 1, 79 | message: "X", 80 | evidence: "Y", 81 | rule: { 82 | id: "Z" 83 | } 84 | }], 85 | stats: [] 86 | }; 87 | 88 | var expected = 89 | "" + 90 | "" + 91 | "" + 92 | "" + 93 | "" + 94 | ""; 95 | 96 | var actual = CSSLint.format(result, "FILE", "lint-xml"); 97 | 98 | Assert.areEqual(expected, actual); 99 | } 100 | 101 | })); 102 | })(); 103 | -------------------------------------------------------------------------------- /tests/formatters/text.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Text formatter", 8 | 9 | "File with no problems should say so": function() { 10 | var result = { 11 | messages: [], 12 | stats: [] 13 | }, 14 | actual = CSSLint.getFormatter("text").formatResults(result, "path/to/FILE", { 15 | fullPath: "/absolute/path/to/FILE" 16 | }); 17 | Assert.areEqual("\n\ncsslint: No errors in path/to/FILE.", actual); 18 | }, 19 | 20 | "File with one problem should use proper grammar": function() { 21 | var result = { 22 | messages: [{ 23 | type: "warning", 24 | line: 1, 25 | col: 1, 26 | message: "BOGUS", 27 | evidence: "ALSO BOGUS", 28 | rule: [] 29 | }], 30 | stats: [] 31 | }, 32 | error1 = "\n1: warning at line 1, col 1\nBOGUS\nALSO BOGUS", 33 | expected = "\n\ncsslint: There is 1 problem in path/to/FILE.\n\nFILE" + error1, 34 | actual = CSSLint.getFormatter("text").formatResults(result, "path/to/FILE", { 35 | fullPath: "/absolute/path/to/FILE" 36 | }); 37 | Assert.areEqual(expected, actual); 38 | }, 39 | 40 | "Should have no output when quiet option is specified and no errors": function() { 41 | var result = { 42 | messages: [], 43 | stats: [] 44 | }, 45 | actual = CSSLint.getFormatter("text").formatResults(result, "path/to/FILE", { 46 | fullPath: "/absolute/path/to/FILE", 47 | quiet: "true" 48 | }); 49 | Assert.areEqual("", actual); 50 | }, 51 | 52 | "File with problems should list them": function() { 53 | var result = { 54 | messages: [{ 55 | type: "warning", 56 | line: 1, 57 | col: 1, 58 | message: "BOGUS", 59 | evidence: "ALSO BOGUS", 60 | rule: [] 61 | }, { 62 | type: "error", 63 | line: 2, 64 | col: 1, 65 | message: "BOGUS", 66 | evidence: "ALSO BOGUS", 67 | rule: [] 68 | }], 69 | stats: [] 70 | }, 71 | error1 = "\n1: warning at line 1, col 1\nBOGUS\nALSO BOGUS", 72 | error2 = "\n2: error at line 2, col 1\nBOGUS\nALSO BOGUS", 73 | expected = "\n\ncsslint: There are 2 problems in path/to/FILE.\n\nFILE" + error1 + "\n\nFILE" + error2, 74 | actual = CSSLint.getFormatter("text").formatResults(result, "path/to/FILE", { 75 | fullPath: "/absolute/path/to/FILE" 76 | }); 77 | Assert.areEqual(expected, actual); 78 | } 79 | 80 | })); 81 | 82 | })(); 83 | -------------------------------------------------------------------------------- /tests/rules/adjoining-classes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Adjoining Selector Rule Errors", 8 | 9 | "Adjoining classes should result in a warning": function() { 10 | var result = CSSLint.verify(".foo.bar { }", { "adjoining-classes": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Adjoining classes: .foo.bar", result.messages[0].message); 14 | }, 15 | 16 | "Adjoining classes should result in an error": function() { 17 | var result = CSSLint.verify(".foo.bar { }", { "adjoining-classes": 2 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("error", result.messages[0].type); 20 | Assert.areEqual("Adjoining classes: .foo.bar", result.messages[0].message); 21 | }, 22 | 23 | "Descendant selector with classes should not result in a warning": function() { 24 | var result = CSSLint.verify(".foo .bar { }", { "adjoining-classes": 1 }); 25 | Assert.areEqual(0, result.messages.length); 26 | } 27 | 28 | })); 29 | 30 | })(); 31 | -------------------------------------------------------------------------------- /tests/rules/box-sizing.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Box Sizing Rule Errors", 8 | 9 | "Using box-sizing should result in a warning": function() { 10 | var result = CSSLint.verify(".foo { box-sizing: border-box; }", { "box-sizing": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("The box-sizing property isn't supported in IE6 and IE7.", result.messages[0].message); 14 | }, 15 | 16 | "No box-sizing should not result in a warning": function() { 17 | var result = CSSLint.verify(".foo { width: 100px; padding: 0; }", { "box-sizing": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | } 20 | })); 21 | 22 | })(); 23 | -------------------------------------------------------------------------------- /tests/rules/compatible-vendor-prefixes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Compatible Vendor Prefix Warnings", 8 | 9 | "Using -webkit-border-radius should not warn to also include -moz-border-radius.": function() { 10 | var result = CSSLint.verify("h1 { -webkit-border-radius: 5px; }", { "compatible-vendor-prefixes": 1 }); 11 | Assert.areEqual(0, result.messages.length); 12 | }, 13 | 14 | "Using -webkit-transition and -moz-transition should not warn to also include -o-transition.": function() { 15 | var result = CSSLint.verify("h1 { -webkit-transition: height 20px 1s; -moz-transition: height 20px 1s; }", { "compatible-vendor-prefixes": 1 }); 16 | Assert.areEqual(0, result.messages.length); 17 | }, 18 | 19 | "Using -webkit-transform should warn to also include -ms-transform.": function() { 20 | var result = CSSLint.verify("div.box { -webkit-transform: translate(50px, 100px); }", { "compatible-vendor-prefixes": 3 }); 21 | Assert.areEqual(1, result.messages.length); 22 | Assert.areEqual("warning", result.messages[0].type); 23 | Assert.areEqual("The property -ms-transform is compatible with -webkit-transform and should be included as well.", result.messages[0].message); 24 | }, 25 | 26 | "Using -webkit-transform inside of an @-webkit- block shouldn't cause a warning": function() { 27 | 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 }); 28 | Assert.areEqual(0, result.messages.length); 29 | }, 30 | 31 | "Using all compatible vendor prefixes for animation should be allowed with no warnings.": function() { 32 | 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 }); 33 | Assert.areEqual(0, result.messages.length); 34 | }, 35 | 36 | "Using box-shadow with no vendor prefixes should be allowed with no warnings.": function() { 37 | var result = CSSLint.verify("h1 { box-shadow: 5px 5px 5px #ccc; }", { "compatible-vendor-prefixes": 1 }); 38 | Assert.areEqual(0, result.messages.length); 39 | } 40 | 41 | })); 42 | 43 | })(); 44 | -------------------------------------------------------------------------------- /tests/rules/duplicate-background-images.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Duplicate Background-URL Rule Errors", 8 | 9 | "duplicate background-image should result in a warning": function() { 10 | var result = CSSLint.verify(".foo { background-image: url('mega-sprite.png'); } .foofoo { background-image: url('fancy-sprite.png'); } .bar { background-image: url(\"mega-sprite.png\"); } .foobar { background: white url(mega-sprite.png); }", { "duplicate-background-images": 1 }); 11 | Assert.areEqual(2, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Background image 'mega-sprite.png' was used multiple times, first declared at line 1, col 8.", result.messages[0].message); 14 | }, 15 | 16 | "duplicate background with url should result in a warning": function() { 17 | var result = CSSLint.verify(".foo { background: url(mega-sprite.png) repeat-x; } .foofoo { background-image: url('fancy-sprite.png'); } .bar { background: white url(\"mega-sprite.png\") no-repeat left top; } .foobar { background: white url('mega-sprite.png'); }", { "duplicate-background-images": 1 }); 18 | Assert.areEqual(2, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("Background image 'mega-sprite.png' was used multiple times, first declared at line 1, col 8.", result.messages[0].message); 21 | } 22 | })); 23 | 24 | })(); 25 | -------------------------------------------------------------------------------- /tests/rules/duplicate-properties.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Duplicate Property Rule Errors", 8 | 9 | "Duplicate properties back-to-back should not result in a warning": function() { 10 | var result = CSSLint.verify(".foo { float: left; float: right }", { "duplicate-properties": 1 }); 11 | Assert.areEqual(0, result.messages.length); 12 | }, 13 | 14 | "Duplicate properties in @font-face back-to-back should not result in a warning": function() { 15 | var result = CSSLint.verify("@font-face { src: url(foo.svg); src: url(foo1.svg) }", { "duplicate-properties": 1 }); 16 | Assert.areEqual(0, result.messages.length); 17 | }, 18 | 19 | "Duplicate properties in @page back-to-back should not result in a warning": function() { 20 | var result = CSSLint.verify("@page :left { margin: 5px; margin: 4px; }", { "duplicate-properties": 1 }); 21 | Assert.areEqual(0, result.messages.length); 22 | }, 23 | 24 | "Duplicate properties not back-to-back should result in a warning": function() { 25 | var result = CSSLint.verify(".foo { float: left; margin: 0; float: right }", { "duplicate-properties": 1 }); 26 | Assert.areEqual(1, result.messages.length); 27 | Assert.areEqual("warning", result.messages[0].type); 28 | Assert.areEqual("Duplicate property 'float' found.", result.messages[0].message); 29 | }, 30 | 31 | "Duplicate properties not back-to-back with same values should result in a warning": function() { 32 | var result = CSSLint.verify(".foo { float: left; margin: 0; float: left }", { "duplicate-properties": 1 }); 33 | Assert.areEqual(1, result.messages.length); 34 | Assert.areEqual("warning", result.messages[0].type); 35 | Assert.areEqual("Duplicate property 'float' found.", result.messages[0].message); 36 | }, 37 | 38 | "Duplicate properties back-to-back with same values should result in a warning": function() { 39 | var result = CSSLint.verify(".foo { float: left; float: left }", { "duplicate-properties": 1 }); 40 | Assert.areEqual(1, result.messages.length); 41 | Assert.areEqual("warning", result.messages[0].type); 42 | Assert.areEqual("Duplicate property 'float' found.", result.messages[0].message); 43 | }, 44 | 45 | "Duplicate properties in @keyframe rules should not result in a warning": function() { 46 | var result = CSSLint.verify("@-webkit-keyframes slide_up { from { bottom:-91px; } to { bottom:0; } }", { "duplicate-properties": 1 }); 47 | Assert.areEqual(0, result.messages.length); 48 | } 49 | 50 | 51 | })); 52 | 53 | })(); 54 | -------------------------------------------------------------------------------- /tests/rules/empty-rules.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Empty Rule Errors", 8 | 9 | "Empty rule should result in a warning": function() { 10 | var result = CSSLint.verify("li { }", { "empty-rules": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Rule is empty.", result.messages[0].message); 14 | } 15 | })); 16 | 17 | })(); 18 | -------------------------------------------------------------------------------- /tests/rules/errors.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Parsing Errors", 8 | 9 | "Parsing error should result in one parsing error message": function() { 10 | var result = CSSLint.verify("li { float left;}", { errors: 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("error", result.messages[0].type); 13 | } 14 | })); 15 | 16 | })(); 17 | -------------------------------------------------------------------------------- /tests/rules/floats.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Floats Rule Errors", 8 | 9 | "10 floats should result in a warning": function() { 10 | var result = CSSLint.verify(".foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; }", { "floats": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Too many floats (10), you're probably using them for layout. Consider using a grid system instead.", result.messages[0].message); 14 | }, 15 | 16 | "9 floats should not result in a warning": function() { 17 | var result = CSSLint.verify(".foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; }", { "floats": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | }, 20 | 21 | "11 floats should result in a warning": function() { 22 | var result = CSSLint.verify(".foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; }", { "floats": 1 }); 23 | Assert.areEqual(1, result.messages.length); 24 | Assert.areEqual("warning", result.messages[0].type); 25 | Assert.areEqual("Too many floats (11), you're probably using them for layout. Consider using a grid system instead.", result.messages[0].message); 26 | }, 27 | 28 | "float: none should not count and therefore should not result in a warning": function() { 29 | var result = CSSLint.verify(".foo { float: none; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; } .foo { float: left; }", { "floats": 1 }); 30 | Assert.areEqual(0, result.messages.length); 31 | }, 32 | 33 | "Ignore should remove rollup warning message for floats": function() { 34 | var report = CSSLint.verify("/* csslint ignore:start */\n.test1 {float:left}\n.test2 {float:left}\n.test3 {float:left}\n.test4 {float:left}\n.test5 {float:left}\n.test6 {float:left}\n.test7 {float:left}\n.test8 {float:left}\n.test9 {float:left}\n.test10 {float:left}\n.test11 {float:left}\n/* csslint ignore:end */h2 {color: #fff}\n"); 35 | Assert.areEqual(0, report.messages.length); 36 | } 37 | })); 38 | 39 | })(); 40 | -------------------------------------------------------------------------------- /tests/rules/font-faces.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "font-faces Rule Errors", 8 | 9 | "5 font-faces should result in a warning": function() { 10 | var result = CSSLint.verify("@font-face{ } @font-face{ } @font-face{ } @font-face{ } @font-face{ }", { "font-faces": 1 }); 11 | Assert.areEqual(0, result.messages.length); 12 | }, 13 | 14 | "4 font-faces should not result in a warning": function() { 15 | var result = CSSLint.verify("@font-face{} @font-face{} @font-face{} @font-face{}", { "font-faces": 1 }); 16 | Assert.areEqual(0, result.messages.length); 17 | }, 18 | 19 | "6 font-faces should result in a warning": function() { 20 | var result = CSSLint.verify("@font-face{} @font-face{} @font-face{} @font-face{} @font-face{} @font-face{}", { "font-faces": 1 }); 21 | Assert.areEqual(1, result.messages.length); 22 | Assert.areEqual("warning", result.messages[0].type); 23 | Assert.areEqual("Too many @font-face declarations (6).", result.messages[0].message); 24 | }, 25 | 26 | "Ignore should remove rollup warning message for font-face": function() { 27 | var report = CSSLint.verify("/* csslint ignore:start */\n@font-face{} @font-face{} @font-face{} @font-face{} @font-face{} @font-face{}\n/* csslint ignore:end */@font-face{}\n"); 28 | Assert.areEqual(0, report.messages.length); 29 | } 30 | })); 31 | 32 | })(); 33 | -------------------------------------------------------------------------------- /tests/rules/font-sizes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "font-size Rule Errors", 8 | 9 | "10 font-sizes should result in a warning": function() { 10 | var result = CSSLint.verify(".foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } ", { "font-sizes": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Too many font-size declarations (10), abstraction needed.", result.messages[0].message); 14 | }, 15 | 16 | "9 font-sizes should not result in a warning": function() { 17 | var result = CSSLint.verify(" .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } ", { "font-sizes": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | }, 20 | 21 | "11 font-sizes should result in a warning": function() { 22 | var result = CSSLint.verify(".foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } .foo { font-size: 10px; } ", { "font-sizes": 1 }); 23 | Assert.areEqual(1, result.messages.length); 24 | Assert.areEqual("warning", result.messages[0].type); 25 | Assert.areEqual("Too many font-size declarations (11), abstraction needed.", result.messages[0].message); 26 | }, 27 | 28 | "Ignore should remove rollup warning message for font-sizes": function() { 29 | var report = CSSLint.verify("/* csslint ignore:start */\n.test1 {font-size: 10px;}\n.test2 {font-size: 10px;}\n.test3 {font-size: 10px;}\n.test4 {font-size: 10px;}\n.test5 {font-size: 10px;}\n.test6 {font-size: 10px;}\n.test7 {font-size: 10px;}\n.test8 {font-size: 10px;}\n.test9 {font-size: 10px;}\n.test10 {font-size: 10px;}\n.test11 {font-size: 10px;}\n/* csslint ignore:end */h2 {color: #fff}\n"); 30 | Assert.areEqual(0, report.messages.length); 31 | } 32 | })); 33 | 34 | })(); 35 | -------------------------------------------------------------------------------- /tests/rules/gradients.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | /* 8 | background: -moz-linear-gradient(top, #1e5799 , #2989d8 , #207cca , #7db9e8 ); 9 | background: -webkit-gradient(linear, left top, left bottom, color-stop(,#1e5799), color-stop(,#2989d8), color-stop(,#207cca), color-stop(10,#7db9e8)); 10 | background: -webkit-linear-gradient(top, #1e5799 ,#2989d8 ,#207cca ,#7db9e8 ); 11 | background: -o-linear-gradient(top, #1e5799 ,#2989d8 ,#207cca ,#7db9e8 ); 12 | 13 | */ 14 | 15 | name: "Gradients Rule Errors", 16 | 17 | "Only using Mozilla gradients should result in a warning": function() { 18 | var result = CSSLint.verify(".foo { background: -moz-linear-gradient(top, #1e5799 , #2989d8 , #207cca , #7db9e8 ); }", { "gradients": 1 }); 19 | Assert.areEqual(1, result.messages.length); 20 | Assert.areEqual("warning", result.messages[0].type); 21 | Assert.areEqual("Missing vendor-prefixed CSS gradients for Webkit (Safari 5+, Chrome), Old Webkit (Safari 4+, Chrome), Opera 11.1+.", result.messages[0].message); 22 | }, 23 | 24 | "Only using Opera gradients should result in a warning": function() { 25 | var result = CSSLint.verify(".foo { background: -o-linear-gradient(top, #1e5799 , #2989d8 , #207cca , #7db9e8 ); }", { "gradients": 1 }); 26 | Assert.areEqual(1, result.messages.length); 27 | Assert.areEqual("warning", result.messages[0].type); 28 | Assert.areEqual("Missing vendor-prefixed CSS gradients for Firefox 3.6+, Webkit (Safari 5+, Chrome), Old Webkit (Safari 4+, Chrome).", result.messages[0].message); 29 | }, 30 | 31 | "Only using WebKit gradients should result in a warning": function() { 32 | var result = CSSLint.verify(".foo { background: -webkit-linear-gradient(top, #1e5799 , #2989d8 , #207cca , #7db9e8 ); }", { "gradients": 1 }); 33 | Assert.areEqual(1, result.messages.length); 34 | Assert.areEqual("warning", result.messages[0].type); 35 | Assert.areEqual("Missing vendor-prefixed CSS gradients for Firefox 3.6+, Old Webkit (Safari 4+, Chrome), Opera 11.1+.", result.messages[0].message); 36 | }, 37 | 38 | "Only using old WebKit gradients should result in a warning": function() { 39 | var result = CSSLint.verify(".foo { background: -webkit-gradient(linear, left top, left bottom, color-stop(10%,#1e5799), color-stop(20%,#2989d8), color-stop(30%,#207cca), color-stop(100%,#7db9e8)); }", { "gradients": 1 }); 40 | Assert.areEqual(1, result.messages.length); 41 | Assert.areEqual("warning", result.messages[0].type); 42 | Assert.areEqual("Missing vendor-prefixed CSS gradients for Firefox 3.6+, Webkit (Safari 5+, Chrome), Opera 11.1+.", result.messages[0].message); 43 | }, 44 | 45 | "Using all vendor-prefixed gradients should not result in a warning": function() { 46 | var result = CSSLint.verify("div.box {\n background: -moz-linear-gradient(top, #1e5799 0%, #7db9e8 100%);\n background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color- stop(100%,#7db9e8));\n background: -webkit-linear-gradient(top, #1e5799 0%,#7db9e8 100%);\n background: -o-linear-gradient(top, #1e5799 0%,#7db9e8 100%);\n}", { "gradients": 1 }); 47 | Assert.areEqual(0, result.messages.length); 48 | } 49 | })); 50 | 51 | })(); 52 | -------------------------------------------------------------------------------- /tests/rules/ids.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "IDs Rule Errors", 8 | 9 | "Using an ID should result in one warning": function() { 10 | var result = CSSLint.verify("#foo { float: left;}", { ids: 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Don't use IDs in selectors.", result.messages[0].message); 14 | }, 15 | 16 | "Using multiple IDs should result in one warning": function() { 17 | var result = CSSLint.verify("#foo #bar { float: left;}", { ids: 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("2 IDs in the selector, really?", result.messages[0].message); 21 | } 22 | })); 23 | 24 | })(); 25 | -------------------------------------------------------------------------------- /tests/rules/import-ie-limit.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert, 4 | IMPORT_STATEMENT = "@import url('foo.css');", 5 | MAX_IMPORT_LIMIT = 31, 6 | withinLimitCss = "", 7 | exceedLimitCss = "", 8 | greatlyExceedLimitCss = "", 9 | i; 10 | 11 | // Build CSS strings to be used in tests 12 | withinLimitCss = IMPORT_STATEMENT; 13 | 14 | for (i = 0; i < MAX_IMPORT_LIMIT + 1; i++) { 15 | exceedLimitCss += IMPORT_STATEMENT; 16 | } 17 | 18 | for (i = 0; i < MAX_IMPORT_LIMIT + 100; i++) { 19 | greatlyExceedLimitCss += IMPORT_STATEMENT; 20 | } 21 | 22 | YUITest.TestRunner.add(new YUITest.TestCase({ 23 | 24 | name: "Import IE Limit Rule Error", 25 | 26 | "Using @import <= 31 times should not result in error": function() { 27 | 28 | var result = CSSLint.verify(withinLimitCss, { "import-ie-limit": 1 }); 29 | Assert.areEqual(0, result.messages.length); 30 | }, 31 | 32 | "Using @import > 31 times should result in error": function() { 33 | var result = CSSLint.verify(exceedLimitCss, { "import-ie-limit": 1 }); 34 | Assert.areEqual(1, result.messages.length); 35 | Assert.areEqual("error", result.messages[0].type); 36 | Assert.areEqual("Too many @import rules (32). IE6-9 supports up to 31 import per stylesheet.", result.messages[0].message); 37 | }, 38 | 39 | "Using @import > 31 times repeatedly should result in a single error": function() { 40 | var result = CSSLint.verify(greatlyExceedLimitCss, { "import-ie-limit": 1 }); 41 | Assert.areEqual(1, result.messages.length); 42 | Assert.areEqual("error", result.messages[0].type); 43 | Assert.areEqual("Too many @import rules (131). IE6-9 supports up to 31 import per stylesheet.", result.messages[0].message); 44 | } 45 | })); 46 | 47 | })(); 48 | -------------------------------------------------------------------------------- /tests/rules/import.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Import Rule Errors", 8 | 9 | "Using @import should result in a warning": function() { 10 | var result = CSSLint.verify("@import url('foo.css');", { "import": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("@import prevents parallel downloads, use instead.", result.messages[0].message); 14 | } 15 | })); 16 | 17 | })(); 18 | -------------------------------------------------------------------------------- /tests/rules/important.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "!important; Errors", 8 | 9 | "!important declarations should result in a warning": function() { 10 | var result = CSSLint.verify("h1 { color:#fff !important; }", { "important": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Use of !important", result.messages[0].message); 14 | }, 15 | 16 | "Using !important at least 10 times should result in an error": function() { 17 | var css = "h1 { color:#fff !important; } h2 { color:#fff !important; } h3 { color:#fff !important; } h4 { color:#fff !important; } h5 { color:#fff !important; } h6 { color:#fff !important; } p { color:#fff !important; } ul { color:#fff !important; } ol { color:#fff !important; } li { color:#fff !important; }"; 18 | var result = CSSLint.verify(css, { "important": 1 }); 19 | Assert.areEqual(11, result.messages.length); 20 | Assert.areEqual("warning", result.messages[10].type); 21 | Assert.areEqual("Too many !important declarations (10), try to use less than 10 to avoid specificity issues.", result.messages[10].message); 22 | }, 23 | 24 | "Ignore should remove rollup warning message for important": function() { 25 | var report = CSSLint.verify("/* csslint ignore:start */\n.test1 {color:#fff !important;}\n.test2 {color:#fff !important;}\n.test3 {color:#fff !important;}\n.test4 {color:#fff !important;}\n.test5 {color:#fff !important;}\n.test6 {color:#fff !important;}\n.test7 {color:#fff !important;}\n.test8 {color:#fff !important;}\n.test9 {color:#fff !important;}\n.test10 {color:#fff !important;}\n.test11 {color:#fff !important;}\n/* csslint ignore:end */h2 {color: #fff}\n"); 26 | Assert.areEqual(0, report.messages.length); 27 | } 28 | 29 | })); 30 | 31 | })(); 32 | -------------------------------------------------------------------------------- /tests/rules/known-properties.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Known Properties Errors", 8 | 9 | "Using an unknown property should result in a warning": function() { 10 | var result = CSSLint.verify("h1 { foo: red;}", { "known-properties": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Unknown property 'foo'.", result.messages[0].message); 14 | }, 15 | 16 | "Using a known property should not result in a warning": function() { 17 | var result = CSSLint.verify("h1 { color: red;}", { "known-properties": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | }, 20 | 21 | "Using a known property with the star hack should not result in a warning": function() { 22 | var result = CSSLint.verify("h1 { *color: red;}", { "known-properties": 1 }); 23 | Assert.areEqual(0, result.messages.length); 24 | }, 25 | 26 | "Using a known property with the underscore hack should not result in a warning": function() { 27 | var result = CSSLint.verify("h1 { _color: red;}", { "known-properties": 1 }); 28 | Assert.areEqual(0, result.messages.length); 29 | }, 30 | 31 | "Using a vendor-prefix property should not result in a warning": function() { 32 | var result = CSSLint.verify("h2 { -moz-border-radius: 5px; }", { "known-properties": 1 }); 33 | Assert.areEqual(0, result.messages.length); 34 | }, 35 | 36 | "Using src in @font-face should not result in a warning": function() { 37 | var result = CSSLint.verify("@font-face { src: url(foo.otf); }", { "known-properties": 1 }); 38 | Assert.areEqual(0, result.messages.length); 39 | } 40 | 41 | })); 42 | 43 | })(); 44 | -------------------------------------------------------------------------------- /tests/rules/order-alphabetical.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Alphabetical order Errors", 8 | 9 | "Rules with properties not in alphabetical order should result in a warning": function() { 10 | var result = CSSLint.verify("li { z-index: 2; color: red; }", { "order-alphabetical": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Rule doesn't have all its properties in alphabetical order.", result.messages[0].message); 14 | }, 15 | 16 | "Rules with prefixed properties not in alphabetical order (without the prefix) should result in a warning": function() { 17 | var result = CSSLint.verify("li { -moz-transition: none; -webkit-box-shadow: none; }", { "order-alphabetical": 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("Rule doesn't have all its properties in alphabetical order.", result.messages[0].message); 21 | }, 22 | 23 | "Rules with properties in alphabetical order should not result in a warning": function() { 24 | var result = CSSLint.verify("li { box-shadow: none; color: red; transition: none; }", { "order-alphabetical": 1 }); 25 | Assert.areEqual(0, result.messages.length); 26 | }, 27 | 28 | "Rules with prefixed properties in alphabetical order should not result in a warning": function() { 29 | var result = CSSLint.verify("li { -webkit-box-shadow: none; color: red; -moz-transition: none; }", { "order-alphabetical": 1 }); 30 | Assert.areEqual(0, result.messages.length); 31 | } 32 | 33 | })); 34 | 35 | })(); 36 | -------------------------------------------------------------------------------- /tests/rules/outline-none.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Outline:none Errors", 8 | 9 | "Using outline: none should result in a warning": function() { 10 | var result = CSSLint.verify(".foo { outline: none; }", { "outline-none": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Outlines should only be modified using :focus.", result.messages[0].message); 14 | }, 15 | 16 | "Using outline: 0 should result in a warning": function() { 17 | var result = CSSLint.verify(".foo { outline: 0; }", { "outline-none": 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("Outlines should only be modified using :focus.", result.messages[0].message); 21 | }, 22 | 23 | "Using outline: none alone with :focus should result in a warning": function() { 24 | var result = CSSLint.verify(".foo:focus { outline: none; }", { "outline-none": 1 }); 25 | Assert.areEqual(1, result.messages.length); 26 | Assert.areEqual("warning", result.messages[0].type); 27 | Assert.areEqual("Outlines shouldn't be hidden unless other visual changes are made.", result.messages[0].message); 28 | }, 29 | 30 | "Using outline: 0 alone with :focus should result in a warning": function() { 31 | var result = CSSLint.verify(".foo:focus { outline: 0; }", { "outline-none": 1 }); 32 | Assert.areEqual(1, result.messages.length); 33 | Assert.areEqual("warning", result.messages[0].type); 34 | Assert.areEqual("Outlines shouldn't be hidden unless other visual changes are made.", result.messages[0].message); 35 | }, 36 | 37 | "Using outline: none with :focus and another property should not result in a warning": function() { 38 | var result = CSSLint.verify(".foo:focus { outline: none; border: 1px solid black; }", { "outline-none": 1 }); 39 | Assert.areEqual(0, result.messages.length); 40 | }, 41 | 42 | "Using outline: 0 with :focus and another property should not result in a warning": function() { 43 | var result = CSSLint.verify(".foo:focus { outline: 0; border: 1px solid black;}", { "outline-none": 1 }); 44 | Assert.areEqual(0, result.messages.length); 45 | } 46 | 47 | })); 48 | 49 | })(); 50 | -------------------------------------------------------------------------------- /tests/rules/overqualified-elements.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Overqualified Elements Errors", 8 | 9 | "Using an ID with an element should result in one warning": function() { 10 | var result = CSSLint.verify("li#foo { float: left;}", { "overqualified-elements": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Element (li#foo) is overqualified, just use #foo without element name.", result.messages[0].message); 14 | }, 15 | 16 | "Using a class without an element should not result in a warning": function() { 17 | var result = CSSLint.verify(".foo { float: left;}", { "overqualified-elements": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | }, 20 | 21 | "Using a class with an element should result in one warning": function() { 22 | var result = CSSLint.verify("li.foo { float: left;}", { "overqualified-elements": 1 }); 23 | Assert.areEqual(1, result.messages.length); 24 | Assert.areEqual("warning", result.messages[0].type); 25 | Assert.areEqual("Element (li.foo) is overqualified, just use .foo without element name.", result.messages[0].message); 26 | }, 27 | 28 | "Using a class with two different elements should not result in a warning": function() { 29 | var result = CSSLint.verify("li.foo { float: left;} p.foo { float: right; }", { "overqualified-elements": 1 }); 30 | Assert.areEqual(0, result.messages.length); 31 | }, 32 | 33 | "Using a class with an element and without should not result in a warning": function() { 34 | var result = CSSLint.verify("li.foo { float: left;} .foo { float: right; }", { "overqualified-elements": 1 }); 35 | Assert.areEqual(0, result.messages.length); 36 | } 37 | 38 | })); 39 | 40 | })(); 41 | -------------------------------------------------------------------------------- /tests/rules/performant-transitions.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | "use strict"; 3 | 4 | /*global YUITest, CSSLint*/ 5 | var Assert = YUITest.Assert; 6 | 7 | YUITest.TestRunner.add(new YUITest.TestCase({ 8 | 9 | name: "Performant Transitions Tests", 10 | 11 | "Using a non-performant transition property (width) should result in one warning": function(){ 12 | var result = CSSLint.verify("div { transition: width 0.5s linear; }", { "performant-transitions": 1 }); 13 | Assert.areEqual(1, result.messages.length); 14 | Assert.areEqual("warning", result.messages[0].type); 15 | Assert.areEqual("Unexpected transition property 'width 0.5s linear'", result.messages[0].message); 16 | }, 17 | "Using a non-performant and performant transition properties (transform, width) should result in one warning": function(){ 18 | var result = CSSLint.verify("div { transition: transform 0.5s linear, width 0.2s ease-in-out; }", { "performant-transitions": 1 }); 19 | Assert.areEqual(1, result.messages.length); 20 | Assert.areEqual("warning", result.messages[0].type); 21 | Assert.areEqual("Unexpected transition property 'width 0.2s ease-in-out'", result.messages[0].message); 22 | }, 23 | "Using a performant transition property (transform) should result in 0 warnings": function(){ 24 | var result = CSSLint.verify("div { transition: transform 0.5s linear; }", { "performant-transitions": 1 }); 25 | Assert.areEqual(0, result.messages.length); 26 | }, 27 | "Using a performant transition property (-webkit-transform) should result in 0 warnings": function(){ 28 | var result = CSSLint.verify("div { transition: -webkit-transform 0.5s linea atomr; }", { "performant-transitions": 1 }); 29 | Assert.areEqual(0, result.messages.length); 30 | }, 31 | "Using a performant transition property (-ms-transform) should result in 0 warnings": function(){ 32 | var result = CSSLint.verify("div { transition: -ms-transform 0.5s linear; }", { "performant-transitions": 1 }); 33 | Assert.areEqual(0, result.messages.length); 34 | }, 35 | "Using a performant transition property (opacity) should result in 0 warnings": function(){ 36 | var result = CSSLint.verify("div { transition: opacity 0.5s linear; }", { "performant-transitions": 1 }); 37 | Assert.areEqual(0, result.messages.length); 38 | }, 39 | "Using multiple performant transition properties (opacity, transform) should result in 0 warnings": function(){ 40 | var result = CSSLint.verify("div { transition: opacity 0.5s linear, transform 0.25s ease-in-out; }", { "performant-transitions": 1 }); 41 | Assert.areEqual(0, result.messages.length); 42 | } 43 | })); 44 | })(); 45 | -------------------------------------------------------------------------------- /tests/rules/qualified-headings.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Qualified Headings Errors", 8 | 9 | "Using a heading as a descendant should result in one warning": function() { 10 | var result = CSSLint.verify("li h3{ float: left;}", { "qualified-headings": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Heading (h3) should not be qualified.", result.messages[0].message); 14 | } 15 | 16 | })); 17 | 18 | })(); 19 | -------------------------------------------------------------------------------- /tests/rules/regex-selectors.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Regex Selectors Errors", 8 | 9 | "Using |= in an attribute selector should result in one warning": function() { 10 | var result = CSSLint.verify("li[class|=foo]{ color: red; }", { "regex-selectors": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Attribute selectors with |= are slow!", result.messages[0].message); 14 | }, 15 | 16 | "Using *= in an attribute selector should result in one warning": function() { 17 | var result = CSSLint.verify("li[class*=foo]{ color: red; }", { "regex-selectors": 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("Attribute selectors with *= are slow!", result.messages[0].message); 21 | }, 22 | 23 | "Using $= in an attribute selector should result in one warning": function() { 24 | var result = CSSLint.verify("li[class$=foo]{ color: red; }", { "regex-selectors": 1 }); 25 | Assert.areEqual(1, result.messages.length); 26 | Assert.areEqual("warning", result.messages[0].type); 27 | Assert.areEqual("Attribute selectors with $= are slow!", result.messages[0].message); 28 | }, 29 | 30 | "Using ~= in an attribute selector should result in one warning": function() { 31 | var result = CSSLint.verify("li[class~=foo]{ color: red; }", { "regex-selectors": 1 }); 32 | Assert.areEqual(1, result.messages.length); 33 | Assert.areEqual("warning", result.messages[0].type); 34 | Assert.areEqual("Attribute selectors with ~= are slow!", result.messages[0].message); 35 | }, 36 | 37 | "Using ^= in an attribute selector should result in one warning": function() { 38 | var result = CSSLint.verify("li[class^=foo]{ color: red; }", { "regex-selectors": 1 }); 39 | Assert.areEqual(1, result.messages.length); 40 | Assert.areEqual("warning", result.messages[0].type); 41 | Assert.areEqual("Attribute selectors with ^= are slow!", result.messages[0].message); 42 | }, 43 | 44 | "Using = in an attribute selector should not result in a warning": function() { 45 | var result = CSSLint.verify("li[class=foo]{ color: red; }", { "regex-selectors": 1 }); 46 | Assert.areEqual(0, result.messages.length); 47 | } 48 | 49 | })); 50 | 51 | })(); 52 | -------------------------------------------------------------------------------- /tests/rules/selector-max-approaching.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert, i, j, css1 = "", css2 = "", css3 = "", css4 = ""; 4 | 5 | // create css1, which has only 4095 rules and 4095 selectors 6 | for (i = 1; i <= 4095; i++) { 7 | css1 += ".selector" + i + " { background:red; } "; 8 | } 9 | 10 | // create css2, which has 4096 rules and 4096 selectors 11 | for (i = 1; i <= 4096; i++) { 12 | css2 += ".selector" + i + " { background:red; } "; 13 | } 14 | 15 | // create css3, which has 1024 and but only 4095 selectors 16 | for (i = 0; i <= 1022; i++) { 17 | j = i * 4; 18 | css3 += ".selector" + (j+1) + ", .selector" + (j+2) + ", .selector" + (j+3) + ", .selector" + (j+4) + " { background:red; } "; 19 | } 20 | css3 += ".selector4093 { background:red; }.selector4094, .selector4095 { background:red; } "; 21 | 22 | // create css4, which has 1024 rules and 4096 selectors 23 | for (i = 0; i <= 1023; i++) { 24 | j = i * 4; 25 | css4 += ".selector" + (j+1) + ", .selector" + (j+2) + ", .selector" + (j+3) + ", .selector" + (j+4) + " { background:red; } "; 26 | } 27 | 28 | YUITest.TestRunner.add(new YUITest.TestCase({ 29 | 30 | name: "Selector Max Errors Approaching", 31 | 32 | "Using 4095 or fewer single-selector rules should not result in a warning": function() { 33 | var result = CSSLint.verify(css1, { "selector-max-approaching": 1 }); 34 | Assert.areEqual(1, result.messages.length); 35 | Assert.areEqual("warning", result.messages[0].type); 36 | Assert.areEqual("You have 4095 selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", result.messages[0].message); 37 | }, 38 | 39 | "Using 4096 or more single-selector rules should result in a warning": function() { 40 | var result = CSSLint.verify(css2, { "selector-max-approaching": 1 }); 41 | Assert.areEqual(1, result.messages.length); 42 | Assert.areEqual("warning", result.messages[0].type); 43 | Assert.areEqual("You have 4096 selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", result.messages[0].message); 44 | }, 45 | 46 | "Using 4095 or fewer selectors should not result in a warning": function() { 47 | var result = CSSLint.verify(css3, { "selector-max-approaching": 1 }); 48 | Assert.areEqual(1, result.messages.length); 49 | Assert.areEqual("warning", result.messages[0].type); 50 | Assert.areEqual("You have 4095 selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", result.messages[0].message); 51 | }, 52 | 53 | "Using 4096 or more selectors should result in a warning": function() { 54 | var result = CSSLint.verify(css4, { "selector-max-approaching": 1 }); 55 | Assert.areEqual(1, result.messages.length); 56 | Assert.areEqual("warning", result.messages[0].type); 57 | Assert.areEqual("You have 4096 selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", result.messages[0].message); 58 | }, 59 | 60 | "Using fewer than 3800 selectors should not result in a warning": function() { 61 | var result = CSSLint.verify(".selector1 { background: red; }", { "selector-max-approaching": 1 }); 62 | Assert.areEqual(0, result.messages.length); 63 | } 64 | 65 | })); 66 | 67 | })(); 68 | -------------------------------------------------------------------------------- /tests/rules/selector-max.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert, i, j, css1 = "", css2 = "", css3 = "", css4 = ""; 4 | 5 | // create css1, which has only 4095 rules and 4095 selectors 6 | for (i = 1; i <= 4095; i++) { 7 | css1 += ".selector" + i + " { background:red; } "; 8 | } 9 | 10 | // create css2, which has 4096 rules and 4096 selectors 11 | for (i = 1; i <= 4096; i++) { 12 | css2 += ".selector" + i + " { background:red; } "; 13 | } 14 | 15 | // create css3, which has 1024 and but only 4095 selectors 16 | for (i = 0; i <= 1022; i++) { 17 | j = i * 4; 18 | css3 += ".selector" + (j+1) + ", .selector" + (j+2) + ", .selector" + (j+3) + ", .selector" + (j+4) + " { background:red; } "; 19 | } 20 | css3 += ".selector4093 { background:red; }.selector4094, .selector4095 { background:red; } "; 21 | 22 | // create css4, which has 1024 rules and 4096 selectors 23 | for (i = 0; i <= 1023; i++) { 24 | j = i * 4; 25 | css4 += ".selector" + (j+1) + ", .selector" + (j+2) + ", .selector" + (j+3) + ", .selector" + (j+4) + " { background:red; } "; 26 | } 27 | 28 | YUITest.TestRunner.add(new YUITest.TestCase({ 29 | 30 | name: "Selector Max Errors", 31 | 32 | "Using 4095 or fewer single-selector rules should not result in a warning": function() { 33 | var result = CSSLint.verify(css1, { "selector-max": 1 }); 34 | Assert.areEqual(0, result.messages.length); 35 | }, 36 | 37 | "Using 4096 or more single-selector rules should result in a warning": function() { 38 | var result = CSSLint.verify(css2, { "selector-max": 1 }); 39 | Assert.areEqual(1, result.messages.length); 40 | Assert.areEqual("warning", result.messages[0].type); 41 | Assert.areEqual("You have 4096 selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", result.messages[0].message); 42 | }, 43 | 44 | "Using 4095 or fewer selectors should not result in a warning": function() { 45 | var result = CSSLint.verify(css3, { "selector-max": 1 }); 46 | Assert.areEqual(0, result.messages.length); 47 | }, 48 | 49 | "Using 4096 or more selectors should result in a warning": function() { 50 | var result = CSSLint.verify(css4, { "selector-max": 1 }); 51 | Assert.areEqual(1, result.messages.length); 52 | Assert.areEqual("warning", result.messages[0].type); 53 | Assert.areEqual("You have 4096 selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", result.messages[0].message); 54 | } 55 | 56 | })); 57 | 58 | })(); 59 | -------------------------------------------------------------------------------- /tests/rules/selector-newline.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | var ruleId = "selector-newline", expectWarning, expectPass; 4 | 5 | expectWarning = function (ruleset, expectedMessage) { 6 | var result, enabledRules = {}; 7 | enabledRules[ruleId] = 1; 8 | result = CSSLint.verify(ruleset, enabledRules); 9 | YUITest.Assert.areEqual(1, result.messages.length); 10 | YUITest.Assert.areEqual("warning", result.messages[0].type); 11 | YUITest.Assert.areEqual(expectedMessage, result.messages[0].message); 12 | }; 13 | 14 | expectPass = function (ruleset) { 15 | var result, enabledRules = {}; 16 | enabledRules[ruleId] = 1; 17 | result = CSSLint.verify(ruleset, enabledRules); 18 | YUITest.Assert.areEqual(0, result.messages.length); 19 | }; 20 | 21 | YUITest.TestRunner.add(new YUITest.TestCase({ 22 | 23 | name: ruleId + " Rule Errors", 24 | 25 | "a newline in a selector should result in a warning": function () { 26 | expectWarning(".foo\n.bar{}", "newline character found in selector (forgot a comma?)"); 27 | }, 28 | "a newline between selectors should not result in a warning": function () { 29 | expectPass(".foo,\n.bar{}"); 30 | }, 31 | "'+' or '>' should not result in a warning": function () { 32 | expectPass(".foo > .bar,\n.foo + .bar,\n.foo >\n.bar{}"); 33 | } 34 | })); 35 | 36 | }()); 37 | -------------------------------------------------------------------------------- /tests/rules/shorthand.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Shorthand Rule Errors", 8 | 9 | "All padding properties should result in a warning": function() { 10 | var result = CSSLint.verify(".foo{padding-top: 0px; padding-left: 3px; padding-right: 25px; padding-bottom: 10px;}", { "shorthand": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("The properties padding-top, padding-bottom, padding-left, padding-right can be replaced by padding.", result.messages[0].message); 14 | }, 15 | 16 | "All margin properties should result in a warning": function() { 17 | var result = CSSLint.verify(".foo{margin-top: 0px; margin-left: 3px; margin-right: 25px; margin-bottom: 10px;}", { "shorthand": 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("The properties margin-top, margin-bottom, margin-left, margin-right can be replaced by margin.", result.messages[0].message); 21 | }, 22 | 23 | "padding-left should not result in a warning": function() { 24 | var result = CSSLint.verify(".foo{ padding-left: 8px;} ", { "shorthand": 1 }); 25 | Assert.areEqual(0, result.messages.length); 26 | }, 27 | 28 | "margin-top should not result in a warning": function() { 29 | var result = CSSLint.verify(".foo{ margin-top: 8px;} ", { "shorthand": 1 }); 30 | Assert.areEqual(0, result.messages.length); 31 | } 32 | 33 | })); 34 | 35 | })(); 36 | -------------------------------------------------------------------------------- /tests/rules/star-property-hack.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "star-property-hack Rule Errors", 8 | 9 | "a property with a star prefix should result in a warning": function() { 10 | var result = CSSLint.verify(".foo{*width: 100px;}", { "star-property-hack": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Property with star prefix found.", result.messages[0].message); 14 | }, 15 | 16 | "a property without a star prefix should not result in a warning": function() { 17 | var result = CSSLint.verify(".foo{width: 100px;}", { "star-property-hack": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | } 20 | 21 | })); 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /tests/rules/text-indent.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "text-indent Rule Errors", 8 | 9 | "-100px text-indent should result in a warning": function() { 10 | var result = CSSLint.verify(".foo{text-indent: -100px;}", { "text-indent": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", result.messages[0].message); 14 | }, 15 | 16 | "-99px text-indent should not result in a warning": function() { 17 | var result = CSSLint.verify(".foo{text-indent: -99px;} ", { "text-indent": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | }, 20 | 21 | "-99em text-indent should not result in a warning": function() { 22 | var result = CSSLint.verify(".foo{text-indent: -99em;} ", { "text-indent": 1 }); 23 | Assert.areEqual(0, result.messages.length); 24 | }, 25 | 26 | "-100px text-indent with LTR should not result in a warning": function() { 27 | var result = CSSLint.verify(".foo{text-indent: -100px; direction: ltr; }", { "text-indent": 1 }); 28 | Assert.areEqual(0, result.messages.length); 29 | result = CSSLint.verify(".foo{direction: ltr; text-indent: -100px; }", { "text-indent": 1 }); 30 | Assert.areEqual(0, result.messages.length); 31 | }, 32 | 33 | "-100em text-indent with RTL should result in a warning": function() { 34 | var result = CSSLint.verify(".foo{text-indent: -100em; direction: rtl; }", { "text-indent": 1 }); 35 | Assert.areEqual(1, result.messages.length); 36 | Assert.areEqual("warning", result.messages[0].type); 37 | Assert.areEqual("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", result.messages[0].message); 38 | }, 39 | 40 | "5px text-indent should not result in a warning": function() { 41 | var result = CSSLint.verify(".foo{text-indent: 5px;}", { "text-indent": 1 }); 42 | Assert.areEqual(0, result.messages.length); 43 | }, 44 | 45 | "This should cause a warning, not an error": function() { 46 | var result = CSSLint.verify(".top h1 a { background: url(../images/background/logo.png) no-repeat; display: block; height: 44px; position: relative; text-indent: -9999px; width: 250px; }", { "text-indent": 1 }); 47 | Assert.areEqual(1, result.messages.length); 48 | Assert.areEqual("warning", result.messages[0].type); 49 | Assert.areEqual("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", result.messages[0].message); 50 | } 51 | 52 | })); 53 | 54 | })(); 55 | -------------------------------------------------------------------------------- /tests/rules/underscore-property-hack.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "underscore-property-hack Rule Errors", 8 | 9 | "a property with an underscore prefix should result in a warning": function() { 10 | var result = CSSLint.verify(".foo{_width: 100px;}", { "underscore-property-hack": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Property with underscore prefix found.", result.messages[0].message); 14 | }, 15 | 16 | "a property without an underscore prefix should not result in a warning": function() { 17 | var result = CSSLint.verify(".foo{width: 100px;}", { "underscore-property-hack": 1 }); 18 | Assert.areEqual(0, result.messages.length); 19 | } 20 | 21 | })); 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /tests/rules/unique-headings.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Unique Headings Errors", 8 | 9 | "Defining two rules for h1 should result in two warnings": function() { 10 | var result = CSSLint.verify("h1 { color: red;} h1 {color: blue;}", { "unique-headings": 1 }); 11 | Assert.areEqual(2, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Heading (h1) has already been defined.", result.messages[0].message); 14 | Assert.areEqual("warning", result.messages[1].type); 15 | Assert.areEqual("You have 2 h1s defined in this stylesheet.", result.messages[1].message); 16 | }, 17 | 18 | "Defining two rules for h1 and h2 should result in one warning": function() { 19 | var result = CSSLint.verify("h1 { color: red;} h1 {color: blue;} h2 { color: red;} h2 {color: blue;}", { "unique-headings": 1 }); 20 | Assert.areEqual(3, result.messages.length); 21 | Assert.areEqual("warning", result.messages[0].type); 22 | Assert.areEqual("Heading (h1) has already been defined.", result.messages[0].message); 23 | Assert.areEqual("warning", result.messages[1].type); 24 | Assert.areEqual("Heading (h2) has already been defined.", result.messages[1].message); 25 | Assert.areEqual("warning", result.messages[2].type); 26 | Assert.areEqual("You have 2 h1s, 2 h2s defined in this stylesheet.", result.messages[2].message); 27 | }, 28 | 29 | "Defining one rule for h1 should not result in a warning": function() { 30 | var result = CSSLint.verify("h1 { color: red;}", { "unique-headings": 1 }); 31 | Assert.areEqual(0, result.messages.length); 32 | }, 33 | 34 | "Defining a rule for h1 and h1:hover should not result in a warning": function() { 35 | var result = CSSLint.verify("h1 { color: red;} h1:hover { color: blue; }", { "unique-headings": 1 }); 36 | Assert.areEqual(0, result.messages.length); 37 | }, 38 | 39 | "Defining multiple rules that contain h1 should not result in a warning": function() { 40 | var result = CSSLint.verify("h2 a, h2 a:active, h2 a:hover, h2 a:visited, h2 a:link { color: red;}", { "unique-headings": 1 }); 41 | Assert.areEqual(0, result.messages.length); 42 | }, 43 | 44 | "Ignore should remove rollup warning messages for unique headings": function() { 45 | var report = CSSLint.verify("/* csslint ignore:start */\nh1 {color: #f0f}\nh1 {color: #ff0}/* csslint ignore:end */h2 {color: #fff}\n"); 46 | Assert.areEqual(0, report.messages.length); 47 | } 48 | 49 | })); 50 | 51 | })(); 52 | -------------------------------------------------------------------------------- /tests/rules/universal-selector.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Universal Selector Errors", 8 | 9 | "Using a universal selector alone should result in a warning": function() { 10 | var result = CSSLint.verify("* { font-size: 10px; }", { "universal-selector": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("The universal selector (*) is known to be slow.", result.messages[0].message); 14 | }, 15 | 16 | "Using a universal selector as the right-most part should result in a warning": function() { 17 | var result = CSSLint.verify("p div * { font-size: 10px; }", { "universal-selector": 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("The universal selector (*) is known to be slow.", result.messages[0].message); 21 | }, 22 | 23 | "Using a universal selector in the middle should not result in a warning": function() { 24 | var result = CSSLint.verify("* .foo { font-size: 10px; } ", { "universal-selector": 1 }); 25 | Assert.areEqual(0, result.messages.length); 26 | } 27 | 28 | })); 29 | 30 | })(); 31 | -------------------------------------------------------------------------------- /tests/rules/unqualified-attributes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Unqualified Attributes Errors", 8 | 9 | "Using an unqualified attribute selector alone should result in a warning": function() { 10 | var result = CSSLint.verify("[type=text] { font-size: 10px; }", { "unqualified-attributes": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Unqualified attribute selectors are known to be slow.", result.messages[0].message); 14 | }, 15 | 16 | "Using an unqualified attribute selector as the right-most part should result in a warning": function() { 17 | var result = CSSLint.verify("p div [type=text] { font-size: 10px; }", { "unqualified-attributes": 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("Unqualified attribute selectors are known to be slow.", result.messages[0].message); 21 | }, 22 | 23 | "Using an unqualified attribute selector in the middle should not result in a warning": function() { 24 | var result = CSSLint.verify("[type=text] .foo { font-size: 10px; } ", { "unqualified-attributes": 1 }); 25 | Assert.areEqual(0, result.messages.length); 26 | }, 27 | 28 | "Using a qualified attribute selector should not result in a warning": function() { 29 | var result = CSSLint.verify("input[type=text] { font-size: 10px; } ", { "unqualified-attributes": 1 }); 30 | Assert.areEqual(0, result.messages.length); 31 | }, 32 | 33 | "Using an attribute selector qualified by a class should not result in a warning": function() { 34 | var result = CSSLint.verify(".fancy[type=text] { font-size: 10px; }", { "unqualified-attributes": 1 }); 35 | Assert.areEqual(0, result.messages.length); 36 | }, 37 | 38 | "Using an attribute selector qualified by an ID should not result in a warning": function() { 39 | var result = CSSLint.verify("#fancy[type=text] { font-size: 10px; }", { "unqualified-attributes": 1 }); 40 | Assert.areEqual(0, result.messages.length); 41 | } 42 | 43 | })); 44 | 45 | })(); 46 | -------------------------------------------------------------------------------- /tests/rules/vendor-prefix.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Vendor Prefix Errors", 8 | 9 | "Using -moz-border-radius without border-radius should result in one warning": function() { 10 | var result = CSSLint.verify("h1 {\n -moz-border-radius: 5px; \n}", { "vendor-prefix": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Missing standard property 'border-radius' to go along with '-moz-border-radius'.", result.messages[0].message); 14 | Assert.areEqual(2, result.messages[0].line); 15 | Assert.areEqual(5, result.messages[0].col); 16 | }, 17 | 18 | "Using -webkit-border-radius without border-radius should result in one warning": function() { 19 | var result = CSSLint.verify("h1 { -webkit-border-radius: 5px; }", { "vendor-prefix": 1 }); 20 | Assert.areEqual(1, result.messages.length); 21 | Assert.areEqual("warning", result.messages[0].type); 22 | Assert.areEqual("Missing standard property 'border-radius' to go along with '-webkit-border-radius'.", result.messages[0].message); 23 | }, 24 | 25 | "Using -o-border-radius without border-radius should result in one warning": function() { 26 | var result = CSSLint.verify("h1 { -o-border-radius: 5px; }", { "vendor-prefix": 1 }); 27 | Assert.areEqual(1, result.messages.length); 28 | Assert.areEqual("warning", result.messages[0].type); 29 | Assert.areEqual("Missing standard property 'border-radius' to go along with '-o-border-radius'.", result.messages[0].message); 30 | }, 31 | 32 | "Using -moz-border-radius after border-radius should result in one warning": function() { 33 | var result = CSSLint.verify("h1 { \nborder-radius: 5px; \n -moz-border-radius: 5px; }", { "vendor-prefix": 1 }); 34 | Assert.areEqual(1, result.messages.length); 35 | Assert.areEqual("warning", result.messages[0].type); 36 | Assert.areEqual("Standard property 'border-radius' should come after vendor-prefixed property '-moz-border-radius'.", result.messages[0].message); 37 | Assert.areEqual(3, result.messages[0].line); 38 | Assert.areEqual(5, result.messages[0].col); 39 | 40 | }, 41 | 42 | "Using -webkit-border-bottom-left-radius with border-bottom-left-radius should not result in a warning.": function() { 43 | var result = CSSLint.verify("h1 { -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }", { "vendor-prefix": 1 }); 44 | Assert.areEqual(0, result.messages.length); 45 | }, 46 | 47 | "Using -moz-border-radius-bottomleft should result in a warning.": function() { 48 | var result = CSSLint.verify("h1 { -moz-border-radius-bottomleft: 5px; }", { "vendor-prefix": 1 }); 49 | Assert.areEqual(1, result.messages.length); 50 | Assert.areEqual("warning", result.messages[0].type); 51 | Assert.areEqual("Missing standard property 'border-bottom-left-radius' to go along with '-moz-border-radius-bottomleft'.", result.messages[0].message); 52 | }, 53 | 54 | "Using -moz-box-shadow should result in a warning.": function() { 55 | var result = CSSLint.verify("h1 { -moz-box-shadow: 5px; }", { "vendor-prefix": 1 }); 56 | Assert.areEqual(1, result.messages.length); 57 | Assert.areEqual("warning", result.messages[0].type); 58 | Assert.areEqual("Missing standard property 'box-shadow' to go along with '-moz-box-shadow'.", result.messages[0].message); 59 | }, 60 | 61 | "Using -moz-user-select should not result in a warning.": function() { 62 | var result = CSSLint.verify("h1 { -moz-user-select:none; }", { "vendor-prefix": 1 }); 63 | Assert.areEqual(0, result.messages.length); 64 | }, 65 | 66 | "Using @font-face should not result in an error (#90)": function() { 67 | var result = CSSLint.verify("@font-face { src:url('../fonts/UniversBold.otf');font-family:Univers;advancedAntiAliasing: true;}", { "vendor-prefix": 1 }); 68 | Assert.areEqual(0, result.messages.length); 69 | } 70 | 71 | })); 72 | 73 | })(); 74 | -------------------------------------------------------------------------------- /tests/rules/zero-units.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | var Assert = YUITest.Assert; 4 | 5 | YUITest.TestRunner.add(new YUITest.TestCase({ 6 | 7 | name: "Zero Units Errors", 8 | 9 | "Using 0px should result in one warning": function() { 10 | var result = CSSLint.verify("h1 { left: 0px; }", { "zero-units": 1 }); 11 | Assert.areEqual(1, result.messages.length); 12 | Assert.areEqual("warning", result.messages[0].type); 13 | Assert.areEqual("Values of 0 shouldn't have units specified.", result.messages[0].message); 14 | }, 15 | 16 | "Using 0em should result in one warning": function() { 17 | var result = CSSLint.verify("h1 { left: 0em; }", { "zero-units": 1 }); 18 | Assert.areEqual(1, result.messages.length); 19 | Assert.areEqual("warning", result.messages[0].type); 20 | Assert.areEqual("Values of 0 shouldn't have units specified.", result.messages[0].message); 21 | }, 22 | 23 | "Using 0% should result in one warning": function() { 24 | var result = CSSLint.verify("h1 { left: 0%; }", { "zero-units": 1 }); 25 | Assert.areEqual(1, result.messages.length); 26 | Assert.areEqual("warning", result.messages[0].type); 27 | Assert.areEqual("Values of 0 shouldn't have units specified.", result.messages[0].message); 28 | }, 29 | 30 | "Using 0 should not result in a warning": function() { 31 | var result = CSSLint.verify("h1 { left: 0; }", { "zero-units": 1 }); 32 | Assert.areEqual(0, result.messages.length); 33 | }, 34 | 35 | "Using 0s for animation-duration should not result in a warning": function() { 36 | var result = CSSLint.verify("h1 { animation-duration: 0s; }", { "zero-units": 1 }); 37 | Assert.areEqual(0, result.messages.length); 38 | } 39 | 40 | 41 | })); 42 | 43 | })(); 44 | -------------------------------------------------------------------------------- /tests/testrunner.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | YUI Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 |
22 |

YUI Test - Test Runner

23 | 24 | 25 |

Results

26 |
    27 | 28 |
    29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/testrunner.js: -------------------------------------------------------------------------------- 1 | /* jshint browser:true, loopfunc:true */ 2 | 3 | (function() { 4 | "use strict"; 5 | 6 | window.onload = function() { 7 | 8 | // some helpful variables 9 | var runButton = window.document.getElementById("run"), 10 | resultsList = window.document.getElementById("results"), 11 | resultNode = resultsList, 12 | events = [ 13 | YUITest.TestRunner.TEST_CASE_BEGIN_EVENT, 14 | YUITest.TestRunner.TEST_CASE_COMPLETE_EVENT, 15 | YUITest.TestRunner.TEST_SUITE_BEGIN_EVENT, 16 | YUITest.TestRunner.TEST_SUITE_COMPLETE_EVENT, 17 | YUITest.TestRunner.TEST_PASS_EVENT, 18 | YUITest.TestRunner.TEST_FAIL_EVENT, 19 | YUITest.TestRunner.TEST_IGNORE_EVENT, 20 | YUITest.TestRunner.COMPLETE_EVENT, 21 | YUITest.TestRunner.BEGIN_EVENT, 22 | YUITest.TestRunner.ERROR_EVENT 23 | ]; 24 | 25 | for (var i=0; i < events.length; i++) { 26 | YUITest.TestRunner.attach(events[i], function(event) { 27 | var node, 28 | message, 29 | messageType; 30 | 31 | switch (event.type) { 32 | case this.BEGIN_EVENT: 33 | message = "Testing began at " + (new Date()).toString() + "."; 34 | messageType = "info"; 35 | break; 36 | 37 | case this.COMPLETE_EVENT: 38 | message = "Testing completed at " + (new Date()).toString() + ".\nPassed:" + 39 | event.results.passed + " Failed:" + event.results.failed + " Total:" + event.results.total; 40 | messageType = "info"; 41 | break; 42 | 43 | case this.TEST_FAIL_EVENT: 44 | node = window.document.createElement("li"); 45 | node.className = "failed"; 46 | node.innerHTML = event.testName + ": " + event.error.getMessage().replace(/\n/g, "
    "); 47 | resultNode.appendChild(node); 48 | break; 49 | 50 | case this.ERROR_EVENT: 51 | node = window.document.createElement("li"); 52 | node.className = "error"; 53 | node.innerHTML = "ERROR: " + event.methodName + "() caused an error: " + event.error.message.replace(/\n/g, "
    "); 54 | resultNode.appendChild(node); 55 | break; 56 | 57 | case this.TEST_IGNORE_EVENT: 58 | node = window.document.createElement("li"); 59 | node.className = "ignored"; 60 | node.innerHTML = event.testName; 61 | resultNode.appendChild(node); 62 | break; 63 | 64 | case this.TEST_PASS_EVENT: 65 | node = window.document.createElement("li"); 66 | node.className = "passed"; 67 | node.innerHTML = event.testName; 68 | resultNode.appendChild(node); 69 | break; 70 | 71 | case this.TEST_SUITE_BEGIN_EVENT: 72 | node = window.document.createElement("li"); 73 | node.innerHTML = event.testSuite.name; 74 | resultNode.appendChild(node); 75 | resultNode = resultNode.appendChild(window.document.createElement("ul")); 76 | break; 77 | 78 | case this.TEST_CASE_COMPLETE_EVENT: 79 | case this.TEST_SUITE_COMPLETE_EVENT: 80 | resultNode.previousSibling.innerHTML += " (passed: " + event.results.passed + ", failed: " + event.results.failed + ", total: " + event.results.total + ", errors: " + event.results.errors + ", ignored: " + event.results.ignored + ")"; 81 | resultNode = resultNode.parentNode; 82 | break; 83 | 84 | case this.TEST_CASE_BEGIN_EVENT: 85 | node = window.document.createElement("li"); 86 | node.innerHTML = event.testCase.name; 87 | resultNode.appendChild(node); 88 | resultNode = resultNode.appendChild(window.document.createElement("ul")); 89 | break; 90 | 91 | } 92 | 93 | }); 94 | } 95 | 96 | runButton.onclick = function() { 97 | // reset the interface 98 | resultsList.innerHTML = ""; 99 | resultNode = resultsList; 100 | 101 | YUITest.TestRunner.run(); 102 | }; 103 | }; 104 | 105 | })(); 106 | --------------------------------------------------------------------------------