• A herd of sheep

    Sass Lint Or: How I Learned to Love the Lint

    September 21, 2017 by Adam Fuchs

I’m not talking about the stuff you pull out of the pocket of an old pair of jeans, but a front-end development system I’ve recently started using—Stylelint and Gulp.

Linting has been around for a long time. It all started back in 1979 with a program called… wait for it… Lint! Written for the 7th version of Unix at Bell Labs, Lint was used to flag suspicious code and mistakes going along for the ride with good clean code. Aptly, it was called Lint after the small bits of dirt and fluff that get caught in a sheep’s undercoat.

Nowadays, there are linting programs for just about every programming language with some of the most popular being JavaScript, CSS, HTML, Python and now Sass.

Why Stylelint?

If you’re like me, you’ve been writing Sass for a while, but how good at it are you really? Not everyone needs to code the same, but chances are you picked up some bad habits along the way.

Every developer writes in a slightly different way—tabs or spaces, single line comments or multi, space before a curly bracket or not—how we style is a bit of who we are and bad habits are both easy to pick and very tough to break.

However, like great novelists, we should strive to at least use the same grammar constructions, spell things in the same way (we’ll leave the British out of this one), and have a consistent layout to our manuscript so that not only can it be easily read by someone else, but they can read it as fast as possible and get the most out of it.

This is where Sass linting comes in. Not only can you enforce consistency, but you can also check for human error, prevent lazy coding, and enforce a consistent way of doing things for everyone who may ever work on the project in the future.

What is Linting, Exactly?

Linting is exactly.....the process of making things exact. A lint program takes a set of rules and checks your files against those rules when the Sass is compiled into css. You might check to make sure that you always have a space after a colon but never before, or that decimal numbers always have leading zeroes.

You can set up as many or as few rules as you like. You can bring in rules from existing libraries through extending and plugins. You can set severity levels to your heart’s content. Should a broken rule just throw a warning, should it stop compiling completely?

As with any other front-end practice, there are multiple ways to skin this cat—and like most things, the more options you have the more difficult it can be to get set up. This post will concentrate on setting up Sass linting with PostCSS, Stylelint, and Gulp and will show examples of some basic rules and syntax to get you going. What rules you use and how you use them is entirely up to you.

Stylelint with Gulp

This post is about using linting with Gulp. If you’re not using Gulp, there are other ways to lint and tutorials on how to get started.

Firstly, you’ll want to install the following tools:  gulp-postcss, postcss-scss, postcss-reporter and stylelint. Here is a handy npm command to do that:  
npm install gulp-postcss stylelint postcss-scss postcss-reporter --save-dev

You can also define these as devDependencies in a package.json so they will be installed when running just plain old ‘npm install’ at the start of a project. You’d define them with the name of the package in quotes, then a colon, then the version or minimum version of the project in quotes:
"devDependencies": {
  "gulp-postcss": "^7.0.0",
  "styelintr": "^08.1.1",
}

Once you have the npm packages installed you need to set up either a gulp.js file to run it, or wire the tasks into your existing gulp.js or gulp-config.js.  Either way, there are some things we need to require:
var postcss = require('gulp-postcss');
var reporter = require('postcss-reporter');
var syntax_scss = require('postcss-scss');
var stylelint = require('stylelint');

If you are creating a new gulp file for linting, then you must also first require Gulp:
var gulp = require(gulp);

Next we need to tell Gulp what to lint and how to lint it. I’m defining my style rules in a .stylelinrc file in the root of my Drupal theme, but you can define them in a .stylelintrc.json or .yml file or even right in your gulp file itself. There really isn’t a right or wrong way so go with whatever language you are familiar with or what seems easiest to you. Depending on how you decide to bring the rules in, you’ll set up your gulp function differently.

To run linting directly from your gulp.js file:
gulp.task("scss-lint", function() {
  // Stylelint config rules
  var stylelintConfig = {
    "rules": {
      "color-no-invalid-hex": true,
      "function-url-quotes": "double",
      "rule-trailing-semicolon": "always",
    }
  }

  //reporting and messaging treatment
  var processors = [
    stylelint(stylelintConfig),
    reporter({
      clearMessages: true,
      throwError: true
    })
  ];

  //tell gulp where to check (i.e. path to your sass root file)
  return gulp.src('src/**/*.scss')
  .pipe(postcss(processors, {syntax: syntax_scss}));
});

If you are using a separate file for your rule configuration, you'll want to change the gulp function a bit. The stylelint({/*options*/)}, config below is the placeholder for your .stylelintrc file. If using a json or yml file, you'll have to specify that in place of /*options*/, otherwise gulp will automatically look for and find .stylelintrc if it's in your root folder.
gulp.task('lint:css', function() {
  //path to sass files
  return gulp.src('src/**/*.scss')
    //reporting and options
    .pipe(postcss(
      [
        stylelint({/*options*/}),
        reporter({ clearMessages: true })
      ],
      //syntax to follow when linting
      {
        syntax: scss
      }
    ));
});

