@satinhq/lint 0.0.0-stage → 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +104 -2
- package/oxlint.json +100 -0
- package/package.json +38 -3
- package/scripts/check-fidelity.mjs +28 -0
- package/scripts/check-modal-lifecycle.mjs +49 -0
- package/scripts/check-sidebar-artwork.mjs +20 -0
- package/scripts/check-webgpu-worker.mjs +144 -0
- package/src/checker.mjs +71 -0
- package/src/css-analysis.mjs +595 -0
- package/src/html-analysis.mjs +49 -0
- package/src/native-typescript.mjs +59 -0
- package/src/rules.mjs +276 -0
- package/src/typescript-analysis.mjs +1461 -0
package/src/rules.mjs
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
const definitions = [
|
|
2
|
+
{
|
|
3
|
+
id: 'no-executable-url',
|
|
4
|
+
description: 'Reject executable and private schemes in URL actions and embedded pages.',
|
|
5
|
+
autofixable: false,
|
|
6
|
+
message:
|
|
7
|
+
'Use an http or https URL with Link, useOpenURL or WebView; executable and private URL schemes are unsupported.',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: 'no-private-css-variable',
|
|
11
|
+
description: 'Keep framework geometry and styles behind the public API.',
|
|
12
|
+
autofixable: false,
|
|
13
|
+
message:
|
|
14
|
+
'Do not read --satin-* variables in app code. Use Overlay, SafeAreaInset, useSafeAreaInsets or public component props.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'no-raw-image',
|
|
18
|
+
description: 'Keep artwork decoding and intrinsic geometry framework-owned.',
|
|
19
|
+
autofixable: false,
|
|
20
|
+
message:
|
|
21
|
+
'Use Image for local artwork or AsyncImage for remote URLs from @satinhq/kit. They own placeholders, failed requests and reconnect recovery.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'no-handrolled-context-menu',
|
|
25
|
+
description: 'Keep chrome context menus framework-owned.',
|
|
26
|
+
autofixable: false,
|
|
27
|
+
message:
|
|
28
|
+
'Use contextMenu or ContextMenu from @satinhq/kit instead of canceling contextmenu on app chrome.',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'no-internal-import',
|
|
32
|
+
description: 'Use the stable app API.',
|
|
33
|
+
autofixable: false,
|
|
34
|
+
message:
|
|
35
|
+
'@satinhq/kit/internal is reserved for framework tests, the lab and the parity kit. Use satin or explicitly opt into @satinhq/kit/unstable.',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'no-global-focus-reset',
|
|
39
|
+
description: 'Keep keyboard focus indicators while Satin quiets touch focus.',
|
|
40
|
+
autofixable: false,
|
|
41
|
+
message:
|
|
42
|
+
'Do not globally remove focus outlines. Satin suppresses non-keyboard indicators on the focused element and preserves keyboard focus.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'no-hidden-focus-heading',
|
|
46
|
+
description: 'Focus a painted heading or control.',
|
|
47
|
+
autofixable: false,
|
|
48
|
+
message:
|
|
49
|
+
'A programmatic heading focus target must be visible. Do not use hidden or screen-reader-only headings for navigation focus.',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'require-icon-label',
|
|
53
|
+
description: 'Name icon-only controls.',
|
|
54
|
+
autofixable: false,
|
|
55
|
+
message: 'An icon-only control requires a nonempty aria-label or aria-labelledby.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'no-browser-dialog',
|
|
59
|
+
description: 'Keep modal presentation framework-owned.',
|
|
60
|
+
autofixable: false,
|
|
61
|
+
message:
|
|
62
|
+
'Use Alert or ConfirmationDialog from @satinhq/kit instead of browser alert, confirm or prompt.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'no-browser-history',
|
|
66
|
+
description: 'Keep app navigation and URL projection framework-owned.',
|
|
67
|
+
autofixable: false,
|
|
68
|
+
message:
|
|
69
|
+
'Use the Satin navigator. Browser history mutation and traversal belong to the framework.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'no-app-reload',
|
|
73
|
+
description: 'Keep release updates and push activation from reloading the running app.',
|
|
74
|
+
autofixable: false,
|
|
75
|
+
message:
|
|
76
|
+
'Let the Satin app shell apply updates at a safe launch boundary; do not call location.reload.',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'no-global-touch-listener',
|
|
80
|
+
description: 'Keep touch policy on framework semantic roles.',
|
|
81
|
+
autofixable: false,
|
|
82
|
+
message:
|
|
83
|
+
'Do not install document/window touch listeners. Use Satin controls and scroll surfaces.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'no-fixed-document',
|
|
87
|
+
description: 'Keep document viewport ownership in Satin.',
|
|
88
|
+
autofixable: false,
|
|
89
|
+
message:
|
|
90
|
+
'Do not fix html, body, or the app mount. Satin owns document layout so the installed viewport can expand under the status bar.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'no-global-selection-reset',
|
|
94
|
+
description: 'Keep selection policies scoped to semantic roles.',
|
|
95
|
+
autofixable: false,
|
|
96
|
+
message: 'Use Chrome or Selectable instead of a root or universal user-select reset.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'no-direct-push-api',
|
|
100
|
+
description: 'Keep notification permission and badges framework-owned.',
|
|
101
|
+
autofixable: false,
|
|
102
|
+
message:
|
|
103
|
+
'Use NotificationPermissionExplainer and useNotifications from @satinhq/kit. Never prompt on launch.',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'no-handrolled-glass',
|
|
107
|
+
description: 'Keep glass materials on the shared framework renderer.',
|
|
108
|
+
autofixable: false,
|
|
109
|
+
message: 'Use GlassEffect or a glass control from @satinhq/kit instead of backdrop-filter.',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'valid-glass-morph',
|
|
113
|
+
description: 'Keep glass relations scoped and transitions explicit.',
|
|
114
|
+
autofixable: false,
|
|
115
|
+
message:
|
|
116
|
+
'GlassEffect identity/union requires namespace; union requires a container; use matchedGeometry, materialize or identity.',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'no-raw-text-input',
|
|
120
|
+
description: 'Use framework fields or document a native-input escape.',
|
|
121
|
+
autofixable: false,
|
|
122
|
+
message:
|
|
123
|
+
'Use TextField, SecureField or TextEditor. A raw control requires data-satin-native-input with a nonempty reason.',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'no-small-input-font',
|
|
127
|
+
description: 'Keep editable text at least 16 CSS pixels.',
|
|
128
|
+
autofixable: false,
|
|
129
|
+
message: 'Input fonts below 16px cause focus zoom. Use framework field typography.',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'no-duplicate-tab-id',
|
|
133
|
+
description: 'Require unique literal Tab ids within each TabView.',
|
|
134
|
+
autofixable: false,
|
|
135
|
+
message:
|
|
136
|
+
'Tab ids must be unique within TabView so each tab retains its own content and navigator.',
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
id: 'no-custom-linear-easing',
|
|
141
|
+
description: 'Keep custom motion curves on compositor property keyframes.',
|
|
142
|
+
autofixable: false,
|
|
143
|
+
message:
|
|
144
|
+
'CSS linear(...) easing can require the iOS main thread. Use Satin motion or sampled property keyframes with plain linear easing.',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'no-raw-appearance-query',
|
|
148
|
+
description: 'Keep appearance CSS on the accepted framework state.',
|
|
149
|
+
autofixable: false,
|
|
150
|
+
message:
|
|
151
|
+
'Raw appearance queries can be stale after resume. Use html[data-satin-appearance="dark"] or the framework appearance store.',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'no-direct-react-root',
|
|
155
|
+
description: 'Reserve initial React scheduling for mountSatin.',
|
|
156
|
+
autofixable: false,
|
|
157
|
+
message:
|
|
158
|
+
'Initial mounting is framework-owned. Use runApp from @satinhq/kit instead of react-dom/client.',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'no-page-zoom',
|
|
162
|
+
description: 'Keep page scaling owned by Satin and content zoom inside Zoomable.',
|
|
163
|
+
autofixable: false,
|
|
164
|
+
message: 'Page zoom bypasses the fixed shell. Use Zoomable for content magnification.',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'no-handwritten-app-head',
|
|
168
|
+
description: 'Reserve app head configuration for defineApp and satinApp.',
|
|
169
|
+
autofixable: false,
|
|
170
|
+
message:
|
|
171
|
+
'App head configuration is definition-owned. Remove this tag and use defineApp with satinApp.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'no-menu-motion-properties',
|
|
175
|
+
description: 'Keep menu motion on concrete document-timeline properties.',
|
|
176
|
+
autofixable: false,
|
|
177
|
+
message:
|
|
178
|
+
'Menu motion custom properties require main-thread style updates. Use Satin sampled transform/opacity timelines.',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'no-native-menu-active',
|
|
182
|
+
description: 'Require pointer-owned menu highlights instead of CSS :active.',
|
|
183
|
+
autofixable: false,
|
|
184
|
+
message:
|
|
185
|
+
'CSS :active cannot follow a captured menu drag on iOS. Use the Satin menu gesture state.',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'no-raw-viewport-height',
|
|
189
|
+
description: 'Require Satin geometry tokens instead of raw vh-family units.',
|
|
190
|
+
autofixable: false,
|
|
191
|
+
message: 'Raw vh units bypass Satin geometry. Consume a --satin-* geometry token.',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'no-raw-safe-area-env',
|
|
195
|
+
description: 'Require calibrated Satin inset tokens instead of safe-area env() access.',
|
|
196
|
+
autofixable: false,
|
|
197
|
+
message: 'Raw safe-area env() access bypasses Satin calibration. Consume --satin-inset-*.',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'no-raw-css-overflow-scroll',
|
|
201
|
+
description: 'Require ScrollSurface instead of scrollable CSS overflow declarations.',
|
|
202
|
+
autofixable: false,
|
|
203
|
+
message:
|
|
204
|
+
'Raw overflow scrolling bypasses ScrollSurface. Render scrolling content through Satin.',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'no-raw-inline-overflow-scroll',
|
|
208
|
+
description: 'Require ScrollSurface instead of scrollable inline style properties.',
|
|
209
|
+
autofixable: false,
|
|
210
|
+
message:
|
|
211
|
+
'Raw inline overflow scrolling bypasses ScrollSurface. Render scrolling content through Satin.',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'no-webkit-overflow-scrolling-css',
|
|
215
|
+
description: 'Reserve the -webkit-overflow-scrolling CSS property for Satin.',
|
|
216
|
+
autofixable: false,
|
|
217
|
+
message:
|
|
218
|
+
'-webkit-overflow-scrolling is framework-owned. Render scrolling content through ScrollSurface.',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'no-webkit-overflow-scrolling-inline',
|
|
222
|
+
description: 'Reserve the WebkitOverflowScrolling inline style property for Satin.',
|
|
223
|
+
autofixable: false,
|
|
224
|
+
message:
|
|
225
|
+
'WebkitOverflowScrolling is framework-owned. Render scrolling content through ScrollSurface.',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'no-raw-button',
|
|
229
|
+
description: 'Require Pressable instead of raw button elements.',
|
|
230
|
+
autofixable: false,
|
|
231
|
+
message: 'Raw button activation bypasses Pressable. Render the control through Satin.',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'no-onclick-activation',
|
|
235
|
+
description: 'Require Pressable onPress instead of raw onClick activation.',
|
|
236
|
+
autofixable: false,
|
|
237
|
+
message: 'onClick activation bypasses Pressable. Express activation with Pressable onPress.',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: 'no-direct-glass-import',
|
|
241
|
+
description: 'Reserve liquid-glass and OpenGlass package imports for Satin.',
|
|
242
|
+
autofixable: false,
|
|
243
|
+
message:
|
|
244
|
+
'Direct glass-library imports bypass Satin material ownership. Use GlassBar or TabBar from @satinhq/kit.',
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'no-direct-sw-registration',
|
|
248
|
+
description: 'Require registerAppShell instead of direct service-worker registration.',
|
|
249
|
+
autofixable: false,
|
|
250
|
+
message:
|
|
251
|
+
'Direct service-worker registration bypasses Satin safe updates. Use registerAppShell.',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'no-raw-persistent-storage',
|
|
255
|
+
description: 'Keep app data and restoration checkpoints framework-owned.',
|
|
256
|
+
autofixable: false,
|
|
257
|
+
message:
|
|
258
|
+
'Use defineDatabase for records or createSceneRestoration/useSceneStorage for UI checkpoints instead of raw localStorage or indexedDB. See the documented escape hatch for integrations.',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'require-bottom-safe-area-inset',
|
|
262
|
+
description: 'Require full-width bottom-pinned CSS geometry to use the Satin bottom inset.',
|
|
263
|
+
autofixable: false,
|
|
264
|
+
message: 'Bottom-pinned lab geometry must account for --satin-inset-bottom in the same rule.',
|
|
265
|
+
},
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
export const rules = Object.freeze(
|
|
269
|
+
Object.fromEntries(definitions.map(definition => [definition.id, Object.freeze(definition)])),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
export const ruleManifest = Object.freeze(
|
|
273
|
+
definitions.map(({ id, description, autofixable }) =>
|
|
274
|
+
Object.freeze({ id, description, autofixable }),
|
|
275
|
+
),
|
|
276
|
+
)
|