@esportsplus/typescript 0.31.0 → 0.31.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/README.md +86 -0
- package/bin/tsc-lsp +3 -0
- package/build/cli/diagnostics.d.ts +5 -1
- package/build/cli/diagnostics.js +13 -8
- package/build/cli/tsc.d.ts +3 -1
- package/build/cli/tsc.js +77 -87
- package/build/compiler/coordinator.d.ts +1 -2
- package/build/compiler/coordinator.js +33 -22
- package/build/compiler/imports.d.ts +2 -0
- package/build/compiler/imports.js +9 -2
- package/build/compiler/language-service.d.ts +15 -4
- package/build/compiler/language-service.js +56 -24
- package/build/compiler/plugins/vite.js +7 -5
- package/build/compiler/sourcemap.d.ts +3 -1
- package/build/compiler/sourcemap.js +23 -5
- package/build/jsonc.d.ts +2 -0
- package/build/jsonc.js +85 -0
- package/build/lsp/bin.d.ts +1 -0
- package/build/lsp/bin.js +2 -0
- package/build/lsp/diagnostics.d.ts +10 -0
- package/build/lsp/diagnostics.js +69 -0
- package/build/lsp/index.d.ts +2 -0
- package/build/lsp/index.js +2 -0
- package/build/lsp/server.d.ts +4 -0
- package/build/lsp/server.js +130 -0
- package/build/lsp/workspace.d.ts +14 -0
- package/build/lsp/workspace.js +46 -0
- package/build/probe/adapter.d.ts +14 -0
- package/build/probe/adapter.js +42 -0
- package/build/probe/async/channel.d.ts +4 -0
- package/build/probe/async/channel.js +560 -0
- package/build/probe/async/value.d.ts +9 -0
- package/build/probe/async/value.js +16 -0
- package/build/probe/channels.d.ts +4 -0
- package/build/probe/channels.js +16 -0
- package/build/probe/exceptions/channel.d.ts +5 -0
- package/build/probe/exceptions/channel.js +657 -0
- package/build/probe/exceptions/jsdoc.d.ts +8 -0
- package/build/probe/exceptions/jsdoc.js +34 -0
- package/build/probe/exceptions/value.d.ts +39 -0
- package/build/probe/exceptions/value.js +158 -0
- package/build/probe/kernel/analyze.d.ts +8 -0
- package/build/probe/kernel/analyze.js +68 -0
- package/build/probe/kernel/ast.d.ts +11 -0
- package/build/probe/kernel/ast.js +49 -0
- package/build/probe/kernel/config.d.ts +5 -0
- package/build/probe/kernel/config.js +209 -0
- package/build/probe/kernel/fixpoint.d.ts +6 -0
- package/build/probe/kernel/fixpoint.js +206 -0
- package/build/probe/kernel/format.d.ts +4 -0
- package/build/probe/kernel/format.js +32 -0
- package/build/probe/kernel/graph.d.ts +4 -0
- package/build/probe/kernel/graph.js +507 -0
- package/build/probe/kernel/ids.d.ts +8 -0
- package/build/probe/kernel/ids.js +66 -0
- package/build/probe/kernel/program.d.ts +8 -0
- package/build/probe/kernel/program.js +13 -0
- package/build/probe/kernel/types.d.ts +134 -0
- package/build/probe/kernel/types.js +1 -0
- package/build/probe/overlay/base/async.jsonc +13 -0
- package/build/probe/overlay/base/exceptions.jsonc +54 -0
- package/build/probe/overlay/base/resources.jsonc +57 -0
- package/build/probe/overlay/load.d.ts +18 -0
- package/build/probe/overlay/load.js +302 -0
- package/build/probe/overlay/presets/express.jsonc +25 -0
- package/build/probe/overlay/presets/node.jsonc +17 -0
- package/build/probe/resources/channel.d.ts +4 -0
- package/build/probe/resources/channel.js +798 -0
- package/build/probe/resources/value.d.ts +9 -0
- package/build/probe/resources/value.js +36 -0
- package/build/tsconfig.d.ts +2 -0
- package/build/tsconfig.js +150 -0
- package/package.json +10 -4
- package/tsconfig.base.json +19 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as ts from '../../probe/adapter.js';
|
|
2
|
+
import { bottom, join, single, top, TOP_KEY, typeRef, } from './value.js';
|
|
3
|
+
function declaredExceptions(node, checker) {
|
|
4
|
+
let value = bottom();
|
|
5
|
+
let declared = false;
|
|
6
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
7
|
+
const name = tag.tagName.text;
|
|
8
|
+
if (name !== 'throws' && name !== 'exception')
|
|
9
|
+
continue;
|
|
10
|
+
declared = true;
|
|
11
|
+
const typeExpr = tag
|
|
12
|
+
.typeExpression;
|
|
13
|
+
if (typeExpr && typeExpr.type) {
|
|
14
|
+
const t = checker.getTypeAtLocation(typeExpr.type);
|
|
15
|
+
if (t)
|
|
16
|
+
value = join(value, refsToValue(typeRef(checker, t)));
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const text = typeof tag.comment === 'string'
|
|
20
|
+
? tag.comment
|
|
21
|
+
: ts.getTextOfJSDocComment(tag.comment);
|
|
22
|
+
const bare = (text ?? '').trim().split(/[\s,|]+/)[0];
|
|
23
|
+
if (bare)
|
|
24
|
+
value = join(value, single(bare, bare));
|
|
25
|
+
}
|
|
26
|
+
return { value, declared };
|
|
27
|
+
}
|
|
28
|
+
function refsToValue(refs) {
|
|
29
|
+
let v = bottom();
|
|
30
|
+
for (const r of refs)
|
|
31
|
+
v = join(v, r.key === TOP_KEY ? top() : single(r.key, r.display));
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
export { declaredExceptions };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as ts from '../../probe/adapter.js';
|
|
2
|
+
type ExceptionOrigin = {
|
|
3
|
+
readonly fileName: string;
|
|
4
|
+
readonly pos: number;
|
|
5
|
+
readonly end: number;
|
|
6
|
+
readonly line: number;
|
|
7
|
+
readonly column: number;
|
|
8
|
+
readonly text: string;
|
|
9
|
+
};
|
|
10
|
+
type ExceptionsValue = {
|
|
11
|
+
readonly top: boolean;
|
|
12
|
+
readonly types: ReadonlyMap<string, string>;
|
|
13
|
+
readonly origins: ReadonlyMap<string, ReadonlyArray<ExceptionOrigin>>;
|
|
14
|
+
};
|
|
15
|
+
declare const TOP_KEY = "\0top";
|
|
16
|
+
declare function bottom(): ExceptionsValue;
|
|
17
|
+
declare function top(): ExceptionsValue;
|
|
18
|
+
declare function single(key: string, display: string): ExceptionsValue;
|
|
19
|
+
declare function withOrigin(v: ExceptionsValue, origin: ExceptionOrigin): ExceptionsValue;
|
|
20
|
+
declare function originsOf(v: ExceptionsValue): ReadonlyArray<ExceptionOrigin>;
|
|
21
|
+
declare function join(a: ExceptionsValue, b: ExceptionsValue): ExceptionsValue;
|
|
22
|
+
declare function equals(a: ExceptionsValue, b: ExceptionsValue): boolean;
|
|
23
|
+
declare function widen(prev: ExceptionsValue, next: ExceptionsValue, round: number): ExceptionsValue;
|
|
24
|
+
declare function subtract(a: ExceptionsValue, keys: ReadonlySet<string>): ExceptionsValue;
|
|
25
|
+
declare function isEmpty(v: ExceptionsValue): boolean;
|
|
26
|
+
declare function render(v: ExceptionsValue): string;
|
|
27
|
+
declare function constituentsOf(type: ts.Type): ReadonlyArray<ts.Type>;
|
|
28
|
+
declare function refOfType(checker: ts.TypeChecker, t: ts.Type): {
|
|
29
|
+
top: true;
|
|
30
|
+
} | {
|
|
31
|
+
top: false;
|
|
32
|
+
key: string;
|
|
33
|
+
display: string;
|
|
34
|
+
};
|
|
35
|
+
declare function typeRef(checker: ts.TypeChecker, type: ts.Type): ReadonlyArray<{
|
|
36
|
+
key: string;
|
|
37
|
+
display: string;
|
|
38
|
+
}>;
|
|
39
|
+
export { TOP_KEY, bottom, constituentsOf, equals, isEmpty, join, originsOf, refOfType, render, single, subtract, top, type ExceptionOrigin, type ExceptionsValue, typeRef, widen, withOrigin };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as ts from '../../probe/adapter.js';
|
|
2
|
+
const TOP_KEY = '\u0000top';
|
|
3
|
+
const TOP_DISPLAY = 'an unknown error';
|
|
4
|
+
const WIDEN_CAP = 8;
|
|
5
|
+
const ORIGIN_CAP = 4;
|
|
6
|
+
const NO_ORIGINS = new Map();
|
|
7
|
+
function originId(o) {
|
|
8
|
+
return `${o.fileName}:${o.pos}`;
|
|
9
|
+
}
|
|
10
|
+
function mergeOrigins(a, b) {
|
|
11
|
+
const m = new Map(a);
|
|
12
|
+
for (const [k, list] of b) {
|
|
13
|
+
const prev = m.get(k);
|
|
14
|
+
if (!prev) {
|
|
15
|
+
m.set(k, list.slice(0, ORIGIN_CAP));
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const seen = new Set(prev.map(originId));
|
|
19
|
+
const out = [...prev];
|
|
20
|
+
for (const o of list) {
|
|
21
|
+
if (!seen.has(originId(o)) && out.length < ORIGIN_CAP) {
|
|
22
|
+
seen.add(originId(o));
|
|
23
|
+
out.push(o);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
m.set(k, out);
|
|
27
|
+
}
|
|
28
|
+
return m;
|
|
29
|
+
}
|
|
30
|
+
function bottom() {
|
|
31
|
+
return { top: false, types: new Map(), origins: NO_ORIGINS };
|
|
32
|
+
}
|
|
33
|
+
function top() {
|
|
34
|
+
return { top: true, types: new Map(), origins: NO_ORIGINS };
|
|
35
|
+
}
|
|
36
|
+
function single(key, display) {
|
|
37
|
+
return {
|
|
38
|
+
top: false,
|
|
39
|
+
types: new Map([[key, display]]),
|
|
40
|
+
origins: NO_ORIGINS,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function withOrigin(v, origin) {
|
|
44
|
+
if (v.top)
|
|
45
|
+
return v;
|
|
46
|
+
const origins = new Map(v.origins);
|
|
47
|
+
for (const k of v.types.keys()) {
|
|
48
|
+
const prev = origins.get(k) ?? [];
|
|
49
|
+
if (prev.some((o) => originId(o) === originId(origin)))
|
|
50
|
+
continue;
|
|
51
|
+
origins.set(k, prev.length < ORIGIN_CAP ? [...prev, origin] : prev);
|
|
52
|
+
}
|
|
53
|
+
return { top: false, types: v.types, origins };
|
|
54
|
+
}
|
|
55
|
+
function originsOf(v) {
|
|
56
|
+
const out = [];
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
for (const list of v.origins.values()) {
|
|
59
|
+
for (const o of list) {
|
|
60
|
+
if (!seen.has(originId(o))) {
|
|
61
|
+
seen.add(originId(o));
|
|
62
|
+
out.push(o);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function join(a, b) {
|
|
69
|
+
if (a.top || b.top)
|
|
70
|
+
return top();
|
|
71
|
+
const m = new Map(a.types);
|
|
72
|
+
for (const [k, d] of b.types)
|
|
73
|
+
if (!m.has(k))
|
|
74
|
+
m.set(k, d);
|
|
75
|
+
if (m.size > WIDEN_CAP)
|
|
76
|
+
return top();
|
|
77
|
+
return {
|
|
78
|
+
top: false,
|
|
79
|
+
types: m,
|
|
80
|
+
origins: mergeOrigins(a.origins, b.origins),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function equals(a, b) {
|
|
84
|
+
if (a.top || b.top)
|
|
85
|
+
return a.top === b.top;
|
|
86
|
+
if (a.types.size !== b.types.size)
|
|
87
|
+
return false;
|
|
88
|
+
for (const k of a.types.keys())
|
|
89
|
+
if (!b.types.has(k))
|
|
90
|
+
return false;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
function widen(prev, next, round) {
|
|
94
|
+
if (next.top)
|
|
95
|
+
return next;
|
|
96
|
+
if (next.types.size > WIDEN_CAP)
|
|
97
|
+
return top();
|
|
98
|
+
if (round >= 3 && !prev.top && next.types.size > prev.types.size)
|
|
99
|
+
return top();
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
function subtract(a, keys) {
|
|
103
|
+
if (a.top)
|
|
104
|
+
return a;
|
|
105
|
+
const m = new Map();
|
|
106
|
+
const origins = new Map();
|
|
107
|
+
for (const [k, d] of a.types) {
|
|
108
|
+
if (keys.has(k))
|
|
109
|
+
continue;
|
|
110
|
+
m.set(k, d);
|
|
111
|
+
const o = a.origins.get(k);
|
|
112
|
+
if (o)
|
|
113
|
+
origins.set(k, o);
|
|
114
|
+
}
|
|
115
|
+
return { top: false, types: m, origins };
|
|
116
|
+
}
|
|
117
|
+
function isEmpty(v) {
|
|
118
|
+
return !v.top && v.types.size === 0;
|
|
119
|
+
}
|
|
120
|
+
function render(v) {
|
|
121
|
+
if (v.top)
|
|
122
|
+
return TOP_DISPLAY;
|
|
123
|
+
return Array.from(v.types.values()).sort().join(' | ');
|
|
124
|
+
}
|
|
125
|
+
function constituentsOf(type) {
|
|
126
|
+
return ts.unionTypes(type) ?? [type];
|
|
127
|
+
}
|
|
128
|
+
function basename(p) {
|
|
129
|
+
const i = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
|
|
130
|
+
return i >= 0 ? p.slice(i + 1) : p;
|
|
131
|
+
}
|
|
132
|
+
function refOfType(checker, t) {
|
|
133
|
+
if ((t.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
|
134
|
+
return { top: true };
|
|
135
|
+
const sym = t.getAliasSymbol() ?? t.getSymbol();
|
|
136
|
+
if (sym) {
|
|
137
|
+
const decl = ts.symbolDeclarations(sym)[0];
|
|
138
|
+
const file = decl ? basename(decl.getSourceFile().fileName) : '?';
|
|
139
|
+
const name = sym.name;
|
|
140
|
+
const qname = name && name !== '__type' ? name : checker.typeToString(t);
|
|
141
|
+
const display = name && name !== '__type' ? name : checker.typeToString(t);
|
|
142
|
+
return { top: false, key: `${file}:${qname}`, display };
|
|
143
|
+
}
|
|
144
|
+
const s = checker.typeToString(t);
|
|
145
|
+
return { top: false, key: s, display: s };
|
|
146
|
+
}
|
|
147
|
+
function typeRef(checker, type) {
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const t of constituentsOf(type)) {
|
|
150
|
+
const r = refOfType(checker, t);
|
|
151
|
+
if (r.top)
|
|
152
|
+
out.push({ key: TOP_KEY, display: TOP_DISPLAY });
|
|
153
|
+
else
|
|
154
|
+
out.push({ key: r.key, display: r.display });
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
export { TOP_KEY, bottom, constituentsOf, equals, isEmpty, join, originsOf, refOfType, render, single, subtract, top, typeRef, widen, withOrigin };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as ts from '../../probe/adapter.js';
|
|
2
|
+
import type { Diagnostic, AnalyzeConfig } from './types.js';
|
|
3
|
+
type AnalyzeResult = {
|
|
4
|
+
readonly diagnostics: ReadonlyArray<Diagnostic>;
|
|
5
|
+
};
|
|
6
|
+
declare function analyzeProgram(program: ts.Program, checker: ts.TypeChecker, config: AnalyzeConfig): AnalyzeResult;
|
|
7
|
+
declare function analyze(config: AnalyzeConfig): AnalyzeResult;
|
|
8
|
+
export { analyze, analyzeProgram, type AnalyzeResult };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { channelFor } from '../channels.js';
|
|
2
|
+
import { buildCallGraph } from './graph.js';
|
|
3
|
+
import { buildProgram } from './program.js';
|
|
4
|
+
import { runChannel } from './fixpoint.js';
|
|
5
|
+
import { loadOverlays } from '../overlay/load.js';
|
|
6
|
+
function analyzeProgram(program, checker, config) {
|
|
7
|
+
const overlays = loadOverlays(config);
|
|
8
|
+
const effectiveConfig = {
|
|
9
|
+
...config,
|
|
10
|
+
handlerBoundaries: [
|
|
11
|
+
...config.handlerBoundaries,
|
|
12
|
+
...overlays.boundariesFromPresets(),
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
const graph = buildCallGraph(program, checker, effectiveConfig, overlays);
|
|
16
|
+
const analysis = {
|
|
17
|
+
program,
|
|
18
|
+
checker,
|
|
19
|
+
config: effectiveConfig,
|
|
20
|
+
overlays,
|
|
21
|
+
graph,
|
|
22
|
+
};
|
|
23
|
+
const diagnostics = [];
|
|
24
|
+
const runnable = new Map();
|
|
25
|
+
for (const [name, channelConfig] of Object.entries(effectiveConfig.channels)) {
|
|
26
|
+
if (!channelConfig.enabled) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
runnable.set(name, {
|
|
30
|
+
config: channelConfig,
|
|
31
|
+
channel: channelFor(name, channelConfig.options),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const ordered = [];
|
|
35
|
+
const placed = new Set();
|
|
36
|
+
const place = (name, stack) => {
|
|
37
|
+
if (placed.has(name) || !runnable.has(name) || stack.has(name)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const next = new Set(stack).add(name);
|
|
41
|
+
for (const dep of runnable.get(name).channel.dependsOn ?? []) {
|
|
42
|
+
place(dep, next);
|
|
43
|
+
}
|
|
44
|
+
placed.add(name);
|
|
45
|
+
ordered.push(name);
|
|
46
|
+
};
|
|
47
|
+
for (const name of runnable.keys()) {
|
|
48
|
+
place(name, new Set());
|
|
49
|
+
}
|
|
50
|
+
const peers = new Map();
|
|
51
|
+
for (const name of ordered) {
|
|
52
|
+
const { config, channel } = runnable.get(name);
|
|
53
|
+
const result = runChannel(analysis, channel, config.dispatch, config.options, peers);
|
|
54
|
+
peers.set(name, result.store);
|
|
55
|
+
diagnostics.push(...result.diagnostics);
|
|
56
|
+
}
|
|
57
|
+
return { diagnostics };
|
|
58
|
+
}
|
|
59
|
+
function analyze(config) {
|
|
60
|
+
const built = buildProgram(config.tsconfigPath);
|
|
61
|
+
try {
|
|
62
|
+
return analyzeProgram(built.program, built.checker, config);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
built.dispose();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export { analyze, analyzeProgram };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as ts from '../../probe/adapter.js';
|
|
2
|
+
import type { FunctionLike } from './types.js';
|
|
3
|
+
type UnwrapOptions = {
|
|
4
|
+
assertions?: boolean;
|
|
5
|
+
awaits?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const bodyOf: (node: FunctionLike) => ts.Node | undefined;
|
|
8
|
+
declare const calleeSelectors: (callee: ts.Expression) => Set<string>;
|
|
9
|
+
declare const paramSymbols: (checker: ts.TypeChecker, fn: FunctionLike) => Map<ts.Symbol, number>;
|
|
10
|
+
declare const unwrap: (expr: ts.Expression, options?: UnwrapOptions) => ts.Expression;
|
|
11
|
+
export { bodyOf, calleeSelectors, paramSymbols, unwrap };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as ts from '../../probe/adapter.js';
|
|
2
|
+
function shouldUnwrap(expr, options) {
|
|
3
|
+
return (ts.isParenthesizedExpression(expr) ||
|
|
4
|
+
(options.assertions === true &&
|
|
5
|
+
(ts.isAsExpression(expr) || ts.isNonNullExpression(expr))) ||
|
|
6
|
+
(options.awaits === true && ts.isAwaitExpression(expr)));
|
|
7
|
+
}
|
|
8
|
+
const bodyOf = (node) => {
|
|
9
|
+
return node.body;
|
|
10
|
+
};
|
|
11
|
+
const calleeSelectors = (callee) => {
|
|
12
|
+
let selectors = new Set();
|
|
13
|
+
if (ts.isIdentifier(callee)) {
|
|
14
|
+
selectors.add(callee.text);
|
|
15
|
+
}
|
|
16
|
+
else if (ts.isPropertyAccessExpression(callee)) {
|
|
17
|
+
let member = callee.name.text;
|
|
18
|
+
selectors.add(member);
|
|
19
|
+
if (ts.isIdentifier(callee.expression)) {
|
|
20
|
+
selectors.add(`${callee.expression.text}.${member}`);
|
|
21
|
+
selectors.add(`${callee.expression.text}#${member}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return selectors;
|
|
25
|
+
};
|
|
26
|
+
const paramSymbols = (checker, fn) => {
|
|
27
|
+
let symbols = new Map();
|
|
28
|
+
let params = fn
|
|
29
|
+
.parameters;
|
|
30
|
+
if (params) {
|
|
31
|
+
params.forEach((param, index) => {
|
|
32
|
+
if (ts.isIdentifier(param.name)) {
|
|
33
|
+
let symbol = checker.getSymbolAtLocation(param.name);
|
|
34
|
+
if (symbol) {
|
|
35
|
+
symbols.set(symbol, index);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return symbols;
|
|
41
|
+
};
|
|
42
|
+
const unwrap = (expr, options = {}) => {
|
|
43
|
+
let unwrapped = expr;
|
|
44
|
+
while (shouldUnwrap(unwrapped, options)) {
|
|
45
|
+
unwrapped = unwrapped.expression;
|
|
46
|
+
}
|
|
47
|
+
return unwrapped;
|
|
48
|
+
};
|
|
49
|
+
export { bodyOf, calleeSelectors, paramSymbols, unwrap };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AnalyzeConfig } from './types.js';
|
|
2
|
+
declare function parseConfig(text: string, projectRoot: string): AnalyzeConfig;
|
|
3
|
+
declare function configFromObject(raw: unknown, projectRoot: string): AnalyzeConfig;
|
|
4
|
+
declare function loadConfigFromTsconfig(tsconfigPath: string): AnalyzeConfig | undefined;
|
|
5
|
+
export { configFromObject, loadConfigFromTsconfig, parseConfig };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as NodePath from 'node:path';
|
|
2
|
+
import { readPlugins } from '../../tsconfig.js';
|
|
3
|
+
import { stripJsonc } from '../../jsonc.js';
|
|
4
|
+
const CHANNEL_NAMES = ['exceptions', 'resources', 'async'];
|
|
5
|
+
const DEFAULT_ENABLED = {
|
|
6
|
+
exceptions: true,
|
|
7
|
+
resources: false,
|
|
8
|
+
async: false,
|
|
9
|
+
};
|
|
10
|
+
function fail(message) {
|
|
11
|
+
throw new Error(`analyze config: ${message}`);
|
|
12
|
+
}
|
|
13
|
+
function legacyStripJsonc(text) {
|
|
14
|
+
let out = '';
|
|
15
|
+
let i = 0;
|
|
16
|
+
const n = text.length;
|
|
17
|
+
let inString = false;
|
|
18
|
+
let quote = '';
|
|
19
|
+
while (i < n) {
|
|
20
|
+
const ch = text[i];
|
|
21
|
+
const next = i + 1 < n ? text[i + 1] : '';
|
|
22
|
+
if (inString) {
|
|
23
|
+
out += ch;
|
|
24
|
+
if (ch === '\\') {
|
|
25
|
+
out += next;
|
|
26
|
+
i += 2;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (ch === quote) {
|
|
30
|
+
inString = false;
|
|
31
|
+
}
|
|
32
|
+
i += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (ch === '"' || ch === "'") {
|
|
36
|
+
inString = true;
|
|
37
|
+
quote = ch;
|
|
38
|
+
out += ch;
|
|
39
|
+
i += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (ch === '/' && next === '/') {
|
|
43
|
+
while (i < n && text[i] !== '\n') {
|
|
44
|
+
i += 1;
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (ch === '/' && next === '*') {
|
|
49
|
+
i += 2;
|
|
50
|
+
while (i < n && !(text[i] === '*' && text[i + 1] === '/')) {
|
|
51
|
+
i += 1;
|
|
52
|
+
}
|
|
53
|
+
i += 2;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
out += ch;
|
|
57
|
+
i += 1;
|
|
58
|
+
}
|
|
59
|
+
return out.replace(/,(\s*[}\]])/g, '$1');
|
|
60
|
+
}
|
|
61
|
+
void legacyStripJsonc;
|
|
62
|
+
function asStringArray(value, field) {
|
|
63
|
+
if (value === undefined) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== 'string')) {
|
|
67
|
+
fail(`"${field}" must be an array of strings`);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
function parseHandlerBoundaries(value) {
|
|
72
|
+
if (value === undefined) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
if (!Array.isArray(value)) {
|
|
76
|
+
fail(`"handlerBoundaries" must be an array`);
|
|
77
|
+
}
|
|
78
|
+
return value.map((raw, index) => {
|
|
79
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
80
|
+
fail(`handlerBoundaries[${index}] must be an object`);
|
|
81
|
+
}
|
|
82
|
+
const entry = raw;
|
|
83
|
+
if (typeof entry['callee'] !== 'string') {
|
|
84
|
+
fail(`handlerBoundaries[${index}].callee must be a string`);
|
|
85
|
+
}
|
|
86
|
+
const callbackArgs = entry['callbackArgs'];
|
|
87
|
+
if (!Array.isArray(callbackArgs) ||
|
|
88
|
+
callbackArgs.some((v) => typeof v !== 'number' || !Number.isInteger(v))) {
|
|
89
|
+
fail(`handlerBoundaries[${index}].callbackArgs must be an array of integers`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
callee: entry['callee'],
|
|
93
|
+
callbackArgs: callbackArgs,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function parseSinks(value) {
|
|
98
|
+
if (value === undefined) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
if (!Array.isArray(value)) {
|
|
102
|
+
fail(`"sinks" must be an array`);
|
|
103
|
+
}
|
|
104
|
+
return value.map((raw, index) => {
|
|
105
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
106
|
+
fail(`sinks[${index}] must be an object`);
|
|
107
|
+
}
|
|
108
|
+
const entry = raw;
|
|
109
|
+
if (typeof entry['callee'] !== 'string') {
|
|
110
|
+
fail(`sinks[${index}].callee must be a string`);
|
|
111
|
+
}
|
|
112
|
+
let absorbs;
|
|
113
|
+
if (entry['absorbs'] !== undefined) {
|
|
114
|
+
absorbs = asStringArray(entry['absorbs'], `sinks[${index}].absorbs`);
|
|
115
|
+
}
|
|
116
|
+
return { callee: entry['callee'], absorbs };
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function parseDispatch(value, channel) {
|
|
120
|
+
if (value === undefined) {
|
|
121
|
+
return 'optimist';
|
|
122
|
+
}
|
|
123
|
+
if (value !== 'optimist' && value !== 'pessimist') {
|
|
124
|
+
fail(`channels.${channel}.dispatch must be "optimist" or "pessimist"`);
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
function parseChannels(value) {
|
|
129
|
+
const channels = {};
|
|
130
|
+
const raw = value === undefined ? {} : value;
|
|
131
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
132
|
+
fail(`"channels" must be an object`);
|
|
133
|
+
}
|
|
134
|
+
for (const key of Object.keys(raw)) {
|
|
135
|
+
if (!CHANNEL_NAMES.includes(key)) {
|
|
136
|
+
fail(`unknown channel "${key}" (known: ${CHANNEL_NAMES.join(', ')})`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const name of CHANNEL_NAMES) {
|
|
140
|
+
const entry = (raw[name] ?? {});
|
|
141
|
+
if (typeof entry !== 'object' ||
|
|
142
|
+
entry === null ||
|
|
143
|
+
Array.isArray(entry)) {
|
|
144
|
+
fail(`channels.${name} must be an object`);
|
|
145
|
+
}
|
|
146
|
+
const enabled = entry['enabled'] === undefined
|
|
147
|
+
? DEFAULT_ENABLED[name]
|
|
148
|
+
: entry['enabled'];
|
|
149
|
+
if (typeof enabled !== 'boolean') {
|
|
150
|
+
fail(`channels.${name}.enabled must be a boolean`);
|
|
151
|
+
}
|
|
152
|
+
const { enabled: _e, dispatch: _d, ...options } = entry;
|
|
153
|
+
channels[name] = {
|
|
154
|
+
enabled,
|
|
155
|
+
dispatch: parseDispatch(entry['dispatch'], name),
|
|
156
|
+
options,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return channels;
|
|
160
|
+
}
|
|
161
|
+
function normalizeConfig(raw, projectRoot) {
|
|
162
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
163
|
+
fail(`root must be a JSON object`);
|
|
164
|
+
}
|
|
165
|
+
const tsconfig = raw.tsconfig === undefined ? 'tsconfig.json' : raw.tsconfig;
|
|
166
|
+
if (typeof tsconfig !== 'string') {
|
|
167
|
+
fail(`"tsconfig" must be a string`);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
projectRoot,
|
|
171
|
+
tsconfigPath: NodePath.resolve(projectRoot, tsconfig),
|
|
172
|
+
entryPoints: asStringArray(raw.entryPoints, 'entryPoints'),
|
|
173
|
+
handlerBoundaries: parseHandlerBoundaries(raw.handlerBoundaries),
|
|
174
|
+
sinks: parseSinks(raw.sinks),
|
|
175
|
+
presets: asStringArray(raw.presets, 'presets'),
|
|
176
|
+
overlays: asStringArray(raw.overlays, 'overlays').map((p) => NodePath.resolve(projectRoot, p)),
|
|
177
|
+
channels: parseChannels(raw.channels),
|
|
178
|
+
failOnFindings: raw.failOnFindings === true,
|
|
179
|
+
severity: raw.severity === 'warn' || raw.severity === 'warning'
|
|
180
|
+
? 'warning'
|
|
181
|
+
: 'error',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function parseConfig(text, projectRoot) {
|
|
185
|
+
let parsed;
|
|
186
|
+
try {
|
|
187
|
+
parsed = JSON.parse(stripJsonc(text));
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
fail(`invalid JSONC: ${error.message}`);
|
|
191
|
+
}
|
|
192
|
+
return normalizeConfig(parsed, projectRoot);
|
|
193
|
+
}
|
|
194
|
+
function configFromObject(raw, projectRoot) {
|
|
195
|
+
return normalizeConfig(raw, projectRoot);
|
|
196
|
+
}
|
|
197
|
+
function loadConfigFromTsconfig(tsconfigPath) {
|
|
198
|
+
const resolved = NodePath.resolve(tsconfigPath);
|
|
199
|
+
const plugins = readPlugins(resolved) ?? [];
|
|
200
|
+
const entry = plugins.find((p) => p !== null && typeof p === 'object' && p.name === 'ts-probe');
|
|
201
|
+
if (!entry) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
...configFromObject(entry, NodePath.dirname(resolved)),
|
|
206
|
+
tsconfigPath: resolved,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export { configFromObject, loadConfigFromTsconfig, parseConfig };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Analysis, Channel, Diagnostic, Dispatch, SummaryStore } from './types.js';
|
|
2
|
+
declare function runChannel<V>(analysis: Analysis, channel: Channel<V>, dispatch: Dispatch, channelConfig: unknown, peers: ReadonlyMap<string, SummaryStore<unknown>>): {
|
|
3
|
+
store: SummaryStore<V>;
|
|
4
|
+
diagnostics: Diagnostic[];
|
|
5
|
+
};
|
|
6
|
+
export { runChannel };
|