See Testim web in action. Live demo with Q&A on Apr 29 | Save your spot

Writing Your First Custom Stylelint Rule

At Testim.io we care about code quality and UX. For this reason, we use various tools that make development easier and more…

Author Avatar
By Omri Lavi,
  1. Declaring and creating the plugin
  2. Adding a rule to the plugin
  3. Implementing the linting function along with auto-fix support
  4. Integrating the plugin with your project’s Stylelint configuration

Background story

Our custom rule in action
In the following paragraphs, we will describe the steps for adding a custom rule of your own. As an abbreviated example, we’ll write a rule which will replace the expression blue with the hex value #0000FF.

Writing the Plugin — First Steps

// no-color-blue.js
 
const stylelint = require('stylelint');
const { ruleMessages, validateOptions } = stylelint.utils;
 
const ruleName = 'testim-plugin/no-color-blue';
const messages = ruleMessages(ruleName, {
   //… linting messages can be specified here
});

 

module.exports.ruleName = ruleName;
module.exports.messages = messages;
module.exports = stylelint.createPlugin(ruleName, function ruleFunction(primaryOption, secondaryOptionObject, context) {
   return function lint(postcssRoot, postcssResult) {
   // ... 
}});

Creating a Plugin

Adding a Rule to the Plugin

module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
   return function lint(postcssRoot, postcssResult) {
       const validOptions = validateOptions(
           postcssResult,
           ruleName,
           {
               //Options schema goes here
           }
       );
 
       if (!validOptions) { //If the options are invalid, don't lint
           return;
       }
      const shouldDoExtraCoolStuff = secondaryOptionObject.doStuff; //Parse the options to customize behaviour
       const isAutoFixing = Boolean(context.fix); //context.fix will be "true" if auto-fix mode is active

Performing the Linting

  1. “Walk” through the AST (the parsed CSS nodes). Use one of the root.walk methods for this task.
  2. Detect invalid nodes.
  3. Report these nodes (or fix them, depending on the “fix” flag).
//no-blue-color.js
 
const stylelint = require('stylelint');
 
const { report, ruleMessages, validateOptions } = stylelint.utils;
const ruleName = 'testim-plugin/no-blue-color';
const messages = ruleMessages(ruleName, {
   expected: (unfixed, fixed) => `Expected "${unfixed}" to be "${fixed}"`,
});
 
 
module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
   return function lint(postcssRoot, postcssResult) {
       const validOptions = validateOptions(
           postcssResult,
           ruleName,
           {
               //No options for now...
           }
       );
 
       if (!validOptions) { //If the options are invalid, don't lint
           return;
       }
       const isAutoFixing = Boolean(context.fix);
       postcssRoot.walkDecls(decl => { //Iterate CSS declarations
           const hasBlue = decl.value.includes('blue');
           if (!hasBlue) {
               return; //Nothing to do with this node - continue
           }
           if (isAutoFixing) { //We are in “fix” mode
               const newValue = decl.value.replace('blue', '#0000FF');
               //Apply the fix. It's not pretty, but that's the way to do it
               if (decl.raws.value) {
                   decl.raws.value.raw = newValue;
               } else {
                   decl.value = newValue;
               }
           } else { //We are in “report only” mode
               report({
                   ruleName,
                   result: postcssResult,
                   message: messages.expected('blue', '#0000FF'), // Build the reported message
                   node: decl, // Specify the reported node
                   word: 'blue', // Which exact word caused the error? This positions the error properly
               });
           }
       });
   };
});
 
module.exports.ruleName = ruleName;
module.exports.messages = messages;

 

Custom Rule Integration

//.stylelintrc.json
 
{
   "plugins": [
       "./no-blue-color.js"
   ],
   "rules": {
       "testim-plugin/no-blue-color": true
   }
}

 

That’s it! You and your team can now enjoy your custom linting errors.

Image for post

Summary

Further reading