@esportsplus/typescript 0.31.1 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -44
- package/build/cli/tsc.d.ts +3 -2
- package/build/cli/tsc.js +15 -97
- package/build/{probe → guard}/adapter.d.ts +1 -2
- package/build/{probe → guard}/adapter.js +1 -4
- package/build/{probe → guard}/async/channel.d.ts +1 -1
- package/build/{probe → guard}/async/channel.js +12 -192
- package/build/{probe → guard}/async/value.d.ts +1 -2
- package/build/{probe → guard}/async/value.js +1 -4
- package/build/guard/channels.d.ts +4 -0
- package/build/guard/channels.js +13 -0
- package/build/{probe → guard}/exceptions/channel.d.ts +3 -2
- package/build/{probe → guard}/exceptions/channel.js +173 -101
- package/build/guard/exceptions/derive.d.ts +49 -0
- package/build/guard/exceptions/derive.js +478 -0
- package/build/{probe → guard}/exceptions/jsdoc.d.ts +1 -1
- package/build/{probe → guard}/exceptions/jsdoc.js +1 -1
- package/build/guard/exceptions/resolve-js.d.ts +2 -0
- package/build/guard/exceptions/resolve-js.js +26 -0
- package/build/{probe → guard}/exceptions/value.d.ts +4 -3
- package/build/{probe → guard}/exceptions/value.js +13 -5
- package/build/{probe → guard}/kernel/analyze.d.ts +3 -2
- package/build/guard/kernel/analyze.js +84 -0
- package/build/guard/kernel/ast.d.ts +13 -0
- package/build/guard/kernel/ast.js +56 -0
- package/build/guard/kernel/config.js +93 -0
- package/build/guard/kernel/fixpoint.d.ts +6 -0
- package/build/{probe → guard}/kernel/fixpoint.js +8 -48
- package/build/guard/kernel/graph.d.ts +4 -0
- package/build/guard/kernel/graph.js +268 -0
- package/build/{probe → guard}/kernel/ids.d.ts +3 -4
- package/build/{probe → guard}/kernel/ids.js +2 -7
- package/build/{probe → guard}/kernel/program.d.ts +1 -1
- package/build/{probe → guard}/kernel/types.d.ts +4 -33
- package/build/{probe → guard}/overlay/base/async.jsonc +1 -1
- package/build/{probe → guard}/overlay/base/exceptions.jsonc +16 -3
- package/build/{probe → guard}/overlay/base/resources.jsonc +7 -8
- package/build/{probe → guard}/overlay/load.d.ts +3 -7
- package/build/{probe → guard}/overlay/load.js +38 -121
- package/build/{probe → guard}/resources/channel.d.ts +1 -1
- package/build/{probe → guard}/resources/channel.js +80 -158
- package/build/{probe → guard}/resources/value.d.ts +1 -2
- package/build/{probe → guard}/resources/value.js +1 -14
- package/build/lsp/diagnostics.d.ts +2 -2
- package/build/lsp/diagnostics.js +2 -2
- package/build/lsp/server.js +41 -3
- package/build/lsp/workspace.d.ts +6 -3
- package/build/lsp/workspace.js +19 -5
- package/build/tsconfig.d.ts +2 -1
- package/build/tsconfig.js +44 -102
- package/package.json +8 -8
- package/tsconfig.base.json +12 -19
- package/tsconfig.package.json +6 -1
- package/build/probe/channels.d.ts +0 -4
- package/build/probe/channels.js +0 -16
- package/build/probe/kernel/analyze.js +0 -68
- package/build/probe/kernel/ast.d.ts +0 -11
- package/build/probe/kernel/ast.js +0 -49
- package/build/probe/kernel/config.js +0 -209
- package/build/probe/kernel/fixpoint.d.ts +0 -6
- package/build/probe/kernel/graph.d.ts +0 -4
- package/build/probe/kernel/graph.js +0 -507
- package/build/probe/overlay/presets/express.jsonc +0 -25
- package/build/probe/overlay/presets/node.jsonc +0 -17
- /package/build/{probe → guard}/kernel/config.d.ts +0 -0
- /package/build/{probe → guard}/kernel/format.d.ts +0 -0
- /package/build/{probe → guard}/kernel/format.js +0 -0
- /package/build/{probe → guard}/kernel/program.js +0 -0
- /package/build/{probe → guard}/kernel/types.js +0 -0
package/build/tsconfig.js
CHANGED
|
@@ -3,6 +3,18 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { stripJsonc } from './jsonc.js';
|
|
5
5
|
let require = createRequire(import.meta.url);
|
|
6
|
+
function deepMerge(base, override) {
|
|
7
|
+
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
8
|
+
return override;
|
|
9
|
+
}
|
|
10
|
+
let result = { ...base };
|
|
11
|
+
for (let key of Object.keys(override)) {
|
|
12
|
+
result[key] = key in result
|
|
13
|
+
? deepMerge(result[key], override[key])
|
|
14
|
+
: override[key];
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
6
18
|
function extendsTarget(specifier, fromDir) {
|
|
7
19
|
if (typeof specifier !== 'string') {
|
|
8
20
|
return null;
|
|
@@ -30,97 +42,15 @@ function extendsTarget(specifier, fromDir) {
|
|
|
30
42
|
}
|
|
31
43
|
}
|
|
32
44
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
for (let i = 0, n = text.length; i < n; i++) {
|
|
36
|
-
let char = text[i], next = text[i + 1];
|
|
37
|
-
if (inLineComment) {
|
|
38
|
-
if (char === '\n') {
|
|
39
|
-
inLineComment = false;
|
|
40
|
-
stripped += char;
|
|
41
|
-
}
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (inBlockComment) {
|
|
45
|
-
if (char === '*' && next === '/') {
|
|
46
|
-
inBlockComment = false;
|
|
47
|
-
i++;
|
|
48
|
-
}
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (inString) {
|
|
52
|
-
stripped += char;
|
|
53
|
-
if (escaped) {
|
|
54
|
-
escaped = false;
|
|
55
|
-
}
|
|
56
|
-
else if (char === '\\') {
|
|
57
|
-
escaped = true;
|
|
58
|
-
}
|
|
59
|
-
else if (char === '"') {
|
|
60
|
-
inString = false;
|
|
61
|
-
}
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (char === '"') {
|
|
65
|
-
inString = true;
|
|
66
|
-
stripped += char;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (char === '/' && next === '/') {
|
|
70
|
-
inLineComment = true;
|
|
71
|
-
i++;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (char === '/' && next === '*') {
|
|
75
|
-
inBlockComment = true;
|
|
76
|
-
i++;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
stripped += char;
|
|
80
|
-
}
|
|
81
|
-
escaped = false;
|
|
82
|
-
inString = false;
|
|
83
|
-
let result = '';
|
|
84
|
-
for (let i = 0, n = stripped.length; i < n; i++) {
|
|
85
|
-
let char = stripped[i];
|
|
86
|
-
if (inString) {
|
|
87
|
-
result += char;
|
|
88
|
-
if (escaped) {
|
|
89
|
-
escaped = false;
|
|
90
|
-
}
|
|
91
|
-
else if (char === '\\') {
|
|
92
|
-
escaped = true;
|
|
93
|
-
}
|
|
94
|
-
else if (char === '"') {
|
|
95
|
-
inString = false;
|
|
96
|
-
}
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (char === '"') {
|
|
100
|
-
inString = true;
|
|
101
|
-
result += char;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (char === ',') {
|
|
105
|
-
let j = i + 1;
|
|
106
|
-
while (j < n && /\s/.test(stripped[j])) {
|
|
107
|
-
j++;
|
|
108
|
-
}
|
|
109
|
-
if (stripped[j] === ']' || stripped[j] === '}') {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
result += char;
|
|
114
|
-
}
|
|
115
|
-
return result;
|
|
45
|
+
function isPlainObject(value) {
|
|
46
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
116
47
|
}
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
let id = path.resolve(configPath);
|
|
48
|
+
function walkExtends(tsconfigPath, fold) {
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
function read(configPath, inherited) {
|
|
51
|
+
const id = path.resolve(configPath);
|
|
122
52
|
if (seen.has(id)) {
|
|
123
|
-
return
|
|
53
|
+
return inherited;
|
|
124
54
|
}
|
|
125
55
|
seen.add(id);
|
|
126
56
|
let config;
|
|
@@ -128,23 +58,35 @@ const readPlugins = (tsconfigPath) => {
|
|
|
128
58
|
config = JSON.parse(stripJsonc(fs.readFileSync(id, 'utf8')));
|
|
129
59
|
}
|
|
130
60
|
catch {
|
|
131
|
-
return
|
|
61
|
+
return inherited;
|
|
132
62
|
}
|
|
133
|
-
let plugins;
|
|
134
63
|
if (config.extends !== undefined) {
|
|
135
|
-
|
|
136
|
-
for (
|
|
137
|
-
|
|
64
|
+
const bases = Array.isArray(config.extends) ? config.extends : [config.extends];
|
|
65
|
+
for (const base of bases) {
|
|
66
|
+
const target = extendsTarget(base, path.dirname(id));
|
|
138
67
|
if (target) {
|
|
139
|
-
|
|
140
|
-
if (inherited !== undefined) {
|
|
141
|
-
plugins = inherited;
|
|
142
|
-
}
|
|
68
|
+
inherited = read(target, inherited);
|
|
143
69
|
}
|
|
144
70
|
}
|
|
145
71
|
}
|
|
146
|
-
return
|
|
72
|
+
return fold(config, inherited);
|
|
73
|
+
}
|
|
74
|
+
return read(tsconfigPath, undefined);
|
|
75
|
+
}
|
|
76
|
+
const readPlugins = (tsconfigPath) => walkExtends(tsconfigPath, (config, inherited) => {
|
|
77
|
+
const compilerOptions = config['compilerOptions'];
|
|
78
|
+
const plugins = isPlainObject(compilerOptions)
|
|
79
|
+
? compilerOptions['plugins']
|
|
80
|
+
: undefined;
|
|
81
|
+
return Array.isArray(plugins) ? plugins : inherited;
|
|
82
|
+
});
|
|
83
|
+
const readGuardConfig = (tsconfigPath) => walkExtends(tsconfigPath, (config, inherited) => {
|
|
84
|
+
const own = config['tsc-guard'];
|
|
85
|
+
if (!isPlainObject(own)) {
|
|
86
|
+
return inherited;
|
|
147
87
|
}
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
|
|
88
|
+
return inherited === undefined
|
|
89
|
+
? own
|
|
90
|
+
: deepMerge(inherited, own);
|
|
91
|
+
});
|
|
92
|
+
export { readPlugins, readGuardConfig };
|
package/package.json
CHANGED
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@esportsplus/cli-passthrough": "^0.0.15",
|
|
12
12
|
"@esportsplus/utilities": "^0.28.0",
|
|
13
|
-
"@types/node": "^26.
|
|
14
|
-
"tsc-alias": "^1.9.
|
|
13
|
+
"@types/node": "^26.4.1",
|
|
14
|
+
"tsc-alias": "^1.9.4",
|
|
15
15
|
"typescript": "7.0.2",
|
|
16
|
-
"vscode-languageserver": "^10.1.
|
|
17
|
-
"vscode-languageserver-textdocument": "^1.0.
|
|
16
|
+
"vscode-languageserver": "^10.1.1",
|
|
17
|
+
"vscode-languageserver-textdocument": "^1.0.14"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"vite": "^8.
|
|
21
|
-
"vitest": "^4.1.
|
|
20
|
+
"vite": "^8.2.2",
|
|
21
|
+
"vitest": "^4.1.11"
|
|
22
22
|
},
|
|
23
23
|
"exports": {
|
|
24
24
|
"./package.json": "./package.json",
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
},
|
|
61
61
|
"type": "module",
|
|
62
62
|
"types": "build/index.d.ts",
|
|
63
|
-
"version": "0.
|
|
63
|
+
"version": "0.33.0",
|
|
64
64
|
"scripts": {
|
|
65
65
|
"agent:bench": "pnpm bench:run",
|
|
66
66
|
"agent:build": "pnpm build",
|
|
67
67
|
"agent:test": "pnpm typecheck && pnpm test",
|
|
68
68
|
"bench:run": "vitest bench --run",
|
|
69
|
-
"build": "tsc -p tsconfig.package.json && tsc-alias -p tsconfig.package.json && node -e \"const fs=require('fs');fs.cpSync('src/
|
|
69
|
+
"build": "tsc -p tsconfig.package.json && tsc-alias -p tsconfig.package.json && node -e \"const fs=require('fs');fs.cpSync('src/guard/overlay','build/guard/overlay',{recursive:true,filter:(s)=>!/\\.[cm]?tsx?$/.test(s)})\"",
|
|
70
70
|
"test": "vitest run",
|
|
71
71
|
"typecheck": "tsc"
|
|
72
72
|
}
|
package/tsconfig.base.json
CHANGED
|
@@ -23,25 +23,6 @@
|
|
|
23
23
|
"${configDir}/storage/*"
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
|
-
"plugins": [
|
|
27
|
-
{
|
|
28
|
-
"name": "ts-probe",
|
|
29
|
-
// Editor squiggle color; "warn" opts down. CLI/build ignore this.
|
|
30
|
-
"severity": "error",
|
|
31
|
-
// Fail the tsc/build run when there are findings.
|
|
32
|
-
"failOnFindings": true,
|
|
33
|
-
"channels": {
|
|
34
|
-
"exceptions": {
|
|
35
|
-
"enabled": true,
|
|
36
|
-
"report": "consumers",
|
|
37
|
-
// Flag `throw`s inside `catch` that drop the caught error's cause.
|
|
38
|
-
"errorCause": true
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
// Model third-party throw behavior: "node", "express".
|
|
42
|
-
"presets": ["node"]
|
|
43
|
-
}
|
|
44
|
-
],
|
|
45
26
|
"removeComments": true,
|
|
46
27
|
"resolveJsonModule": true,
|
|
47
28
|
"rootDir": "${configDir}/src",
|
|
@@ -54,5 +35,17 @@
|
|
|
54
35
|
],
|
|
55
36
|
"tsc-alias": {
|
|
56
37
|
"resolveFullPaths": true
|
|
38
|
+
},
|
|
39
|
+
"tsc-guard": {
|
|
40
|
+
"exceptions": {
|
|
41
|
+
"severity": "error",
|
|
42
|
+
"report": "cross-module"
|
|
43
|
+
},
|
|
44
|
+
"resources": {
|
|
45
|
+
"severity": "error"
|
|
46
|
+
},
|
|
47
|
+
"async": {
|
|
48
|
+
"severity": "info"
|
|
49
|
+
}
|
|
57
50
|
}
|
|
58
51
|
}
|
package/tsconfig.package.json
CHANGED
package/build/probe/channels.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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 };
|
|
@@ -1,68 +0,0 @@
|
|
|
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 };
|
|
@@ -1,11 +0,0 @@
|
|
|
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 };
|
|
@@ -1,49 +0,0 @@
|
|
|
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 };
|
|
@@ -1,209 +0,0 @@
|
|
|
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 };
|
|
@@ -1,6 +0,0 @@
|
|
|
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 };
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import * as ts from '../../probe/adapter.js';
|
|
2
|
-
import type { CallGraph, OverlaySet, AnalyzeConfig } from './types.js';
|
|
3
|
-
declare function buildCallGraph(program: ts.Program, checker: ts.TypeChecker, config: AnalyzeConfig, overlays: OverlaySet): CallGraph;
|
|
4
|
-
export { buildCallGraph };
|