Static Analysis For CSS

I have had fun writing about static analysis for Javascript recently. This time, let's do something that's the same kinda thing, and yet different - static analysis for CSS.

The CSS Module

The library we'll use is simply called css and is found on npm. So, just do npm install css to install it. It contains both a parser and an emitter.

Parsing CSS

Say you have a sample.css

.btn:hover,
.btn:focus {
  color: #333333;
  text-decoration: none;
}

To parse it, write this

var css = require('css');
var fs = require('fs');
var code = fs.readFileSync('sample.css') + '';
var ast = css.parse(code);
console.log(JSON.stringify(ast, null, '  '));

Run it, and you'd see its AST printed to the console:

{
  "type": "stylesheet",
  "stylesheet": {
    "rules": [
      {
        "type": "rule",
        "selectors": [
          ".btn:hover",
          ".btn:focus"
        ],
        "declarations": [
          {
            "type": "declaration",
            "property": "color",
            "value": "#333333"
          },
          {
            "type": "declaration",
            "property": "text-decoration",
            "value": "none"
          }
        ]
      }
    ]
  }
}

Modifying the AST

Let's say that, for whatever reason, we want all the rules in the CSS file to only take effect for elements which are descendants of certain elements - specified by the class root. We can do that by prefixing every selector of every rule with: .root.

ast.stylesheet.rules.forEach(function(rule){
  for (var i = 0; i < rule.selectors.length; i++){
    rule.selectors[i] = '.root ' + rule.selectors[i];
  }
});

Emitting the Modified CSS

Now that we have modified the AST, we can use the stringify() method to regenerate the CSS from it

console.log(css.stringify(ast))

And the output is

.root .btn:hover,
.root .btn:focus {
  color: #333333;
  text-decoration: none;
}

Yah! Easy, right?

Other Possibilities

What are other interesting things you can do? You are limited only by your imagination. If you want to automatically rename classes, you can do that. If you want to automatically sort or group rules by their selector, you can do that. If you want to automatially detect and remove unused rules (combined with html parsing), you can do that! You can do that!

Homework

It turns out that our code didn't cover all the cases. You homework is to run it on the CSS file that's bundle with Bootstrap. Figure out what's broken, and fix it. Good luck!

blog comments powered by Disqus