prettier-plugin-bootstrap 0.1.3 → 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} +61 -4
- 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);
|
|
@@ -340,7 +368,8 @@ function processHtmlAst(ast, targetAttrs) {
|
|
|
340
368
|
});
|
|
341
369
|
return ast;
|
|
342
370
|
}
|
|
343
|
-
function processJsxAst(ast, targetAttrs) {
|
|
371
|
+
function processJsxAst(ast, targetAttrs, targetFunctions = []) {
|
|
372
|
+
const functionSet = new Set(targetFunctions);
|
|
344
373
|
walk(ast, (node) => {
|
|
345
374
|
if (node.type === "JSXAttribute" || node.type === "JSXSpreadAttribute") {
|
|
346
375
|
const name = node.name && (node.name.name || node.name.value);
|
|
@@ -359,6 +388,33 @@ function processJsxAst(ast, targetAttrs) {
|
|
|
359
388
|
}
|
|
360
389
|
}
|
|
361
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
|
+
}
|
|
362
418
|
});
|
|
363
419
|
return ast;
|
|
364
420
|
}
|
|
@@ -407,7 +463,7 @@ function createParserWrapper(parserName, processAst, astFormat) {
|
|
|
407
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.`);
|
|
408
464
|
if (originalParser.locStart) wrapper.locStart = originalParser.locStart;
|
|
409
465
|
if (originalParser.locEnd) wrapper.locEnd = originalParser.locEnd;
|
|
410
|
-
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 || []);
|
|
411
467
|
}
|
|
412
468
|
};
|
|
413
469
|
return wrapper;
|
|
@@ -421,7 +477,8 @@ const parsers = {
|
|
|
421
477
|
typescript: createParserWrapper("typescript", processJsxAst, "estree"),
|
|
422
478
|
acorn: createParserWrapper("acorn", processJsxAst, "estree"),
|
|
423
479
|
meriyah: createParserWrapper("meriyah", processJsxAst, "estree"),
|
|
424
|
-
astro: createParserWrapper("astro", processHtmlAst, "astro")
|
|
480
|
+
astro: createParserWrapper("astro", processHtmlAst, "astro"),
|
|
481
|
+
svelte: createParserWrapper("svelte", processSvelteAst, "svelte-ast")
|
|
425
482
|
};
|
|
426
483
|
//#endregion
|
|
427
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
|
}
|