0
# Plugin Development
1
2
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.
3
4
## Capabilities
5
6
### Plugin Creation
7
8
Create custom Stylelint plugins with the createPlugin utility function.
9
10
```typescript { .api }
11
/**
12
* Create a Stylelint plugin
13
* @param ruleName - Unique name for the rule
14
* @param rule - Rule implementation function
15
* @returns Plugin object for registration
16
*/
17
function createPlugin(ruleName: string, rule: Rule): Plugin;
18
19
interface Plugin {
20
ruleName: string;
21
rule: Rule;
22
}
23
```
24
25
**Usage Example:**
26
27
```javascript
28
import stylelint from "stylelint";
29
const { createPlugin, utils } = stylelint;
30
31
const myCustomRule = (primaryOption, secondaryOptions, context) => {
32
return (root, result) => {
33
// Rule implementation
34
root.walkDecls((decl) => {
35
if (decl.prop === 'color' && decl.value === 'red') {
36
utils.report({
37
ruleName: 'my-namespace/no-red-colors',
38
result,
39
node: decl,
40
message: 'Unexpected red color'
41
});
42
}
43
});
44
};
45
};
46
47
const plugin = createPlugin('my-namespace/no-red-colors', myCustomRule);
48
export default plugin;
49
```
50
51
### Rule Interface
52
53
Core rule function signature and structure for implementing custom linting logic.
54
55
```typescript { .api }
56
interface Rule<P = any, S = any, M = RuleMessages> {
57
/**
58
* Rule implementation function
59
* @param primaryOption - Main rule configuration
60
* @param secondaryOptions - Additional rule options
61
* @param context - Rule execution context
62
* @returns PostCSS plugin function
63
*/
64
(primaryOption: P, secondaryOptions: S, context: RuleContext):
65
(root: PostCSS.Root, result: PostcssResult) => Promise<void> | void;
66
67
/** Unique rule name */
68
ruleName: string;
69
/** Rule message templates */
70
messages: M;
71
/** Whether primary option is an array */
72
primaryOptionArray?: boolean;
73
/** Rule metadata */
74
meta?: RuleMeta;
75
}
76
77
interface RuleContext {
78
/** Configuration comment prefix */
79
configurationComment?: string;
80
/** Fix mode enabled */
81
fix?: boolean;
82
/** Newline character for the file */
83
newline?: string;
84
}
85
86
interface RuleMeta {
87
/** URL to rule documentation */
88
url: string;
89
/** Whether rule is deprecated */
90
deprecated?: boolean;
91
/** Whether rule supports auto-fixing */
92
fixable?: boolean;
93
}
94
```
95
96
### Rule Messages
97
98
Define message templates for rule violations and user-facing text.
99
100
```typescript { .api }
101
type RuleMessages = { [message: string]: RuleMessage };
102
type RuleMessage = string | RuleMessageFunc;
103
type RuleMessageFunc = (...args: (string | number | boolean | RegExp)[]) => string;
104
105
/**
106
* Create rule-specific messages with rule name suffix
107
* @param ruleName - Name of the rule
108
* @param messages - Message templates
109
* @returns Processed messages with rule names
110
*/
111
function ruleMessages<T extends RuleMessages>(
112
ruleName: string,
113
messages: T
114
): T;
115
```
116
117
**Usage Example:**
118
119
```javascript
120
import stylelint from "stylelint";
121
const { utils } = stylelint;
122
123
const messages = utils.ruleMessages('my-rule/no-red', {
124
rejected: (value) => `Unexpected red color "${value}"`,
125
expected: 'Expected a color other than red'
126
});
127
128
// Usage in rule
129
utils.report({
130
ruleName: 'my-rule/no-red',
131
result,
132
node: decl,
133
message: messages.rejected(decl.value),
134
messageArgs: [decl.value]
135
});
136
```
137
138
### Option Validation
139
140
Validate rule options using the built-in validation system.
141
142
```typescript { .api }
143
/**
144
* Validate rule options
145
* @param result - PostCSS result object
146
* @param ruleName - Name of the rule
147
* @param optionDescriptions - Option validation descriptors
148
* @returns Whether all options are valid
149
*/
150
function validateOptions(
151
result: PostcssResult,
152
ruleName: string,
153
...optionDescriptions: RuleOptions[]
154
): boolean;
155
156
interface RuleOptions {
157
/** Actual option value to validate */
158
actual: unknown;
159
/** Possible valid values or validation functions */
160
possible?: RuleOptionsPossibleFunc | RuleOptionsPossible[] | Record<string, RuleOptionsPossible[]>;
161
/** Whether this option is optional */
162
optional?: boolean;
163
}
164
165
type RuleOptionsPossible = boolean | number | string | RuleOptionsPossibleFunc;
166
type RuleOptionsPossibleFunc = (value: unknown) => boolean;
167
```
168
169
**Usage Example:**
170
171
```javascript
172
const rule = (primaryOption, secondaryOptions) => {
173
return (root, result) => {
174
// Validate options
175
const validOptions = utils.validateOptions(
176
result,
177
ruleName,
178
{
179
actual: primaryOption,
180
possible: ['always', 'never']
181
},
182
{
183
actual: secondaryOptions,
184
possible: {
185
ignore: ['comments', 'whitespace'],
186
severity: ['warning', 'error']
187
},
188
optional: true
189
}
190
);
191
192
if (!validOptions) return;
193
194
// Rule implementation...
195
};
196
};
197
```
198
199
### Problem Reporting
200
201
Report linting violations using the utils.report function.
202
203
```typescript { .api }
204
/**
205
* Report a linting problem
206
* @param problem - Problem details and location
207
*/
208
function report(problem: Problem): void;
209
210
interface Problem {
211
/** Rule name generating the problem */
212
ruleName: string;
213
/** PostCSS result object */
214
result: PostcssResult;
215
/** Problem message text or function */
216
message: RuleMessage;
217
/** Arguments for message functions */
218
messageArgs?: Parameters<RuleMessageFunc>;
219
/** CSS node where problem occurs */
220
node: PostCSS.Node;
221
/** Start index within node */
222
index?: number;
223
/** End index within node */
224
endIndex?: number;
225
/** Start position within node */
226
start?: Position;
227
/** End position within node */
228
end?: Position;
229
/** Specific word causing the problem */
230
word?: string;
231
/** Severity override */
232
severity?: Severity;
233
/** Auto-fix callback or object */
234
fix?: FixCallback | FixObject;
235
}
236
237
interface Position {
238
line: number;
239
column: number;
240
}
241
242
type FixCallback = () => void;
243
interface FixObject {
244
apply?: FixCallback;
245
node?: PostCSS.Node;
246
}
247
```
248
249
### Testing Custom Rules
250
251
Utilities for testing custom rules against CSS inputs.
252
253
```typescript { .api }
254
/**
255
* Test a rule against CSS input
256
* @param options - Test configuration
257
* @param callback - Callback for handling warnings
258
*/
259
function checkAgainstRule<T, O extends Object>(
260
options: {
261
ruleName: string;
262
ruleSettings: ConfigRuleSettings<T, O>;
263
root: PostCSS.Root;
264
result?: PostcssResult;
265
context?: RuleContext;
266
},
267
callback: (warning: PostCSS.Warning) => void
268
): Promise<void>;
269
```
270
271
**Usage Example:**
272
273
```javascript
274
import stylelint from "stylelint";
275
const { utils } = stylelint;
276
import postcss from "postcss";
277
278
// Test the rule
279
const root = postcss.parse('.example { color: red; }');
280
const warnings = [];
281
282
await utils.checkAgainstRule(
283
{
284
ruleName: 'my-rule/no-red',
285
ruleSettings: true,
286
root
287
},
288
(warning) => warnings.push(warning)
289
);
290
291
console.log('Warnings:', warnings.length);
292
```
293
294
### Complete Plugin Example
295
296
```javascript
297
import stylelint from "stylelint";
298
const { createPlugin, utils } = stylelint;
299
300
const ruleName = "my-plugin/no-hardcoded-colors";
301
302
const messages = utils.ruleMessages(ruleName, {
303
rejected: (color) => `Unexpected hardcoded color "${color}". Use CSS custom properties instead.`,
304
rejectedHex: (hex) => `Unexpected hex color "${hex}". Use CSS custom properties instead.`
305
});
306
307
const meta = {
308
url: "https://github.com/my-org/stylelint-my-plugin#no-hardcoded-colors",
309
fixable: false
310
};
311
312
const ruleFunction = (primaryOption, secondaryOptions = {}) => {
313
return (root, result) => {
314
// Validate options
315
const validOptions = utils.validateOptions(
316
result,
317
ruleName,
318
{
319
actual: primaryOption,
320
possible: [true, false]
321
},
322
{
323
actual: secondaryOptions,
324
possible: {
325
ignore: [utils.isString],
326
ignoreProperties: [utils.isString],
327
severity: ['warning', 'error']
328
},
329
optional: true
330
}
331
);
332
333
if (!validOptions || !primaryOption) return;
334
335
const { ignore = [], ignoreProperties = [] } = secondaryOptions;
336
337
root.walkDecls((decl) => {
338
// Skip ignored properties
339
if (ignoreProperties.includes(decl.prop)) return;
340
341
const value = decl.value;
342
343
// Check for hex colors
344
const hexMatch = value.match(/#[0-9a-fA-F]{3,8}/);
345
if (hexMatch && !ignore.includes(hexMatch[0])) {
346
utils.report({
347
ruleName,
348
result,
349
node: decl,
350
message: messages.rejectedHex,
351
messageArgs: [hexMatch[0]],
352
index: decl.source.start.column - 1 + value.indexOf(hexMatch[0]),
353
endIndex: decl.source.start.column - 1 + value.indexOf(hexMatch[0]) + hexMatch[0].length
354
});
355
}
356
357
// Check for named colors
358
const namedColors = ['red', 'blue', 'green', 'black', 'white'];
359
for (const color of namedColors) {
360
if (value.includes(color) && !ignore.includes(color)) {
361
utils.report({
362
ruleName,
363
result,
364
node: decl,
365
message: messages.rejected,
366
messageArgs: [color]
367
});
368
}
369
}
370
});
371
};
372
};
373
374
ruleFunction.ruleName = ruleName;
375
ruleFunction.messages = messages;
376
ruleFunction.meta = meta;
377
378
export default createPlugin(ruleName, ruleFunction);
379
```
380
381
### Plugin Registration
382
383
Register and use custom plugins in Stylelint configuration.
384
385
```javascript
386
// stylelint.config.js
387
import myPlugin from "./my-custom-plugin.js";
388
389
export default {
390
plugins: [myPlugin],
391
rules: {
392
"my-plugin/no-hardcoded-colors": [true, {
393
ignore: ["transparent", "inherit"],
394
ignoreProperties: ["box-shadow"]
395
}]
396
}
397
};
398
399
// Or with plugin name
400
export default {
401
plugins: ["./my-custom-plugin.js"],
402
rules: {
403
"my-plugin/no-hardcoded-colors": true
404
}
405
};
406
```