SCSS Static Analysis and Linting

Introduction to SCSS Static Analysis

Static analysis is the process of examining code without executing it to find potential issues, enforce coding standards, and improve code quality. For SCSS, static analysis helps maintain consistent styles, prevent common errors, and ensure best practices.

Why Use Static Analysis for SCSS?
  • Catch errors before they reach production
  • Enforce consistent coding styles across teams
  • Prevent CSS specificity issues and selector complexity
  • Identify performance bottlenecks
  • Maintain accessibility standards
  • Automate code quality checks in CI/CD pipelines

Stylelint: The Modern SCSS Linter

What is Stylelint?

Stylelint is a powerful, modern linter for CSS and SCSS that helps you enforce consistent conventions and avoid errors in your stylesheets. It's highly configurable and can be integrated with most development workflows.

Setting Up Stylelint

Installation


# Install Stylelint and SCSS support
npm install --save-dev stylelint stylelint-scss

# Install a config preset (optional)
npm install --save-dev stylelint-config-standard-scss

Configuration

Create a .stylelintrc.json file in your project root:


{
  "extends": "stylelint-config-standard-scss",
  "plugins": [
    "stylelint-scss"
  ],
  "rules": {
    "indentation": 2,
    "string-quotes": "single",
    "no-duplicate-selectors": true,
    "color-hex-case": "lower",
    "color-hex-length": "short",
    "color-named": "never",
    "selector-max-id": 0,
    "selector-combinator-space-after": "always",
    "declaration-block-trailing-semicolon": "always",
    "declaration-no-important": true,
    "declaration-colon-space-before": "never",
    "declaration-colon-space-after": "always",
    "property-no-vendor-prefix": true,
    "value-no-vendor-prefix": true,
    "number-leading-zero": "always",
    "function-url-quotes": "always",
    "font-weight-notation": "numeric",
    "font-family-name-quotes": "always-where-recommended",
    "comment-whitespace-inside": "always",
    "comment-empty-line-before": "always",
    "selector-pseudo-element-colon-notation": "double",
    "selector-pseudo-class-parentheses-space-inside": "never",
    "media-feature-range-operator-space-before": "always",
    "media-feature-range-operator-space-after": "always",
    "media-feature-parentheses-space-inside": "never",
    "media-feature-colon-space-before": "never",
    "media-feature-colon-space-after": "always",
    "scss/at-rule-no-unknown": true,
    "scss/selector-no-redundant-nesting-selector": true,
    "scss/no-duplicate-dollar-variables": true
  }
}

Running Stylelint


# Add to package.json scripts
"scripts": {
  "lint:scss": "stylelint \"src/**/*.scss\"",
  "lint:scss:fix": "stylelint \"src/**/*.scss\" --fix"
}

# Run the linter
npm run lint:scss

# Fix automatically fixable issues
npm run lint:scss:fix

Common Stylelint Rules for SCSS

Category Rule Description Example
SCSS-Specific scss/at-rule-no-unknown Disallow unknown at-rules, but allow SCSS at-rules @include, @mixin
scss/selector-no-redundant-nesting-selector Disallow redundant nesting selectors (&) .foo { & { ... } }
scss/no-duplicate-dollar-variables Disallow duplicate variables within the same scope $color: red; $color: blue;
Formatting indentation Enforce consistent indentation 2 spaces, 4 spaces, or tabs
string-quotes Enforce single or double quotes for strings 'string' or "string"
max-nesting-depth Limit nesting depth Usually 3-4 levels max
Selectors selector-max-id Limit the number of ID selectors Usually set to 0 or 1
selector-max-universal Limit universal selectors * { ... }
selector-max-specificity Limit selector specificity "0,3,0" (0 IDs, 3 classes, 0 elements)
Properties declaration-no-important Disallow !important color: red !important;
property-no-vendor-prefix Disallow vendor prefixes (use Autoprefixer) -webkit-transform
shorthand-property-no-redundant-values Disallow redundant values in shorthand properties margin: 1px 1px 1px 1px;

Advanced Stylelint Configuration

