prettier-plugin-bootstrap 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/dist/index.d.mts +39 -0
- package/dist/{index.js → index.mjs} +69 -6
- package/dist/traversal.d.ts +3 -2
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/pierluigilenoci/prettier-plugin-bootstrap/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/prettier-plugin-bootstrap)
|
|
5
|
+
[](https://codecov.io/gh/pierluigilenoci/prettier-plugin-bootstrap)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
8
|
A [Prettier](https://prettier.io/) plugin that automatically sorts Bootstrap CSS classes following the framework's recommended order.
|
|
8
9
|
|
|
9
|
-
Works with **HTML**, **JSX/TSX**, **Vue**, **Angular**, and **Astro** templates.
|
|
10
|
+
Works with **HTML**, **JSX/TSX**, **Vue**, **Angular**, **Svelte**, and **Astro** templates.
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
12
13
|
|
|
@@ -62,17 +63,18 @@ Unknown classes are preserved in their original relative order and placed after
|
|
|
62
63
|
|
|
63
64
|
## Options
|
|
64
65
|
|
|
65
|
-
| Option
|
|
66
|
-
|
|
67
|
-
| `bootstrapAttributes` | `string[]` | `[]`
|
|
68
|
-
| `bootstrapFunctions`
|
|
66
|
+
| Option | Type | Default | Description |
|
|
67
|
+
| --------------------- | ---------- | ------- | -------------------------------------------------------------------------- |
|
|
68
|
+
| `bootstrapAttributes` | `string[]` | `[]` | Additional HTML attributes to sort (beyond `class` and `className`) |
|
|
69
|
+
| `bootstrapFunctions` | `string[]` | `[]` | Function names whose arguments are class lists (e.g. `clsx`, `classNames`) |
|
|
69
70
|
|
|
70
71
|
### Example
|
|
71
72
|
|
|
72
73
|
```json
|
|
73
74
|
{
|
|
74
75
|
"plugins": ["prettier-plugin-bootstrap"],
|
|
75
|
-
"bootstrapAttributes": ["ngClass", "v-bind:class"]
|
|
76
|
+
"bootstrapAttributes": ["ngClass", "v-bind:class"],
|
|
77
|
+
"bootstrapFunctions": ["clsx", "cn", "classNames"]
|
|
76
78
|
}
|
|
77
79
|
```
|
|
78
80
|
|
|
@@ -83,8 +85,19 @@ Unknown classes are preserved in their original relative order and placed after
|
|
|
83
85
|
- `angular` — Angular templates
|
|
84
86
|
- `babel` / `babel-ts` / `typescript` — JSX/TSX files
|
|
85
87
|
- `acorn` / `meriyah` — Alternative JS parsers
|
|
88
|
+
- `svelte` — Svelte components (requires `prettier-plugin-svelte`)
|
|
86
89
|
- `astro` — Astro components (requires `prettier-plugin-astro`)
|
|
87
90
|
|
|
91
|
+
### Svelte
|
|
92
|
+
|
|
93
|
+
When using with `prettier-plugin-svelte`, list `prettier-plugin-bootstrap` **after** `prettier-plugin-svelte` in your config:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"plugins": ["prettier-plugin-svelte", "prettier-plugin-bootstrap"]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
88
101
|
### Astro
|
|
89
102
|
|
|
90
103
|
When using with `prettier-plugin-astro`, list `prettier-plugin-bootstrap` **after** `prettier-plugin-astro` in your config so it can wrap the Astro parser:
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Options, Parser } from "prettier";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
interface BootstrapPluginOptions extends Options {
|
|
5
|
+
bootstrapAttributes?: string[];
|
|
6
|
+
bootstrapFunctions?: string[];
|
|
7
|
+
}
|
|
8
|
+
type SortKey = [categoryIndex: number, breakpointIndex: number];
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/class-order.d.ts
|
|
11
|
+
declare const BREAKPOINTS: readonly ["sm", "md", "lg", "xl", "xxl"];
|
|
12
|
+
declare const CLASS_ORDER: readonly string[];
|
|
13
|
+
declare function classKey(className: string): SortKey;
|
|
14
|
+
declare function sortClasses(classes: string[]): string[];
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/index.d.ts
|
|
17
|
+
declare const options: {
|
|
18
|
+
bootstrapAttributes: {
|
|
19
|
+
type: "string";
|
|
20
|
+
array: boolean;
|
|
21
|
+
default: {
|
|
22
|
+
value: never[];
|
|
23
|
+
}[];
|
|
24
|
+
category: string;
|
|
25
|
+
description: string;
|
|
26
|
+
};
|
|
27
|
+
bootstrapFunctions: {
|
|
28
|
+
type: "string";
|
|
29
|
+
array: boolean;
|
|
30
|
+
default: {
|
|
31
|
+
value: never[];
|
|
32
|
+
}[];
|
|
33
|
+
category: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
declare const parsers: Record<string, Partial<Parser>>;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { BREAKPOINTS, type BootstrapPluginOptions, CLASS_ORDER, classKey, options, parsers, sortClasses };
|
|
@@ -319,7 +319,35 @@ function walk(node, visitor) {
|
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
|
-
function
|
|
322
|
+
function sortStringNode(node) {
|
|
323
|
+
if (!node) return;
|
|
324
|
+
if (node.type === "StringLiteral") {
|
|
325
|
+
const sorted = sortClassString(node.value);
|
|
326
|
+
node.value = sorted;
|
|
327
|
+
if (node.extra) {
|
|
328
|
+
node.extra.rawValue = sorted;
|
|
329
|
+
node.extra.raw = `"${sorted}"`;
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
334
|
+
const sorted = sortClassString(node.value);
|
|
335
|
+
node.value = sorted;
|
|
336
|
+
if (node.raw) {
|
|
337
|
+
const quote = node.raw[0];
|
|
338
|
+
node.raw = `${quote}${sorted}${quote}`;
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (node.type === "TemplateLiteral" && (!node.expressions || node.expressions.length === 0)) {
|
|
343
|
+
for (const quasi of node.quasis) if (quasi.value && typeof quasi.value.raw === "string") {
|
|
344
|
+
const sorted = sortClassString(quasi.value.raw);
|
|
345
|
+
quasi.value.raw = sorted;
|
|
346
|
+
quasi.value.cooked = sorted;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function processHtmlAst(ast, targetAttrs, _targetFunctions) {
|
|
323
351
|
walk(ast, (node) => {
|
|
324
352
|
if (node.attrs && Array.isArray(node.attrs)) {
|
|
325
353
|
for (const attr of node.attrs) if (targetAttrs.includes(attr.name) && typeof attr.value === "string") attr.value = sortClassString(attr.value);
|
|
@@ -327,14 +355,21 @@ function processHtmlAst(ast, targetAttrs) {
|
|
|
327
355
|
if (node.attributes && Array.isArray(node.attributes)) for (const attr of node.attributes) {
|
|
328
356
|
const name = attr.name || attr.key && attr.key.value;
|
|
329
357
|
if (targetAttrs.includes(name) && attr.value) {
|
|
330
|
-
if (typeof attr.value === "string")
|
|
331
|
-
|
|
358
|
+
if (typeof attr.value === "string") {
|
|
359
|
+
if (attr.kind && attr.kind !== "quoted") continue;
|
|
360
|
+
if (attr.value.includes("${")) continue;
|
|
361
|
+
attr.value = sortClassString(attr.value);
|
|
362
|
+
} else if (attr.value && typeof attr.value.value === "string") {
|
|
363
|
+
if (attr.value.value.includes("${")) continue;
|
|
364
|
+
attr.value.value = sortClassString(attr.value.value);
|
|
365
|
+
}
|
|
332
366
|
}
|
|
333
367
|
}
|
|
334
368
|
});
|
|
335
369
|
return ast;
|
|
336
370
|
}
|
|
337
|
-
function processJsxAst(ast, targetAttrs) {
|
|
371
|
+
function processJsxAst(ast, targetAttrs, targetFunctions = []) {
|
|
372
|
+
const functionSet = new Set(targetFunctions);
|
|
338
373
|
walk(ast, (node) => {
|
|
339
374
|
if (node.type === "JSXAttribute" || node.type === "JSXSpreadAttribute") {
|
|
340
375
|
const name = node.name && (node.name.name || node.name.value);
|
|
@@ -353,6 +388,33 @@ function processJsxAst(ast, targetAttrs) {
|
|
|
353
388
|
}
|
|
354
389
|
}
|
|
355
390
|
}
|
|
391
|
+
if (node.type === "CallExpression" && functionSet.size > 0) {
|
|
392
|
+
const callee = node.callee;
|
|
393
|
+
if (callee && callee.type === "Identifier" && functionSet.has(callee.name)) for (const arg of node.arguments || []) sortStringNode(arg);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
return ast;
|
|
397
|
+
}
|
|
398
|
+
function walkSvelte(node, visitor) {
|
|
399
|
+
if (!node) return;
|
|
400
|
+
visitor(node);
|
|
401
|
+
if (node.fragment && Array.isArray(node.fragment.nodes)) for (const child of node.fragment.nodes) walkSvelte(child, visitor);
|
|
402
|
+
if (Array.isArray(node.children)) for (const child of node.children) walkSvelte(child, visitor);
|
|
403
|
+
}
|
|
404
|
+
function processSvelteAst(ast, targetAttrs, _targetFunctions) {
|
|
405
|
+
walkSvelte(ast, (node) => {
|
|
406
|
+
if (!node.attributes || !Array.isArray(node.attributes)) return;
|
|
407
|
+
for (const attr of node.attributes) {
|
|
408
|
+
if (attr.type !== "Attribute") continue;
|
|
409
|
+
if (!targetAttrs.includes(attr.name)) continue;
|
|
410
|
+
if (Array.isArray(attr.value)) {
|
|
411
|
+
for (const item of attr.value) if (item.type === "Text" && typeof item.data === "string") {
|
|
412
|
+
const sorted = sortClassString(item.data);
|
|
413
|
+
item.data = sorted;
|
|
414
|
+
if (typeof item.raw === "string") item.raw = sorted;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
356
418
|
});
|
|
357
419
|
return ast;
|
|
358
420
|
}
|
|
@@ -401,7 +463,7 @@ function createParserWrapper(parserName, processAst, astFormat) {
|
|
|
401
463
|
if (!originalParser) throw new Error(`prettier-plugin-bootstrap: could not find the "${parserName}" parser. Make sure Prettier and the relevant parser plugin are installed.`);
|
|
402
464
|
if (originalParser.locStart) wrapper.locStart = originalParser.locStart;
|
|
403
465
|
if (originalParser.locEnd) wrapper.locEnd = originalParser.locEnd;
|
|
404
|
-
return processAst(await originalParser.parse(text, options), [...DEFAULT_ATTRIBUTES, ...options.bootstrapAttributes || []]);
|
|
466
|
+
return processAst(await originalParser.parse(text, options), [...DEFAULT_ATTRIBUTES, ...options.bootstrapAttributes || []], options.bootstrapFunctions || []);
|
|
405
467
|
}
|
|
406
468
|
};
|
|
407
469
|
return wrapper;
|
|
@@ -415,7 +477,8 @@ const parsers = {
|
|
|
415
477
|
typescript: createParserWrapper("typescript", processJsxAst, "estree"),
|
|
416
478
|
acorn: createParserWrapper("acorn", processJsxAst, "estree"),
|
|
417
479
|
meriyah: createParserWrapper("meriyah", processJsxAst, "estree"),
|
|
418
|
-
astro: createParserWrapper("astro", processHtmlAst, "astro")
|
|
480
|
+
astro: createParserWrapper("astro", processHtmlAst, "astro"),
|
|
481
|
+
svelte: createParserWrapper("svelte", processSvelteAst, "svelte-ast")
|
|
419
482
|
};
|
|
420
483
|
//#endregion
|
|
421
484
|
export { BREAKPOINTS, CLASS_ORDER, classKey, options, parsers, sortClasses };
|
package/dist/traversal.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export declare function processHtmlAst(ast: any, targetAttrs: string[]): any;
|
|
2
|
-
export declare function processJsxAst(ast: any, targetAttrs: string[]): any;
|
|
1
|
+
export declare function processHtmlAst(ast: any, targetAttrs: string[], _targetFunctions?: string[]): any;
|
|
2
|
+
export declare function processJsxAst(ast: any, targetAttrs: string[], targetFunctions?: string[]): any;
|
|
3
|
+
export declare function processSvelteAst(ast: any, targetAttrs: string[], _targetFunctions?: string[]): any;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prettier-plugin-bootstrap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A Prettier plugin for automatic Bootstrap class sorting",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
-
"import": "./dist/index.
|
|
9
|
+
"import": "./dist/index.mjs",
|
|
10
10
|
"types": "./dist/index.d.ts"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
@@ -38,14 +38,21 @@
|
|
|
38
38
|
"prettier": "^3.0.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
+
"@eslint/js": "^10.0.1",
|
|
42
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
43
|
+
"eslint": "^10.3.0",
|
|
41
44
|
"prettier": "^3.5.0",
|
|
42
|
-
"tsdown": "^0.
|
|
45
|
+
"tsdown": "^0.21.10",
|
|
43
46
|
"typescript": "^5.8.0",
|
|
47
|
+
"typescript-eslint": "^8.59.1",
|
|
44
48
|
"vitest": "^3.1.0"
|
|
45
49
|
},
|
|
46
50
|
"scripts": {
|
|
47
51
|
"build": "tsdown && tsc -p tsconfig.build.json",
|
|
52
|
+
"format:check": "prettier --check .",
|
|
53
|
+
"lint": "eslint .",
|
|
48
54
|
"test": "vitest run",
|
|
55
|
+
"test:coverage": "vitest run --coverage",
|
|
49
56
|
"test:watch": "vitest",
|
|
50
57
|
"typecheck": "tsc --noEmit"
|
|
51
58
|
}
|