@airframeui/eslint-plugin 0.2.0 → 0.3.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 +98 -72
- package/dist/data.json +19 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @airframeui/eslint-plugin
|
|
2
2
|
|
|
3
|
-
ESLint rules that keep
|
|
3
|
+
ESLint rules that keep markup on [Airframe](https://airframeui.com): leftover Tailwind/Bootstrap utilities, unknown `af-*` names, redundant defaults, layout recipes, and field-error ARIA.
|
|
4
4
|
|
|
5
|
-
Docs: [airframeui.com/docs/eslint-plugin](https://airframeui.com/docs/eslint-plugin)
|
|
5
|
+
Same version as `@airframeui/core`. Docs: [airframeui.com/docs/eslint-plugin](https://airframeui.com/docs/eslint-plugin)
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -10,70 +10,79 @@ Docs: [airframeui.com/docs/eslint-plugin](https://airframeui.com/docs/eslint-plu
|
|
|
10
10
|
npm install -D @airframeui/eslint-plugin
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Requires ESLint 9+ (flat config) or ESLint 8 with eslintrc.
|
|
13
|
+
Requires ESLint 9+ (flat config) or ESLint 8.57+ with eslintrc. Core and tokens are optional peers — if they are installed, their catalogs win so lint matches the CSS you ship.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Setup
|
|
16
16
|
|
|
17
|
-
### Flat config (
|
|
17
|
+
### Flat config (ESLint 9+)
|
|
18
18
|
|
|
19
19
|
```js
|
|
20
20
|
// eslint.config.js
|
|
21
21
|
import airframe from '@airframeui/eslint-plugin';
|
|
22
22
|
|
|
23
|
-
export default [
|
|
23
|
+
export default [
|
|
24
|
+
airframe.configs['flat/recommended'],
|
|
25
|
+
];
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
Drop `airframe.configs['flat/recommended']` into an existing `export default [ ... ]` array. It enables JSX parsing for the Airframe rules.
|
|
29
|
+
|
|
30
|
+
Override a rule after the preset:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
export default [
|
|
34
|
+
airframe.configs['flat/recommended'],
|
|
35
|
+
{
|
|
36
|
+
rules: {
|
|
37
|
+
'@airframeui/no-tailwind-classes': 'off',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Legacy eslintrc
|
|
27
44
|
|
|
28
45
|
```js
|
|
46
|
+
// .eslintrc.cjs
|
|
29
47
|
module.exports = {
|
|
30
48
|
plugins: ['@airframeui'],
|
|
31
49
|
extends: ['plugin:@airframeui/recommended'],
|
|
32
50
|
};
|
|
33
51
|
```
|
|
34
52
|
|
|
53
|
+
## Rules
|
|
54
|
+
|
|
55
|
+
All seven rules ship in `recommended`. They only inspect **string literals** on `class` / `className` in JSX. Existing `af-*` tokens are ignored by the Tailwind and Bootstrap detectors.
|
|
56
|
+
|
|
57
|
+
| Rule | Recommended | Autofix | What it does |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `@airframeui/no-bootstrap-classes` | `warn` | no | Leftover Bootstrap 5 tokens (`btn`, `row`, `col-*`, `d-flex`, …) |
|
|
60
|
+
| `@airframeui/no-redundant-defaults` | `warn` | yes | Restated defaults: `af-card af-stack`, `af-container af-container-xl`, matching `af-text-h*` |
|
|
61
|
+
| `@airframeui/no-redundant-width` | `warn` | yes | `af-w-full` on form controls; `af-block` on `af-btn` |
|
|
62
|
+
| `@airframeui/no-tailwind-classes` | `warn` | no | Leftover Tailwind utilities (`flex`, `gap-4`, `md:gap-4`, `bg-blue-600`, …) |
|
|
63
|
+
| `@airframeui/no-unknown-airframe-class` | `error` | yes\* | Invented `af-*` names. Autofix for known aliases and catalog heuristics |
|
|
64
|
+
| `@airframeui/prefer-airframe-layout-recipes` | `warn` | yes | Rewrites `flex flex-col`, `grid grid-cols-*`, `row`, `container`, … |
|
|
65
|
+
| `@airframeui/require-field-error-aria` | `error` | no | `af-field__error` needs `id`; error inputs need `aria-describedby` |
|
|
66
|
+
|
|
35
67
|
## Autofix
|
|
36
68
|
|
|
37
|
-
Rules
|
|
69
|
+
Rules with a fix rewrite the `class` / `className` string (quotes stay; an empty class attribute is removed).
|
|
38
70
|
|
|
39
71
|
```bash
|
|
40
72
|
npx eslint . --fix
|
|
41
73
|
```
|
|
42
74
|
|
|
43
|
-
Same command as the editor **Fix all auto-fixable problems** action.
|
|
75
|
+
Same command as the editor **Fix all auto-fixable problems** action.
|
|
44
76
|
|
|
45
|
-
|
|
46
|
-
| --- | --- |
|
|
47
|
-
| Known aliases (`af-btn-danger` → `af-btn af-is-danger`) | Unknown `af-*` with only a fuzzy guess |
|
|
48
|
-
| Heuristic `af-{pattern}-{variant}` when both parts exist in `classes.json` | Tailwind / Bootstrap leftovers (unless they also match a layout recipe) |
|
|
49
|
-
| Layout recipes (`flex flex-col gap-4` → `af-stack af-gap-lg`) | Field-error ARIA (you must choose the `id`) |
|
|
50
|
-
| Redundant defaults and width (`af-card af-stack af-gap-md` → `af-card`) | |
|
|
51
|
-
|
|
52
|
-
## Rules
|
|
77
|
+
Autofix only runs when the replacement is deterministic: known aliases, catalog heuristics, layout recipes, redundant defaults, and redundant width. Unknown names with only a fuzzy guess, leftover Tailwind/Bootstrap that is not a layout recipe, and field-error ARIA are reported without a fix.
|
|
53
78
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
| Rule | Recommended | Autofix | What it means |
|
|
57
|
-
| --- | --- | --- | --- |
|
|
58
|
-
| `@airframeui/no-tailwind-classes` | warn | no | A class token looks like Tailwind (layout, spacing, type, colour, `sm:` / `md:` prefixes). Prefer Airframe classes. |
|
|
59
|
-
| `@airframeui/no-bootstrap-classes` | warn | no | A class token looks like Bootstrap (`btn`, `row`, `col-*`, `card-*`, `d-flex`, …). Prefer Airframe classes. |
|
|
60
|
-
| `@airframeui/prefer-airframe-layout-recipes` | warn | yes | A known utility combo should be an Airframe layout recipe (`af-stack`, `af-inline`, `af-grid`, `af-container`). |
|
|
61
|
-
| `@airframeui/require-field-error-aria` | error | no | Error copy and error inputs must be wired for assistive tech. |
|
|
62
|
-
| `@airframeui/no-unknown-airframe-class` | error | yes* | Invented `af-*` names. Autofix when there is a known alias or catalog heuristic. |
|
|
63
|
-
| `@airframeui/no-redundant-defaults` | warn | yes | Classes that restate a pattern default (`af-card` + `af-stack`, `af-container af-container-xl`, `<h1 class="af-text-h1">`). |
|
|
64
|
-
| `@airframeui/no-redundant-width` | warn | yes | `af-w-full` on form controls (already 100%); `af-block` on `af-btn` (use `af-w-full`). |
|
|
79
|
+
## Rewrites
|
|
65
80
|
|
|
66
|
-
###
|
|
81
|
+
### Layout recipes
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
`@airframeui/prefer-airframe-layout-recipes` — skipped if the attribute already has `af-stack`, `af-inline`, `af-grid`, `af-cluster`, `af-spread`, `af-center`, or `af-container`.
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Flags whole tokens such as `btn`, `btn-primary`, `row`, `col-lg-6`, `d-flex`, `container`, `card-body`, `alert-warning`. Does not flag `af-card` or `af-btn`.
|
|
73
|
-
|
|
74
|
-
### `@airframeui/prefer-airframe-layout-recipes`
|
|
75
|
-
|
|
76
|
-
Rewrites these combos. Gap numbers map to the Airframe scale (`4` → `lg`, `8` → `2xl`). Default `md` (`gap-3` / `g-3`) is omitted because `af-stack` / `af-grid` already use it.
|
|
85
|
+
Gap numbers map to the Airframe named scale (`0` · `xs` · `sm` · `md` · `lg` · `xl` · `2xl` · `3xl` · `4xl`). Default `md` (`gap-3` / `g-3`) is omitted because `af-stack` / `af-grid` already use it.
|
|
77
86
|
|
|
78
87
|
| Before | After |
|
|
79
88
|
| --- | --- |
|
|
@@ -85,20 +94,9 @@ Rewrites these combos. Gap numbers map to the Airframe scale (`4` → `lg`, `8`
|
|
|
85
94
|
| `d-grid` / `row` | `af-grid` |
|
|
86
95
|
| `container` / `container-fluid` | `af-container` |
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
### `@airframeui/require-field-error-aria`
|
|
91
|
-
|
|
92
|
-
Two checks:
|
|
97
|
+
### Unknown `af-*` names
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
| --- | --- |
|
|
96
|
-
| `class` contains `af-field__error` | `id` on that element (for `aria-describedby`) |
|
|
97
|
-
| `class` contains `af-is-error`, or `aria-invalid="true"` | `aria-describedby` on that element |
|
|
98
|
-
|
|
99
|
-
### `@airframeui/no-unknown-airframe-class`
|
|
100
|
-
|
|
101
|
-
Flags `af-*` tokens that are not in the class catalog. Prefers the linted project's `@airframeui/core/classes.json`, then the snapshot generated from core at plugin build time.
|
|
99
|
+
`@airframeui/no-unknown-airframe-class` — prefers the project's `@airframeui/core/classes.json`, then the snapshot bundled at plugin build time.
|
|
102
100
|
|
|
103
101
|
| Invented | Autofix |
|
|
104
102
|
| --- | --- |
|
|
@@ -108,39 +106,67 @@ Flags `af-*` tokens that are not in the class catalog. Prefers the linted projec
|
|
|
108
106
|
| `af-{pattern}-{variant}` when both `af-{pattern}` and `af-is-{variant}` exist | `af-{pattern} af-is-{variant}` |
|
|
109
107
|
| Anything else unknown | Message may suggest a close name; no autofix |
|
|
110
108
|
|
|
111
|
-
|
|
109
|
+
Do not invent `af-*` names. Use catalog classes (`af-btn af-is-danger`, not `af-btn-danger`).
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
| --- | --- |
|
|
115
|
-
| `af-card af-stack af-gap-md` | `af-card` |
|
|
116
|
-
| `af-grid af-gap-md` / `af-stack af-gap-md` | `af-grid` / `af-stack` |
|
|
117
|
-
| `af-container af-container-xl` | `af-container-xl` |
|
|
118
|
-
| `<h1 class="af-text-h1">` | `<h1>` |
|
|
111
|
+
### Redundant markup
|
|
119
112
|
|
|
120
|
-
|
|
113
|
+
| Before | After | Rule |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `af-card af-stack af-gap-md` | `af-card` | `no-redundant-defaults` |
|
|
116
|
+
| `af-grid af-gap-md` / `af-stack af-gap-md` | `af-grid` / `af-stack` | `no-redundant-defaults` |
|
|
117
|
+
| `af-container af-container-xl` | `af-container-xl` | `no-redundant-defaults` |
|
|
118
|
+
| `<h1 class="af-text-h1">` | `<h1>` | `no-redundant-defaults` |
|
|
119
|
+
| `af-input af-w-full` | `af-input` | `no-redundant-width` |
|
|
120
|
+
| `af-btn af-block` | `af-btn af-w-full` | `no-redundant-width` |
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
Kept: `af-card af-gap-lg`, `af-card af-inline`, `af-container af-container-lg@xl`, `<h2 class="af-text-h1">`, `af-btn af-w-full`, `af-input af-is-autosize af-w-full`.
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
Form controls that already span full width: `af-input`, `af-textarea`, `af-select`, `af-range`, `af-progress`, `af-meter`, `af-input-group`, `af-field__control`.
|
|
125
|
+
|
|
126
|
+
### Field-error ARIA
|
|
127
|
+
|
|
128
|
+
No autofix — you must choose the `id`.
|
|
129
|
+
|
|
130
|
+
| Markup | Required |
|
|
125
131
|
| --- | --- |
|
|
126
|
-
| `
|
|
127
|
-
| `af-
|
|
132
|
+
| `class` contains `af-field__error` | `id` on that element (for `aria-describedby`) |
|
|
133
|
+
| `class` contains `af-is-error`, or `aria-invalid="true"` | `aria-describedby` on that element |
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
```jsx
|
|
136
|
+
<label className="af-field">
|
|
137
|
+
<span className="af-field__label">Email</span>
|
|
138
|
+
<input className="af-input af-is-error" aria-describedby="email-error" />
|
|
139
|
+
<p className="af-field__error" id="email-error">Enter a valid email.</p>
|
|
140
|
+
</label>
|
|
141
|
+
```
|
|
130
142
|
|
|
131
|
-
|
|
143
|
+
## For coding agents
|
|
132
144
|
|
|
133
|
-
|
|
134
|
-
- Tailwind/Bootstrap detectors ignore existing `af-*` tokens so `af-grid` / `af-btn` do not false-positive.
|
|
145
|
+
Enable `recommended`, then treat lint as the source of truth for `af-*` markup:
|
|
135
146
|
|
|
136
|
-
|
|
147
|
+
```text
|
|
148
|
+
write class / className string literals → npx eslint . --fix → fix remaining reports by hand
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- Apply `--fix` first. Do not invent a different `af-*` name when the message already suggests one.
|
|
152
|
+
- Map leftover Tailwind/Bootstrap layout combos with the tables above. Do not leave `flex flex-col` next to `af-stack`.
|
|
153
|
+
- For field-error ARIA, add a real `id` and matching `aria-describedby`. There is no autofix.
|
|
154
|
+
- Do not wrap classes in template literals, `clsx`, or `class:list` to dodge the rules — those forms are not checked yet, and the markup is still wrong.
|
|
155
|
+
|
|
156
|
+
## Current limitations
|
|
157
|
+
|
|
158
|
+
- Checks **string literals** on `class` / `className` only (React JSX). Not yet: template literals, `class:list`, Vue/Svelte bindings, Astro expression attributes, or HTML files.
|
|
159
|
+
- `@airframeui/no-unknown-airframe-class` prefers the project's `@airframeui/core/classes.json`, then the snapshot generated from core at plugin build time (same `classes.json` + token catalog as [IntelliSense for Airframe](https://airframeui.com/docs/vscode-extension)).
|
|
160
|
+
- Tailwind/Bootstrap detectors ignore existing `af-*` tokens so `af-grid` / `af-btn` are not flagged.
|
|
161
|
+
|
|
162
|
+
## Related
|
|
137
163
|
|
|
138
164
|
- Docs: [airframeui.com/docs/eslint-plugin](https://airframeui.com/docs/eslint-plugin)
|
|
139
|
-
-
|
|
140
|
-
-
|
|
165
|
+
- [`@airframeui/core`](https://www.npmjs.com/package/@airframeui/core) — CSS system (install this in the app)
|
|
166
|
+
- [IntelliSense for Airframe](https://airframeui.com/docs/vscode-extension) — autocomplete for `af-*` / `--af-*`
|
|
167
|
+
- [Coming from Bootstrap 5 or Tailwind](https://airframeui.com/docs/migrate)
|
|
168
|
+
- [Packages](https://airframeui.com/docs/packages)
|
|
141
169
|
|
|
142
170
|
## License
|
|
143
171
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Commercial tooling, premium themes, and hosted services may be offered under separate commercial licenses.
|
|
172
|
+
MIT
|
package/dist/data.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"generatedAt": "2026-08-
|
|
2
|
+
"version": "0.3.0",
|
|
3
|
+
"generatedAt": "2026-08-22T08:45:57.971Z",
|
|
4
4
|
"source": {
|
|
5
5
|
"classes": "packages/core/dist/classes.json",
|
|
6
6
|
"tokens": "packages/tokens/dist/catalog.json"
|
|
7
7
|
},
|
|
8
|
-
"classCount":
|
|
9
|
-
"tokenCount":
|
|
8
|
+
"classCount": 2497,
|
|
9
|
+
"tokenCount": 366,
|
|
10
10
|
"classes": [
|
|
11
11
|
"af-absolute",
|
|
12
12
|
"af-accordion",
|
|
@@ -548,6 +548,7 @@
|
|
|
548
548
|
"af-drawer-header",
|
|
549
549
|
"af-drawer-right",
|
|
550
550
|
"af-drawer-title",
|
|
551
|
+
"af-dropdown",
|
|
551
552
|
"af-dropdown-menu",
|
|
552
553
|
"af-dropdown-menu-open",
|
|
553
554
|
"af-empty-state",
|
|
@@ -867,6 +868,7 @@
|
|
|
867
868
|
"af-inline@xs",
|
|
868
869
|
"af-input",
|
|
869
870
|
"af-input-group",
|
|
871
|
+
"af-input-group-action",
|
|
870
872
|
"af-input-group-addon",
|
|
871
873
|
"af-inset-0",
|
|
872
874
|
"af-is-active",
|
|
@@ -876,6 +878,7 @@
|
|
|
876
878
|
"af-is-danger",
|
|
877
879
|
"af-is-disabled",
|
|
878
880
|
"af-is-dropzone",
|
|
881
|
+
"af-is-end",
|
|
879
882
|
"af-is-error",
|
|
880
883
|
"af-is-fading",
|
|
881
884
|
"af-is-ghost",
|
|
@@ -892,6 +895,7 @@
|
|
|
892
895
|
"af-is-primary",
|
|
893
896
|
"af-is-secondary",
|
|
894
897
|
"af-is-sm",
|
|
898
|
+
"af-is-start",
|
|
895
899
|
"af-is-success",
|
|
896
900
|
"af-is-tertiary",
|
|
897
901
|
"af-is-vertical",
|
|
@@ -2059,6 +2063,7 @@
|
|
|
2059
2063
|
"af-py-xs@xl",
|
|
2060
2064
|
"af-py-xs@xs",
|
|
2061
2065
|
"af-radio",
|
|
2066
|
+
"af-radius",
|
|
2062
2067
|
"af-radius-full",
|
|
2063
2068
|
"af-radius-full@2xl",
|
|
2064
2069
|
"af-radius-full@lg",
|
|
@@ -2087,6 +2092,12 @@
|
|
|
2087
2092
|
"af-radius-sm@sm",
|
|
2088
2093
|
"af-radius-sm@xl",
|
|
2089
2094
|
"af-radius-sm@xs",
|
|
2095
|
+
"af-radius@2xl",
|
|
2096
|
+
"af-radius@lg",
|
|
2097
|
+
"af-radius@md",
|
|
2098
|
+
"af-radius@sm",
|
|
2099
|
+
"af-radius@xl",
|
|
2100
|
+
"af-radius@xs",
|
|
2090
2101
|
"af-range",
|
|
2091
2102
|
"af-reduce-motion",
|
|
2092
2103
|
"af-reduce-motion@2xl",
|
|
@@ -2777,12 +2788,16 @@
|
|
|
2777
2788
|
"--af-link-hover-decoration",
|
|
2778
2789
|
"--af-link-hover-primary",
|
|
2779
2790
|
"--af-link-primary",
|
|
2791
|
+
"--af-pill-radius",
|
|
2792
|
+
"--af-radius",
|
|
2780
2793
|
"--af-radius-0",
|
|
2781
2794
|
"--af-radius-full",
|
|
2782
2795
|
"--af-radius-lg",
|
|
2783
2796
|
"--af-radius-md",
|
|
2784
2797
|
"--af-radius-sm",
|
|
2785
2798
|
"--af-radius-xl",
|
|
2799
|
+
"--af-range-thumb-size",
|
|
2800
|
+
"--af-range-track-height",
|
|
2786
2801
|
"--af-shadow-lg",
|
|
2787
2802
|
"--af-shadow-md",
|
|
2788
2803
|
"--af-shadow-sm",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@airframeui/eslint-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Airframe UI ESLint plugin — catch Tailwind/Bootstrap drift, unknown af-* classes, and redundant defaults",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://airframeui.com/docs/eslint-plugin",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@types/node": "26.2.0",
|
|
53
53
|
"eslint": "10.8.1",
|
|
54
54
|
"typescript": "6.0.3",
|
|
55
|
-
"@airframeui/core": "0.
|
|
56
|
-
"@airframeui/tokens": "0.
|
|
55
|
+
"@airframeui/core": "0.3.0",
|
|
56
|
+
"@airframeui/tokens": "0.3.0"
|
|
57
57
|
},
|
|
58
58
|
"turbo": {
|
|
59
59
|
"tasks": {
|