@esportsplus/typescript 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +91 -0
  2. package/bin/tsc-lsp +3 -0
  3. package/build/cli/diagnostics.d.ts +5 -1
  4. package/build/cli/diagnostics.js +13 -8
  5. package/build/cli/tsc.d.ts +3 -1
  6. package/build/cli/tsc.js +77 -87
  7. package/build/compiler/coordinator.d.ts +1 -2
  8. package/build/compiler/coordinator.js +33 -22
  9. package/build/compiler/imports.d.ts +2 -0
  10. package/build/compiler/imports.js +9 -2
  11. package/build/compiler/language-service.d.ts +15 -4
  12. package/build/compiler/language-service.js +56 -24
  13. package/build/compiler/plugins/vite.js +7 -5
  14. package/build/compiler/sourcemap.d.ts +3 -1
  15. package/build/compiler/sourcemap.js +23 -5
  16. package/build/jsonc.d.ts +2 -0
  17. package/build/jsonc.js +85 -0
  18. package/build/lsp/bin.d.ts +1 -0
  19. package/build/lsp/bin.js +2 -0
  20. package/build/lsp/diagnostics.d.ts +10 -0
  21. package/build/lsp/diagnostics.js +69 -0
  22. package/build/lsp/index.d.ts +2 -0
  23. package/build/lsp/index.js +2 -0
  24. package/build/lsp/server.d.ts +4 -0
  25. package/build/lsp/server.js +130 -0
  26. package/build/lsp/workspace.d.ts +14 -0
  27. package/build/lsp/workspace.js +46 -0
  28. package/build/probe/adapter.d.ts +14 -0
  29. package/build/probe/adapter.js +42 -0
  30. package/build/probe/async/channel.d.ts +4 -0
  31. package/build/probe/async/channel.js +560 -0
  32. package/build/probe/async/value.d.ts +9 -0
  33. package/build/probe/async/value.js +16 -0
  34. package/build/probe/channels.d.ts +4 -0
  35. package/build/probe/channels.js +16 -0
  36. package/build/probe/exceptions/channel.d.ts +5 -0
  37. package/build/probe/exceptions/channel.js +669 -0
  38. package/build/probe/exceptions/jsdoc.d.ts +8 -0
  39. package/build/probe/exceptions/jsdoc.js +34 -0
  40. package/build/probe/exceptions/value.d.ts +39 -0
  41. package/build/probe/exceptions/value.js +158 -0
  42. package/build/probe/kernel/analyze.d.ts +8 -0
  43. package/build/probe/kernel/analyze.js +68 -0
  44. package/build/probe/kernel/ast.d.ts +11 -0
  45. package/build/probe/kernel/ast.js +49 -0
  46. package/build/probe/kernel/config.d.ts +5 -0
  47. package/build/probe/kernel/config.js +209 -0
  48. package/build/probe/kernel/fixpoint.d.ts +6 -0
  49. package/build/probe/kernel/fixpoint.js +206 -0
  50. package/build/probe/kernel/format.d.ts +4 -0
  51. package/build/probe/kernel/format.js +32 -0
  52. package/build/probe/kernel/graph.d.ts +4 -0
  53. package/build/probe/kernel/graph.js +507 -0
  54. package/build/probe/kernel/ids.d.ts +8 -0
  55. package/build/probe/kernel/ids.js +66 -0
  56. package/build/probe/kernel/program.d.ts +8 -0
  57. package/build/probe/kernel/program.js +13 -0
  58. package/build/probe/kernel/types.d.ts +134 -0
  59. package/build/probe/kernel/types.js +1 -0
  60. package/build/probe/overlay/base/async.jsonc +13 -0
  61. package/build/probe/overlay/base/exceptions.jsonc +54 -0
  62. package/build/probe/overlay/base/resources.jsonc +57 -0
  63. package/build/probe/overlay/load.d.ts +18 -0
  64. package/build/probe/overlay/load.js +302 -0
  65. package/build/probe/overlay/presets/express.jsonc +25 -0
  66. package/build/probe/overlay/presets/node.jsonc +17 -0
  67. package/build/probe/resources/channel.d.ts +4 -0
  68. package/build/probe/resources/channel.js +798 -0
  69. package/build/probe/resources/value.d.ts +9 -0
  70. package/build/probe/resources/value.js +36 -0
  71. package/build/tsconfig.d.ts +2 -0
  72. package/build/tsconfig.js +150 -0
  73. package/package.json +14 -8
  74. package/tsconfig.base.json +19 -0
