A mighty CSS linter that helps you avoid errors and enforce conventions.
—
Plugin system for creating custom rules and extending Stylelint functionality. Provides utilities for rule creation, validation, option handling, and seamless integration with the core linting engine.
Create custom Stylelint plugins with the createPlugin utility function.
/**
* Create a Stylelint plugin
* @param ruleName - Unique name for the rule
* @param rule - Rule implementation function
* @returns Plugin object for registration
*/
function createPlugin(ruleName: string, rule: Rule): Plugin;
interface Plugin {
ruleName: string;
rule: Rule;
}Usage Example:
import stylelint from "stylelint";
const { createPlugin, utils } = stylelint;
const myCustomRule = (primaryOption, secondaryOptions, context) => {
return (root, result) => {
// Rule implementation
root.walkDecls((decl) => {
if (decl.prop === 'color' && decl.value === 'red') {
utils.report({
ruleName: 'my-namespace/no-red-colors',
result,
node: decl,
message: 'Unexpected red color'
});
}
});
};
};
const plugin = createPlugin('my-namespace/no-red-colors', myCustomRule);
export default plugin;Core rule function signature and structure for implementing custom linting logic.
interface Rule<P = any, S = any, M = RuleMessages> {
/**
* Rule implementation function
* @param primaryOption - Main rule configuration
* @param secondaryOptions - Additional rule options
* @param context - Rule execution context
* @returns PostCSS plugin function
*/
(primaryOption: P, secondaryOptions: S, context: RuleContext):
(root: PostCSS.Root, result: PostcssResult) => Promise<void> | void;
/** Unique rule name */
ruleName: string;
/** Rule message templates */
messages: M;
/** Whether primary option is an array */
primaryOptionArray?: boolean;
/** Rule metadata */
meta?: RuleMeta;
}
interface RuleContext {
/** Configuration comment prefix */
configurationComment?: string;
/** Fix mode enabled */
fix?: boolean;
/** Newline character for the file */
newline?: string;
}
interface RuleMeta {
/** URL to rule documentation */
url: string;
/** Whether rule is deprecated */
deprecated?: boolean;
/** Whether rule supports auto-fixing */
fixable?: boolean;
}Define message templates for rule violations and user-facing text.
type RuleMessages = { [message: string]: RuleMessage };
type RuleMessage = string | RuleMessageFunc;
type RuleMessageFunc = (...args: (string | number | boolean | RegExp)[]) => string;
/**
* Create rule-specific messages with rule name suffix
* @param ruleName - Name of the rule
* @param messages - Message templates
* @returns Processed messages with rule names
*/
function ruleMessages<T extends RuleMessages>(
ruleName: string,
messages: T
): T;Usage Example:
import stylelint from "stylelint";
const { utils } = stylelint;
const messages = utils.ruleMessages('my-rule/no-red', {
rejected: (value) => `Unexpected red color "${value}"`,
expected: 'Expected a color other than red'
});
// Usage in rule
utils.report({
ruleName: 'my-rule/no-red',
result,
node: decl,
message: messages.rejected(decl.value),
messageArgs: [decl.value]
});Validate rule options using the built-in validation system.
/**
* Validate rule options
* @param result - PostCSS result object
* @param ruleName - Name of the rule
* @param optionDescriptions - Option validation descriptors
* @returns Whether all options are valid
*/
function validateOptions(
result: PostcssResult,
ruleName: string,
...optionDescriptions: RuleOptions[]
): boolean;
interface RuleOptions {
/** Actual option value to validate */
actual: unknown;
/** Possible valid values or validation functions */
possible?: RuleOptionsPossibleFunc | RuleOptionsPossible[] | Record<string, RuleOptionsPossible[]>;
/** Whether this option is optional */
optional?: boolean;
}
type RuleOptionsPossible = boolean | number | string | RuleOptionsPossibleFunc;
type RuleOptionsPossibleFunc = (value: unknown) => boolean;Usage Example:
const rule = (primaryOption, secondaryOptions) => {
return (root, result) => {
// Validate options
const validOptions = utils.validateOptions(
result,
ruleName,
{
actual: primaryOption,
possible: ['always', 'never']
},
{
actual: secondaryOptions,
possible: {
ignore: ['comments', 'whitespace'],
severity: ['warning', 'error']
},
optional: true
}
);
if (!validOptions) return;
// Rule implementation...
};
};Report linting violations using the utils.report function.
/**
* Report a linting problem
* @param problem - Problem details and location
*/
function report(problem: Problem): void;
interface Problem {
/** Rule name generating the problem */
ruleName: string;
/** PostCSS result object */
result: PostcssResult;
/** Problem message text or function */
message: RuleMessage;
/** Arguments for message functions */
messageArgs?: Parameters<RuleMessageFunc>;
/** CSS node where problem occurs */
node: PostCSS.Node;
/** Start index within node */
index?: number;
/** End index within node */
endIndex?: number;
/** Start position within node */
start?: Position;
/** End position within node */
end?: Position;
/** Specific word causing the problem */
word?: string;
/** Severity override */
severity?: Severity;
/** Auto-fix callback or object */
fix?: FixCallback | FixObject;
}
interface Position {
line: number;
column: number;
}
type FixCallback = () => void;
interface FixObject {
apply?: FixCallback;
node?: PostCSS.Node;
}Utilities for testing custom rules against CSS inputs.
/**
* Test a rule against CSS input
* @param options - Test configuration
* @param callback - Callback for handling warnings
*/
function checkAgainstRule<T, O extends Object>(
options: {
ruleName: string;
ruleSettings: ConfigRuleSettings<T, O>;
root: PostCSS.Root;
result?: PostcssResult;
context?: RuleContext;
},
callback: (warning: PostCSS.Warning) => void
): Promise<void>;Usage Example:
import stylelint from "stylelint";
const { utils } = stylelint;
import postcss from "postcss";
// Test the rule
const root = postcss.parse('.example { color: red; }');
const warnings = [];
await utils.checkAgainstRule(
{
ruleName: 'my-rule/no-red',
ruleSettings: true,
root
},
(warning) => warnings.push(warning)
);
console.log('Warnings:', warnings.length);import stylelint from "stylelint";
const { createPlugin, utils } = stylelint;
const ruleName = "my-plugin/no-hardcoded-colors";
const messages = utils.ruleMessages(ruleName, {
rejected: (color) => `Unexpected hardcoded color "${color}". Use CSS custom properties instead.`,
rejectedHex: (hex) => `Unexpected hex color "${hex}". Use CSS custom properties instead.`
});
const meta = {
url: "https://github.com/my-org/stylelint-my-plugin#no-hardcoded-colors",
fixable: false
};
const ruleFunction = (primaryOption, secondaryOptions = {}) => {
return (root, result) => {
// Validate options
const validOptions = utils.validateOptions(
result,
ruleName,
{
actual: primaryOption,
possible: [true, false]
},
{
actual: secondaryOptions,
possible: {
ignore: [utils.isString],
ignoreProperties: [utils.isString],
severity: ['warning', 'error']
},
optional: true
}
);
if (!validOptions || !primaryOption) return;
const { ignore = [], ignoreProperties = [] } = secondaryOptions;
root.walkDecls((decl) => {
// Skip ignored properties
if (ignoreProperties.includes(decl.prop)) return;
const value = decl.value;
// Check for hex colors
const hexMatch = value.match(/#[0-9a-fA-F]{3,8}/);
if (hexMatch && !ignore.includes(hexMatch[0])) {
utils.report({
ruleName,
result,
node: decl,
message: messages.rejectedHex,
messageArgs: [hexMatch[0]],
index: decl.source.start.column - 1 + value.indexOf(hexMatch[0]),
endIndex: decl.source.start.column - 1 + value.indexOf(hexMatch[0]) + hexMatch[0].length
});
}
// Check for named colors
const namedColors = ['red', 'blue', 'green', 'black', 'white'];
for (const color of namedColors) {
if (value.includes(color) && !ignore.includes(color)) {
utils.report({
ruleName,
result,
node: decl,
message: messages.rejected,
messageArgs: [color]
});
}
}
});
};
};
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
ruleFunction.meta = meta;
export default createPlugin(ruleName, ruleFunction);Register and use custom plugins in Stylelint configuration.
// stylelint.config.js
import myPlugin from "./my-custom-plugin.js";
export default {
plugins: [myPlugin],
rules: {
"my-plugin/no-hardcoded-colors": [true, {
ignore: ["transparent", "inherit"],
ignoreProperties: ["box-shadow"]
}]
}
};
// Or with plugin name
export default {
plugins: ["./my-custom-plugin.js"],
rules: {
"my-plugin/no-hardcoded-colors": true
}
};Install with Tessl CLI
npx tessl i tessl/npm-stylelint