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.
- 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/
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
- Stylelint - A mighty, modern linter for CSS and SCSS
- stylelint-config-standard-scss - Standard SCSS configuration for Stylelint
- stylelint-scss - SCSS-specific rules for Stylelint
- stylelint-order - Order-related linting rules for Stylelint
- CSS Stats - Analytics for your CSS
Articles and Guides
- Stylelint User Guide
- Stylelint and Its Plugins for SCSS - CSS-Tricks
- Stylelint: The Style Sheet Linter We've Always Wanted - Smashing Magazine
- Awesome Stylelint - Curated list of Stylelint resources