@rivetjs/typescript-plugin 0.1.0-alpha.0 → 0.1.0-alpha.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/dist/index.cjs +518 -114
- package/dist/rivet_compiler.wasm +0 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -24,15 +24,15 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
var import_node_fs5 = require("node:fs");
|
|
27
|
-
var
|
|
27
|
+
var import_node_path7 = require("node:path");
|
|
28
28
|
|
|
29
29
|
// libs/language-core/index.ts
|
|
30
|
-
var
|
|
30
|
+
var import_node_path6 = require("node:path");
|
|
31
31
|
|
|
32
32
|
// libs/compiler/codes.json
|
|
33
33
|
var codes_default = {
|
|
34
34
|
$comment: "Rivet diagnostic code registry. Compiler codes are assigned in rust/src/diagnostics.rs (tests/codes.test.ts keeps both in sync); language-service codes (RV2101, RV3101, RV7101, RV7102) in libs/language-core. Codes are never reused; retired codes stay listed. `docs` per code = the docs base + code (placeholder origin until the docs site is published).",
|
|
35
|
-
docs: "https://rivetjs.
|
|
35
|
+
docs: "https://rivetjs.severyn.pro/errors/",
|
|
36
36
|
ranges: {
|
|
37
37
|
RV1: "syntax / JSX / attributes",
|
|
38
38
|
RV2: "resume",
|
|
@@ -63,418 +63,460 @@ var codes_default = {
|
|
|
63
63
|
code: "RV1000",
|
|
64
64
|
severity: "error",
|
|
65
65
|
title: "Parse error",
|
|
66
|
-
docs: "https://rivetjs.
|
|
66
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1000"
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
code: "RV1001",
|
|
70
70
|
severity: "error",
|
|
71
71
|
title: "Spread attributes (retired: spreads compile)",
|
|
72
72
|
retired: true,
|
|
73
|
-
docs: "https://rivetjs.
|
|
73
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1001"
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
code: "RV1002",
|
|
77
77
|
severity: "error",
|
|
78
78
|
title: "Spread children are not supported",
|
|
79
|
-
docs: "https://rivetjs.
|
|
79
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1002"
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
code: "RV1003",
|
|
83
83
|
severity: "error",
|
|
84
84
|
title: "Unsupported namespaced tag",
|
|
85
|
-
docs: "https://rivetjs.
|
|
85
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1003"
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
code: "RV1004",
|
|
89
89
|
severity: "error",
|
|
90
90
|
title: "Unsupported native tag",
|
|
91
|
-
docs: "https://rivetjs.
|
|
91
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1004"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
code: "RV1005",
|
|
95
95
|
severity: "error",
|
|
96
96
|
title: "Parser-repaired native nesting",
|
|
97
|
-
docs: "https://rivetjs.
|
|
97
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1005"
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
code: "RV1006",
|
|
101
101
|
severity: "error",
|
|
102
102
|
title: "Text directly in table structure",
|
|
103
|
-
docs: "https://rivetjs.
|
|
103
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1006"
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
code: "RV1007",
|
|
107
107
|
severity: "error",
|
|
108
108
|
title: "<script> / <style> JSX content",
|
|
109
|
-
docs: "https://rivetjs.
|
|
109
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1007"
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
code: "RV1008",
|
|
113
113
|
severity: "error",
|
|
114
114
|
title: "Void element with children",
|
|
115
|
-
docs: "https://rivetjs.
|
|
115
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1008"
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
code: "RV1009",
|
|
119
119
|
severity: "error",
|
|
120
120
|
title: "Textarea model with children",
|
|
121
|
-
docs: "https://rivetjs.
|
|
121
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1009"
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
code: "RV1010",
|
|
125
125
|
severity: "error",
|
|
126
126
|
title: "Textarea / title children must be scalar text",
|
|
127
|
-
docs: "https://rivetjs.
|
|
127
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1010"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
code: "RV1011",
|
|
131
131
|
severity: "error",
|
|
132
132
|
title: "Unsupported model binding",
|
|
133
|
-
docs: "https://rivetjs.
|
|
133
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1011"
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
code: "RV1012",
|
|
137
137
|
severity: "error",
|
|
138
138
|
title: "Unknown native event",
|
|
139
|
-
docs: "https://rivetjs.
|
|
139
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1012"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
code: "RV1013",
|
|
143
143
|
severity: "error",
|
|
144
144
|
title: "Unsupported global event attribute",
|
|
145
|
-
docs: "https://rivetjs.
|
|
145
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1013"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
code: "RV1014",
|
|
149
149
|
severity: "error",
|
|
150
150
|
title: "Structural prop passes a value instead of an accessor (retired: component props are live, each={xs()} / when={on()} update)",
|
|
151
151
|
retired: true,
|
|
152
|
-
docs: "https://rivetjs.
|
|
152
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1014"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
code: "RV1015",
|
|
156
156
|
severity: "error",
|
|
157
157
|
title: "Event handler is a literal (string / number / boolean), not a function",
|
|
158
|
-
docs: "https://rivetjs.
|
|
158
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1015"
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
code: "RV1016",
|
|
162
162
|
severity: "error",
|
|
163
163
|
title: "Event handler reads state when it binds (handlers bind once; wrap it in an arrow)",
|
|
164
|
-
docs: "https://rivetjs.
|
|
164
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1016"
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
code: "RV1101",
|
|
168
168
|
severity: "error",
|
|
169
169
|
title: "Unsafe or unsupported native attribute / URL",
|
|
170
|
-
docs: "https://rivetjs.
|
|
170
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1101"
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
code: "RV1102",
|
|
174
174
|
severity: "error",
|
|
175
175
|
title: "Invalid preload directive value",
|
|
176
|
-
docs: "https://rivetjs.
|
|
176
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1102"
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
code: "RV1103",
|
|
180
180
|
severity: "error",
|
|
181
181
|
title: "Invalid clientEvent target",
|
|
182
|
-
docs: "https://rivetjs.
|
|
182
|
+
docs: "https://rivetjs.severyn.pro/errors/RV1103"
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
code: "RV2001",
|
|
186
186
|
severity: "error",
|
|
187
187
|
title: "Browser global read during server rendering (resume)",
|
|
188
|
-
docs: "https://rivetjs.
|
|
188
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2001"
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
191
|
code: "RV2002",
|
|
192
192
|
severity: "error",
|
|
193
193
|
title: "Resumable ref callback must assign a local",
|
|
194
|
-
docs: "https://rivetjs.
|
|
194
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2002"
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
code: "RV2003",
|
|
198
198
|
severity: "error",
|
|
199
199
|
title: "Captured class instance is not serializable",
|
|
200
|
-
docs: "https://rivetjs.
|
|
200
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2003"
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
code: "RV2004",
|
|
204
204
|
severity: "error",
|
|
205
205
|
title: "Capture-phase handlers are not resumable (retired: onXCapture resumes)",
|
|
206
206
|
retired: true,
|
|
207
|
-
docs: "https://rivetjs.
|
|
207
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2004"
|
|
208
208
|
},
|
|
209
209
|
{
|
|
210
210
|
code: "RV2005",
|
|
211
211
|
severity: "error",
|
|
212
212
|
title: "Ref-bound local assigned outside ref callbacks",
|
|
213
|
-
docs: "https://rivetjs.
|
|
213
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2005"
|
|
214
214
|
},
|
|
215
215
|
{
|
|
216
216
|
code: "RV2006",
|
|
217
217
|
severity: "error",
|
|
218
218
|
title: "JSX inside handlers, effects and computeds",
|
|
219
|
-
docs: "https://rivetjs.
|
|
219
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2006"
|
|
220
220
|
},
|
|
221
221
|
{
|
|
222
222
|
code: "RV2007",
|
|
223
223
|
severity: "error",
|
|
224
224
|
title: "Resumable code captures `this`",
|
|
225
|
-
docs: "https://rivetjs.
|
|
225
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2007"
|
|
226
226
|
},
|
|
227
227
|
{
|
|
228
228
|
code: "RV2008",
|
|
229
229
|
severity: "error",
|
|
230
230
|
title: "Resumed module exports a different binding under a captured name",
|
|
231
|
-
docs: "https://rivetjs.
|
|
231
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2008"
|
|
232
232
|
},
|
|
233
233
|
{
|
|
234
234
|
code: "RV2009",
|
|
235
235
|
severity: "error",
|
|
236
236
|
title: "Two module references share a browser name",
|
|
237
|
-
docs: "https://rivetjs.
|
|
237
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2009"
|
|
238
238
|
},
|
|
239
239
|
{
|
|
240
240
|
code: "RV2010",
|
|
241
241
|
severity: "error",
|
|
242
242
|
title: "One module reference id resolves to two installed copies (Vite build)",
|
|
243
|
-
docs: "https://rivetjs.
|
|
243
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2010"
|
|
244
244
|
},
|
|
245
245
|
{
|
|
246
246
|
code: "RV2011",
|
|
247
247
|
severity: "error",
|
|
248
248
|
title: "Mutually recursive functions separated by an early exit (resume)",
|
|
249
|
-
docs: "https://rivetjs.
|
|
249
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2011"
|
|
250
250
|
},
|
|
251
251
|
{
|
|
252
252
|
code: "RV2012",
|
|
253
253
|
severity: "error",
|
|
254
254
|
title: "`.bind` with a spread first argument (resume; a warning when the receiver may be an object with its own `bind`)",
|
|
255
|
-
docs: "https://rivetjs.
|
|
255
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2012"
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
code: "RV2013",
|
|
259
259
|
severity: "warning",
|
|
260
260
|
title: "`.bind` of a value Rivet cannot resume (resume; an error when it is provably a function)",
|
|
261
|
-
docs: "https://rivetjs.
|
|
261
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2013"
|
|
262
262
|
},
|
|
263
263
|
{
|
|
264
264
|
code: "RV2014",
|
|
265
265
|
severity: "warning",
|
|
266
266
|
title: "`.bind(\u2026)` inside an object literal a resumable closure reaches (resume)",
|
|
267
|
-
docs: "https://rivetjs.
|
|
267
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2014"
|
|
268
268
|
},
|
|
269
269
|
{
|
|
270
270
|
code: "RV2015",
|
|
271
271
|
severity: "warning",
|
|
272
272
|
title: "Component returns a function that cannot resume: anonymous default export or wrapper call (resume)",
|
|
273
|
-
docs: "https://rivetjs.
|
|
273
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2015"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
code: "RV2016",
|
|
277
|
+
severity: "warning",
|
|
278
|
+
title: "`resource()` in a form that cannot resume: destructured, a `let`, not a `const` initializer, or inside a hook (resume)",
|
|
279
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2016"
|
|
274
280
|
},
|
|
275
281
|
{
|
|
276
282
|
code: "RV2101",
|
|
277
283
|
severity: "warning",
|
|
278
284
|
title: "Captured value not serializable (type-aware, language service)",
|
|
279
|
-
docs: "https://rivetjs.
|
|
285
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2101"
|
|
280
286
|
},
|
|
281
287
|
{
|
|
282
288
|
code: "RV2102",
|
|
283
289
|
severity: "error",
|
|
284
290
|
title: "Captured class instance cannot resume as data (private field, decorator, non-data base or resource); at render: [serialize] without static [revive]",
|
|
285
|
-
docs: "https://rivetjs.
|
|
291
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2102"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
code: "RV2103",
|
|
295
|
+
severity: "error",
|
|
296
|
+
title: "Render is not deterministic under the derived resume model (Date.now / Math.random / performance.now / crypto.randomUUID / new Date() while rendering a boundary)",
|
|
297
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2103"
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
code: "RV2104",
|
|
301
|
+
severity: "warning",
|
|
302
|
+
title: "Keyed state (signal / createStore) created in a loop under the derived resume model",
|
|
303
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2104"
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
code: "RV2105",
|
|
307
|
+
severity: "warning",
|
|
308
|
+
title: "Keyed `For` row renders non-deterministically (Date.now / Math.random / performance.now / crypto.randomUUID / getRandomValues / Date() / new Date()): its rows stay recorded instead of lazy (resume)",
|
|
309
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2105"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
code: "RV2106",
|
|
313
|
+
severity: "warning",
|
|
314
|
+
title: "Keyed `For` row holds a site a lazy row cannot replay (clientOnly / Async / Portal / a ref into a local outside the row): its rows stay recorded (resume)",
|
|
315
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2106"
|
|
286
316
|
},
|
|
287
317
|
{
|
|
288
318
|
code: "RV2145",
|
|
289
319
|
severity: "error",
|
|
290
320
|
title: "A module is claimed by both the Rivet compiler and a framework bridge, or by two bridges (Vite build)",
|
|
291
|
-
docs: "https://rivetjs.
|
|
321
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2145"
|
|
292
322
|
},
|
|
293
323
|
{
|
|
294
324
|
code: "RV2146",
|
|
295
325
|
severity: "warning",
|
|
296
326
|
title: "Non-component export of a framework module used in browser code of a Rivet module (Vite build)",
|
|
297
|
-
docs: "https://rivetjs.
|
|
327
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2146"
|
|
298
328
|
},
|
|
299
329
|
{
|
|
300
330
|
code: "RV2148",
|
|
301
331
|
severity: "error",
|
|
302
332
|
title: "A module owned by a framework bridge uses the Rivet runtime (Vite build)",
|
|
303
|
-
docs: "https://rivetjs.
|
|
333
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2148"
|
|
304
334
|
},
|
|
305
335
|
{
|
|
306
336
|
code: "RV2149",
|
|
307
337
|
severity: "warning",
|
|
308
338
|
title: "A framework island recovered from a hydration error by rendering on the client (browser)",
|
|
309
|
-
docs: "https://rivetjs.
|
|
339
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2149"
|
|
310
340
|
},
|
|
311
341
|
{
|
|
312
342
|
code: "RV2150",
|
|
313
343
|
severity: "error",
|
|
314
344
|
title: "The installed Preact no longer reads the island useId mask: Preact island ids would not be unique (server)",
|
|
315
|
-
docs: "https://rivetjs.
|
|
345
|
+
docs: "https://rivetjs.severyn.pro/errors/RV2150"
|
|
316
346
|
},
|
|
317
347
|
{
|
|
318
348
|
code: "RV3001",
|
|
319
349
|
severity: "error",
|
|
320
350
|
title: "Target helper re-exports",
|
|
321
|
-
docs: "https://rivetjs.
|
|
351
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3001"
|
|
322
352
|
},
|
|
323
353
|
{
|
|
324
354
|
code: "RV3002",
|
|
325
355
|
severity: "error",
|
|
326
356
|
title: "Target helper / runtime namespace imports",
|
|
327
|
-
docs: "https://rivetjs.
|
|
357
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3002"
|
|
328
358
|
},
|
|
329
359
|
{
|
|
330
360
|
code: "RV3003",
|
|
331
361
|
severity: "error",
|
|
332
362
|
title: "Structural helper namespace imports",
|
|
333
|
-
docs: "https://rivetjs.
|
|
363
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3003"
|
|
334
364
|
},
|
|
335
365
|
{
|
|
336
366
|
code: "RV3004",
|
|
337
367
|
severity: "error",
|
|
338
368
|
title: "Structural helper aliases",
|
|
339
|
-
docs: "https://rivetjs.
|
|
369
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3004"
|
|
340
370
|
},
|
|
341
371
|
{
|
|
342
372
|
code: "RV3005",
|
|
343
373
|
severity: "error",
|
|
344
374
|
title: "Server clientOnly must be called directly",
|
|
345
|
-
docs: "https://rivetjs.
|
|
375
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3005"
|
|
346
376
|
},
|
|
347
377
|
{
|
|
348
378
|
code: "RV3006",
|
|
349
379
|
severity: "error",
|
|
350
380
|
title: "Server clientOnly erasure shape",
|
|
351
|
-
docs: "https://rivetjs.
|
|
381
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3006"
|
|
352
382
|
},
|
|
353
383
|
{
|
|
354
384
|
code: "RV3007",
|
|
355
385
|
severity: "error",
|
|
356
|
-
title: "Route module server export shape",
|
|
357
|
-
docs: "https://rivetjs.
|
|
386
|
+
title: "Route module server export shape (own statement, wrapper position, duplicate kind, export *)",
|
|
387
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3007"
|
|
358
388
|
},
|
|
359
389
|
{
|
|
360
390
|
code: "RV3008",
|
|
361
391
|
severity: "error",
|
|
362
392
|
title: "Server-only code referenced by browser code",
|
|
363
|
-
docs: "https://rivetjs.
|
|
393
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3008"
|
|
364
394
|
},
|
|
365
395
|
{
|
|
366
396
|
code: "RV3009",
|
|
367
397
|
severity: "error",
|
|
368
398
|
title: "Server-only code renders JSX in a resumed route module",
|
|
369
|
-
docs: "https://rivetjs.
|
|
399
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3009"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
code: "RV3010",
|
|
403
|
+
severity: "error",
|
|
404
|
+
title: "Untagged server export: a former magic export name without its wrapper call",
|
|
405
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3010"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
code: "RV3011",
|
|
409
|
+
severity: "error",
|
|
410
|
+
title: "Server-only module reached from client-rendered code (router build)",
|
|
411
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3011"
|
|
370
412
|
},
|
|
371
413
|
{
|
|
372
414
|
code: "RV3101",
|
|
373
415
|
severity: "warning",
|
|
374
416
|
title: "Link matches no route (language service)",
|
|
375
|
-
docs: "https://rivetjs.
|
|
417
|
+
docs: "https://rivetjs.severyn.pro/errors/RV3101"
|
|
376
418
|
},
|
|
377
419
|
{
|
|
378
420
|
code: "RV4001",
|
|
379
421
|
severity: "error",
|
|
380
422
|
title: "Unknown message key / prefix",
|
|
381
|
-
docs: "https://rivetjs.
|
|
423
|
+
docs: "https://rivetjs.severyn.pro/errors/RV4001"
|
|
382
424
|
},
|
|
383
425
|
{
|
|
384
426
|
code: "RV4002",
|
|
385
427
|
severity: "error",
|
|
386
428
|
title: "Unknown or missing message param",
|
|
387
|
-
docs: "https://rivetjs.
|
|
429
|
+
docs: "https://rivetjs.severyn.pro/errors/RV4002"
|
|
388
430
|
},
|
|
389
431
|
{
|
|
390
432
|
code: "RV4003",
|
|
391
433
|
severity: "error",
|
|
392
434
|
title: "Static call arguments",
|
|
393
|
-
docs: "https://rivetjs.
|
|
435
|
+
docs: "https://rivetjs.severyn.pro/errors/RV4003"
|
|
394
436
|
},
|
|
395
437
|
{
|
|
396
438
|
code: "RV4004",
|
|
397
439
|
severity: "error",
|
|
398
440
|
title: "Message tags",
|
|
399
|
-
docs: "https://rivetjs.
|
|
441
|
+
docs: "https://rivetjs.severyn.pro/errors/RV4004"
|
|
400
442
|
},
|
|
401
443
|
{
|
|
402
444
|
code: "RV4005",
|
|
403
445
|
severity: "error",
|
|
404
446
|
title: "Static call configuration",
|
|
405
|
-
docs: "https://rivetjs.
|
|
447
|
+
docs: "https://rivetjs.severyn.pro/errors/RV4005"
|
|
406
448
|
},
|
|
407
449
|
{
|
|
408
450
|
code: "RV5001",
|
|
409
451
|
severity: "error",
|
|
410
452
|
title: "css`` syntax",
|
|
411
|
-
docs: "https://rivetjs.
|
|
453
|
+
docs: "https://rivetjs.severyn.pro/errors/RV5001"
|
|
412
454
|
},
|
|
413
455
|
{
|
|
414
456
|
code: "RV5002",
|
|
415
457
|
severity: "error",
|
|
416
458
|
title: "css`` usage (declaration, interpolation, unknown or written class)",
|
|
417
|
-
docs: "https://rivetjs.
|
|
459
|
+
docs: "https://rivetjs.severyn.pro/errors/RV5002"
|
|
418
460
|
},
|
|
419
461
|
{
|
|
420
462
|
code: "RV5003",
|
|
421
463
|
severity: "error",
|
|
422
464
|
title: "Invalid or unsafe class / style attribute value",
|
|
423
|
-
docs: "https://rivetjs.
|
|
465
|
+
docs: "https://rivetjs.severyn.pro/errors/RV5003"
|
|
424
466
|
},
|
|
425
467
|
{
|
|
426
468
|
code: "RV6001",
|
|
427
469
|
severity: "error",
|
|
428
470
|
title: "<slot> outside a component or without its props parameter",
|
|
429
|
-
docs: "https://rivetjs.
|
|
471
|
+
docs: "https://rivetjs.severyn.pro/errors/RV6001"
|
|
430
472
|
},
|
|
431
473
|
{
|
|
432
474
|
code: "RV6002",
|
|
433
475
|
severity: "error",
|
|
434
476
|
title: "Invalid r:name / r:args",
|
|
435
|
-
docs: "https://rivetjs.
|
|
477
|
+
docs: "https://rivetjs.severyn.pro/errors/RV6002"
|
|
436
478
|
},
|
|
437
479
|
{
|
|
438
480
|
code: "RV6003",
|
|
439
481
|
severity: "error",
|
|
440
482
|
title: "Invalid r:slot usage",
|
|
441
|
-
docs: "https://rivetjs.
|
|
483
|
+
docs: "https://rivetjs.severyn.pro/errors/RV6003"
|
|
442
484
|
},
|
|
443
485
|
{
|
|
444
486
|
code: "RV6004",
|
|
445
487
|
severity: "error",
|
|
446
488
|
title: "Slot content passed twice",
|
|
447
|
-
docs: "https://rivetjs.
|
|
489
|
+
docs: "https://rivetjs.severyn.pro/errors/RV6004"
|
|
448
490
|
},
|
|
449
491
|
{
|
|
450
492
|
code: "RV7001",
|
|
451
493
|
severity: "error",
|
|
452
494
|
title: "Invalid compiler option",
|
|
453
|
-
docs: "https://rivetjs.
|
|
495
|
+
docs: "https://rivetjs.severyn.pro/errors/RV7001"
|
|
454
496
|
},
|
|
455
497
|
{
|
|
456
498
|
code: "RV7101",
|
|
457
499
|
severity: "error",
|
|
458
500
|
title: "Editor manifest (.rivet/editor.json) missing (rivet check)",
|
|
459
|
-
docs: "https://rivetjs.
|
|
501
|
+
docs: "https://rivetjs.severyn.pro/errors/RV7101"
|
|
460
502
|
},
|
|
461
503
|
{
|
|
462
504
|
code: "RV7102",
|
|
463
505
|
severity: "warning",
|
|
464
506
|
title: "Editor manifest (.rivet/editor.json) stale (rivet check)",
|
|
465
|
-
docs: "https://rivetjs.
|
|
507
|
+
docs: "https://rivetjs.severyn.pro/errors/RV7102"
|
|
466
508
|
},
|
|
467
509
|
{
|
|
468
510
|
code: "RV9000",
|
|
469
511
|
severity: "error",
|
|
470
512
|
title: "Unclassified compiler error",
|
|
471
|
-
docs: "https://rivetjs.
|
|
513
|
+
docs: "https://rivetjs.severyn.pro/errors/RV9000"
|
|
472
514
|
},
|
|
473
515
|
{
|
|
474
516
|
code: "RV9001",
|
|
475
517
|
severity: "error",
|
|
476
518
|
title: "Internal compiler error (incl. compiler panic)",
|
|
477
|
-
docs: "https://rivetjs.
|
|
519
|
+
docs: "https://rivetjs.severyn.pro/errors/RV9001"
|
|
478
520
|
}
|
|
479
521
|
]
|
|
480
522
|
};
|
|
@@ -530,7 +572,7 @@ function routeCompletion(route) {
|
|
|
530
572
|
}
|
|
531
573
|
|
|
532
574
|
// libs/language-core/links.ts
|
|
533
|
-
var LINK_MODULES = ["@rivetjs/router", "@rivetjs/router/client"];
|
|
575
|
+
var LINK_MODULES = ["@rivetjs/router", "@rivetjs/router/client", "@rivetjs/router/link"];
|
|
534
576
|
function narrowed(ts, type) {
|
|
535
577
|
if (!type) return false;
|
|
536
578
|
const members = type.isUnion() ? type.types : [type];
|
|
@@ -659,8 +701,88 @@ function manifestStatus(root) {
|
|
|
659
701
|
return { status: "ok" };
|
|
660
702
|
}
|
|
661
703
|
|
|
704
|
+
// libs/language-core/server-exports.ts
|
|
705
|
+
var import_node_path2 = require("node:path");
|
|
706
|
+
var ROUTER = "@rivetjs/router";
|
|
707
|
+
var MAGIC_SERVER_EXPORTS = {
|
|
708
|
+
serverLoad: { wrapper: "load", name: "loader", argument: "(event) => \u2026" },
|
|
709
|
+
serverGuard: { wrapper: "guard", name: "access", argument: "(event) => \u2026" },
|
|
710
|
+
serverActions: { wrapper: "actions", name: "forms", argument: "{ name: (event) => \u2026 }" },
|
|
711
|
+
serverEntries: { wrapper: "entries", name: "paths", argument: "() => [\u2026]" },
|
|
712
|
+
serverHeaders: { wrapper: "headers", name: "responseHeaders", argument: "(event) => ({ \u2026 })" },
|
|
713
|
+
onRequest: { wrapper: "onRequest", name: "middleware", argument: "(event) => \u2026" },
|
|
714
|
+
onGet: { wrapper: "onGet", name: "GET", argument: "(event) => \u2026" },
|
|
715
|
+
onPost: { wrapper: "onPost", name: "POST", argument: "(event) => \u2026" },
|
|
716
|
+
onPut: { wrapper: "onPut", name: "PUT", argument: "(event) => \u2026" },
|
|
717
|
+
onPatch: { wrapper: "onPatch", name: "PATCH", argument: "(event) => \u2026" },
|
|
718
|
+
onDelete: { wrapper: "onDelete", name: "DELETE", argument: "(event) => \u2026" },
|
|
719
|
+
onOptions: { wrapper: "onOptions", name: "OPTIONS", argument: "(event) => \u2026" }
|
|
720
|
+
};
|
|
721
|
+
function untaggedMessage(name) {
|
|
722
|
+
const magic = MAGIC_SERVER_EXPORTS[name];
|
|
723
|
+
if (!magic) return void 0;
|
|
724
|
+
return `Untagged server export \`${name}\`: route server code is a wrapper call \u2014 \`export const ${magic.name} = ${magic.wrapper}(${magic.argument})\` with \`import { ${magic.wrapper} } from "${ROUTER}"\``;
|
|
725
|
+
}
|
|
726
|
+
function isRouteModule(root, routesDir, fileName) {
|
|
727
|
+
if (!routesDir || !/\.[cm]?[jt]sx?$/.test(fileName) || /\.d\.[cm]?ts$/.test(fileName)) return false;
|
|
728
|
+
const path = (0, import_node_path2.relative)((0, import_node_path2.join)(root, routesDir), fileName);
|
|
729
|
+
return path !== "" && !path.startsWith("..") && !(0, import_node_path2.isAbsolute)(path);
|
|
730
|
+
}
|
|
731
|
+
function wrapperBindings(ts, file) {
|
|
732
|
+
const named = /* @__PURE__ */ new Set();
|
|
733
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
734
|
+
const wrappers = new Set(Object.values(MAGIC_SERVER_EXPORTS).map((m) => m.wrapper));
|
|
735
|
+
for (const statement of file.statements) {
|
|
736
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== ROUTER) continue;
|
|
737
|
+
const clause = statement.importClause;
|
|
738
|
+
if (!clause || clause.isTypeOnly || !clause.namedBindings) continue;
|
|
739
|
+
if (ts.isNamespaceImport(clause.namedBindings)) namespaces.add(clause.namedBindings.name.text);
|
|
740
|
+
else
|
|
741
|
+
for (const element of clause.namedBindings.elements)
|
|
742
|
+
if (!element.isTypeOnly && wrappers.has((element.propertyName ?? element.name).text)) named.add(element.name.text);
|
|
743
|
+
}
|
|
744
|
+
return { named, namespaces };
|
|
745
|
+
}
|
|
746
|
+
function isWrapperCall(ts, init2, bindings) {
|
|
747
|
+
while (init2 && ts.isParenthesizedExpression(init2)) init2 = init2.expression;
|
|
748
|
+
if (!init2 || !ts.isCallExpression(init2)) return false;
|
|
749
|
+
const callee = init2.expression;
|
|
750
|
+
if (ts.isIdentifier(callee)) return bindings.named.has(callee.text);
|
|
751
|
+
return ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && bindings.namespaces.has(callee.expression.text);
|
|
752
|
+
}
|
|
753
|
+
var exported = (ts, node) => ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
754
|
+
function untaggedServerExports(ts, file) {
|
|
755
|
+
if (!/\b(server(Load|Guard|Actions|Entries|Headers)|on(Request|Get|Post|Put|Patch|Delete|Options))\b/.test(file.text)) return [];
|
|
756
|
+
const bindings = wrapperBindings(ts, file);
|
|
757
|
+
const out = [];
|
|
758
|
+
const report = (name) => {
|
|
759
|
+
const message = untaggedMessage(name.text);
|
|
760
|
+
if (!message) return;
|
|
761
|
+
const start = name.getStart(file);
|
|
762
|
+
const { line, character } = file.getLineAndCharacterOfPosition(start);
|
|
763
|
+
out.push({ code: "RV3010", severity: "error", message, filename: file.fileName, start, end: name.end, line: line + 1, column: character + 1, targets: [] });
|
|
764
|
+
};
|
|
765
|
+
for (const statement of file.statements) {
|
|
766
|
+
if (ts.isVariableStatement(statement) && exported(ts, statement)) {
|
|
767
|
+
const isConst = Boolean(statement.declarationList.flags & ts.NodeFlags.Const);
|
|
768
|
+
for (const d of statement.declarationList.declarations)
|
|
769
|
+
if (ts.isIdentifier(d.name) && !(isConst && isWrapperCall(ts, d.initializer, bindings))) report(d.name);
|
|
770
|
+
} else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name && exported(ts, statement)) report(statement.name);
|
|
771
|
+
else if (ts.isExportDeclaration(statement) && !statement.isTypeOnly && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause))
|
|
772
|
+
for (const element of statement.exportClause.elements) {
|
|
773
|
+
if (element.isTypeOnly || !MAGIC_SERVER_EXPORTS[element.name.text]) continue;
|
|
774
|
+
const local = (element.propertyName ?? element.name).text;
|
|
775
|
+
const declared = file.statements.some(
|
|
776
|
+
(s) => ts.isVariableStatement(s) && s.declarationList.flags & ts.NodeFlags.Const && s.declarationList.declarations.some((d) => ts.isIdentifier(d.name) && d.name.text === local && isWrapperCall(ts, d.initializer, bindings))
|
|
777
|
+
);
|
|
778
|
+
if (!declared) report(element.name);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return out;
|
|
782
|
+
}
|
|
783
|
+
|
|
662
784
|
// libs/language-core/fixes.ts
|
|
663
|
-
var FIXABLE_CODES = ["RV1008", "RV1009", "RV1016", "RV1102", "RV2001", "RV2002", "RV2003", "RV2102", "RV3002"];
|
|
785
|
+
var FIXABLE_CODES = ["RV1008", "RV1009", "RV1016", "RV1102", "RV2001", "RV2002", "RV2003", "RV2016", "RV2102", "RV3002", "RV3010"];
|
|
664
786
|
var PRELOAD = ["eager", "idle", "visible", "intent", "interaction"];
|
|
665
787
|
function distance(a, b) {
|
|
666
788
|
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
@@ -819,10 +941,212 @@ function importedAs(ts, file, modules, name) {
|
|
|
819
941
|
}
|
|
820
942
|
return void 0;
|
|
821
943
|
}
|
|
944
|
+
function usesFunctionScope(ts, body) {
|
|
945
|
+
let found = false;
|
|
946
|
+
const visit = (n) => {
|
|
947
|
+
if (found) return;
|
|
948
|
+
if (n.kind === ts.SyntaxKind.ThisKeyword || n.kind === ts.SyntaxKind.SuperKeyword || ts.isIdentifier(n) && n.text === "arguments") found = true;
|
|
949
|
+
else if (!(ts.isFunctionLike(n) && !ts.isArrowFunction(n)) && !ts.isClassLike(n)) ts.forEachChild(n, visit);
|
|
950
|
+
};
|
|
951
|
+
ts.forEachChild(body, visit);
|
|
952
|
+
return found;
|
|
953
|
+
}
|
|
954
|
+
function moduleNames(ts, file) {
|
|
955
|
+
const names = /* @__PURE__ */ new Set();
|
|
956
|
+
const bind = (name) => {
|
|
957
|
+
if (ts.isIdentifier(name)) names.add(name.text);
|
|
958
|
+
else for (const element of name.elements) if (!ts.isOmittedExpression(element)) bind(element.name);
|
|
959
|
+
};
|
|
960
|
+
for (const statement of file.statements) {
|
|
961
|
+
if (ts.isVariableStatement(statement)) for (const d of statement.declarationList.declarations) bind(d.name);
|
|
962
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isEnumDeclaration(statement)) && statement.name) names.add(statement.name.text);
|
|
963
|
+
else if (ts.isImportDeclaration(statement) && statement.importClause) {
|
|
964
|
+
const clause = statement.importClause;
|
|
965
|
+
if (clause.name) names.add(clause.name.text);
|
|
966
|
+
if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) names.add(clause.namedBindings.name.text);
|
|
967
|
+
else if (clause.namedBindings) for (const element of clause.namedBindings.elements) names.add(element.name.text);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return names;
|
|
971
|
+
}
|
|
972
|
+
function wrapServerExport(ts, file, node) {
|
|
973
|
+
if (!ts.isIdentifier(node)) return [];
|
|
974
|
+
const old = node.text;
|
|
975
|
+
const magic = MAGIC_SERVER_EXPORTS[old];
|
|
976
|
+
if (!magic) return [];
|
|
977
|
+
const statement = ts.findAncestor(node, (n) => n.parent === file);
|
|
978
|
+
if (!statement) return [];
|
|
979
|
+
const local = importedAs(ts, file, [ROUTER], magic.wrapper);
|
|
980
|
+
const names = moduleNames(ts, file);
|
|
981
|
+
const declared = (name) => name !== old && names.has(name);
|
|
982
|
+
if (!local && declared(magic.wrapper)) return [];
|
|
983
|
+
const call = local ?? magic.wrapper;
|
|
984
|
+
const next = !declared(magic.name) ? magic.name : !declared(magic.name + "Export") ? magic.name + "Export" : void 0;
|
|
985
|
+
if (!next) return [];
|
|
986
|
+
const edits = [];
|
|
987
|
+
const text = file.text;
|
|
988
|
+
if (ts.isVariableStatement(statement)) {
|
|
989
|
+
const [d, ...rest] = statement.declarationList.declarations;
|
|
990
|
+
if (!d || rest.length || d.name !== node || !d.initializer) return [];
|
|
991
|
+
const init2 = d.initializer;
|
|
992
|
+
const type = d.type ? text.slice(d.type.getStart(file), d.type.end) : void 0;
|
|
993
|
+
edits.push({ start: statement.getStart(file), end: init2.getStart(file), newText: `export const ${next} = ${call}(${type ? "(" : ""}` });
|
|
994
|
+
edits.push({ start: init2.end, end: init2.end, newText: type ? `) satisfies ${type})` : ")" });
|
|
995
|
+
} else if (ts.isFunctionDeclaration(statement) && statement.name === node && statement.body) {
|
|
996
|
+
const async = (ts.getModifiers(statement) ?? []).some((m) => m.kind === ts.SyntaxKind.AsyncKeyword);
|
|
997
|
+
const open = statement.parameters.pos - 1;
|
|
998
|
+
if (statement.asteriskToken || statement.typeParameters || usesFunctionScope(ts, statement.body)) {
|
|
999
|
+
const from = statement.typeParameters ? statement.typeParameters.pos - 1 : open;
|
|
1000
|
+
edits.push({ start: statement.getStart(file), end: from, newText: `export const ${next} = ${call}(${async ? "async " : ""}function${statement.asteriskToken ? "*" : ""} ${next}` });
|
|
1001
|
+
} else {
|
|
1002
|
+
edits.push({ start: statement.getStart(file), end: open, newText: `export const ${next} = ${call}(${async ? "async " : ""}` });
|
|
1003
|
+
const head = statement.type ? statement.type.end : statement.parameters.end + 1;
|
|
1004
|
+
edits.push({ start: head, end: statement.body.getStart(file), newText: " => " });
|
|
1005
|
+
}
|
|
1006
|
+
edits.push({ start: statement.body.end, end: statement.body.end, newText: ");" });
|
|
1007
|
+
} else return [];
|
|
1008
|
+
const { copy, checker } = fileChecker(ts, file);
|
|
1009
|
+
const own = nodeAt(ts, copy, node.getStart(file), node.end);
|
|
1010
|
+
const target = own && checker.getSymbolAtLocation(own);
|
|
1011
|
+
if (target) {
|
|
1012
|
+
const visit = (n) => {
|
|
1013
|
+
if (ts.isIdentifier(n) && n.text === old && n !== own) {
|
|
1014
|
+
if (ts.isShorthandPropertyAssignment(n.parent) && n.parent.name === n) {
|
|
1015
|
+
if (checker.getShorthandAssignmentValueSymbol(n.parent) === target) edits.push({ start: n.getStart(copy), end: n.end, newText: `${old}: ${next}` });
|
|
1016
|
+
} else if (checker.getSymbolAtLocation(n) === target) edits.push({ start: n.getStart(copy), end: n.end, newText: next });
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
ts.forEachChild(n, visit);
|
|
1020
|
+
};
|
|
1021
|
+
visit(copy);
|
|
1022
|
+
}
|
|
1023
|
+
if (!local) edits.push(...addNamedImport(ts, file, ROUTER, magic.wrapper));
|
|
1024
|
+
return [{ fixName: "rivetWrapServerExport", description: `Wrap in \`${magic.wrapper}(\u2026)\`: export const ${next} = ${magic.wrapper}(\u2026)`, edits }];
|
|
1025
|
+
}
|
|
1026
|
+
function handleName(file, ts, call) {
|
|
1027
|
+
for (const arg of call.arguments) {
|
|
1028
|
+
const m = ts.isIdentifier(arg) ? /^(?:load|fetch|get)([A-Z][\w$]*)$/.exec(arg.text) : null;
|
|
1029
|
+
if (m) return uniqueName(file, m[1][0].toLowerCase() + m[1].slice(1));
|
|
1030
|
+
}
|
|
1031
|
+
return uniqueName(file, "res");
|
|
1032
|
+
}
|
|
1033
|
+
function replaceReads(ts, file, bindings) {
|
|
1034
|
+
const { copy, checker } = fileChecker(ts, file);
|
|
1035
|
+
const targets = /* @__PURE__ */ new Map();
|
|
1036
|
+
for (const [identifier, replacement] of bindings) {
|
|
1037
|
+
const own = nodeAt(ts, copy, identifier.getStart(file), identifier.end);
|
|
1038
|
+
const symbol = own && checker.getSymbolAtLocation(own);
|
|
1039
|
+
if (!symbol) return void 0;
|
|
1040
|
+
targets.set(symbol, [replacement, own]);
|
|
1041
|
+
}
|
|
1042
|
+
const edits = [];
|
|
1043
|
+
let refused = false;
|
|
1044
|
+
const visit = (n) => {
|
|
1045
|
+
if (ts.isIdentifier(n)) {
|
|
1046
|
+
const parent = n.parent;
|
|
1047
|
+
if (ts.isShorthandPropertyAssignment(parent) && parent.name === n) {
|
|
1048
|
+
const hit2 = targets.get(checker.getShorthandAssignmentValueSymbol(parent));
|
|
1049
|
+
if (hit2) edits.push({ start: n.end, end: n.end, newText: `: ${hit2[0]}` });
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const symbol = checker.getSymbolAtLocation(n);
|
|
1053
|
+
const hit = symbol && targets.get(symbol);
|
|
1054
|
+
if (!hit || hit[1] === n) return;
|
|
1055
|
+
if (ts.isExportSpecifier(parent) || (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) && (parent.operator === ts.SyntaxKind.PlusPlusToken || parent.operator === ts.SyntaxKind.MinusMinusToken) || ts.isBinaryExpression(parent) && parent.left === n && parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
|
|
1056
|
+
refused = true;
|
|
1057
|
+
else edits.push({ start: n.getStart(copy), end: n.end, newText: hit[0] });
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
ts.forEachChild(n, visit);
|
|
1061
|
+
};
|
|
1062
|
+
visit(copy);
|
|
1063
|
+
return refused ? void 0 : edits;
|
|
1064
|
+
}
|
|
1065
|
+
function resourceFixes(ts, file, node) {
|
|
1066
|
+
const call = ts.findAncestor(node, ts.isCallExpression);
|
|
1067
|
+
if (!call) return [];
|
|
1068
|
+
let at = call;
|
|
1069
|
+
while (ts.isParenthesizedExpression(at.parent) || ts.isAsExpression(at.parent) || ts.isSatisfiesExpression(at.parent) || ts.isNonNullExpression(at.parent))
|
|
1070
|
+
at = at.parent;
|
|
1071
|
+
const callText = file.text.slice(call.getStart(file), call.end);
|
|
1072
|
+
const declaration = at.parent;
|
|
1073
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer === at) {
|
|
1074
|
+
const list = declaration.parent;
|
|
1075
|
+
if (!ts.isVariableDeclarationList(list) || list.declarations.length !== 1) return [];
|
|
1076
|
+
const pattern = declaration.name;
|
|
1077
|
+
if (ts.isIdentifier(pattern)) {
|
|
1078
|
+
if (list.flags & ts.NodeFlags.Const) return [];
|
|
1079
|
+
if (!replaceReads(ts, file, [[pattern, pattern.text]])) return [];
|
|
1080
|
+
const keyword = list.getStart(file);
|
|
1081
|
+
return [
|
|
1082
|
+
{
|
|
1083
|
+
fixName: "rivetResourceConst",
|
|
1084
|
+
description: `Declare it with const: const ${pattern.text} = resource(\u2026)`,
|
|
1085
|
+
edits: [{ start: keyword, end: keyword + 3, newText: "const" }]
|
|
1086
|
+
}
|
|
1087
|
+
];
|
|
1088
|
+
}
|
|
1089
|
+
const name2 = handleName(file, ts, call);
|
|
1090
|
+
const bindings = [];
|
|
1091
|
+
const members = (object, base) => object.elements.every((element) => {
|
|
1092
|
+
if (element.dotDotDotToken || element.initializer || !ts.isIdentifier(element.name)) return false;
|
|
1093
|
+
const key = element.propertyName ?? element.name;
|
|
1094
|
+
if (!ts.isIdentifier(key)) return false;
|
|
1095
|
+
bindings.push([element.name, `${base}.${key.text}`]);
|
|
1096
|
+
return true;
|
|
1097
|
+
});
|
|
1098
|
+
if (ts.isObjectBindingPattern(pattern)) {
|
|
1099
|
+
if (!members(pattern, name2)) return [];
|
|
1100
|
+
} else {
|
|
1101
|
+
if (pattern.elements.length > 2) return [];
|
|
1102
|
+
for (const element of pattern.elements) {
|
|
1103
|
+
if (ts.isOmittedExpression(element)) continue;
|
|
1104
|
+
if (element.dotDotDotToken || element.initializer) return [];
|
|
1105
|
+
if (ts.isIdentifier(element.name)) bindings.push([element.name, name2]);
|
|
1106
|
+
else if (ts.isObjectBindingPattern(element.name) && element === pattern.elements[1]) {
|
|
1107
|
+
if (!members(element.name, name2)) return [];
|
|
1108
|
+
} else return [];
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
const reads = replaceReads(ts, file, bindings);
|
|
1112
|
+
if (!reads) return [];
|
|
1113
|
+
const end = declaration.type ? declaration.type.end : pattern.end;
|
|
1114
|
+
return [
|
|
1115
|
+
{
|
|
1116
|
+
fixName: "rivetResourceConst",
|
|
1117
|
+
description: `Keep the handle: const ${name2} = resource(\u2026); use ${name2}.member`,
|
|
1118
|
+
edits: [{ start: pattern.getStart(file), end, newText: name2 }, ...reads]
|
|
1119
|
+
}
|
|
1120
|
+
];
|
|
1121
|
+
}
|
|
1122
|
+
if (ts.isVariableDeclaration(declaration)) return [];
|
|
1123
|
+
const statement = bodyStatement(ts, call);
|
|
1124
|
+
if (!statement || ts.findAncestor(call.parent, (n) => n === statement || ts.isFunctionLike(n) || ts.isClassLike(n)) !== statement) return [];
|
|
1125
|
+
const fn = statement.parent.parent;
|
|
1126
|
+
const fnName = fn.name ?? (ts.isVariableDeclaration(fn.parent) && ts.isIdentifier(fn.parent.name) ? fn.parent.name : void 0);
|
|
1127
|
+
if (fnName && /^[a-z_$]/.test(fnName.text) && !(ts.getCombinedModifierFlags(fn) & ts.ModifierFlags.Default)) return [];
|
|
1128
|
+
const name = handleName(file, ts, call);
|
|
1129
|
+
const nl = newLineOf(file);
|
|
1130
|
+
const start = statement.getStart(file);
|
|
1131
|
+
return [
|
|
1132
|
+
{
|
|
1133
|
+
fixName: "rivetResourceConst",
|
|
1134
|
+
description: `Create it in the component: const ${name} = resource(\u2026)`,
|
|
1135
|
+
edits: [
|
|
1136
|
+
{ start, end: start, newText: `const ${name} = ${callText};${nl}${indentOf(file, start)}` },
|
|
1137
|
+
{ start: at.getStart(file), end: at.end, newText: name }
|
|
1138
|
+
]
|
|
1139
|
+
}
|
|
1140
|
+
];
|
|
1141
|
+
}
|
|
822
1142
|
function codeFixes(ts, file, diagnostic) {
|
|
823
1143
|
const node = nodeAt(ts, file, diagnostic.start, diagnostic.end);
|
|
824
1144
|
if (!node) return [];
|
|
825
1145
|
switch (diagnostic.code) {
|
|
1146
|
+
case "RV3010":
|
|
1147
|
+
return wrapServerExport(ts, file, node);
|
|
1148
|
+
case "RV2016":
|
|
1149
|
+
return resourceFixes(ts, file, node);
|
|
826
1150
|
case "RV1102": {
|
|
827
1151
|
const attribute = ts.findAncestor(node, ts.isJsxAttribute);
|
|
828
1152
|
const value = attribute?.initializer;
|
|
@@ -1028,18 +1352,18 @@ function serializabilityDiagnostics(ts, checker, file, sites, verdict) {
|
|
|
1028
1352
|
|
|
1029
1353
|
// libs/language-core/sources.ts
|
|
1030
1354
|
var import_node_fs2 = require("node:fs");
|
|
1031
|
-
var
|
|
1355
|
+
var import_node_path3 = require("node:path");
|
|
1032
1356
|
var cache = /* @__PURE__ */ new Map();
|
|
1033
1357
|
function sourcePackagesOf(fileName, text) {
|
|
1034
1358
|
const found = /* @__PURE__ */ new Set();
|
|
1035
1359
|
for (const [, specifier = ""] of text.matchAll(/(?:\bfrom|\bimport)\s*\(?\s*["']([^"'./][^"']*)["']/g)) {
|
|
1036
1360
|
const name = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1037
|
-
const key = (0,
|
|
1361
|
+
const key = (0, import_node_path3.dirname)(fileName) + "\0" + name;
|
|
1038
1362
|
let hit = cache.get(key);
|
|
1039
1363
|
if (hit === void 0) {
|
|
1040
1364
|
hit = null;
|
|
1041
|
-
for (let dir = (0,
|
|
1042
|
-
const file = (0,
|
|
1365
|
+
for (let dir = (0, import_node_path3.dirname)(fileName); ; dir = (0, import_node_path3.dirname)(dir)) {
|
|
1366
|
+
const file = (0, import_node_path3.join)(dir, "node_modules", name, "package.json");
|
|
1043
1367
|
if ((0, import_node_fs2.existsSync)(file)) {
|
|
1044
1368
|
try {
|
|
1045
1369
|
const pkg = JSON.parse((0, import_node_fs2.readFileSync)(file, "utf8"));
|
|
@@ -1048,7 +1372,7 @@ function sourcePackagesOf(fileName, text) {
|
|
|
1048
1372
|
}
|
|
1049
1373
|
break;
|
|
1050
1374
|
}
|
|
1051
|
-
if ((0,
|
|
1375
|
+
if ((0, import_node_path3.dirname)(dir) === dir) break;
|
|
1052
1376
|
}
|
|
1053
1377
|
cache.set(key, hit);
|
|
1054
1378
|
}
|
|
@@ -1060,7 +1384,7 @@ function sourcePackagesOf(fileName, text) {
|
|
|
1060
1384
|
// libs/language-core/compiler.ts
|
|
1061
1385
|
var import_node_fs3 = require("node:fs");
|
|
1062
1386
|
var import_node_module = require("node:module");
|
|
1063
|
-
var
|
|
1387
|
+
var import_node_path4 = require("node:path");
|
|
1064
1388
|
function loadCompiler(wasm) {
|
|
1065
1389
|
const module2 = new WebAssembly.Module((0, import_node_fs3.readFileSync)(wasm));
|
|
1066
1390
|
let exports2 = new WebAssembly.Instance(module2, {}).exports;
|
|
@@ -1097,8 +1421,8 @@ function loadCompiler(wasm) {
|
|
|
1097
1421
|
}
|
|
1098
1422
|
function workspaceCompilerWasm(directory) {
|
|
1099
1423
|
try {
|
|
1100
|
-
const require2 = (0, import_node_module.createRequire)((0,
|
|
1101
|
-
return (0,
|
|
1424
|
+
const require2 = (0, import_node_module.createRequire)((0, import_node_path4.join)(directory, "noop.js"));
|
|
1425
|
+
return (0, import_node_path4.join)((0, import_node_path4.dirname)(require2.resolve("@rivetjs/compiler")), "rivet_compiler.wasm");
|
|
1102
1426
|
} catch {
|
|
1103
1427
|
return null;
|
|
1104
1428
|
}
|
|
@@ -1118,10 +1442,10 @@ function findCompiler(candidates, log) {
|
|
|
1118
1442
|
// libs/language-core/doctor.ts
|
|
1119
1443
|
var import_node_fs4 = require("node:fs");
|
|
1120
1444
|
var import_node_module2 = require("node:module");
|
|
1121
|
-
var
|
|
1445
|
+
var import_node_path5 = require("node:path");
|
|
1122
1446
|
function workspaceTypeScriptVersion(directory) {
|
|
1123
1447
|
try {
|
|
1124
|
-
const require2 = (0, import_node_module2.createRequire)((0,
|
|
1448
|
+
const require2 = (0, import_node_module2.createRequire)((0, import_node_path5.join)(directory, "noop.js"));
|
|
1125
1449
|
return JSON.parse((0, import_node_fs4.readFileSync)(require2.resolve("typescript/package.json"), "utf8")).version;
|
|
1126
1450
|
} catch {
|
|
1127
1451
|
return void 0;
|
|
@@ -1143,7 +1467,7 @@ function doctor(input) {
|
|
|
1143
1467
|
message: `${input.configFile} compiles JSX with jsx=${input.jsx ?? "unset"}, jsxImportSource=${input.jsxImportSource ?? "unset"}; Rivet needs "preserve" and "@rivetjs/runtime".`,
|
|
1144
1468
|
fix: `Extend tsconfig.base.json in ${input.configFile}.`
|
|
1145
1469
|
});
|
|
1146
|
-
const workspace = workspaceTypeScriptVersion((0,
|
|
1470
|
+
const workspace = workspaceTypeScriptVersion((0, import_node_path5.dirname)(input.fileName));
|
|
1147
1471
|
if (workspace && workspace !== input.tsVersion)
|
|
1148
1472
|
findings.push({
|
|
1149
1473
|
id: "typescript-version",
|
|
@@ -1175,14 +1499,14 @@ function doctor(input) {
|
|
|
1175
1499
|
fix: "Run `rivet sync` (or restart `vite`) in the app."
|
|
1176
1500
|
});
|
|
1177
1501
|
const root = input.project?.root;
|
|
1178
|
-
if (root && input.project.manifest.routesDir && !(0, import_node_fs4.existsSync)((0,
|
|
1502
|
+
if (root && input.project.manifest.routesDir && !(0, import_node_fs4.existsSync)((0, import_node_path5.join)(root, ".rivet", "routes.d.ts")))
|
|
1179
1503
|
findings.push({ id: "route-types", severity: "info", message: "Route types (.rivet/routes.d.ts) are missing.", fix: "Run `rivet sync` (or `vite` / `vite build`) in the app." });
|
|
1180
1504
|
return findings;
|
|
1181
1505
|
}
|
|
1182
1506
|
|
|
1183
1507
|
// libs/language-core/explain.json
|
|
1184
1508
|
var explain_default = {
|
|
1185
|
-
$comment: "Fix texts per RV code (`explain.ts` `explainCode`): what the code means in practice and the mechanical fix, for AI assistants (MCP explain_diagnostic / check_code), rivet check and editors. Severity, title and docs URL stay in libs/compiler/codes.json; every code there has an entry here (tests/explain.test.ts). `messages` = the emitted texts ({x} = placeholder); `avoid` triggers the code, `prefer` compiles clean (verified 2026-10-08 against both targets); `quickFix` = the language-core code fix that applies, if any.",
|
|
1509
|
+
$comment: "Fix texts per RV code (`explain.ts` `explainCode`): what the code means in practice and the mechanical fix, for AI assistants (MCP explain_diagnostic / check_code), rivet check and editors. Severity, title and docs URL stay in libs/compiler/codes.json; every code there has an entry here (tests/explain.test.ts). Also the prose of the docs pages /errors/RVxxxx (docs-kit tools/errors.ts \u2192 apps/docs/content/errors/, regenerate after edits). `messages` = the emitted texts ({x} = placeholder); `avoid` triggers the code, `prefer` compiles clean (verified 2026-10-08 against both targets); `quickFix` = the language-core code fix that applies, if any.",
|
|
1186
1510
|
codes: [
|
|
1187
1511
|
{
|
|
1188
1512
|
code: "RV1000",
|
|
@@ -1411,9 +1735,7 @@ var explain_default = {
|
|
|
1411
1735
|
},
|
|
1412
1736
|
{
|
|
1413
1737
|
code: "RV2009",
|
|
1414
|
-
messages: [
|
|
1415
|
-
"Cannot resume {module}: module references `{first}` and `{second}` share the browser name `{name}`; rename one import"
|
|
1416
|
-
],
|
|
1738
|
+
messages: ["Cannot resume {module}: module references `{first}` and `{second}` share the browser name `{name}`; rename one import"],
|
|
1417
1739
|
fix: "Import one of the two values under another exported name (re-export it from a small module) so their module reference names differ.",
|
|
1418
1740
|
avoid: 'import { fmt } from "./a";\nimport { fmt as fmt2 } from "./b";\nexport function A() { return <List items={[fmt, fmt2]} />; }',
|
|
1419
1741
|
prefer: 'import { fmt } from "./a";\nimport { fmtB } from "./b-reexport";\nexport function A() { return <List items={[fmt, fmtB]} />; }',
|
|
@@ -1481,6 +1803,17 @@ var explain_default = {
|
|
|
1481
1803
|
prefer: "export function Count(props: { n: () => number }) {\n return () => <b>{props.n()}</b>;\n}",
|
|
1482
1804
|
note: "Resume mode (RESUME-ACCESSOR-ROW fixes 1\u20132). A warning: the module compiles, but the server traversal calls the accessor outside every resume site, so a resumed page never updates it. Resumable without a warning, in a PascalCase module-level function: `return () => \u2026`, `return c ? () => a : () => b` (also `&&` / `||` / `??`), `const view = () => \u2026; return view`, a hoisted parameterless `function view() {\u2026}` returned by name, concise `(props) => () => \u2026`. Reported for an anonymous default export, a function passed to a call whose result is a PascalCase `const` or the default export (`const Card = memo((props) => () => \u2026)`), and a returned `let` bound to an accessor (declare it with `const`)."
|
|
1483
1805
|
},
|
|
1806
|
+
{
|
|
1807
|
+
code: "RV2016",
|
|
1808
|
+
messages: [
|
|
1809
|
+
"`resource()` cannot resume {form}: create it in the component as `const name = resource(\u2026)` and call its methods on the handle (`name.loading()`)"
|
|
1810
|
+
],
|
|
1811
|
+
fix: "Create the resource in the component body as the whole initializer of one `const` (`const user = resource(() => id(), loadUser)`) and call its methods on that handle (`user.refetch()`, `user.loading()`); pass the handle, not a destructured method, to children and handlers. For a reusable helper, export the fetcher and call `resource(\u2026)` in each component.",
|
|
1812
|
+
avoid: 'import { resource } from "@rivetjs/runtime/dom";\nasync function loadUser(_: true) {\n return "Ada";\n}\nexport function User() {\n const { refetch } = resource(loadUser);\n return <button onClick={() => refetch()}>reload</button>;\n}',
|
|
1813
|
+
prefer: 'import { resource } from "@rivetjs/runtime/dom";\nasync function loadUser(_: true) {\n return "Ada";\n}\nexport function User() {\n const user = resource(loadUser);\n return <button onClick={() => user.refetch()}>{user()}</button>;\n}',
|
|
1814
|
+
note: "Resume mode (RESOURCE-COMPILER). A warning: the module compiles, but the server encodes a resource only through the recipe of the `const` it is created in, so rendering one of these forms resumably throws `resource is not resumable here` once a resumable site captures it. Resumable without a warning: any `const r = resource(\u2026)` in a component (also in `For` rows and `Show` branches), whatever its arguments \u2014 inline arrows, module functions or imports by name (`resource(fetchUser)`, resumed by reference), local `const` fetchers, `initialValue` from props, `ssr: false`; aliased imports (`import { resource as res }`) too. Reported forms: destructuring (`const { loading } = resource(\u2026)` drops the handle its methods need; `const [data] = resource(\u2026)` \u2014 the handle is not iterable), a `let` / `var`, a call that is not a `const` initializer (JSX attribute, object value, argument, returned value, concise arrow body), and a `const` inside a named non-component function (`useUser`: its caller's `const` holds the result, not a recipe). Not reported: module scope, a discarded call, code that runs in the browser only (handlers, effects).",
|
|
1815
|
+
quickFix: 'rivetResourceConst: "Keep the handle: const user = resource(\u2026); use user.member" \u2192 const user = resource(\u2026); destructured reads become user.loading, user.refetch or user (Solid-style [data, { refetch }] too). The same action is "Declare it with const" for a let / var never reassigned and "Create it in the component: const user = resource(\u2026)" for a call outside a const initializer (hoisted before its statement). The handle is named after a loadUser / fetchUser / getUser fetcher, else res; no fix in a hook, for rest / default / nested patterns or for a call inside a nested function'
|
|
1816
|
+
},
|
|
1484
1817
|
{
|
|
1485
1818
|
code: "RV2101",
|
|
1486
1819
|
messages: [
|
|
@@ -1508,6 +1841,48 @@ var explain_default = {
|
|
|
1508
1841
|
note: "Resume mode (Function Bridge T24). A data class declared at module scope of a resume module registers (`_rClass`) and its captured instances resume without running the constructor. Not eligible: an instance `#private` element or `accessor` field (no private brand without the constructor), a class or member decorator (runs code while constructing), a field arrow reading `super` / `new.target` (cannot run outside the class), an `extends` other than a data class of the same module (built-ins, packages, Node `EventEmitter`), a resource read while constructing (`window`, `document`, `navigator`, `globalThis`, `localStorage`, `sessionStorage`, `WebSocket`, `EventSource`, `Worker`, `BroadcastChannel` in a field initializer or the constructor). Classes with an own or inherited `static [revive]` register as resource holders (T25): an instance with `[serialize]()` sends only that data and the browser rebuilds it with `static [revive](data)` on first use; package classes register the same hooks with `registerClass(cls, { id, serialize, revive })` from `@rivetjs/runtime/resume-format`. At render, `[serialize]` without `static [revive]` is `RV2102: {Class} has [serialize] but no static [revive]` (dev and production).",
|
|
1509
1842
|
quickFix: 'rivetReconstruct: "Build it in the browser: reconstruct(() => \u2026)" \u2192 const x = reconstruct(() => new X(\u2026)); reads become x() (+ import from @rivetjs/runtime/dom)'
|
|
1510
1843
|
},
|
|
1844
|
+
{
|
|
1845
|
+
code: "RV2103",
|
|
1846
|
+
messages: [
|
|
1847
|
+
"Render is not deterministic: `{call}` runs while rendering `{Component}`; move it into a keyed signal (its value is shipped) or a clientEffect"
|
|
1848
|
+
],
|
|
1849
|
+
fix: "Compute the value once into state the page ships (`const seed = signal(Math.random())` \u2014 the browser adopts the server value) or read it in a `clientEffect` / handler; `Date.now()`, `Math.random()`, `performance.now()`, `crypto.randomUUID()` / `getRandomValues()` and `new Date()` without arguments are reported in a boundary's render path.",
|
|
1850
|
+
avoid: 'import { signal } from "@rivetjs/reactivity";\nexport function Stamp() {\n const n = signal(0);\n const t = Date.now();\n return <button onClick={() => n.set(t)}>{n()}</button>;\n}',
|
|
1851
|
+
prefer: 'import { signal } from "@rivetjs/reactivity";\nexport function Stamp() {\n const n = signal(0);\n const t = signal(Date.now());\n return <button onClick={() => n.set(t())}>{n()}</button>;\n}',
|
|
1852
|
+
note: 'Derived resume model only (`resume: { model: "derived" }`, plan M2 C2). The browser derives an interactive component\'s structure by running its compiled DOM function over the server nodes (adopt run): the render must be a function of its props, keyed state and route data. A non-deterministic read outside keyed state would make the adopt run disagree with the markup. Handlers, effects, client events and other browser-only closures are not checked (they never run on the server). Error: the module does not compile under the derived model.'
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
code: "RV2104",
|
|
1856
|
+
messages: [
|
|
1857
|
+
"Keyed state `signal()` is created inside a loop: the browser pairs a site's repeated creations with the server's by order, so a loop that runs a different number of times in the adopt run adopts the wrong values; give each iteration its own component (a `<For>` row) or create the state once outside the loop",
|
|
1858
|
+
"Keyed state `createStore()` is created inside a loop: the browser pairs a site's repeated creations with the server's by order, so a loop that runs a different number of times in the adopt run adopts the wrong values; give each iteration its own component (a `<For>` row) or create the state once outside the loop"
|
|
1859
|
+
],
|
|
1860
|
+
fix: "Give each iteration its own component (render a `<For>` row / child component that creates its state) or create one `signal()` / `createStore()` outside the loop holding the list (`const picked = signal(props.items.map(() => false))`).",
|
|
1861
|
+
avoid: 'import { signal } from "@rivetjs/reactivity";\nexport function Checks(props) {\n const picked = [];\n for (const item of props.items) picked.push(signal(false));\n return <ul>{props.items.map((item, i) => <li onClick={() => picked[i].set(true)}>{item}</li>)}</ul>;\n}',
|
|
1862
|
+
prefer: 'import { signal } from "@rivetjs/reactivity";\nexport function Checks(props) {\n const picked = signal(props.items.map(() => false));\n return <ul>{props.items.map((item, i) => <li onClick={() => picked.set(picked().with(i, true))}>{item}</li>)}</ul>;\n}',
|
|
1863
|
+
note: 'Derived resume model only (`resume: { model: "derived" }`, plan M2 C2). Every `signal(x)` / `createStore(x)` call of a resume module compiles to a keyed registration (`_rSig(key, x)`: key = module prefix + source-order site ordinal); the server ships the values per boundary and the adopt run reads them by key. Keys are per site, so state created under an `if` / `?:` / `&&` / `switch` / `try` is fine (a render that skips it skips its key; M2-R5 2026-10-20 narrowed the warning to loops). A site that registers more than once in a boundary gets an occurrence suffix (`#2`, `#3`, \u2026): those pair by order, which a loop makes depend on the iteration count. Warning: the module compiles.'
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
code: "RV2105",
|
|
1867
|
+
messages: [
|
|
1868
|
+
"Keyed `For` row renders non-deterministically: `{call}` runs while rendering the row; a lazy row renders again in the browser, so read it outside the row (loader data, a prop, a signal) or in a handler / clientEffect (the For's rows stay recorded)"
|
|
1869
|
+
],
|
|
1870
|
+
fix: "Compute the value outside the row (in the component body, loader data or a prop) and read that inside the row, or move the read into a handler / `clientEffect`; `Date.now()`, `Date()`, `new Date()` without arguments, `Math.random()`, `performance.now()`, `crypto.randomUUID()` and `crypto.getRandomValues()` are reported on a keyed `For` row's render path.",
|
|
1871
|
+
avoid: 'import { For } from "@rivetjs/runtime/dom";\nexport function Feed(props: { items: { id: string; title: string }[] }) {\n return (\n <ul>\n <For each={props.items} by={(item) => item.id}>\n {(item) => <li data-seen={Date.now()}>{item().title}</li>}\n </For>\n </ul>\n );\n}',
|
|
1872
|
+
prefer: 'import { For } from "@rivetjs/runtime/dom";\nexport function Feed(props: { items: { id: string; title: string }[]; seen: number }) {\n return (\n <ul>\n <For each={props.items} by={(item) => item.id}>\n {(item) => <li data-seen={props.seen}>{item().title}</li>}\n </For>\n </ul>\n );\n}',
|
|
1873
|
+
note: "Resume mode (lazy collection rows, LAZY-ROWS T2). A warning: the module compiles and the `For` keeps today's per-row resume records. A lazy row (the `For`'s row function as a resumable symbol, materialised in the browser per row) must render the same markup in the browser as on the server; a time / random read in the row's render path would not. Handlers, effects, client events, refs and timer callbacks inside the row are not its render and are not checked. Reported once per `For`, on the server compile."
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
code: "RV2106",
|
|
1877
|
+
messages: [
|
|
1878
|
+
"Keyed `For` row holds {site}, which a lazy row cannot replay: move it out of the row or into a component the row renders lazily (the For's rows stay recorded)",
|
|
1879
|
+
"Keyed `For` row holds a `ref` into `{name}`, declared outside the row, which a lazy row cannot replay: keep the ref local to the row or read the element through the event (the For's rows stay recorded)"
|
|
1880
|
+
],
|
|
1881
|
+
fix: "Render the client-only / streaming / portal content outside the row (one instance next to the list), or keep it in a component the row renders; for a ref, declare it inside the row (`{(row) => { const el = ref(); \u2026 }}`) or read the element from the event (`event.currentTarget`).",
|
|
1882
|
+
avoid: 'import { For, ref } from "@rivetjs/runtime/dom";\nexport function Menu(props: { items: { id: string; label: string }[] }) {\n const last = ref<HTMLElement>();\n return (\n <ul onKeyDown={() => last.current?.focus()}>\n <For each={props.items} by={(item) => item.id}>\n {(item) => <li ref={last}>{item().label}</li>}\n </For>\n </ul>\n );\n}',
|
|
1883
|
+
prefer: 'import { For } from "@rivetjs/runtime/dom";\nexport function Menu(props: { items: { id: string; label: string }[] }) {\n return (\n <ul onKeyDown={(event) => (event.currentTarget.lastElementChild as HTMLElement | null)?.focus()}>\n <For each={props.items} by={(item) => item.id}>\n {(item) => <li tabIndex={-1}>{item().label}</li>}\n </For>\n </ul>\n );\n}',
|
|
1884
|
+
note: "Resume mode (lazy collection rows, LAZY-ROWS T2). A warning: the module compiles and the `For` keeps today's per-row resume records. A lazy row is re-run in the browser over its server markup (adopt mode); `clientOnly(\u2026)` / an element of a `clientOnly` component, `<Async>`, a `<Portal>` element and a `ref` into a local declared outside the row cannot be replayed that way, so the whole `For` stays recorded (one rule per `For`). Reported once per `For`, on the server compile."
|
|
1885
|
+
},
|
|
1511
1886
|
{
|
|
1512
1887
|
code: "RV2145",
|
|
1513
1888
|
messages: [
|
|
@@ -1531,7 +1906,7 @@ var explain_default = {
|
|
|
1531
1906
|
code: "RV2148",
|
|
1532
1907
|
messages: ["{file} is owned by {framework} but uses the Rivet runtime ({use}): Rivet code in a framework module is never compiled by Rivet"],
|
|
1533
1908
|
fix: "Move the Rivet code (signals bound to Rivet JSX, `@rivetjs/runtime` helpers) into a Rivet module and import the island from it, or take the file out of the bridge's ownership: drop the framework suffix (`Chart.react.tsx` \u2192 `Chart.tsx`) or narrow the bridge's `include`.",
|
|
1534
|
-
avoid: '// Chart.react.tsx (owned by react())\nimport { Show } from "@rivetjs/runtime";\nexport function Chart() { return <Show when={true}>x</Show>; }',
|
|
1909
|
+
avoid: '// Chart.react.tsx (owned by react())\nimport { Show } from "@rivetjs/runtime/dom";\nexport function Chart() { return <Show when={true}>x</Show>; }',
|
|
1535
1910
|
prefer: '// Chart.react.tsx: React only\nexport function Chart() { return <div>x</div>; }\n// page.tsx (Rivet): <Show when={ready()}><Chart client="visible" /></Show>',
|
|
1536
1911
|
note: "Vite plugin (`rivet({ bridges })`). A module a bridge owns is compiled by that framework's plugins only: an `@rivetjs/runtime` import (or `@jsxImportSource @rivetjs/runtime`) there would run Rivet ABI code the Rivet compiler never produced. `@rivetjs/reactivity` signals are fine inside framework components (read them through the framework's own subscription)."
|
|
1537
1912
|
},
|
|
@@ -1545,7 +1920,9 @@ var explain_default = {
|
|
|
1545
1920
|
},
|
|
1546
1921
|
{
|
|
1547
1922
|
code: "RV2150",
|
|
1548
|
-
messages: [
|
|
1923
|
+
messages: [
|
|
1924
|
+
"Preact useId mask not applied (probe rendered {html}): Preact island ids would not be unique. This Preact version is not supported by @rivetjs/bridge-preact; update it or pin preact to a supported version."
|
|
1925
|
+
],
|
|
1549
1926
|
fix: "Install a Preact version inside `@rivetjs/bridge-preact`'s peer range that the adapter supports (pin `preact` to the last working version), or update `@rivetjs/bridge-preact`.",
|
|
1550
1927
|
avoid: `// package.json
|
|
1551
1928
|
"preact": "^11" // a minor that moved Preact's internal useId mask`,
|
|
@@ -1605,13 +1982,15 @@ var explain_default = {
|
|
|
1605
1982
|
{
|
|
1606
1983
|
code: "RV3007",
|
|
1607
1984
|
messages: [
|
|
1608
|
-
"Declare server exports (
|
|
1609
|
-
"`export *` is not allowed in a route module: server exports must be named"
|
|
1985
|
+
"Declare server exports (`load(\u2026)`, `onGet(\u2026)`, \u2026) in their own export statement",
|
|
1986
|
+
"`export *` is not allowed in a route module: server exports must be named",
|
|
1987
|
+
"Server export wrapper `{name}` must initialize a module-scope `const` (`export const {export} = {name}(\u2026)`)",
|
|
1988
|
+
"Duplicate server export: `{first}` and `{name}` are both `{kind}(\u2026)`; keep one"
|
|
1610
1989
|
],
|
|
1611
|
-
fix: "Give each server export
|
|
1612
|
-
avoid: "
|
|
1613
|
-
prefer: "
|
|
1614
|
-
note: "serverExports option (route modules)."
|
|
1990
|
+
fix: "Give each server export its own `export const x = wrapper(\u2026)` statement at module scope (one per kind; request handlers one per method) and replace `export *` with named exports.",
|
|
1991
|
+
avoid: 'import { load } from "@rivetjs/router";\nexport const loader = load(() => 1), other = 2;\nexport default () => <p />;',
|
|
1992
|
+
prefer: 'import { load } from "@rivetjs/router";\nexport const loader = load(() => 1);\nexport const other = 2;\nexport default () => <p />;',
|
|
1993
|
+
note: "serverExports option (route modules). Wrappers: load, guard, actions, entries, headers, onRequest, onGet \u2026 onOptions from @rivetjs/router (aliases and namespace imports tracked)."
|
|
1615
1994
|
},
|
|
1616
1995
|
{
|
|
1617
1996
|
code: "RV3008",
|
|
@@ -1619,25 +1998,47 @@ var explain_default = {
|
|
|
1619
1998
|
"`{name}` is server-only (removed from browser builds) but browser code references it at line {line}",
|
|
1620
1999
|
'`{name}` (from "{source}") is used by browser code at line {line}; server-only modules cannot reach the browser \u2014 use it only inside server exports'
|
|
1621
2000
|
],
|
|
1622
|
-
fix: "Use server exports and server-only imports only inside server exports; pass their results to the page as
|
|
1623
|
-
avoid: 'import { readFileSync } from "node:fs";\nexport const
|
|
1624
|
-
prefer: 'import { readFileSync } from "node:fs";\nexport const
|
|
2001
|
+
fix: "Use server exports and server-only imports only inside server exports; pass their results to the page as loader data instead.",
|
|
2002
|
+
avoid: 'import { readFileSync } from "node:fs";\nimport { load } from "@rivetjs/router";\nexport const loader = load(() => 1);\nexport default () => <p>{String(readFileSync)}</p>;',
|
|
2003
|
+
prefer: 'import { readFileSync } from "node:fs";\nimport { load } from "@rivetjs/router";\nexport const loader = load(() => readFileSync("a.txt", "utf8"));\nexport default () => <p />;',
|
|
1625
2004
|
note: "First variant lacks a fix hint."
|
|
1626
2005
|
},
|
|
1627
2006
|
{
|
|
1628
2007
|
code: "RV3009",
|
|
1629
2008
|
messages: ["Server-only code cannot render JSX in a resumed route module: return data and render it in the page"],
|
|
1630
|
-
fix: "Return plain data from
|
|
1631
|
-
avoid: "
|
|
1632
|
-
prefer: '
|
|
2009
|
+
fix: "Return plain data from load(\u2026) / onGet(\u2026) exports and render the JSX in the page component.",
|
|
2010
|
+
avoid: 'import { load } from "@rivetjs/router";\nexport const loader = load(() => ({ x: <p /> }));\nexport default () => <p />;',
|
|
2011
|
+
prefer: 'import { load } from "@rivetjs/router";\nexport const loader = load(() => ({ x: "text" }));\nexport default () => <p />;',
|
|
1633
2012
|
note: "serverExports + resume."
|
|
1634
2013
|
},
|
|
2014
|
+
{
|
|
2015
|
+
code: "RV3010",
|
|
2016
|
+
messages: [
|
|
2017
|
+
'Untagged server export `{name}`: route server code is a wrapper call \u2014 `export const {export} = {wrapper}({argument})` with `import { {wrapper} } from "@rivetjs/router"`'
|
|
2018
|
+
],
|
|
2019
|
+
fix: "Wrap the export in its @rivetjs/router wrapper call: serverLoad \u2192 export const loader = load(\u2026), serverGuard \u2192 access = guard(\u2026), serverActions \u2192 forms = actions({ \u2026 }), serverEntries \u2192 paths = entries(\u2026), serverHeaders \u2192 responseHeaders = headers(\u2026), onRequest \u2192 middleware = onRequest(\u2026), onGet \u2192 GET = onGet(\u2026) (same for onPost \u2026 onOptions). The export name is free; `rivet migrate server-exports` rewrites a whole app.",
|
|
2020
|
+
avoid: "export const serverLoad = () => ({ n: 1 });\nexport default () => <p />;",
|
|
2021
|
+
prefer: 'import { load } from "@rivetjs/router";\nexport const loader = load(() => ({ n: 1 }));\nexport default () => <p />;',
|
|
2022
|
+
quickFix: "rivetWrapServerExport: Wrap in `load(\u2026)` (renames the export and its in-file references, adds the import).",
|
|
2023
|
+
note: "serverExports option (route modules) and the language service (files under the manifest routesDir). Sibling *.server.ts names (load, GET \u2026) without wrappers fail at the first request instead (server/module.ts)."
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
code: "RV3011",
|
|
2027
|
+
messages: [
|
|
2028
|
+
"{chain} (server-only), rendered by {routes} (client-navigated page route[s])",
|
|
2029
|
+
"{chain} (server-only), rendered by the layout of every page under {path} (client-rendered on navigation and resume)"
|
|
2030
|
+
],
|
|
2031
|
+
fix: 'Pass the data to the component as props (inlined at build time, or read in the page\'s `load(\u2026)`), keep server-only modules out of components, or make the page a document route with `import "@rivetjs/router/server-only"` in the page module. Layouts and error pages cannot be document routes: read the data in their `load(\u2026)`. `rivetRouter({ serverOnly: { reach: "warn" } })` turns the page case into a warning (those pages render as document routes).',
|
|
2032
|
+
avoid: '// src/components/Card.tsx\nimport { rows } from "../lib/rows.server.ts";\nexport const Card = () => <p>{rows.length}</p>;',
|
|
2033
|
+
prefer: "// src/components/Card.tsx\nexport const Card = (props: { count: number }) => <p>{props.count}</p>;\n// src/routes/index.tsx: read rows.server.ts in load(\u2026), render <Card count={\u2026} />",
|
|
2034
|
+
note: "Router build (`@rivetjs/router/vite`, configResolved graph scan); TS-side text (not Rust). Server-only = `@rivetjs/router/server-only` marker, `*.server.*` files, `rivetRouter({ serverOnly })` / `rivet({ serverOnly })` patterns, plugin-declared ids (`api.rivetServerOnly`). Dev: overlay + terminal on page load. Pages that reach one only through their own module / non-component helpers become document routes instead (no diagnostic)."
|
|
2035
|
+
},
|
|
1635
2036
|
{
|
|
1636
2037
|
code: "RV3101",
|
|
1637
2038
|
messages: ['No route matches "{path}" (routes from {routesDir ?? "the router"}).'],
|
|
1638
2039
|
fix: "Point the Link at an existing route path (check src/routes), or add the missing route file.",
|
|
1639
|
-
avoid: 'import { Link } from "@rivetjs/router";\nexport const A = () => <Link to="/abuot">About</Link>;',
|
|
1640
|
-
prefer: 'import { Link } from "@rivetjs/router";\nexport const A = () => <Link to="/about">About</Link>;',
|
|
2040
|
+
avoid: 'import { Link } from "@rivetjs/router/client";\nexport const A = () => <Link to="/abuot">About</Link>;',
|
|
2041
|
+
prefer: 'import { Link } from "@rivetjs/router/client";\nexport const A = () => <Link to="/about">About</Link>;',
|
|
1641
2042
|
note: "Language service only; warning; TS-side text (not Rust). Could suggest nearest route."
|
|
1642
2043
|
},
|
|
1643
2044
|
{
|
|
@@ -1792,9 +2193,11 @@ var explain_default = {
|
|
|
1792
2193
|
"Invalid compiler option `styles.layer`: expected a string, null or false",
|
|
1793
2194
|
"Invalid compiler option `resume.module`: expected a string",
|
|
1794
2195
|
"Invalid compiler option `resume`: expected `{ module?: string }`, true or false",
|
|
2196
|
+
'Invalid compiler option `resume.model`: expected "serialized" or "derived"',
|
|
2197
|
+
"Invalid compiler option `resume.{key}`: expected an array of component names",
|
|
1795
2198
|
'Invalid compiler option `targets`: unknown target "{bad}"'
|
|
1796
2199
|
],
|
|
1797
|
-
fix: 'Pass the option in the documented shape (e.g. resume: { module: "src/a.tsx" } or true; styles: { scope: string }).',
|
|
2200
|
+
fix: 'Pass the option in the documented shape (e.g. resume: { module: "src/a.tsx" } or true, resume.model: "serialized" or "derived", resume.boundaries / resume.independent: arrays of component names; styles: { scope: string }).',
|
|
1798
2201
|
avoid: "check(src, { resume: 5 })",
|
|
1799
2202
|
prefer: 'check(src, { resume: { module: "src/a.tsx" } })',
|
|
1800
2203
|
note: "Integration error; no source range."
|
|
@@ -1873,7 +2276,7 @@ function createLanguageCore({ compiler, manifests = new ManifestCache() }) {
|
|
|
1873
2276
|
},
|
|
1874
2277
|
project: (fileName) => manifests.find(fileName),
|
|
1875
2278
|
routes: (fileName) => manifests.find(fileName)?.manifest.routes ?? [],
|
|
1876
|
-
routeFile: (project, route) => (0,
|
|
2279
|
+
routeFile: (project, route) => (0, import_node_path6.resolve)(project.root, route.file)
|
|
1877
2280
|
};
|
|
1878
2281
|
}
|
|
1879
2282
|
function analyzeFile(ts, core, program, file, options = {}) {
|
|
@@ -1893,6 +2296,7 @@ function analyzeFile(ts, core, program, file, options = {}) {
|
|
|
1893
2296
|
diagnostics = [...diagnostics, ...serializabilityDiagnostics(ts, checker, file, result.sites, verdict)];
|
|
1894
2297
|
}
|
|
1895
2298
|
const manifest = result.project?.manifest;
|
|
2299
|
+
if (manifest && isRouteModule(result.project.root, manifest.routesDir, file.fileName)) diagnostics = [...diagnostics, ...untaggedServerExports(ts, file)];
|
|
1896
2300
|
if (manifest?.routes)
|
|
1897
2301
|
diagnostics = [
|
|
1898
2302
|
...diagnostics,
|
|
@@ -1912,7 +2316,7 @@ function analyzeFile(ts, core, program, file, options = {}) {
|
|
|
1912
2316
|
// libs/typescript-plugin/index.ts
|
|
1913
2317
|
var CODE = (code) => 7e4 + Number(code.slice(2));
|
|
1914
2318
|
var FIX_NUMBERS = new Set(FIXABLE_CODES.map(CODE));
|
|
1915
|
-
var bundledWasm = () => typeof __dirname === "string" ? (0,
|
|
2319
|
+
var bundledWasm = () => typeof __dirname === "string" ? (0, import_node_path7.join)(__dirname, "rivet_compiler.wasm") : null;
|
|
1916
2320
|
var cores = /* @__PURE__ */ new Map();
|
|
1917
2321
|
function loadCore(wasm, log) {
|
|
1918
2322
|
let mtime;
|
|
@@ -2180,20 +2584,20 @@ function init({ typescript: ts }) {
|
|
|
2180
2584
|
* the `.rivet/` of each app (Vite root) the project's Rivet files belong to, and the tsconfig's own. */
|
|
2181
2585
|
getExternalFiles(project) {
|
|
2182
2586
|
if (project.getCompilerOptions().jsxImportSource !== "@rivetjs/runtime" || project.projectKind !== ts.server.ProjectKind.Configured) return [];
|
|
2183
|
-
const roots = /* @__PURE__ */ new Set([(0,
|
|
2587
|
+
const roots = /* @__PURE__ */ new Set([(0, import_node_path7.dirname)(project.getProjectName())]);
|
|
2184
2588
|
const byDir = /* @__PURE__ */ new Map();
|
|
2185
2589
|
for (const name of project.getRootFiles() ?? []) {
|
|
2186
2590
|
if (!/\.[jt]sx$/.test(name) || /[/\\]node_modules[/\\]/.test(name)) continue;
|
|
2187
|
-
const dir = (0,
|
|
2591
|
+
const dir = (0, import_node_path7.dirname)(name);
|
|
2188
2592
|
let root = byDir.get(dir);
|
|
2189
2593
|
if (root === void 0) byDir.set(dir, root = viteRoot(name));
|
|
2190
2594
|
if (root) roots.add(root);
|
|
2191
2595
|
}
|
|
2192
2596
|
return [...roots].flatMap((root) => {
|
|
2193
|
-
const dir = (0,
|
|
2597
|
+
const dir = (0, import_node_path7.join)(root, ".rivet");
|
|
2194
2598
|
if (!(0, import_node_fs5.existsSync)(dir)) return [];
|
|
2195
2599
|
try {
|
|
2196
|
-
return (0, import_node_fs5.readdirSync)(dir).filter((name) => name.endsWith(".d.ts")).map((name) => (0,
|
|
2600
|
+
return (0, import_node_fs5.readdirSync)(dir).filter((name) => name.endsWith(".d.ts")).map((name) => (0, import_node_path7.join)(dir, name));
|
|
2197
2601
|
} catch {
|
|
2198
2602
|
return [];
|
|
2199
2603
|
}
|
package/dist/rivet_compiler.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivetjs/typescript-plugin",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "TypeScript language service plugin: Rivet compiler diagnostics, quick fixes, resume hints and route links in TS editors.",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@rivetjs/language-core": "0.1.0-alpha.
|
|
9
|
+
"@rivetjs/language-core": "0.1.0-alpha.1"
|
|
10
10
|
},
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"typescript": ">=5.6"
|