1. Custom Rules

Create custom rules for project-specific requirements:


// stylelint-plugin-custom-rule.js
const stylelint = require('stylelint');

const ruleName = 'plugin/scss-file-naming-convention';
const messages = stylelint.utils.ruleMessages(ruleName, {
  expected: (pattern) => `Filename should match pattern: ${pattern}`
});

module.exports = stylelint.createPlugin(ruleName, (primaryOption) => {
  return (root, result) => {
    const validOptions = stylelint.utils.validateOptions(result, ruleName, {
      actual: primaryOption,
      possible: [
        'kebab-case',
        'camelCase',
        'PascalCase'
      ]
    });

    if (!validOptions) return;

    // Get the filename from the source
    const fileName = root.source.input.file.split('/').pop();
    
    // Check naming convention
    let isValid = false;
    if (primaryOption === 'kebab-case') {
      isValid = /^[a-z][a-z0-9]*(-[a-z0-9]+)*\.scss$/.test(fileName);
    } else if (primaryOption === 'camelCase') {
      isValid = /^[a-z][a-zA-Z0-9]*\.scss$/.test(fileName);
    } else if (primaryOption === 'PascalCase') {
      isValid = /^[A-Z][a-zA-Z0-9]*\.scss$/.test(fileName);
    }

    if (!isValid) {
      stylelint.utils.report({
        message: messages.expected(primaryOption),
        node: root,
        result,
        ruleName
      });
    }
  };
});

module.exports.ruleName = ruleName;
module.exports.messages = messages;

2. Shareable Configs

Create a shareable configuration for team projects:


// stylelint-config-company.js
module.exports = {
  extends: [
    'stylelint-config-standard-scss',
    'stylelint-config-prettier'
  ],
  plugins: [
    'stylelint-scss',
    'stylelint-order',
    './plugins/stylelint-plugin-custom-rule'
  ],
  rules: {
    // Base rules
    'indentation': 2,
    'string-quotes': 'single',
    
    // Custom company rules
    'plugin/scss-file-naming-convention': 'kebab-case',
    
    // Property ordering
    'order/properties-alphabetical-order': true,
    
    // SCSS-specific rules
    'scss/at-rule-no-unknown': true,
    'scss/selector-no-redundant-nesting-selector': true,
    'scss/no-duplicate-dollar-variables': true,
    'scss/dollar-variable-pattern': '^[a-z][a-zA-Z0-9]+$',
    'scss/percent-placeholder-pattern': '^[a-z][a-zA-Z0-9]+$',
    
    // Limit nesting depth
    'max-nesting-depth': 3,
    
    // Selectors
    'selector-max-id': 0,
    'selector-max-universal': 1,
    'selector-max-compound-selectors': 3,
    'selector-no-qualifying-type': true,
    
    // Disallow !important
    'declaration-no-important': true
  }
};

3. Integrating with Design Systems

Enforce design system constraints with Stylelint:


{
  "rules": {
    "scale-unlimited/declaration-strict-value": [
      ["/color/", "z-index", "font-size", "font-family"],
      {
        "ignoreKeywords": ["transparent", "inherit", "currentColor"],
        "disableFix": true,
        "message": "Use design tokens from our system instead of hardcoded values"
      }
    ],
    "function-disallowed-list": ["rgb", "rgba", "hsl", "hsla"],
    "unit-disallowed-list": ["px", "em"],
    "declaration-property-value-disallowed-list": {
      "border-radius": ["0"],
      "font-family": ["/^\\s*['\"]/"]
    }
  }
}

Integrating with Development Workflows

1. Editor Integration

Set up Stylelint in popular code editors:

VS Code


// Install the Stylelint extension
// Add to settings.json:
{
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": true
  },
  "stylelint.validate": [
    "css",
    "scss"
  ],
  "css.validate": false,
  "scss.validate": false
}

WebStorm / IntelliJ IDEA

  • Install the Stylelint plugin
  • Go to Settings → Languages & Frameworks → Stylelint
  • Enable Stylelint and set the configuration file path

2. Git Hooks with Husky and lint-staged