@@ -0,0 +1,16 @@
1
+ import { createAsyncChannel } from './async/channel.js';
2
+ import { createExceptionsChannel } from './exceptions/channel.js';
3
+ import { createResourcesChannel } from './resources/channel.js';
4
+ const FACTORIES = {
5
+ async: (config) => createAsyncChannel(config),
6
+ exceptions: () => createExceptionsChannel(),
7
+ resources: (config) => createResourcesChannel(config),
8
+ };
9
+ function channelFor(name, config) {
10
+ const factory = FACTORIES[name];
11
+ return factory ? factory(config) : undefined;
12
+ }
13
+ function implementedChannels() {
14
+ return Object.keys(FACTORIES);
15
+ }
16
+ export { channelFor, implementedChannels };
@@ -0,0 +1,5 @@
1
+ import type { Channel } from '../kernel/types.js';
2
+ import { type ExceptionsValue } from './value.js';
3
+ declare function overlayThrows(entry: unknown): boolean;
4
+ declare function createExceptionsChannel(): Channel<ExceptionsValue>;
5
+ export { createExceptionsChannel, overlayThrows };
@@ -0,0 +1,669 @@
1
+ import * as NodeFS from 'node:fs';
2
+ import * as ts from '../adapter.js';
3
+ import { bodyOf, calleeSelectors, paramSymbols } from '../kernel/ast.js';
4
+ import { isFunctionLike, locationOf } from '../kernel/ids.js';
5
+ import { declaredExceptions } from './jsdoc.js';
6
+ import { bottom, constituentsOf, equals, isEmpty, join, originsOf, refOfType, render, single, subtract, top, TOP_KEY, widen, withOrigin, } from './value.js';
7
+ function overlayThrows(entry) {
8
+ const e = (entry ?? {});
9
+ return ((Array.isArray(e.exceptions) && e.exceptions.length > 0) ||
10
+ (Array.isArray(e.exceptionsFromCallbacks) &&
11
+ e.exceptionsFromCallbacks.length > 0));
12
+ }
13
+ function createExceptionsChannel() {
14
+ let unhandledCache;
15
+ const typeCache = new Map();
16
+ const typeAt = (checker, node) => {
17
+ if (typeCache.has(node)) {
18
+ return typeCache.get(node);
19
+ }
20
+ const type = checker.getTypeAtLocation(node);
21
+ typeCache.set(node, type);
22
+ return type;
23
+ };
24
+ const computeUnhandled = (ctx) => {
25
+ if (unhandledCache)
26
+ return unhandledCache;
27
+ const roots = ctx.roots();
28
+ const origins = new Set();
29
+ for (const r of roots) {
30
+ for (const o of originsOf(ctx.summaryOf(r).value))
31
+ origins.add(`${o.fileName}:${o.pos}`);
32
+ }
33
+ unhandledCache = { origins, active: roots.length > 0 };
34
+ return unhandledCache;
35
+ };
36
+ return {
37
+ name: 'exceptions',
38
+ bottom,
39
+ equals,
40
+ widen,
41
+ transfer(ctx) {
42
+ const env = makeEnv(ctx.checker, ctx.dispatch, ctx.sinks, ctx.fn, ctx.summaryOf, ctx.resolveCall, typeAt);
43
+ const body = bodyOf(ctx.fn.node);
44
+ let value = body ? escapeOf(env, body, undefined) : bottom();
45
+ const decl = declaredExceptions(ctx.fn.node, ctx.checker);
46
+ if (decl.declared)
47
+ value = decl.value;
48
+ return { value, fromCallbacks: env.fromCallbacks };
49
+ },
50
+ diagnose(ctx) {
51
+ const out = [];
52
+ const base = makeEnv(ctx.checker, ctx.dispatch, ctx.sinks, ctx.fn, ctx.summaryOf, ctx.resolveCall, typeAt);
53
+ const env = reportMode(ctx) === 'all'
54
+ ? { ...base, escapeCache: new Map() }
55
+ : {
56
+ ...base,
57
+ escapeCache: new Map(),
58
+ unhandled: computeUnhandled(ctx),
59
+ };
60
+ const body = bodyOf(ctx.fn.node);
61
+ const decl = declaredExceptions(ctx.fn.node, ctx.checker);
62
+ if (decl.declared && body) {
63
+ const inferred = escapeOf(env, body, undefined);
64
+ const excess = subtract(inferred, new Set(decl.value.types.keys()));
65
+ if (!isEmpty(excess)) {
66
+ const nn = ctx.fn.node.name;
67
+ out.push({
68
+ channel: 'exceptions',
69
+ message: `Throws ${render(excess)} but declares only ${render(decl.value)}`,
70
+ location: locationOf(nn ?? ctx.fn.node, ctx.fn.sourceFile),
71
+ related: [],
72
+ });
73
+ }
74
+ }
75
+ if (errorCauseEnabled(ctx) && body)
76
+ checkErrorCause(body, out);
77
+ if (ctx.isBoundary && body)
78
+ walkDiagnostics(env, ctx, body, undefined, undefined, out);
79
+ return out;
80
+ },
81
+ };
82
+ }
83
+ function makeEnv(checker, dispatch, sinks, fn, summaryOf, resolveCall, typeAt) {
84
+ const symbols = paramSymbols(checker, fn.node);
85
+ return {
86
+ checker,
87
+ dispatch,
88
+ sinks,
89
+ fn,
90
+ summaryOf,
91
+ resolveCall,
92
+ escapeCache: undefined,
93
+ typeTable: new Map(),
94
+ typeAt: (node) => typeAt(checker, node),
95
+ paramSymbols: symbols,
96
+ fromCallbacks: new Set(),
97
+ };
98
+ }
99
+ function escapeOf(env, node, binding) {
100
+ const cached = env.escapeCache?.get(node)?.get(binding);
101
+ if (cached) {
102
+ return cached;
103
+ }
104
+ let acc = bottom();
105
+ const visit = (n) => {
106
+ if (isFunctionLike(n))
107
+ return;
108
+ if (ts.isTryStatement(n)) {
109
+ acc = join(acc, tryEscape(env, n, binding));
110
+ return;
111
+ }
112
+ if (ts.isThrowStatement(n)) {
113
+ if (n.expression && !isBindingRef(env, n.expression, binding)) {
114
+ const thrown = valueOfType(env, env.typeAt(n.expression));
115
+ acc = join(acc, withOrigin(thrown, originOf(n)));
116
+ }
117
+ if (n.expression)
118
+ ts.forEachChild(n.expression, visit);
119
+ return;
120
+ }
121
+ if (ts.isCallExpression(n) || ts.isNewExpression(n)) {
122
+ acc = join(acc, callEscape(env, n));
123
+ ts.forEachChild(n, visit);
124
+ return;
125
+ }
126
+ ts.forEachChild(n, visit);
127
+ };
128
+ ts.forEachChild(node, visit);
129
+ if (env.escapeCache) {
130
+ let bindings = env.escapeCache.get(node);
131
+ if (!bindings) {
132
+ bindings = new Map();
133
+ env.escapeCache.set(node, bindings);
134
+ }
135
+ bindings.set(binding, acc);
136
+ }
137
+ return acc;
138
+ }
139
+ function tryEscape(env, node, binding) {
140
+ const tryEsc = escapeOf(env, node.tryBlock, binding);
141
+ let result;
142
+ if (node.catchClause) {
143
+ const bindSym = bindingSymOf(env, node.catchClause) ?? binding;
144
+ const discharge = dischargedKeys(env, node.catchClause, tryEsc, bindSym);
145
+ const catchEsc = escapeOf(env, node.catchClause.block, bindSym);
146
+ result = join(subtract(tryEsc, discharge), catchEsc);
147
+ }
148
+ else {
149
+ result = tryEsc;
150
+ }
151
+ if (node.finallyBlock)
152
+ result = join(result, escapeOf(env, node.finallyBlock, binding));
153
+ return result;
154
+ }
155
+ function dischargedKeys(env, cc, tryEsc, bindSym) {
156
+ if (tryEsc.top)
157
+ return new Set();
158
+ const allKeys = new Set(tryEsc.types.keys());
159
+ const rethrows = [];
160
+ const find = (n) => {
161
+ if (isFunctionLike(n))
162
+ return;
163
+ if (ts.isThrowStatement(n) &&
164
+ n.expression &&
165
+ isBindingRef(env, n.expression, bindSym)) {
166
+ rethrows.push(n);
167
+ }
168
+ ts.forEachChild(n, find);
169
+ };
170
+ ts.forEachChild(cc.block, find);
171
+ if (rethrows.length === 0)
172
+ return allKeys;
173
+ const rethrown = new Set();
174
+ for (const rt of rethrows) {
175
+ const g = guardsFor(env, rt, cc.block, bindSym);
176
+ if (g.unresolved ||
177
+ (g.positives.length === 0 && g.negatives.length === 0)) {
178
+ return new Set();
179
+ }
180
+ for (const key of allKeys) {
181
+ const c = env.typeTable.get(key) ?? resolveKeyType(env, key);
182
+ if (!c) {
183
+ rethrown.add(key);
184
+ continue;
185
+ }
186
+ const okPos = g.positives.every((p) => isSubclassOf(c, p));
187
+ const okNeg = g.negatives.every((neg) => !isSubclassOf(c, neg));
188
+ if (okPos && okNeg)
189
+ rethrown.add(key);
190
+ }
191
+ }
192
+ const discharge = new Set();
193
+ for (const key of allKeys)
194
+ if (!rethrown.has(key))
195
+ discharge.add(key);
196
+ return discharge;
197
+ }
198
+ function guardsFor(env, rethrow, catchBlock, bindSym) {
199
+ const positives = [];
200
+ const negatives = [];
201
+ let unresolved = false;
202
+ let child = rethrow;
203
+ let parent = rethrow.parent;
204
+ const stop = catchBlock.parent;
205
+ while (parent && parent !== stop) {
206
+ if (ts.isIfStatement(parent) &&
207
+ (child === parent.thenStatement || child === parent.elseStatement)) {
208
+ const info = parseInstanceof(env, parent.expression, bindSym);
209
+ if (!info) {
210
+ unresolved = true;
211
+ }
212
+ else {
213
+ const condTrue = child === parent.thenStatement;
214
+ if (condTrue === info.positive)
215
+ positives.push(info.type);
216
+ else
217
+ negatives.push(info.type);
218
+ }
219
+ }
220
+ if (ts.isBlock(parent)) {
221
+ for (const stmt of parent.statements) {
222
+ if (stmt === child)
223
+ break;
224
+ const g = guardExit(env, stmt, bindSym);
225
+ if (g)
226
+ (g.positive ? positives : negatives).push(g.type);
227
+ }
228
+ }
229
+ child = parent;
230
+ parent = parent.parent;
231
+ }
232
+ return { positives, negatives, unresolved };
233
+ }
234
+ function parseInstanceof(env, expr, bindSym) {
235
+ if (ts.isParenthesizedExpression(expr))
236
+ return parseInstanceof(env, expr.expression, bindSym);
237
+ if (ts.isPrefixUnaryExpression(expr) &&
238
+ expr.operator === ts.SyntaxKind.ExclamationToken) {
239
+ const inner = parseInstanceof(env, expr.operand, bindSym);
240
+ return inner
241
+ ? { type: inner.type, positive: !inner.positive }
242
+ : undefined;
243
+ }
244
+ if (ts.isBinaryExpression(expr) &&
245
+ expr.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
246
+ if (!isBindingRef(env, expr.left, bindSym))
247
+ return undefined;
248
+ const rhsType = env.typeAt(expr.right);
249
+ if (!rhsType)
250
+ return undefined;
251
+ const ctorSig = env.checker.getSignaturesOfType(rhsType, ts.SignatureKind.Construct)[0];
252
+ const type = ctorSig
253
+ ? (env.checker.getReturnTypeOfSignature(ctorSig) ?? rhsType)
254
+ : rhsType;
255
+ return { type, positive: true };
256
+ }
257
+ return undefined;
258
+ }
259
+ function guardExit(env, stmt, bindSym) {
260
+ if (!ts.isIfStatement(stmt) || stmt.elseStatement)
261
+ return undefined;
262
+ if (!exits(stmt.thenStatement))
263
+ return undefined;
264
+ const info = parseInstanceof(env, stmt.expression, bindSym);
265
+ if (!info)
266
+ return undefined;
267
+ return { type: info.type, positive: !info.positive };
268
+ }
269
+ function exits(stmt) {
270
+ if (ts.isBlock(stmt)) {
271
+ const last = stmt.statements[stmt.statements.length - 1];
272
+ return last ? exits(last) : false;
273
+ }
274
+ return (ts.isReturnStatement(stmt) ||
275
+ ts.isThrowStatement(stmt) ||
276
+ ts.isBreakStatement(stmt) ||
277
+ ts.isContinueStatement(stmt));
278
+ }
279
+ function resolveKeyType(env, key) {
280
+ if (key === TOP_KEY)
281
+ return undefined;
282
+ const colon = key.indexOf(':');
283
+ const qname = colon >= 0 ? key.slice(colon + 1) : key;
284
+ const dot = qname.lastIndexOf('.');
285
+ const simple = dot >= 0 ? qname.slice(dot + 1) : qname;
286
+ if (!/^[A-Za-z_$][\w$]*$/.test(simple))
287
+ return undefined;
288
+ const sym = env.checker.resolveName(simple, ts.SymbolFlags.Type | ts.SymbolFlags.Value, env.fn.node, false);
289
+ if (!sym)
290
+ return undefined;
291
+ const t = env.checker.getDeclaredTypeOfSymbol(sym);
292
+ if (t)
293
+ env.typeTable.set(key, t);
294
+ return t;
295
+ }
296
+ function isSubclassOf(c, base, seen = new Set()) {
297
+ if (c === base)
298
+ return true;
299
+ const cs = c.getSymbol();
300
+ const bs = base.getSymbol();
301
+ if (cs && bs && cs === bs)
302
+ return true;
303
+ if (seen.has(c))
304
+ return false;
305
+ seen.add(c);
306
+ const bases = c.getBaseTypes() ?? [];
307
+ for (const b of bases)
308
+ if (isSubclassOf(b, base, seen))
309
+ return true;
310
+ return false;
311
+ }
312
+ function callEscape(env, call) {
313
+ detectParamCall(env, call);
314
+ const res = env.resolveCall(call);
315
+ let v = bottom();
316
+ for (const target of res.targets) {
317
+ const s = env.summaryOf(target);
318
+ v = join(v, s.value);
319
+ for (const i of s.fromCallbacks) {
320
+ for (const f of res.functionArgs.get(i) ?? [])
321
+ v = join(v, env.summaryOf(f).value);
322
+ }
323
+ }
324
+ if (res.overlay)
325
+ v = join(v, overlayValue(env, res, call));
326
+ if (res.unresolved && !res.overlay && env.dispatch === 'pessimist')
327
+ v = join(v, top());
328
+ const sink = matchSink(env, call);
329
+ if (sink)
330
+ v = applySink(v, sink);
331
+ return v;
332
+ }
333
+ function overlayValue(env, res, call) {
334
+ const entry = (res.overlay?.entry ?? {});
335
+ let v = bottom();
336
+ if (Array.isArray(entry.exceptions)) {
337
+ for (const name of entry.exceptions)
338
+ if (typeof name === 'string')
339
+ v = join(v, namedValue(env, name, call));
340
+ }
341
+ if (Array.isArray(entry.exceptionsFromCallbacks)) {
342
+ for (const i of entry.exceptionsFromCallbacks) {
343
+ if (typeof i !== 'number')
344
+ continue;
345
+ for (const f of res.functionArgs.get(i) ?? [])
346
+ v = join(v, env.summaryOf(f).value);
347
+ }
348
+ }
349
+ return v;
350
+ }
351
+ function namedValue(env, name, location) {
352
+ const sym = env.checker.resolveName(name, ts.SymbolFlags.Type, location, false);
353
+ if (sym) {
354
+ const t = env.checker.getDeclaredTypeOfSymbol(sym);
355
+ if (t)
356
+ return valueOfType(env, t);
357
+ }
358
+ return single(name, name);
359
+ }
360
+ function detectParamCall(env, call) {
361
+ if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
362
+ return;
363
+ const sym = env.checker.getSymbolAtLocation(call.expression);
364
+ if (!sym)
365
+ return;
366
+ const idx = env.paramSymbols.get(sym);
367
+ if (idx !== undefined)
368
+ env.fromCallbacks.add(idx);
369
+ }
370
+ function matchSink(env, call) {
371
+ if (!ts.isCallExpression(call))
372
+ return undefined;
373
+ const names = calleeSelectors(call.expression);
374
+ for (const s of env.sinks)
375
+ if (names.has(s.callee))
376
+ return s;
377
+ return undefined;
378
+ }
379
+ function applySink(v, sink) {
380
+ if (!sink.absorbs)
381
+ return bottom();
382
+ if (v.top)
383
+ return v;
384
+ const absorb = new Set(sink.absorbs);
385
+ const kept = new Map();
386
+ const keptOrigins = new Map();
387
+ for (const [k, d] of v.types) {
388
+ if (absorb.has(d))
389
+ continue;
390
+ kept.set(k, d);
391
+ const o = v.origins.get(k);
392
+ if (o)
393
+ keptOrigins.set(k, o);
394
+ }
395
+ return { top: false, types: kept, origins: keptOrigins };
396
+ }
397
+ function reportMode(ctx) {
398
+ const cc = ctx.channelConfig;
399
+ if (typeof cc === 'object' && cc !== null) {
400
+ const r = cc['report'];
401
+ if (typeof r === 'string')
402
+ return r;
403
+ }
404
+ return 'consumers';
405
+ }
406
+ function consumersOnly(ctx) {
407
+ const mode = reportMode(ctx);
408
+ return mode === 'consumers' || mode === 'cross-module';
409
+ }
410
+ function errorCauseEnabled(ctx) {
411
+ const cc = ctx.channelConfig;
412
+ return (typeof cc === 'object' &&
413
+ cc !== null &&
414
+ cc['errorCause'] === true);
415
+ }
416
+ function hasCauseArg(node) {
417
+ for (const arg of node.arguments ?? []) {
418
+ if (!ts.isObjectLiteralExpression(arg))
419
+ continue;
420
+ for (const p of arg.properties) {
421
+ const name = p.name;
422
+ if (name && ts.isIdentifier(name) && name.text === 'cause')
423
+ return true;
424
+ }
425
+ }
426
+ return false;
427
+ }
428
+ function checkErrorCause(node, out) {
429
+ const visit = (n) => {
430
+ if (isFunctionLike(n))
431
+ return;
432
+ if (ts.isCatchClause(n) && n.variableDeclaration) {
433
+ const scan = (m) => {
434
+ if (isFunctionLike(m) || ts.isCatchClause(m))
435
+ return;
436
+ if (ts.isThrowStatement(m) &&
437
+ m.expression &&
438
+ ts.isNewExpression(m.expression) &&
439
+ !hasCauseArg(m.expression)) {
440
+ out.push({
441
+ channel: 'exceptions',
442
+ message: 'Rethrow drops the caught error — pass `{ cause }` to preserve it',
443
+ location: locationOf(m, m.getSourceFile()),
444
+ related: [],
445
+ });
446
+ }
447
+ ts.forEachChild(m, scan);
448
+ };
449
+ ts.forEachChild(n.block, scan);
450
+ }
451
+ ts.forEachChild(n, visit);
452
+ };
453
+ ts.forEachChild(node, visit);
454
+ }
455
+ const packageRootCache = new Map();
456
+ function packageRootOf(fileName) {
457
+ let dir = fileName.replace(/\\/g, '/');
458
+ const slash = dir.lastIndexOf('/');
459
+ dir = slash >= 0 ? dir.slice(0, slash) : dir;
460
+ const visited = [];
461
+ let root;
462
+ for (let cur = dir;;) {
463
+ const cached = packageRootCache.get(cur);
464
+ if (cached !== undefined) {
465
+ root = cached;
466
+ break;
467
+ }
468
+ visited.push(cur);
469
+ if (NodeFS.existsSync(`${cur}/package.json`)) {
470
+ root = cur;
471
+ break;
472
+ }
473
+ const up = cur.lastIndexOf('/');
474
+ if (up <= 0) {
475
+ root = cur;
476
+ break;
477
+ }
478
+ cur = cur.slice(0, up);
479
+ }
480
+ for (const visitedDir of visited) {
481
+ packageRootCache.set(visitedDir, root);
482
+ }
483
+ return root;
484
+ }
485
+ function crossesPackageBoundary(env, ctx, call) {
486
+ const res = env.resolveCall(call);
487
+ if (res.targets.length === 0)
488
+ return true;
489
+ const home = packageRootOf(ctx.fn.fileName);
490
+ return res.targets.some((t) => packageRootOf(t.fileName) !== home);
491
+ }
492
+ function crossesFileBoundary(env, ctx, call) {
493
+ const res = env.resolveCall(call);
494
+ if (res.targets.length === 0)
495
+ return true;
496
+ const home = ctx.fn.fileName.replace(/\\/g, '/');
497
+ return res.targets.some((t) => t.fileName.replace(/\\/g, '/') !== home);
498
+ }
499
+ function reachesTop(env, rem) {
500
+ const u = env.unhandled;
501
+ if (!u || !u.active)
502
+ return true;
503
+ for (const o of originsOf(rem))
504
+ if (u.origins.has(`${o.fileName}:${o.pos}`))
505
+ return true;
506
+ return false;
507
+ }
508
+ function walkDiagnostics(env, ctx, node, binding, remainder, out) {
509
+ const visit = (n) => {
510
+ if (isFunctionLike(n))
511
+ return;
512
+ if (ts.isTryStatement(n)) {
513
+ handleTry(env, ctx, n, binding, remainder, out);
514
+ return;
515
+ }
516
+ if (ts.isThrowStatement(n)) {
517
+ if (n.expression) {
518
+ if (!consumersOnly(ctx)) {
519
+ if (isBindingRef(env, n.expression, binding)) {
520
+ if (remainder && !isEmpty(remainder))
521
+ out.push(throwDiagnostic(ctx, n, remainder));
522
+ }
523
+ else {
524
+ const rem = valueOfType(env, env.typeAt(n.expression));
525
+ if (!isEmpty(rem))
526
+ out.push(throwDiagnostic(ctx, n, rem));
527
+ }
528
+ }
529
+ ts.forEachChild(n.expression, visit);
530
+ }
531
+ return;
532
+ }
533
+ if (ts.isCallExpression(n) || ts.isNewExpression(n)) {
534
+ const rem = callEscape(env, n);
535
+ const mode = reportMode(ctx);
536
+ const report = mode === 'all'
537
+ ? true
538
+ : mode === 'cross-module'
539
+ ? crossesFileBoundary(env, ctx, n) && reachesTop(env, rem)
540
+ : crossesPackageBoundary(env, ctx, n) &&
541
+ reachesTop(env, rem);
542
+ if (!isEmpty(rem) && report)
543
+ out.push(callDiagnostic(env, ctx, n, rem));
544
+ ts.forEachChild(n, visit);
545
+ return;
546
+ }
547
+ ts.forEachChild(n, visit);
548
+ };
549
+ ts.forEachChild(node, visit);
550
+ }
551
+ function handleTry(env, ctx, n, binding, remainder, out) {
552
+ if (n.catchClause) {
553
+ const tryEsc = escapeOf(env, n.tryBlock, binding);
554
+ const bindSym = bindingSymOf(env, n.catchClause) ?? binding;
555
+ const discharge = dischargedKeys(env, n.catchClause, tryEsc, bindSym);
556
+ const rem = subtract(tryEsc, discharge);
557
+ walkDiagnostics(env, ctx, n.catchClause.block, bindSym, rem, out);
558
+ }
559
+ else {
560
+ walkDiagnostics(env, ctx, n.tryBlock, binding, remainder, out);
561
+ }
562
+ if (n.finallyBlock)
563
+ walkDiagnostics(env, ctx, n.finallyBlock, binding, remainder, out);
564
+ }
565
+ function originOf(node) {
566
+ const sf = node.getSourceFile();
567
+ const start = node.getStart(sf);
568
+ const { line, character } = sf.getLineAndCharacterOfPosition(start);
569
+ const raw = node.getText(sf).replace(/\s+/g, ' ').trim();
570
+ return {
571
+ fileName: sf.fileName,
572
+ pos: start,
573
+ end: node.getEnd(),
574
+ line: line + 1,
575
+ column: character + 1,
576
+ text: raw.length > 120 ? `${raw.slice(0, 117)}…` : raw,
577
+ };
578
+ }
579
+ function relatedFromOrigins(origins) {
580
+ return origins.slice(0, 8).map((o) => ({
581
+ message: o.text,
582
+ location: {
583
+ fileName: o.fileName,
584
+ line: o.line,
585
+ column: o.column,
586
+ pos: o.pos,
587
+ end: o.end,
588
+ },
589
+ }));
590
+ }
591
+ function callDiagnostic(env, ctx, call, rem) {
592
+ const res = env.resolveCall(call);
593
+ const from = calleeDisplay(res, call);
594
+ const suffix = from ? ` (from ${from})` : '';
595
+ const origins = originsOf(rem);
596
+ return {
597
+ channel: 'exceptions',
598
+ message: `Call may throw ${render(rem)}${suffix} with no catch on the path to \`${ctx.fn.name}\``,
599
+ location: locationOf(call, call.getSourceFile()),
600
+ related: origins.length > 0
601
+ ? relatedFromOrigins(origins)
602
+ : buildRelated(ctx, res.targets),
603
+ };
604
+ }
605
+ function throwDiagnostic(ctx, node, rem) {
606
+ return {
607
+ channel: 'exceptions',
608
+ message: `Throw may throw ${render(rem)} with no catch on the path to \`${ctx.fn.name}\``,
609
+ location: locationOf(node, node.getSourceFile()),
610
+ related: buildRelated(ctx, []),
611
+ };
612
+ }
613
+ function calleeDisplay(res, call) {
614
+ if (res.overlay)
615
+ return res.overlay.symbol;
616
+ if (res.targets[0])
617
+ return res.targets[0].name;
618
+ const callee = ts.isCallExpression(call) || ts.isNewExpression(call)
619
+ ? call.expression
620
+ : undefined;
621
+ if (callee && ts.isPropertyAccessExpression(callee))
622
+ return callee.getText();
623
+ if (callee && ts.isIdentifier(callee))
624
+ return callee.text;
625
+ return undefined;
626
+ }
627
+ function buildRelated(ctx, targets) {
628
+ const related = [];
629
+ for (const t of targets) {
630
+ related.push({
631
+ message: `may throw here in ${t.name}`,
632
+ location: locationOf(t.node, t.sourceFile),
633
+ });
634
+ }
635
+ for (const f of ctx.pathToBoundary(ctx.fn)) {
636
+ related.push({
637
+ message: `on the path to boundary via ${f.name}`,
638
+ location: locationOf(f.node, f.sourceFile),
639
+ });
640
+ }
641
+ return related;
642
+ }
643
+ function valueOfType(env, type) {
644
+ let v = bottom();
645
+ if (!type)
646
+ return v;
647
+ for (const t of constituentsOf(type)) {
648
+ const r = refOfType(env.checker, t);
649
+ if (r.top) {
650
+ v = join(v, top());
651
+ continue;
652
+ }
653
+ env.typeTable.set(r.key, t);
654
+ v = join(v, single(r.key, r.display));
655
+ }
656
+ return v;
657
+ }
658
+ function bindingSymOf(env, cc) {
659
+ const vd = cc.variableDeclaration;
660
+ if (vd && ts.isIdentifier(vd.name))
661
+ return env.checker.getSymbolAtLocation(vd.name);
662
+ return undefined;
663
+ }
664
+ function isBindingRef(env, expr, bindSym) {
665
+ if (!bindSym || !ts.isIdentifier(expr))
666
+ return false;
667
+ return env.checker.getSymbolAtLocation(expr) === bindSym;
668
+ }
669
+ export { createExceptionsChannel, overlayThrows };
@@ -0,0 +1,8 @@
1
+ import * as ts from '../adapter.js';
2
+ import type { FunctionLike } from '../kernel/types.js';
3
+ import { type ExceptionsValue } from './value.js';
4
+ declare function declaredExceptions(node: FunctionLike, checker: ts.TypeChecker): {
5
+ value: ExceptionsValue;
6
+ declared: boolean;
7
+ };
8
+ export { declaredExceptions };