NOTE: All rule configuration examples in this post use styntax for .stylelintrc. If you are configuring your rules in a json or yml file or directly in gulp you will need to adjust accordingly.

CSS Grammar

If you’re like me, you know all about how to write CSS and Sass, but maybe not what each part of stylesheet is actually called. Without that knowledge, writing helpful lint rules is much more difficult. Linting rules depend on specific vocabulary to know what to check and how to check it.

Here are some helpful terms to know:

  • Property—Human readable name that defines the style. Background-color is a property.
  • Value—How the property is handled by the browser. Red would be value of the background-color property.
  • Declaration—Pairing of property and value. Background-color: red is a declaration. In declarations properties and values are always separated by a colon and closed by a semi-colon.
  • Block—One or more declarations grouped in between curly brackets {}. {background-color: red;} and {background-color: red; height: 30px;} would both be considered blocks.
  • Selectors—Often ids and classes provided by the html structure but they can also be psuedo elements like :before, :after and :first-child.
  • Rulesets (or rule)—Blocks associated with conditions as applied by selectors. #header{background-color: red;} would be a rule that applies a red background only to elements if they satisfy the condition that they have the id of header. Rulesets can also be nested within each other.
  • At Rules—Rules that start with the @ sign and contain conditional or descriptive information such as @media queries or @content.
  • Comments—Notations within a stylesheet that might explain how a ruleset works or define what the rulesets in a particular section apply to. Comments are not styled by the browser and can take several different forms depending on the syntax of the style sheet.
  • Functions—Used to store values for various properties.  Always enclosed in ().  For instance calc(“100% - 30px”) if a function to store the value of 30px less than 100% of something.

Rule Construction

Now that we are familiar with the vocab, let’s look at how a rule is built. There are lot more constructs and rule options but this will get you started and will get you familiar with some of the vocabulary. You can find much more information in the Stylelint User Guide.

  • Rule Names—Rule names are always lowercase and separated by hyphens: “number-leading-zero” 
    The first word in the rule defines the element that the rule is going to check (unless the rule applies to the whole stylesheet). “number-leading-zero” is going to check all numbers whereas “indentation” would check the whole stylesheet. The next part of the rule name (if there is one) is what the rule is checking. Hence, “number-leading-zero” is checking all numbers for a leading zero (unless there is no decimal or there is a whole number already before the decimal).
  • Allowing and Disallowing—Most rules are written simply to allow or disallow something. “number-leading-zero”: "always" is going to check all numbers for leading zeros and throw a flag anytime one is not found so line-height: .5; would throw a flag, but line-height: 0.5; would pass. Commonly used checks for allowances are "always" and "never". However, some rules are only made to disallow things. “block-no-empty” would make sure that blocks are not empty. The converse (make sure all blocks are empty) would make no sense.
  • Max, Min, and Whitespace—Rule names can include variables like max, min, before, after and inside when checking amounts and spacing in and around elements so a rule like “comment-empty-line-before”: “always” would flag any comment that is not preceded by an empty line. Further whitespace rules can be strung together in the name like “function-comma-space-after”: “never” would never allow a space after a comma in a css function like translate(50%,50%).
  • Rules like to work together—You can use multiple rules on a single element to enforce strict linting. You can force a space after a colon but not before with combination of rules like:
    “declaration-colon-space-after”: “always”,
    “declaration-color-space-before”: ”never”
  • Exceptions—Sometimes you don’t want a rule to apply. If you wanted an empty line before At Rules nested inside a block in all cases except when they are the first thing nested you would write a rule like this:
    “at-rule-empty-line-before”: [“always”, {
      “except”: [“first-nested”]
    }]
  • Custom violation messages—You can define custom messages for your rules that will override any standard error or warning messages. The following code would enforce lowercase hex numbers, but rather than just spitting out the name of the rule, it would tell you why its flagging the hex code.
    "color-hex-case": ["lower", {
      "message": "Lowercase letters are easier to distinguish from numbers"
    }],

Start from Scratch or Use a Library?

Now that Gulp is set up to lint and you’re familiar with how rules can be constructed, we come to the strategic dilemma. Do you want to build your rules from scratch, or do you want to use an existing library and change it as needed?  

When developing a linting strategy the most important things to decide are how strict and how comprehensive you want to be. Do you want to just check for invalid hex codes and prevent compiling if any are found? Do you want to check for tabs versus spaces and get a warnings when one or the other is used? Are you going to follow a best practice coding standard? It might be helpful to have a strategy meeting with the front-enders working on the project to figure out what common conventions are already in use, what would be useful to enforce, and how it will be enforced (always, never, exceptions, etc). Defining the rules in human speak will help you translate them into rule syntax as you build your config file.

Depending on the strategy you want to follow you can either write your rules from scratch in your config file (or directly in the gulp file) or you can bring in an existing library and change, disable, or add to those rules as needed. If you are going to be using a comprehensive and fairly strict strategy that covers most of your stylesheet and will enforce common best practices, I would recommend bringing in a base library like stylelint-config-standard. If you are just checking a couple of things then feel free to write from scratch.

