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 CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  [![CI](https://github.com/pierluigilenoci/prettier-plugin-bootstrap/actions/workflows/ci.yml/badge.svg)](https://github.com/pierluigilenoci/prettier-plugin-bootstrap/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/prettier-plugin-bootstrap)](https://www.npmjs.com/package/prettier-plugin-bootstrap)
5
+ [![codecov](https://codecov.io/gh/pierluigilenoci/prettier-plugin-bootstrap/branch/main/graph/badge.svg)](https://codecov.io/gh/pierluigilenoci/prettier-plugin-bootstrap)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 | Type | Default | Description |
66
- |--------|------|---------|-------------|
67
- | `bootstrapAttributes` | `string[]` | `[]` | Additional HTML attributes to sort (beyond `class` and `className`) |
68
- | `bootstrapFunctions` | `string[]` | `[]` | Function names whose arguments are class lists (e.g. `clsx`, `classNames`) |
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:
@@ -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 processHtmlAst(ast, targetAttrs) {
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 };
@@ -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.1.3",
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.js",
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.12.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
  }