@stateward/plugin-errors 0.0.0-stage → 1.0.1-rc.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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -2
  3. package/dist/async-handler-unhandled.d.ts +7 -0
  4. package/dist/async-handler-unhandled.js +342 -0
  5. package/dist/catch-hides-unexpected.d.ts +6 -0
  6. package/dist/catch-hides-unexpected.js +260 -0
  7. package/dist/catch-misses-error.d.ts +6 -0
  8. package/dist/catch-misses-error.js +208 -0
  9. package/dist/catch-without-intent.d.ts +10 -0
  10. package/dist/catch-without-intent.js +590 -0
  11. package/dist/exhaustive-catch.d.ts +6 -0
  12. package/dist/exhaustive-catch.js +190 -0
  13. package/dist/exhaustive-fallback.d.ts +6 -0
  14. package/dist/exhaustive-fallback.js +157 -0
  15. package/dist/finally-can-throw.d.ts +6 -0
  16. package/dist/finally-can-throw.js +184 -0
  17. package/dist/finally-may-throw.d.ts +6 -0
  18. package/dist/finally-may-throw.js +184 -0
  19. package/dist/hover.d.ts +11 -0
  20. package/dist/hover.js +62 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.js +2 -0
  23. package/dist/inference/builtins.d.ts +52 -0
  24. package/dist/inference/builtins.js +179 -0
  25. package/dist/inference/catches.d.ts +9 -0
  26. package/dist/inference/catches.js +33 -0
  27. package/dist/inference/deps.d.ts +12 -0
  28. package/dist/inference/deps.js +79 -0
  29. package/dist/inference/format.d.ts +25 -0
  30. package/dist/inference/format.js +72 -0
  31. package/dist/inference/packages.d.ts +40 -0
  32. package/dist/inference/packages.js +723 -0
  33. package/dist/inference/project.d.ts +8 -0
  34. package/dist/inference/project.js +22 -0
  35. package/dist/inference/solve.d.ts +128 -0
  36. package/dist/inference/solve.js +575 -0
  37. package/dist/inference/summarize.d.ts +50 -0
  38. package/dist/inference/summarize.js +1324 -0
  39. package/dist/inference/summary.d.ts +191 -0
  40. package/dist/inference/summary.js +237 -0
  41. package/dist/inlay.d.ts +19 -0
  42. package/dist/inlay.js +168 -0
  43. package/dist/must-handle-error.d.ts +55 -0
  44. package/dist/must-handle-error.js +794 -0
  45. package/dist/narrow-try.d.ts +52 -0
  46. package/dist/narrow-try.js +641 -0
  47. package/dist/no-swallow.d.ts +8 -0
  48. package/dist/no-swallow.js +320 -0
  49. package/dist/plugin.d.ts +116 -0
  50. package/dist/plugin.js +39 -0
  51. package/dist/react/boundaries.d.ts +83 -0
  52. package/dist/react/boundaries.js +371 -0
  53. package/dist/react/fallbacks.d.ts +12 -0
  54. package/dist/react/fallbacks.js +83 -0
  55. package/dist/react/render-guards.d.ts +24 -0
  56. package/dist/react/render-guards.js +271 -0
  57. package/dist/react/tree.d.ts +98 -0
  58. package/dist/react/tree.js +863 -0
  59. package/dist/refactors.d.ts +15 -0
  60. package/dist/refactors.js +69 -0
  61. package/dist/render-throw-without-boundary.d.ts +11 -0
  62. package/dist/render-throw-without-boundary.js +223 -0
  63. package/dist/render-throws-without-boundary.d.ts +6 -0
  64. package/dist/render-throws-without-boundary.js +151 -0
  65. package/dist/result-ignored.d.ts +6 -0
  66. package/dist/result-ignored.js +167 -0
  67. package/dist/result-type.d.ts +18 -0
  68. package/dist/result-type.js +55 -0
  69. package/dist/result-unchecked.d.ts +2 -0
  70. package/dist/result-unchecked.js +746 -0
  71. package/dist/rethrow-unknown.d.ts +6 -0
  72. package/dist/rethrow-unknown.js +260 -0
  73. package/dist/rules.meta.d.ts +214 -0
  74. package/dist/rules.meta.js +212 -0
  75. package/dist/syntax.d.ts +41 -0
  76. package/dist/syntax.js +171 -0
  77. package/dist/text.d.ts +22 -0
  78. package/dist/text.js +43 -0
  79. package/dist/throw-in-hook.d.ts +8 -0
  80. package/dist/throw-in-hook.js +510 -0
  81. package/dist/throws-doc-mismatch.d.ts +7 -0
  82. package/dist/throws-doc-mismatch.js +188 -0
  83. package/dist/throws-matches-doc.d.ts +7 -0
  84. package/dist/throws-matches-doc.js +188 -0
  85. package/package.json +34 -3