You can extend a library by adding an ‘extends’ line before your rules to bring in all the rules from a library then tailor as needed:
“extends”: “stylelint-config-standard”,
"rules"{
  mycustomrule: “always”
}

You’re much more likely to want to turn off or change some of the rules that come along with a library than you are to add even more rules—“null” is a useful tool for these occasions:
“extends”: “stylelint-config-standard”,
“rules”{
  “indentation”: “tab”,
  “function-comma-space-after”: “never”,
  “number-leading-zero”: “null”,
}

The above configuration would bring in the standard rules but change the indent rule to tabs instead of spaces, check to make sure there is never a space after a comma in a function, and disable checking numbers for leading zeros.

If you’re starting from scratch, your configuration would look very similar:
"rules”{
  “indentation”: “tab”,
  “function-comma-space-after”: “never”,
}

The above configuration would also check to make sure you’re using tabs and never using a space after a comma in a function just like if you were extending—but you wouldn’t need any “null” rules because you have no pre-existing rules to turn off.

Severity

Up to now we've been talking about rules and configuration and forcing best practice behavior. However, this is not boot camp. No one is going to make you do push ups if you don't toe the line.  You are the one who decides which rules to follow and how much Stylelint chastises you when you break them.

Depending on your workflow and how severe you want to be, you can tell Stylelint to either throw a warning or an error when it finds something against the rules. Errors could be handy to use in your gulp config to prevent compiling if a rule is broken but depending on how many rules you have and how strict you are with your standards, this could cause a lot of stoppage time. However, in the case of something like an invalid hex code or missing semi-colon, having a stoppage might be better than ignoring or not seeing a warning and pushing code that may break the theming.

I like the using the warning severity level because I don’t want to always worry about how I’m writing things when testing a new bit of theming or just spitballing something in Sass. As long as I see the warnings, I can go back and clean things up when I’m ready to ship the code but still experiment quickly rather than being forced to follow a lot of grammar rules. Its also just plain friendlier to see yellow warning signs rather than red errors in my terminal windows.

Severity can be changed in your configuration and can be entered either before you define your rules and bring in libraries:
"defaultSeverity": "warning",
“extends”: “stylelint-config-standard”,
“rules”{
  “indentation”: “tab”,
  “function-comma-space-after”: “never”,
  “number-leading-zero”: “null”,
}

Or on a rule by rule basis:
"color-hex-case": ["lower", { "severity": "warning" }]

Plugins

Like any good application, Stylelint has a robust community of happy developers constantly making it bigger and better. Rules and sets of rules that support different things can be brought into your configuration through community built plugins. Rules brought in through plugins may have different names and allowances than standard rules so you’ll need to look at the plugins documentation to make sure your configuration is correct.

You could use something like the Order plugin to force a certain order in your nested elements like making all @ rules come after your declarations:
"defaultSeverity": "warning",
“extends”: “stylelint-config-standard”,
"plugins": [
  "stylelint-order"
],
"rules": {
  "order/order": [
    "Declarations",
    “At Rules”
  ],
  "order/properties-alphabetical-order": true,
}

Disabling and Ignoring

As with the exceptions noted above, every rule is sometimes meant to be broken. If you want a consistent breaking of the rule then you should definitely use the exception method, but every once in a while there comes along that special unicorn that just needs a little extra attention. In that case you can enable and disable your linting in chunks as needed right in your stylesheets.
/* stylelint-disable */
.my-little-pony {}
/* stylelint-enable */

You can also ignore entire files (for instance files that are being generated by an SVG icon to Sass mixin gulp function).  Depending on your configuration, you can either create a .stylelintrcignore file in your theme root folder, you can specify files to ignore in gulp, or you can specify files to ignore right at the top your .stylelintrc file:
"ignoreFiles": [
  "components/_patterns/01-atoms/04-images/icons/_icon_sprite.scss"
],

What the Future Holds

Sass linting is still fairly new and Stylelint is still growing through community plugins, enhanced rules, and config options. One of the most exciting of those is autofix. It's really handy to lint a project if you have a solid strategy and well defined set of rules at the start.

It's not fun to start linting midway through a project with thousands of lines of Sass. Going back and fixing pages of errors and warnings can be extremely tedious. Having an autofix solution that would go back and change all your lazy and/or copy-and-pasted space indentations in to tabs would literally save hours if you started to lint in the middle of a large project.

Unfortunately, Stylint is not quite there. There is a community call for autofix and it does exist in other linters and helper applications like Stylefmt that may definitely be worth checking out even through the rules they apply to are rather limited at this point in time.

Adam Fuchs

About the Author:

Adam has a BFA in graphic design from the Minneapolis College of Art and Design, and has been specializing in web design and development since 2013, working with a variety of large and small clients. Adam leads our site building team, implementing the designs and development work into ... More »