@frame12/lint 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/README.md +225 -2
- package/dist/cli.mjs +4 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +2771 -0
- package/package.json +46 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Laqwan Brodie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,226 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@frame12/lint`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Zero-dependency design-grammar checker for [FRAME/12](../../README.md) across TypeScript/JSX (`.tsx`, `.ts`, `.jsx`, `.js`), CSS (`.css`), and generated HTML (`lintHtml()`).
|
|
4
|
+
|
|
5
|
+
> **Distribution status:** `@frame12/lint` is currently a workspace package in the `lbrodie11/frame12` monorepo (`packages/lint`) and is not yet published to the public npm registry.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
### Library API
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
lintSource,
|
|
16
|
+
lintFiles,
|
|
17
|
+
lintDirectory,
|
|
18
|
+
collectFiles,
|
|
19
|
+
lintHtml,
|
|
20
|
+
RULES,
|
|
21
|
+
type RuleId,
|
|
22
|
+
type Violation,
|
|
23
|
+
} from '@frame12/lint';
|
|
24
|
+
|
|
25
|
+
// 1. Lint TSX / JSX / TS / JS / CSS source text
|
|
26
|
+
const tsxViolations: Violation[] = lintSource(code, 'src/app/page.tsx');
|
|
27
|
+
|
|
28
|
+
// 2. Lint generated HTML documents or fragments
|
|
29
|
+
const htmlViolations: Violation[] = lintHtml(htmlString, {
|
|
30
|
+
file: 'report.html',
|
|
31
|
+
tokenStyleIds: ['f12-tokens'], // <style id="f12-tokens"> or <style data-f12-tokens> is exempt
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 3. Lint directories or file paths on disk (.tsx, .ts, .jsx, .js, .css, .html, .htm)
|
|
35
|
+
const { files, violations } = lintDirectory(['src/components', 'src/app', 'dist/report.html']);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### CLI (`@frame12/lint` / `frame12-lint`)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @frame12/lint src/app src/components dist/report.html
|
|
42
|
+
# PASS check-frame12: 43 file(s) checked, 0 violations.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The CLI accepts `.tsx`, `.ts`, `.jsx`, `.js`, `.css`, `.html`, and `.htm` files or directories (routing `.html` / `.htm` files to `lintHtml()`), and exits with code `1` if violations are found or if `0` lintable files match the target paths.
|
|
46
|
+
|
|
47
|
+
### Optional `typescript` Peer Dependency
|
|
48
|
+
|
|
49
|
+
`typescript` (`>=5.0.0`) is an **optional** peer dependency:
|
|
50
|
+
- When `typescript` is installed, `lintSource()` parses JSX/TSX with the TypeScript compiler AST (`ts.createSourceFile`) to trace `cut-order` and `explicit-cut-in-autocut` through spread props, local variable identifiers, and `<PanelPair>` slots.
|
|
51
|
+
- When `typescript` is not installed (for example, in a standalone HTML generator that only calls `lintHtml()`), `lintHtml()` and all CSS/regex source rules work with zero dependencies, and `lintSource()` emits a one-time `WARN [@frame12/lint]` notice on `stderr` (suppressible via `{ quietFallbackWarning: true }`) before falling back to its built-in regex cut scanner.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Rules
|
|
56
|
+
|
|
57
|
+
Every violation carries a stable `rule` ID (`RuleId`), `file`, 1-based `line` and `column`, human-readable `message`, and `snippet`.
|
|
58
|
+
|
|
59
|
+
| Rule ID | Summary |
|
|
60
|
+
| :--- | :--- |
|
|
61
|
+
| [`color-literal`](#color-literal) | Forbid hard-coded color literals (`#hex`, `rgb`/`hsl`/`oklch`, CSS gradients, font names) outside token definitions; use `var(--f-*)` tokens. |
|
|
62
|
+
| [`illegal-gutter`](#illegal-gutter) | Permit only `0`, `12px` (`--f-gutter-pair`), `20px` (`--f-gutter-scene`), or `32px` (`--f-gutter-change`) gutters on `gap`, `grid-gap`, `row-gap`, and `column-gap`. |
|
|
63
|
+
| [`marker-misuse`](#marker-misuse) | Permit `--f-marker` (`#ffd60a`) only as a surface background paired with `color: var(--f-marker-ink)` in the same rule; never on text, borders, outlines, shadows, or SVG strokes/fills. |
|
|
64
|
+
| [`cut-order`](#cut-order) | Require panel cut labels (`C-01`, `C-02`, …) to use two-digit formatting and appear in strict sequential reading order starting at `C-01` without gaps or duplicates. |
|
|
65
|
+
| [`explicit-cut-in-autocut`](#explicit-cut-in-autocut) | Forbid explicit `cut` props on `Panel`/`PanelPair` inside `<Sheet autoCut>` where cuts are numbered automatically. |
|
|
66
|
+
| [`raw-shadcn-import`](#raw-shadcn-import) | Forbid raw `@/components/ui/*`, `@radix-ui/*`, or third-party chart/motion imports where a FRAME/12 component or adapter exists. |
|
|
67
|
+
| [`unsafe-html`](#unsafe-html) | Forbid external resource loads (`<script src>`, `<link href>`, `<img src>`, CSS `@import`/`url()`), embedded frames (`<iframe>`, `<object>`, `<embed>`, `srcdoc`), `<meta http-equiv="refresh">`, `<base href>`, inline `on*=` handlers, and `javascript:` URLs in generated HTML. |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### `color-literal`
|
|
72
|
+
|
|
73
|
+
Forbid hard-coded color literals (`#hex`, `rgb()`, `hsl()`, `oklch()`, CSS gradients, and raw font-family names) outside token files (`frame12.css`, `frame12-tokens.css`, `<style data-f12-tokens>`, or `opts.tokenStyleIds`). Use `var(--f-*)` and `var(--font-f-*)` tokens instead.
|
|
74
|
+
|
|
75
|
+
**Bad:**
|
|
76
|
+
```css
|
|
77
|
+
.card {
|
|
78
|
+
background: #ffffff;
|
|
79
|
+
color: rgb(18, 18, 18);
|
|
80
|
+
background-image: linear-gradient(180deg, #fff, #eee);
|
|
81
|
+
font-family: "Dela Gothic One", sans-serif;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Good:**
|
|
86
|
+
```css
|
|
87
|
+
.card {
|
|
88
|
+
background: var(--f-panel);
|
|
89
|
+
color: var(--f-ink);
|
|
90
|
+
font-family: var(--font-f-display);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `illegal-gutter`
|
|
97
|
+
|
|
98
|
+
Permit only `0`, `12px` (`--f-gutter-pair`), `20px` (`--f-gutter-scene`), or `32px` (`--f-gutter-change`) gutters on `<Scene gutter="...">`, CSS `gap` / `grid-gap` / `row-gap` / `column-gap`, inline styles, and Tailwind `gap-*` classes.
|
|
99
|
+
|
|
100
|
+
**Bad:**
|
|
101
|
+
```html
|
|
102
|
+
<div class="grid gap-4" style="gap: 14px"></div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Good:**
|
|
106
|
+
```html
|
|
107
|
+
<div class="grid gap-gutter-scene" style="gap: var(--f-gutter-pair)"></div>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### `marker-misuse`
|
|
113
|
+
|
|
114
|
+
Permit `--f-marker` (`#ffd60a`) only as a surface background paired with `color: var(--f-marker-ink)` in the same CSS rule or inline style object. Never use `--f-marker` on `color`, `border`, `outline`, `box-shadow`, or SVG `stroke` / `fill`.
|
|
115
|
+
|
|
116
|
+
**Bad:**
|
|
117
|
+
```css
|
|
118
|
+
.focus-ring:focus-visible {
|
|
119
|
+
outline: var(--f-stroke) solid var(--f-marker);
|
|
120
|
+
}
|
|
121
|
+
.badge {
|
|
122
|
+
background: var(--f-marker);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Good:**
|
|
127
|
+
```css
|
|
128
|
+
.focus-ring:focus-visible {
|
|
129
|
+
outline: var(--f-stroke) solid var(--f-rule);
|
|
130
|
+
}
|
|
131
|
+
.badge {
|
|
132
|
+
background: var(--f-marker);
|
|
133
|
+
color: var(--f-marker-ink);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### `cut-order`
|
|
140
|
+
|
|
141
|
+
Require panel cut labels (`C-01`, `C-02`, `C-03`, …) to use two-digit `C-NN` formatting, start at `C-01`, and increase sequentially by 1 in DOM/reading order without gaps or duplicates. Quiet panels (`variant="quiet"`) must not carry cut labels, and `data-cut="auto"` (or empty `data-cut=""`) is ignored.
|
|
142
|
+
|
|
143
|
+
**Bad:**
|
|
144
|
+
```html
|
|
145
|
+
<section class="panel" data-cut="1">First</section>
|
|
146
|
+
<section class="panel" data-cut="C-03">Skipped C-02</section>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Good:**
|
|
150
|
+
```html
|
|
151
|
+
<section class="panel" data-cut="C-01">First</section>
|
|
152
|
+
<section class="panel" data-cut="C-02">Second</section>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### `explicit-cut-in-autocut`
|
|
158
|
+
|
|
159
|
+
Forbid explicit `cut`, `topCut`, or `bottomCut` props on `<Panel>` or `<PanelPair>` inside `<Sheet autoCut>` where cut numbers are assigned automatically.
|
|
160
|
+
|
|
161
|
+
**Bad:**
|
|
162
|
+
```tsx
|
|
163
|
+
<Sheet autoCut>
|
|
164
|
+
<Panel cut="C-01" title="Overview">...</Panel>
|
|
165
|
+
</Sheet>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Good:**
|
|
169
|
+
```tsx
|
|
170
|
+
<Sheet autoCut>
|
|
171
|
+
<Panel title="Overview">...</Panel>
|
|
172
|
+
</Sheet>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### `raw-shadcn-import`
|
|
178
|
+
|
|
179
|
+
Forbid raw `@/components/ui/*` imports, `@radix-ui/*` primitives, or third-party chart/animation libraries (`recharts`, `chart.js`, `d3`, `framer-motion`, `gsap`) where a FRAME/12 component, adapter, or hook exists.
|
|
180
|
+
|
|
181
|
+
**Bad:**
|
|
182
|
+
```tsx
|
|
183
|
+
import { Button } from '@/components/ui/button';
|
|
184
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Good:**
|
|
188
|
+
```tsx
|
|
189
|
+
import { Button, Dialog, DialogContent } from '@/components/frame12';
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `unsafe-html`
|
|
195
|
+
|
|
196
|
+
Active in `lintHtml()` (and when linting `.html` / `.htm` files via `lintFiles()`, `lintDirectory()`, or the CLI). Forbid external `<script src="…">` tags, external resource loads (`<link rel="stylesheet" href="…">`, `<img src="https://…">`, `<source>`, `<video>`, `<audio>`, `<track>`, `<image>`, CSS `@import`, and external CSS `url(https://…)`), embedded frames (`<iframe>`, `<object>`, `<embed>`, `srcdoc`), `<meta http-equiv="refresh">`, `<base href="…">`, inline event handler attributes (`onclick=`, `onload=`, `onerror=`, …), and `javascript:` / `vbscript:` URLs in attribute values. Self-contained inline `<script>` elements without `src` (such as the on-twos pose clock), relative/anchor `<a href="…">` links, and `data:` URIs are permitted.
|
|
197
|
+
|
|
198
|
+
**Bad:**
|
|
199
|
+
```html
|
|
200
|
+
<script src="https://cdn.example.com/tracker.js"></script>
|
|
201
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter" />
|
|
202
|
+
<iframe src="https://example.com/embed"></iframe>
|
|
203
|
+
<button onclick="doSomething()">Run</button>
|
|
204
|
+
<a href="javascript:alert(1)">Link</a>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Good:**
|
|
208
|
+
```html
|
|
209
|
+
<script>
|
|
210
|
+
// Self-contained on-twos motion pose clock
|
|
211
|
+
</script>
|
|
212
|
+
<button type="button" id="run-btn">Run</button>
|
|
213
|
+
<a href="#details">Link</a>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## `lintHtml()` Scanner Scope & Limits
|
|
219
|
+
|
|
220
|
+
`lintHtml(html, opts)` uses a fast, zero-dependency HTML tag/attribute/style scanner rather than a full WHATWG HTML5 parser:
|
|
221
|
+
- **Supported constructs:** `<!-- comments -->`, `<![CDATA[ ... ]]>`, `<!DOCTYPE ...>`, `<style>` and `<script>` raw-text blocks, single-quoted / double-quoted / unquoted attributes, boolean attributes, HTML void elements (`<img>`, `<meta>`, `<br>`, etc.), and self-closing SVG/HTML tags (`<line />`).
|
|
222
|
+
- **Token `<style>` exemption:** `<style data-f12-tokens>` and any `<style id="...">` whose `id` is listed in `opts.tokenStyleIds` (for example `{ tokenStyleIds: ['f12-tokens'] }`) are skipped by the CSS literal rules.
|
|
223
|
+
- **Known limits:**
|
|
224
|
+
- CSS rules inside `<style>` inspect single-level `{ ... }` declaration blocks; deeply nested CSS rules (`@layer` / native CSS nesting with `--f-marker` split across parent and nested blocks) are evaluated per declaration block.
|
|
225
|
+
- Dynamic DOM mutations performed at runtime by inline `<script>` code are not executed or inspected.
|
|
226
|
+
- Malformed HTML with unescaped `<` followed immediately by an ASCII letter in raw text (e.g. `a <b` instead of `a < b`) is tokenized as an opening tag.
|
package/dist/cli.mjs
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type RuleId =
|
|
2
|
+
| 'color-literal'
|
|
3
|
+
| 'illegal-gutter'
|
|
4
|
+
| 'marker-misuse'
|
|
5
|
+
| 'cut-order'
|
|
6
|
+
| 'explicit-cut-in-autocut'
|
|
7
|
+
| 'raw-shadcn-import'
|
|
8
|
+
| 'unsafe-html';
|
|
9
|
+
|
|
10
|
+
export interface Violation {
|
|
11
|
+
rule: RuleId;
|
|
12
|
+
message: string;
|
|
13
|
+
file: string;
|
|
14
|
+
line: number;
|
|
15
|
+
column: number;
|
|
16
|
+
snippet?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LintHtmlOptions {
|
|
20
|
+
/**
|
|
21
|
+
* File name reported in returned `Violation` entries.
|
|
22
|
+
* @default "input.html"
|
|
23
|
+
*/
|
|
24
|
+
file?: string;
|
|
25
|
+
/**
|
|
26
|
+
* `<style id="...">` element IDs whose contents define design tokens and
|
|
27
|
+
* should be exempt from CSS literal rules (in addition to `<style data-f12-tokens>`).
|
|
28
|
+
*/
|
|
29
|
+
tokenStyleIds?: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LintSourceOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Force fallback behavior as if the optional `typescript` peer dependency is not installed.
|
|
35
|
+
*/
|
|
36
|
+
disableTypeScript?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Suppress the one-time console warning when falling back to regex cut-order checks.
|
|
39
|
+
*/
|
|
40
|
+
quietFallbackWarning?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export declare const RULES: Readonly<Record<RuleId, string>>;
|
|
44
|
+
export declare const FORBIDDEN_SHADCN_MODULES: ReadonlySet<string>;
|
|
45
|
+
export declare const FORBIDDEN_EXTERNAL_PACKAGES: ReadonlySet<string>;
|
|
46
|
+
|
|
47
|
+
export declare function lintSource(
|
|
48
|
+
content: string,
|
|
49
|
+
filePath?: string,
|
|
50
|
+
opts?: LintSourceOptions
|
|
51
|
+
): Violation[];
|
|
52
|
+
|
|
53
|
+
export declare function collectFiles(targetPaths: string[]): string[];
|
|
54
|
+
|
|
55
|
+
export declare function lintFiles(targetPaths: string[]): {
|
|
56
|
+
files: string[];
|
|
57
|
+
violations: Violation[];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export declare const lintDirectory: typeof lintFiles;
|
|
61
|
+
|
|
62
|
+
export declare function lintHtml(
|
|
63
|
+
/** HTML text, or an object whose string form is HTML (e.g. an `@frame12/static` `Html`). */
|
|
64
|
+
html: string | { toString(): string },
|
|
65
|
+
opts?: LintHtmlOptions
|
|
66
|
+
): Violation[];
|
|
67
|
+
|
|
68
|
+
export declare function runCli(argv?: string[], cwd?: string): number;
|