@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.
Files changed (3) hide show
  1. package/README.md +98 -72
  2. package/dist/data.json +19 -4
  3. 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 projects on [@airframeui](https://airframeui.com) — catch leftover Tailwind/Bootstrap classes, unknown `af-*` names, redundant defaults, and field-error ARIA.
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. Same version as `@airframeui/core` / `@airframeui/tokens` (Changesets fixed group). The plugin ships a build-time snapshot of `classes.json` and the token catalog the same sources [IntelliSense for Airframe](https://airframeui.com/docs/vscode-extension) uses. If those packages are installed in the project, their catalogs win so lint matches the CSS you actually ship.
13
+ Requires ESLint 9+ (flat config) or ESLint 8.57+ with eslintrc. Core and tokens are optional peersif they are installed, their catalogs win so lint matches the CSS you ship.
14
14
 
15
- ## Usage
15
+ ## Setup
16
16
 
17
- ### Flat config (recommended)
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 [airframe.configs['flat/recommended']];
23
+ export default [
24
+ airframe.configs['flat/recommended'],
25
+ ];
24
26
  ```
25
27
 
26
- ### Legacy `.eslintrc`
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 marked **yes** in the table set `meta.fixable` and rewrite the `class` / `className` string (quotes are preserved; an empty class attribute is removed).
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. Autofix only runs for deterministic mappings:
75
+ Same command as the editor **Fix all auto-fixable problems** action.
44
76
 
45
- | Fixed | Not fixed |
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
- 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.
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
- ### `@airframeui/no-tailwind-classes`
81
+ ### Layout recipes
67
82
 
68
- Flags whole tokens such as `flex`, `flex-col`, `grid-cols-3`, `gap-4`, `p-4`, `items-center`, `justify-between`, `text-sm`, `bg-blue-600`, `md:gap-4`. Does not flag `af-grid` or `af-btn`.
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
- ### `@airframeui/no-bootstrap-classes`
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
- Skipped if the attribute already has `af-stack`, `af-inline`, `af-grid`, `af-cluster`, `af-spread`, `af-center`, or `af-container`.
89
-
90
- ### `@airframeui/require-field-error-aria`
91
-
92
- Two checks:
97
+ ### Unknown `af-*` names
93
98
 
94
- | Markup | Required |
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
- ### `@airframeui/no-redundant-defaults`
109
+ Do not invent `af-*` names. Use catalog classes (`af-btn af-is-danger`, not `af-btn-danger`).
112
110
 
113
- | Before | After |
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
- Kept: `af-card af-gap-lg`, `af-card af-inline`, `af-container af-container-lg@xl`, `<h2 class="af-text-h1">`.
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
- ### `@airframeui/no-redundant-width`
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
- | Before | After |
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
- | `af-input af-w-full` (also textarea, select, range, progress, meter, input-group, `af-field__control`) | `af-input` |
127
- | `af-btn af-block` | `af-btn af-w-full` |
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
- Not flagged: `af-btn af-w-full`, `af-input af-is-autosize af-w-full`.
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
- ### Notes
143
+ ## For coding agents
132
144
 
133
- - Template literals, `class:list`, Vue/Svelte bindings, and Astro expression attributes are not covered yet.
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
- ## Links
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
- - Site: [airframeui.com](https://airframeui.com)
140
- - Source: [github.com/airframeui/airframeui](https://github.com/airframeui/airframeui/tree/main/packages/eslint-plugin)
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
- Airframe core libraries are licensed under the MIT License.
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.2.0",
3
- "generatedAt": "2026-08-18T14:18:55.957Z",
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": 2486,
9
- "tokenCount": 362,
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.2.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.2.0",
56
- "@airframeui/tokens": "0.2.0"
55
+ "@airframeui/core": "0.3.0",
56
+ "@airframeui/tokens": "0.3.0"
57
57
  },
58
58
  "turbo": {
59
59
  "tasks": {