# Install dependencies
npm install --save-dev husky lint-staged

// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.scss": [
      "stylelint --fix",
      "git add"
    ]
  }
}

3. CI/CD Integration

GitHub Actions


# .github/workflows/stylelint.yml
name: Stylelint

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '14'
      - name: Install dependencies
        run: npm ci
      - name: Run Stylelint
        run: npm run lint:scss

GitLab CI


# .gitlab-ci.yml
stages:
  - lint

stylelint:
  stage: lint
  image: node:14
  script:
    - npm ci
    - npm run lint:scss
  only:
    - merge_requests
    - main

Beyond Stylelint: Advanced Static Analysis

1. SCSS-Lint (Legacy)

An older Ruby-based linter for SCSS:


# Install SCSS-Lint
gem install scss_lint

# Run SCSS-Lint
scss-lint app/assets/stylesheets/
Note: SCSS-Lint is no longer actively maintained. Stylelint is the recommended alternative.

2. CSS Stats

Analyze and visualize stylesheet statistics:


# Install CSS Stats
npm install --save-dev cssstats

# Generate stats
npx cssstats path/to/compiled.css --json > stats.json

CSS Stats provides metrics like:

  • File size and gzipped size
  • Number of rules, selectors, and declarations
  • Specificity graph
  • Color palette usage
  • Font size distribution
  • Media query breakdown

3. Parker

CSS stylesheet analysis tool:


# Install Parker
npm install -g parker

# Analyze CSS
parker path/to/compiled.css

Parker provides metrics like:

  • Total stylesheets size
  • Total rules
  • Total selectors
  • Selectors per rule
  • Specificity per selector
  • Top selector specificity
  • Total ID selectors
  • Total unique colors

4. Wallace CLI

CSS complexity and maintainability analyzer:


# Install Wallace
npm install -g wallace-cli

# Analyze CSS
wallace path/to/compiled.css

Common SCSS Issues and How to Fix Them

1. Selector Specificity

❌ Problematic

#header .navigation ul li a.active {
  color: red;
}
✅ Fixed

.nav-link.is-active {
  color: red;
}

2. Excessive Nesting

❌ Problematic

.card {
  .card-header {
    .title {
      .icon {
        &:hover {
          color: blue;
        }
      }
    }
  }
}
✅ Fixed

.card {
  // Card styles
}

.card-header {
  // Header styles
}

.card-title {
  // Title styles
}

.card-icon {
  // Icon styles
  
  &:hover {
    color: blue;
  }
}

3. Magic Numbers

❌ Problematic

.element {
  margin-top: 27px;
  width: 386px;
  line-height: 1.333;
}
✅ Fixed

$spacing-lg: 1.5rem; // 24px
$container-sm: 24rem; // 384px
$line-height-tight: 1.33;

.element {
  margin-top: $spacing-lg;
  width: $container-sm;
  line-height: $line-height-tight;
}

4. Vendor Prefixes

❌ Problematic

.box {
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  -ms-transition: all 0.3s;
  -o-transition: all 0.3s;
  transition: all 0.3s;
}
✅ Fixed

// Let Autoprefixer handle vendor prefixes
.box {
  transition: all 0.3s;
}

Best Practices for SCSS Linting

1. Project Setup

  • Add linting early in the project lifecycle
  • Document your linting rules and reasoning
  • Include configuration files in version control
  • Set up automated checks in CI/CD pipelines

2. Team Adoption

  • Get team buy-in before implementing strict rules
  • Start with a minimal set of rules and gradually add more
  • Provide documentation and examples for team members
  • Set up editor integration for all team members

3. Rule Selection

  • Focus on rules that prevent errors first
  • Add style consistency rules based on team consensus
  • Consider performance-related rules for large projects
  • Include accessibility-related rules

4. Maintenance

  • Regularly update linting tools and configurations
  • Review and adjust rules as the project evolves
  • Monitor false positives and adjust rules accordingly
  • Use // stylelint-disable comments sparingly for exceptions

Resources and Further Reading

Tools

Articles and Guides

Example Configurations