Skip to content
nzakas edited this page Oct 14, 2011 · 8 revisions

This guide is intended for those who wish to:

  • Contribute code to CSS Lint
  • Create their own rules for CSS Lint
  • Customize a build of CSS Lint

In order to work with CSS Lint as a developer, it's recommended that:

  • You know JavaScript, since CSS Lint is written in JavaScript.
  • You're comfortable with command-line programs.
  • You understand unit tests and why they're important.

Getting the source

CSS Lint is hosted at GitHub and uses Git for source control. In order to obtain the source code, you must first install Git on your system. Instructions for installing and setting up Git can be found at http://help.github.com/set-up-git-redirect.

If you simply want to create a local copy of the source to play with, you can clone the main repository using this command:

git clone git://github.com/stubbornella/csslint.git

If you're planning on contributing to CSS Lint, then it's a good idea to fork the repository. You can find instructions for forking a repository at http://help.github.com/fork-a-repo/. After forking the CSS Lint repository, you'll want to create a local copy of your fork.

Directory structure

The CSS Lint directory and file structure is as follows:

  • demos - collection of demos and sample files
  • docs - the main documentation folder
  • lib - contains third party libraries for use in the build
  • npm - files used exclusively with npm
  • release - contains the distributable files from the last official release
    • docs - the documentation for this release
    • npm - the folder containing all files necessary for publishing to npm
  • src - the main source code folder
    • cli - files for the command line interfaces
    • core - core CSS Lint functionality including the CSSLint object
    • formatters - contains files defining the formatters
    • rules - contains files defining the CSS Lint rules
    • worker - contains files for the CSS Lint Web Worker
  • tests - the main unit test folder
    • core - tests for core CSS Lint functionality including the CSSLint object
    • formatters - tests for the formatters
    • rules - tests for the CSS Lint rules

The first time you run a build, you'll also notice a build directory is created. This directory is not checked in and has the same structure as the release directory.

The build system

CSS Lint uses a build system to make updating code faster and easier. Doing so allows a simple, clean directory and file structure that is easy to navigate and expand upon without needing to reference dozens of JavaScript files in a page. Understanding the build system is key to working with the CSS Lint source code.

Installing Ant

In order to build CSS Lint, you must first download and install Ant. Ant is a Java-based command line build tool that works with the build.xml file found in the root directory.

If you're using Mac OS X, Ant is already installed for you. If for some reason it's not installed, be sure to install MacPorts and run:

sudo port install apache-ant

If you're using Ubuntu, you can simply type:

sudo apt-get install ant

Other operating systems should refer to http://ant.apache.org/manual/install.html for more information.

The build targets

Ant works by defining build targets. A target is really just a collection of steps to take. CSS Lint has several build targets:

  • build.core - builds the core CSS Lint JavaScript library
  • build.worker - builds the CSS Lint JavaScript Web Worker
  • build.rhino - builds the CSS Lint JavaScript CLI for Rhino
  • build.node - builds the CSS Lint JavaScript CLI for Node.js
  • build.tests - combines all unit tests into a single file
  • build.docs - processes the documentation files and places them in the build directory
  • build.all - executes all of the previous targets (those beginning with build.)
  • changelog.update - automatically updates the CHANGELOG file with change information (admin use only)
  • lint - runs lint tools on the source code to find possible errors
  • release - prepares an official release by inputting version numbers and copying files to release directory (admin use only)
  • parser.update - downloads the latest version of the CSS parser and installs it in the lib directory
  • test - runs all unit tests on the command line

In order to run a particular target, go into the csslint directory and use the following command:

ant <target>

For instance, if you just want to build the tests, run this:

ant build.tests

If you want to build everything, type:

ant build.all

Since build.all is the default target, you can shorten this by simply typing:

ant

When there is no target specified on the command line, Ant will automatically use the default. build.all is the most frequently run target so it is set as the default.

Note: It's important that you run this command only in the csslint directory. Builds do not work in subdirectories.

The build.xml file

Ant uses the build.xml to define targets and other related information. In general, you won't ever need to touch this file unless you're creating a new build target.

The property csslint.version is defined at the top of the file. This version number is embedded in the source files when the release target is run. This value should not be changed except by the CSS Lint maintainers.

Workflow

Whenever you make changes to the CSS Lint source files, you'll need to run the build.all target to regenerate the combined source files. The workflow is:

  1. Make changes
  2. Run ant
  3. Verify changes and run tests

Running unit tests

Most parts of CSS Lint have unit tests associated with them. Unit tests are written using YUI Test and are required when making contributions to formatters, rules, or the core library.

When you first get the source code, you need to run ant once initially to generate all of the code and the tests. Once you've done that, there are two ways to run the unit tests:

  1. Browser: Locate the testrunner.htm file in the tests directory. Load this into any web browser and click the "Run Tests" button. This executes all tests and prints out the results.
  2. Command Line: Run ant test from the command line to run all tests.

Working with rules

Each CSS Lint rule has two files: a source file in the src/rules directory and a test file in the tests/rules directory. The basic source code format for a rule is:

