CSS validation at the command line ==================================
Published: 2025-02-26
Table of contents:
Creating a CLI client to the W3C validator service.
TL;DR: Project GitHub repository can be found here.
Motivation
I have got seriously sidetracked in the past couple of weeks, not progressing with my other running projects due to a rabbit-hole I have fallen into. I will write all about it in several other articles, I will just say that it has to do with text editors and color schemes, and yes it involves some CSS as well. I will make sure to link in some of those posts when available.
CSS, the Cascading Style Sheet is one of those things that many people use but much less master. My usual attitude towards working with it reminds me of an old saying I heard in 3D modeling circles: "If it looks good, that's good". In fact I tend to think of that principle every time I work with any sort of computer graphics, but of course there are more complex platforms where one cannot avoid to use proper engineering techniques, it would make a complete mess without it. CSS is obviously not one of those, it is very simple to use, "I will just hack together something real quick", and guess what: one can still make a mess. Parsers (as in browsers, for example) might accept invalid stylesheets that work partially and will brake in subtle ways.
I may have been spoiled by the Rust compiler, because this time I thought there must be a way to catch basic syntactic errors in CSS on the command line. Looking around, I had no luck in finding a dedicated tool for this. I am no frontend developer, so judge me accordingly, I am sure there are great software out there that among other functions, do this as well. But in the Unix philosophy, I wanted something that does this one thing, and does it well. Also keep in mind, that in my particular use case CSS was not even related to the web, the language itself is a fine choice for anyone to use for their offline styling definitions.
The only solution that I have found, which was from a reputable company that can be trusted to provide a well-tested product, is the W3C online CSS validator tool. It has an API that can be accessed easily, so I have built a quick client for it.
Usage
Here is the `--help` text from the program version `0.1.0`:
> CLI client for the W3C CSS validator API. > > Important: Please operate the tool responsibly, with respectful usage of > shared resources. If you wish to call the validator programmatically for a > batch of documents, please make sure that your script will sleep for at least > 1 second between requests. > > Usage: css-validator-client [OPTIONS]> > Arguments: > > Input file, or use '-' to read from STDIN > > Maximum supported percent-encoded length (which is to be used in the > request URL) is 8KB. See the "--length" option to help if you need > to slice your input. > > Options: > -l, --length > Print the calculated length of input, then exit > > This can help with the 8KB payload size limitation, which seem to be > related to the percent-encoded length. > > -h, --help > Print help (see a summary with '-h') > > -V, --version > Print version > > Created by Zoltan Kovari, 2025. Licensed under the Apache License, Version 2.0
Let's see a couple of test runs. I will use the following input as reference:
> foo { > bar: baz; > qux: quux; > zot: gorp; > }
This is a valid ruleset, so we should see no errors:
> $ ./css-validator-client foobar.css > W3C CSS Validator results for file://localhost/TextArea > > > Sorry! We found the following errors (3) > > > URI : file://localhost/TextArea > > Line : 2 foo > Property “bar” doesn't exist. The closest matching property name is “gap” : > baz > Line : 3 foo > Property “qux” doesn't exist. The closest matching property name is “cue” : > quux > Line : 4 foo > Property “zot” doesn't exist. The closest matching property name is “font” : > gorp > > > No style sheet found >
Okay, we are seeing some noise here, as it cannot recognize our made-up properties. We can quickly skim through the errors though, checking that we have made no mistakes, and at the same time there are nothing that seems serious.
Now let's try deleting the semicolon after `quux`:
> $ ./css-validator-client foobar2.css > W3C CSS Validator results for file://localhost/TextArea > > > Sorry! We found the following errors (4) > > > URI : file://localhost/TextArea > > Line : 2 foo > Property “bar” doesn't exist. The closest matching property name is “gap” : > baz > Line : 4 foo > Missing a semicolon before the property name “zot” > Line : 0 foo > Property “qux” doesn't exist. The closest matching property name is “cue” : > quux > Line : 4 foo > Property “zot” doesn't exist. The closest matching property name is “font” : > gorp > > > No style sheet found >
We have got our "Missing a semicolon" error that we expected. Now I put the semicolon back and delete the closing curly brace:
> $ ./css-validator-client foobar3.css > W3C CSS Validator results for file://localhost/TextArea > > > Sorry! We found the following errors (4) > > > URI : file://localhost/TextArea > > Line : 2 foo > Property “bar” doesn't exist. The closest matching property name is “gap” : > baz > Line : 3 foo > Property “qux” doesn't exist. The closest matching property name is “cue” : > quux > Line : 4 foo > Property “zot” doesn't exist. The closest matching property name is “font” : > gorp > Line : 4 foo > Parse Error > > > No style sheet found >
It came back with "Parse Error" so we certainly know something is seriously wrong with our syntax.
Implementation
I will just cover this quickly as the program is very simple and straightforward. It can be summarized basically in one sentence: we parse the command line arguments, do the percent-encoding of the input, build a URL with it, and call a GET request. I am calling this as the minimally viable product, because it kind-of works, but there are a couple of issues which would require further efforts to solve.
Unfortunately I have found that the max length of CSS on the input is seriously limited using the API text input mode, and interestingly it is much less so when using the validator webpage manually by uploading the file directly. This is something that must be accepted I think, because I doubt I will ever be in the mood to automate the whole file-upload mechanism. I tried to determine the exact limit through the API, it seems to be related to the percent-encoded length, but the value was not round by any means (around 11KB), so I have used the largest power of 2 below that as a limit in this client.
The size limitation is annoying if you are working with large stylesheets. At this time a workaround is to slice the input to smaller chunks. Luckily this can be done easily with CSS as it is *very* unlikely that anyone would have one ruleset or at-rule spanning more than the limit, and the file can be split between any two of those. Naturally, it is on my TODO list to automate this.
As a related issue, I am currently using the `text/plain` output format and printing it out verbatim. For single files, it is okay although it has a couple of lines that could be deleted to be more concise. It is more of a problem though when I want to use the tool with split files as mentioned previously, I have to check each result separately. The API provides an option to use XML as output format, so I think we should switch to that when the automatic slicing will require aggregation of the results.
Conclusion
As usual, this seemed like a quick little exercise, and turned out to be the potential start of a real project. I cannot say when will I have the urge (or pressing need) to continue development, it also depends on W3C to maintain the service, naturally. Of course, big thanks to them for providing it so far!
As a spoiler I can say that I have looked at the CSS language specification (for some other reason), and it looks to me now that it might be worthwhile to include an offline mode at least to check the input for obvious syntactical errors. Something like this would require rigorous testing though, or reliance on a parsing library, so it is tricky. Maybe with providing both options, offline *and* online, it would be easy for folks to provide feedback if there is any discrepancy between the two modes.