On Stylint 1.0

Linter Demo (in Sublime)

I just released the 1.0 version of my Stylus linter, Stylint. Although it's not as stable or capable yet as I'd like, it's become the defactor linter in the (admittedly small) Stylus community.

It started with a suggestion by a coworker at WIRED that we use Stylus instead of SASS for the redesign of the site. I wasn't too eager to switch but I figured the new site would be a complete rewrite anyway, so why not?

I like that it's all Node and integrated well with our workflow (sass does have lib-sass, but afaik most people still use compass). I like that it had some features I hadn't seen in other CSS pre-processors. Most importantly, I didn't realize how much I hated semicolons.

If you know one CSS pre-processor you know them all right?

But there was no linter. Linters for every conceivable thing had become a pretty big part of my workflow and I was reluctant to use something that didn't have one.

At first I figured I'd just wait for someone else to write it. Months passed. I kept thinking of starting my own, but at the time I didn't think I had the skills to write a passable linter. Besides, I was sure one would pop up any day now.

Well, that didn't happen, so I decided to do it myself.

I had some basic rules I started off with, simple ones suited to how I write Stylus in particular. I had been wanting to play with Node more and I needed to work on my regex anyway, so it seemed a good learning opportunity if nothing else. I didn't think anyone outside of WIRED would ever use the thing.

The basic problem is simple. You read a directory, or a single file, or raw text (uh, someone is working on that) you parse it, and the contents (broken into lines) get run through a battering ram of tests. How hard could it be? It turns out, much harder than I thought.

The first problem I hit was how to structure my package. Having never done a Node project before (this was a year ago), or any non front-end project, I was at a bit of a loss on how to start. While learning I probably rewrote the core flow of the linter 4-5 times, if you include the latest rewrite for 1.0.

Initially I stuck with the module pattern, then as the package and complexity grew I felt that would be problematic. But in the Node environment, I wasn't sure what to replace it with.

I tried to take a more functional approach. The app would flow from function to function and used dependency injection to ensure every function had access to what it needed. None of the functions at this point relied on anything outside the scope of itself. This simplified testing but became unwieldy as complexity grew. Parameters grew long. I started looking for other ways.

I was reading Eric Elliot's book Programming Javascript Applications (you should too, it's the best) and stampit looked like it would solve my problems.

Using stampit, I was able to break apart my package into the smallest pieces of functionality possible, and then compose them together into one during instantiation. Methods, Objects, whatever. With everything now on the prototype, and the context set consistently, it became easy to add functionality when needed.

The second problem I hit was Stylus itself.

How do you know when a selector ends when brackets are optional?

How to tell when something is a variable when the $ symbol is optional?

How to accurately detect mixins when they can look just like normal properties?

(seriously, do you have a solution for that?)

It turns out that the lack of syntax (besdies whitespace) that I enjoy as a user of Stylus is a problem when you're trying to lint it. And since everyone writes Stylus differently, because it lets you, and I wanted to support everything Stylus itself supports, I found that I was dealing with a minefield of edge cases.

But it turned out it wasn't that bad. Well, not as bad as it could have been. Whitespace is easy to parse and a reliable way to get context in a language where whitespace is significant. In the end, there was regex written, edge cases documented and I think it works alright. Could be better, I take Pull Requests :)

What did I get from all this? Besides bragging rights (i guess?). I learned a lot about structuring a package, keeping complexity down (stylint 1.0 is roughly the same amount of code, less complex, with double the amount of options of 0.* Stylint), the value of open source (fixing my stupid mistakes for me), and the importance of testing. Most importantly, I learned the importance of just going for it.

No better way to learn, than to tackle a big project.

Before I started Stylint last year I didn't know:

1 ) Node (not well anyway)

2 ) Npm (had never made a package that I distributed)

3 ) How to write unit tests (always more to learn here really)

4 ) How to run an open source project (have you? it's like a second, part-time job)

Now I'm confident in all those things, and have taken what I've learned and applied it to my work at WIRED. Some things I would have done differently of course**, now that I know better, but I think all in all Stylint is a strong project and one that I'm pretty proud of.


** If you're curious, I think parsing the raw text with regex works for simple things, but the complexity of linting a syntax-less language and providing a wide range of options results in a lot of bugs and some limitations. If I knew then what I know now, I would looking into parsing the Stylus into an AST using something like esprima, instead of using regex.

There's always 2.0