CSSLint.addRule({

    //rule information
    id: "rule-id",
    name: "Rule name",
    desc: "Short description of rule",
    browsers: "Affected browsers",

    //initialization
    init: function(parser, reporter){
        var rule = this;
        
        //rule initialization
        
    }

});

Each rule is represented by a single object with several properties and one method. The properties are:

  • id - the rule ID. This must be unique across all rules and must also be the name of the JavaScript file. This rule ID is used to configure CSS Lint on the command line as well as used in JavaScript to configure which rules CSS Lint uses.
  • name - a human readable name for the rule. This is used in the web UI to describe the rule.
  • desc - a human readable description for the rule. This is used in the web UI to describe the rule as well as on the command line when all rules are listed out.
  • browsers - a simple string describing which browsers this rule applies to. By default, use "All". If something is specific to a browser, put that in, such as "IE6".

The one method is init(), which sets up the rule. This method is passed in two arguments, a CSS parser and a reporter object. The CSS parser is an instance of parserlib.css.Parser, which is part of the ParserLib open source library (http://github.com/nzakas/parser-lib/). The parser is event-driven, so you can add listeners for certain events as the CSS is being parsed. This allows you to inspect pieces of the CSS as they occur in the code. Refer to the ParserLib documentation for more information on the parser API and the various events that are available.

Quite simply, the

parser.addListener("property", function(event){

    var propertyName    = event.property.toString().toLowerCase(),
        value           = event.value.toString(),
        line            = event.line,
        col             = event.col;
        
    switch(propertyName){
        case "width":
            //do something
            break;
            
        case "height":
            //do something
            break;
            
        case "custom-property":
            reporter.warn("Woah! Why are you using custom-property?", line, col, rule);
            break;
    
    }

});

There are a few things to note about this event handler. First, it listens for the "property" event, which fires for each property-value pair inside of a CSS rule. The event object contains a property property that contains an object with the name of the property in the raw form along with its line and column location. There is also a value object that contains the value for the property. Every event object for every event also has a line and col property that gives you basic location information, typically the location of the first part of the pattern that fired the event.

The second object passed to init(), the reporter, allows you to publish information to the CSS Lint results. The main method you'll use is warn(), which publishes a warning. This method accepts four arguments: a message to display, a line number, a column number, and the rule object itself. For example:

reporter.warn("This is unexpected!", 1, 1, rule);

The line and column number can be accessed from the parser object events, and these help the CSS Lint UI to display the warnings correctly. The last argument, the rule, gives reference information to CSS Lint about the warning so that the UI can display appropriate descriptions of the warnings.

Rule Unit Tests

Each rule must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in tests/. For example, if your rule source file is src/rules/foo.js then your test file should be tests/rules/foo.js. The test file will automatically be built in with all of the other test files when you run ant.

For your rule, be sure to test:

  1. All instances that should be flagged as warnings.
  2. At least two patterns that should not be flagged as warnings.

The basic pattern for a rule unit test file is:

(function(){

    /*global YUITest, CSSLint*/
    var Assert = YUITest.Assert;

    YUITest.TestRunner.add(new YUITest.TestCase({

        name: "Your Rule Tests",

        "Sentence describing what should happen": function(){
            var result = CSSLint.verify("/* CSS String */", { "your-rule": 1 });
            //asserts
        }
    }));

})();   

The test case name should reflect your rule's name. Then, each test should be named as as sentence describing what is being tested. Inside of the rule, call CSSLint.verify() on a sample CSS string and make sure that just your rule is turned on by passing it as an option. Then, do your asserts on the result of the verification to make sure your rule is working. Here's an example:

"Using 0px should result in one warning": function(){
    var result = CSSLint.verify("h1 { left: 0px; }", { "zero-units": 1 });
    Assert.areEqual(1, result.messages.length);
    Assert.areEqual("warning", result.messages[0].type);
    Assert.areEqual("Values of 0 shouldn't have units specified.", result.messages[0].message);
}

You should always check the number of messages as your first assert. This ensures that there aren't any more or less messages than you're expecting. Assuming there are no syntax errors, only your rule will produce messages. The second step is to ensure the type of message is correct. Almost all rules output warnings, so check that this true for each message. Lastly, test the actual message text to ensure it's delivering the correct message to the user.

When testing the cases where your rule should not produce a message, just check that the number of returned messages is zero. For example:

"Using 0 should not result in a warning": function(){
    var result = CSSLint.verify("h1 { left: 0; }", { "zero-units": 1 });
    Assert.areEqual(0, result.messages.length);
}

Provide as many unit tests as possible. Your pull request will never be turned down for having too many tests submitted with it!

Rule writing tips

  • When looking at a property name, always normalize by transferring to lowercase before comparing. The parser always presents the property in the way it was originally in the code.
  • The property event can fire after a startrule event, but also after other events such as startfontface. This is because CSS properties are used in many places aside from regular CSS rules. They may be used in font-faces, animation keyframes, etc. Due to this, it's recommended:
    • If you're listening for the startrule event, also listen to startfontface, startpage, startpagemargin, and startkeyframes.
    • If you're listening for the endrule event, also listen to endfontface, endpage, endpagemargin, and endkeyframes.
  • Always write unit tests for your rule.
Clone this wiki locally