@@ -0,0 +1,371 @@
1
+ import { propagate } from "@stateward/core";
2
+ import { summarizeOnce } from "../inference/project.js";
3
+ import { analyse, evaluated } from "../inference/solve.js";
4
+ import { className, unknownText } from "../inference/format.js";
5
+ import { fieldsOf } from "../inference/summary.js";
6
+ import { ALL_GUARDS } from "./render-guards.js";
7
+ import { LIBRARY_BOUNDARIES, readTree, summarizeTree } from "./tree.js";
8
+ /** The rules whose summaries hold a render tree; the first that ran is read. */
9
+ export const TREE_RULES = [
10
+ "errors/render-throw-without-boundary",
11
+ "errors/catch-misses-error",
12
+ ];
13
+ /**
14
+ * The last file's trees by program, then by the boundaries the rule's config adds (as JSON): the
15
+ * pack's two React rules summarize the same file one after the other. Only the last file is
16
+ * kept, as in `summarizeOnce`.
17
+ */
18
+ const last = new WeakMap();
19
+ export function summarizeReact(ctx, libraryBoundaries = LIBRARY_BOUNDARIES, skips = ALL_GUARDS) {
20
+ let known = last.get(ctx.types);
21
+ if (known?.sourceFile !== ctx.sourceFile) {
22
+ known = { sourceFile: ctx.sourceFile, trees: new Map() };
23
+ last.set(ctx.types, known);
24
+ }
25
+ const key = JSON.stringify([libraryBoundaries, skips.contextGuards, skips.inputGuards]);
26
+ let tree = known.trees.get(key);
27
+ if (tree === undefined) {
28
+ tree = summarizeTree(ctx, libraryBoundaries, skips);
29
+ known.trees.set(key, tree);
30
+ }
31
+ return { throws: summarizeOnce(ctx), tree };
32
+ }
33
+ /** What `nearest` answers for a render stateward cannot follow to where it is placed. */
34
+ const UNFOLLOWED = "?";
35
+ const models = new WeakMap();
36
+ const throwsMaps = new WeakMap();
37
+ /** The throws part of each summary: the same facts `catch-misses-error` solves. */
38
+ export function throwsOf(summaries) {
39
+ let known = throwsMaps.get(summaries);
40
+ if (known === undefined) {
41
+ const made = new Map();
42
+ for (const [file, value] of summaries) {
43
+ const throws = fieldsOf(value)?.throws;
44
+ if (throws !== undefined)
45
+ made.set(file, throws);
46
+ }
47
+ known = made;
48
+ throwsMaps.set(summaries, known);
49
+ }
50
+ return known;
51
+ }
52
+ function compareSites(a, b) {
53
+ if (a.file !== b.file)
54
+ return a.file < b.file ? -1 : 1;
55
+ return a.render.at[0] - b.render.at[0];
56
+ }
57
+ function stepOf(text) {
58
+ return text.endsWith("(…)") ? text.slice(0, -"(…)".length) : text;
59
+ }
60
+ /** The pack's view of the app's render tree, built once per summaries map. */
61
+ export function modelOf(summaries) {
62
+ const known = models.get(summaries);
63
+ if (known !== undefined)
64
+ return known;
65
+ const analysis = analyse(throwsOf(summaries));
66
+ const functions = new Map();
67
+ const boundaries = new Map();
68
+ const byFile = new Map();
69
+ const entries = [];
70
+ const sites = [];
71
+ for (const file of [...summaries.keys()].sort()) {
72
+ const value = summaries.get(file);
73
+ const tree = value === undefined ? undefined : readTree(fieldsOf(value)?.tree);
74
+ if (tree === undefined)
75
+ continue;
76
+ for (const fn of tree.functions)
77
+ functions.set(fn.id, { file, fn });
78
+ for (const element of tree.boundaries)
79
+ boundaries.set(element.key, { file, element });
80
+ byFile.set(file, tree.boundaries);
81
+ for (const render of tree.entries)
82
+ entries.push({ to: render.target, file, render });
83
+ }
84
+ for (const { file, fn } of functions.values()) {
85
+ for (const render of fn.renders)
86
+ sites.push({ to: render.target, file, render });
87
+ }
88
+ sites.sort(compareSites);
89
+ const edgesOf = new Map();
90
+ for (const { file, fn } of functions.values()) {
91
+ edgesOf.set(fn.id, fn.renders
92
+ .filter((render) => functions.has(render.target))
93
+ .map((render) => ({ to: render.target, file, render })));
94
+ }
95
+ const edges = (id) => edgesOf.get(id) ?? [];
96
+ const nearest = (site) => {
97
+ // Not a key of any boundary: nothing is reported for it, and no boundary counts it.
98
+ if (site.render.unknown)
99
+ return UNFOLLOWED;
100
+ for (const one of site.render.around) {
101
+ if ("boundary" in one)
102
+ return one.boundary;
103
+ const inner = functions.get(one.via)?.fn.children?.[0];
104
+ if (inner !== undefined)
105
+ return inner;
106
+ }
107
+ return undefined;
108
+ };
109
+ const effectsOf = (id) => {
110
+ const found = [];
111
+ const seen = new Set();
112
+ const visit = (one) => {
113
+ if (seen.has(one))
114
+ return;
115
+ seen.add(one);
116
+ const entry = functions.get(one);
117
+ if (entry === undefined)
118
+ return;
119
+ for (const start of entry.fn.effects)
120
+ found.push({ file: entry.file, start });
121
+ for (const hook of entry.fn.hooks)
122
+ visit(hook);
123
+ };
124
+ visit(id);
125
+ return found;
126
+ };
127
+ /**
128
+ * Whether every way function `fnId` throws `cls` is a guard: a `throw` its tree summary lists
129
+ * as one, or a call of a function that throws it only from guards. A `@throws` tag, a package
130
+ * or a built-in that throws it is not.
131
+ */
132
+ const guardOnly = (fnId, cls, seen) => {
133
+ const fact = analysis.functions.get(fnId)?.fact;
134
+ if (fact === undefined || seen.has(fnId))
135
+ return false;
136
+ seen.add(fnId);
137
+ if (fact.doc?.entries.some((entry) => entry.id === cls) === true)
138
+ return false;
139
+ const guards = functions.get(fnId)?.fn.guards ?? [];
140
+ let found = false;
141
+ const walk = (effects) => {
142
+ for (const effect of effects) {
143
+ switch (effect.k) {
144
+ case "class":
145
+ if (effect.id !== cls)
146
+ break;
147
+ if (effect.text !== null || !guards.includes(effect.at[0]))
148
+ return false;
149
+ found = true;
150
+ break;
151
+ case "call":
152
+ if (analysis.results.get(effect.id)?.throws.classes.has(cls) !== true)
153
+ break;
154
+ if (!guardOnly(effect.id, cls, seen))
155
+ return false;
156
+ found = true;
157
+ break;
158
+ case "package":
159
+ if (effect.facts[effect.part].classes.includes(cls))
160
+ return false;
161
+ break;
162
+ case "maybe":
163
+ if (!walk(effect.body))
164
+ return false;
165
+ break;
166
+ case "try": {
167
+ const handler = effect.handler;
168
+ const parts = [
169
+ effect.body,
170
+ effect.fin,
171
+ ...(handler === null
172
+ ? []
173
+ : [
174
+ handler.pre,
175
+ ...handler.branches.map((branch) => branch.body),
176
+ handler.otherwise ?? [],
177
+ handler.after,
178
+ ]),
179
+ ];
180
+ if (!parts.every(walk))
181
+ return false;
182
+ break;
183
+ }
184
+ case "unknown":
185
+ case "rethrow":
186
+ break;
187
+ default: {
188
+ const unreachable = effect;
189
+ return unreachable;
190
+ }
191
+ }
192
+ }
193
+ return true;
194
+ };
195
+ return walk(fact.body) && found;
196
+ };
197
+ const owns = new Map();
198
+ const own = (id) => {
199
+ const cached = owns.get(id);
200
+ if (cached !== undefined)
201
+ return cached;
202
+ const entry = functions.get(id);
203
+ const classes = new Map();
204
+ const unknowns = new Map();
205
+ let more = false;
206
+ const guarded = new Set();
207
+ const add = (key, inEffect, file, name) => {
208
+ const fact = analysis.functions.get(key);
209
+ if (fact === undefined)
210
+ return;
211
+ const thrown = evaluated(fact, analysis).body.throws;
212
+ // A class an effect throws is a render failure, whatever the body's guards throw.
213
+ if (inEffect)
214
+ for (const cls of thrown.classes.keys())
215
+ guarded.delete(cls);
216
+ for (const [cls, info] of thrown.classes) {
217
+ if (classes.get(cls)?.certain === true)
218
+ continue;
219
+ const location = info.via === null
220
+ ? { file, start: fact.fact.at[0], end: fact.fact.at[1] }
221
+ : { file: info.via.file, start: info.via.start, end: info.via.end };
222
+ const step = info.via === null ? (inEffect ? "an effect" : null) : stepOf(info.via.text);
223
+ classes.set(cls, {
224
+ certain: info.certain,
225
+ step,
226
+ location,
227
+ what: info.via === null
228
+ ? inEffect
229
+ ? "this effect throws"
230
+ : `\`${name}\` throws`
231
+ : `\`${info.via.text}\` can throw`,
232
+ });
233
+ }
234
+ for (const [k, source] of thrown.unknowns)
235
+ unknowns.set(k, source);
236
+ more ||= thrown.more;
237
+ };
238
+ if (entry !== undefined) {
239
+ const ids = entry.fn.kind === "class" ? entry.fn.methods : [entry.fn.id];
240
+ for (const one of ids)
241
+ add(one, false, entry.file, entry.fn.name);
242
+ if (entry.fn.kind !== "class") {
243
+ for (const cls of classes.keys())
244
+ if (guardOnly(id, cls, new Set()))
245
+ guarded.add(cls);
246
+ }
247
+ for (const effect of effectsOf(id)) {
248
+ add(`${effect.file}#@${String(effect.start)}`, true, effect.file, entry.fn.name);
249
+ }
250
+ }
251
+ const made = {
252
+ classes,
253
+ guarded,
254
+ unknowns: [...unknowns.values()].sort((a, b) => a.file !== b.file ? (a.file < b.file ? -1 : 1) : a.start - b.start),
255
+ more,
256
+ };
257
+ owns.set(id, made);
258
+ return made;
259
+ };
260
+ const ids = [...functions.keys()].sort();
261
+ const { results } = propagate({
262
+ nodes: ids,
263
+ successors: (id) => edges(id)
264
+ .filter((site) => nearest(site) === undefined)
265
+ .map((site) => site.to),
266
+ input: (id) => id,
267
+ sameInput: (a, b) => a === b,
268
+ compute: (id, resultOf) => {
269
+ const found = new Set();
270
+ const mine = own(id);
271
+ for (const [cls, brought] of mine.classes) {
272
+ if (brought.certain && !mine.guarded.has(cls))
273
+ found.add(cls);
274
+ }
275
+ for (const site of edges(id)) {
276
+ if (nearest(site) !== undefined)
277
+ continue;
278
+ for (const cls of resultOf(site.to) ?? [])
279
+ found.add(cls);
280
+ }
281
+ return found;
282
+ },
283
+ initial: new Set(),
284
+ same: (a, b) => a.size === b.size && [...a].every((cls) => b.has(cls)),
285
+ });
286
+ const needs = (id) => results.get(id) ?? new Set();
287
+ const caught = new Map();
288
+ const catches = (key) => {
289
+ const cached = caught.get(key);
290
+ if (cached !== undefined)
291
+ return cached;
292
+ const classes = new Map();
293
+ const labels = new Map();
294
+ const unknowns = new Map();
295
+ let more = false;
296
+ const seen = new Set();
297
+ const visit = (id) => {
298
+ if (seen.has(id))
299
+ return;
300
+ seen.add(id);
301
+ const entry = functions.get(id);
302
+ if (entry === undefined)
303
+ return;
304
+ const mine = own(id);
305
+ for (const [cls, brought] of mine.classes) {
306
+ const before = classes.get(cls);
307
+ if (before !== undefined && (before.certain || !brought.certain))
308
+ continue;
309
+ classes.set(cls, brought);
310
+ const name = `<${entry.fn.name}>`;
311
+ labels.set(cls, brought.step === null ? name : `${name} → ${brought.step}`);
312
+ }
313
+ for (const source of mine.unknowns) {
314
+ unknowns.set(`${source.file}:${String(source.start)}:${source.reason}`, source);
315
+ }
316
+ more ||= mine.more;
317
+ for (const site of edges(id))
318
+ if (nearest(site) === undefined)
319
+ visit(site.to);
320
+ };
321
+ for (const site of [...entries, ...sites]) {
322
+ if (nearest(site) === key)
323
+ visit(site.to);
324
+ }
325
+ const made = { classes, labels, unknowns: [...unknowns.values()], more };
326
+ caught.set(key, made);
327
+ return made;
328
+ };
329
+ const model = {
330
+ analysis,
331
+ functions,
332
+ boundaries,
333
+ byFile,
334
+ entries,
335
+ sites: [...entries, ...sites],
336
+ edges,
337
+ nearest,
338
+ own,
339
+ needs,
340
+ catches,
341
+ };
342
+ models.set(summaries, model);
343
+ return model;
344
+ }
345
+ /** The line every boundary hover ends with: what no boundary ever catches. */
346
+ export const NOT_CAUGHT = "not caught here: errors in event handlers and async code";
347
+ /**
348
+ * On a boundary's tag: `catches: NotFoundError (via <UserPage> → useUser) | others stateward
349
+ * cannot see (from …)`, then `NOT_CAUGHT`. Undefined when `offset` is on no boundary's tag.
350
+ */
351
+ export function boundaryLines(model, file, offset) {
352
+ const element = model.byFile.get(file)?.find((one) => offset >= one.at[0] && offset < one.at[1]);
353
+ if (element === undefined)
354
+ return undefined;
355
+ const caught = model.catches(element.key);
356
+ const names = [...caught.classes.keys()]
357
+ .map((id) => {
358
+ const label = caught.labels.get(id);
359
+ const name = className(id, model.analysis.classes);
360
+ return label === undefined ? name : `${name} (via ${label})`;
361
+ })
362
+ .sort();
363
+ const parts = caught.unknowns.length > 0 || caught.more
364
+ ? [...names, unknownText(caught.unknowns, caught.more)]
365
+ : names;
366
+ return [
367
+ parts.length === 0 ? "catches nothing stateward can see" : `catches: ${parts.join(" | ")}`,
368
+ NOT_CAUGHT,
369
+ ];
370
+ }
371
+ //# sourceMappingURL=boundaries.js.map
@@ -0,0 +1,12 @@
1
+ import type { Location, RelatedLocation } from "@stateward/core";
2
+ import type { Model } from "./boundaries.js";
3
+ type Problem = {
4
+ message: string;
5
+ hint: string;
6
+ location: Location;
7
+ related: readonly RelatedLocation[];
8
+ };
9
+ /** Reports each fallback that leaves out a class its boundary can catch. */
10
+ export declare function checkFallbacks(model: Model, ignored: readonly string[], report: (problem: Problem) => void): void;
11
+ export {};
12
+ //# sourceMappingURL=fallbacks.d.ts.map
@@ -0,0 +1,83 @@
1
+ import { matchesName } from "@stateward/core";
2
+ import { catchName, className } from "../inference/format.js";
3
+ import { relation } from "../inference/solve.js";
4
+ import { codeList, fitted, withExample } from "../text.js";
5
+ /**
6
+ * Error boundary fallbacks, checked as catches (`errors/catch-misses-error`; they were
7
+ * `errors/exhaustive-fallback` in earlier releases): a fallback that branches on the error with
8
+ * `instanceof` handles every class its boundary can catch, or has a branch for the rest.
9
+ */
10
+ /** What the fallback does with any other error, in the example: what it returns or does. */
11
+ function restLine(kind, variable) {
12
+ switch (kind) {
13
+ case "FallbackComponent":
14
+ case "fallbackRender":
15
+ return `return <p>Something went wrong: {String(${variable})}</p>;`;
16
+ case "onError":
17
+ return `console.error(${variable});`;
18
+ case "getDerivedStateFromError":
19
+ return `return { error: ${variable} };`;
20
+ default: {
21
+ const unreachable = kind;
22
+ return unreachable;
23
+ }
24
+ }
25
+ }
26
+ function hint(fallback, chain, handler, count) {
27
+ const line = `add a branch for ${count === 1 ? "it" : "each"}, or end with one for any other error:`;
28
+ const rest = restLine(fallback.kind, chain.variable);
29
+ return chain.dispatch === "switch"
30
+ ? withExample(line, [
31
+ `case ${chain.variable} instanceof ${handler}:`,
32
+ " …",
33
+ "default:",
34
+ ` ${rest}`,
35
+ ])
36
+ : withExample(line, [`if (${chain.variable} instanceof ${handler}) {`, " …", "}", rest]);
37
+ }
38
+ /** Reports each fallback that leaves out a class its boundary can catch. */
39
+ export function checkFallbacks(model, ignored, report) {
40
+ const classes = model.analysis.classes;
41
+ for (const [key, { file, element }] of model.boundaries) {
42
+ for (const fallback of element.fallbacks) {
43
+ const chain = fallback.chain;
44
+ if (chain === null || chain.generic || !chain.exact)
45
+ continue;
46
+ const handled = [];
47
+ let readable = true;
48
+ for (const branch of chain.branches) {
49
+ if (branch === null)
50
+ readable = false;
51
+ else
52
+ handled.push(...branch);
53
+ }
54
+ if (!readable)
55
+ continue;
56
+ const caught = model.catches(key);
57
+ const missing = [...caught.classes]
58
+ .filter(([id, brought]) => brought.certain &&
59
+ !matchesName(catchName(id, classes), ignored) &&
60
+ handled.every((one) => relation(one, id, classes) === "no"))
61
+ .map(([id, brought]) => ({ id, brought, name: className(id, classes) }))
62
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
63
+ const [first] = missing;
64
+ if (first === undefined)
65
+ continue;
66
+ const related = missing.slice(0, 3).map(({ brought, name }) => ({
67
+ location: brought.location,
68
+ message: `${brought.what} \`${name}\``,
69
+ }));
70
+ related.push({
71
+ location: { file: chain.file, start: chain.at[0], end: chain.at[1] },
72
+ message: `\`${fallback.name}\` branches on the error here`,
73
+ });
74
+ report({
75
+ message: fitted((cut) => `\`${cut(fallback.name)}\` does not handle ${codeList(missing.map((one) => one.name), cut)}, which this boundary can catch`),
76
+ hint: hint(fallback, chain, catchName(first.id, classes), missing.length),
77
+ location: { file, start: fallback.at[0], end: fallback.at[1] },
78
+ related,
79
+ });
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=fallbacks.js.map
@@ -0,0 +1,24 @@
1
+ import ts from "@stateward/core/typescript";
2
+ import type { FileContext } from "@stateward/core";
3
+ /**
4
+ * The throws while rendering that `errors/render-throw-without-boundary` leaves out (they were
5
+ * `errors/throw-in-hook`'s skips in earlier releases): the usual check in a context hook
6
+ * (`if (!ctx) throw new Error("useAuth must be used within AuthProvider")`), and a check on what
7
+ * the caller passed (`if (!render && !children) throw …`). Both name a programming mistake of the
8
+ * caller; an error boundary is not what handles them.
9
+ */
10
+ type RenderFunction = ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression;
11
+ /** Which guards to leave out. */
12
+ export type GuardSkips = {
13
+ readonly contextGuards: boolean;
14
+ readonly inputGuards: boolean;
15
+ };
16
+ /** Both left out: the default. */
17
+ export declare const ALL_GUARDS: GuardSkips;
18
+ /**
19
+ * The start of the thrown expression of every guard `throw` in the render function's own body
20
+ * (not in a nested function), as the throws summary records it (`new Error(…)`).
21
+ */
22
+ export declare function guardThrows(fn: RenderFunction, ctx: FileContext, skips: GuardSkips): number[];
23
+ export {};
24
+ //# sourceMappingURL=render-guards.d.ts.map