@pulse-compute/wasm-compiler 0.0.0 → 1.0.0-beta.2
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 +53 -1
- package/bin/provider-proof-composition.js +34 -0
- package/bin/pulsewasm-extract.js +20 -0
- package/package.json +60 -5
- package/src/artifacts-dir.js +13 -0
- package/src/ast-json.js +52 -0
- package/src/build-manifest.js +354 -0
- package/src/canonical-api-compiler.js +269 -0
- package/src/canonical-native-compiler.js +411 -0
- package/src/canonical-native-plan.js +1478 -0
- package/src/canonical-project-compiler.js +1224 -0
- package/src/canonical-router-compiler.js +25 -0
- package/src/cli-intents.js +1235 -0
- package/src/cli.js +1927 -0
- package/src/codegen/assemblyscript-compile.js +3 -0
- package/src/codegen/assemblyscript-core.js +3 -0
- package/src/codegen/assemblyscript-shape.js +3 -0
- package/src/codegen/assemblyscript-wasm-smoke.js +3 -0
- package/src/codegen/backend-capabilities.js +3 -0
- package/src/codegen/channel-broadcaster.js +3 -0
- package/src/codegen/compiled-handlers.js +3 -0
- package/src/codegen/compiled-wasm-runtime.js +3 -0
- package/src/codegen/config-references.js +248 -0
- package/src/codegen/dispatch-ts.js +145 -0
- package/src/codegen/effect-composition.js +331 -0
- package/src/codegen/effect-runtime.js +418 -0
- package/src/codegen/execution-harness-ts.js +467 -0
- package/src/codegen/handler-bindings-ts.js +298 -0
- package/src/codegen/handler-library-contracts.js +3 -0
- package/src/codegen/host-capabilities.js +3 -0
- package/src/codegen/host-runtime-kernel.js +3 -0
- package/src/codegen/integrated-compiled-app.js +3 -0
- package/src/codegen/json-body.js +3 -0
- package/src/codegen/library-sidecars.js +3 -0
- package/src/codegen/local-harness-ts.js +453 -0
- package/src/codegen/pulse-wrapper.js +217 -0
- package/src/codegen/request-result-headers.js +3 -0
- package/src/codegen/schema-json-compile.js +3 -0
- package/src/codegen/schema-json-sidecar-v2.js +3 -0
- package/src/codegen/schema-json-sidecar.js +3 -0
- package/src/codegen/streaming-passthrough.js +3 -0
- package/src/codegen/wasm-host-abi.js +3 -0
- package/src/codegen/wasm-host-bridge.js +3 -0
- package/src/compiled-wasm-host-runtime-kv.js +3 -0
- package/src/config-resolver.js +813 -0
- package/src/crypto-requirement-planner.js +89 -0
- package/src/definitions/config-schema.js +14 -0
- package/src/definitions/handler-roles.js +14 -0
- package/src/definitions/path-grammar.js +14 -0
- package/src/definitions/router-api.js +14 -0
- package/src/diagnostics/codes.js +14 -0
- package/src/diagnostics/reporter.js +14 -0
- package/src/diagnostics.js +14 -0
- package/src/dispatch-table.js +400 -0
- package/src/events/event-emit.js +265 -0
- package/src/events/event-topology.js +127 -0
- package/src/execution-plan.js +463 -0
- package/src/extractor.js +2816 -0
- package/src/handler-eval.js +1154 -0
- package/src/handler-table.js +326 -0
- package/src/index.js +19 -0
- package/src/javascript-application-plan.js +181 -0
- package/src/kv-provider.js +3 -0
- package/src/path-table.js +60 -0
- package/src/path.js +14 -0
- package/src/patterns/config-define.js +30 -0
- package/src/patterns/dependency-call.js +18 -0
- package/src/patterns/env-lookup.js +13 -0
- package/src/patterns/handler-reference.js +29 -0
- package/src/patterns/path-literal.js +35 -0
- package/src/patterns/result.js +15 -0
- package/src/patterns/router-chain-call.js +50 -0
- package/src/patterns/router-construction.js +19 -0
- package/src/project/package-reachability.js +782 -0
- package/src/project/reachable-graph-builder.js +992 -0
- package/src/project/reachable-graph-contract.js +54 -0
- package/src/project/reachable-graph-implementation.js +36 -0
- package/src/project/router-module-linker.js +710 -0
- package/src/project-config-compiler.js +222 -0
- package/src/project-target-support.js +500 -0
- package/src/provider-toolchain.js +299 -0
- package/src/spine/async-surface-normalizer.js +328 -0
- package/src/spine/canonical-handler-ir.js +336 -0
- package/src/spine/canonical-native-module.js +87 -0
- package/src/spine/canonical-native-plan.js +89 -0
- package/src/spine/canonical-project.js +76 -0
- package/src/spine/canonical-router.js +155 -0
- package/src/spine/canonical-source.js +165 -0
- package/src/spine/diagnostic-authority.js +290 -0
- package/src/spine/equivalence.js +262 -0
- package/src/spine/guest-unit-stage.js +87 -0
- package/src/spine/handler-ir-emitter.js +455 -0
- package/src/spine/handler-ir-managed.js +1598 -0
- package/src/spine/handler-ir.js +797 -0
- package/src/spine/handler-surface-authority.js +588 -0
- package/src/spine/package-operation-seam.js +1015 -0
- package/src/spine/pipeline.js +202 -0
- package/src/spine/plain-handler-frontend.js +545 -0
- package/src/spine/provider-requirement-authority.js +208 -0
- package/src/spine/router-control-contract.js +17 -0
- package/src/spine/router-handler-frontend.js +514 -0
- package/src/spine/router-handler-ir.js +372 -0
- package/src/spine/router-topology-frontend.js +638 -0
- package/src/stable-id.js +14 -0
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ts = require('typescript');
|
|
4
|
+
const { PACKAGE_VERSION, normalizeArtifact, sourceLoc, stableFileName } = require('./diagnostics.js');
|
|
5
|
+
const { sourceTextHash } = require('./stable-id.js');
|
|
6
|
+
|
|
7
|
+
const ALLOWED_LITERAL_GLOBALS = new Set(['undefined', 'NaN', 'Infinity']);
|
|
8
|
+
const NETWORK_IMPORT_MODULES = new Set(['axios', 'undici', 'node-fetch', 'got', 'ky', 'http', 'https', 'node:http', 'node:https']);
|
|
9
|
+
const ASSIGNMENT_OPERATORS = new Set([
|
|
10
|
+
ts.SyntaxKind.EqualsToken,
|
|
11
|
+
ts.SyntaxKind.PlusEqualsToken,
|
|
12
|
+
ts.SyntaxKind.MinusEqualsToken,
|
|
13
|
+
ts.SyntaxKind.AsteriskEqualsToken,
|
|
14
|
+
ts.SyntaxKind.AsteriskAsteriskEqualsToken,
|
|
15
|
+
ts.SyntaxKind.SlashEqualsToken,
|
|
16
|
+
ts.SyntaxKind.PercentEqualsToken,
|
|
17
|
+
ts.SyntaxKind.LessThanLessThanEqualsToken,
|
|
18
|
+
ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
|
|
19
|
+
ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
|
|
20
|
+
ts.SyntaxKind.AmpersandEqualsToken,
|
|
21
|
+
ts.SyntaxKind.BarEqualsToken,
|
|
22
|
+
ts.SyntaxKind.CaretEqualsToken,
|
|
23
|
+
ts.SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
24
|
+
ts.SyntaxKind.BarBarEqualsToken,
|
|
25
|
+
ts.SyntaxKind.QuestionQuestionEqualsToken
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function nodeText(sourceFile, node) {
|
|
29
|
+
return node.getText(sourceFile).trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hasModifier(node, kind) {
|
|
33
|
+
return Boolean(node.modifiers && node.modifiers.some((modifier) => modifier.kind === kind));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parameterName(sourceFile, parameter) {
|
|
37
|
+
if (ts.isIdentifier(parameter.name)) return parameter.name.text;
|
|
38
|
+
return nodeText(sourceFile, parameter.name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function describeFunctionSignature(sourceFile, node) {
|
|
42
|
+
const params = Array.from(node.parameters || []).map((parameter) => parameterName(sourceFile, parameter));
|
|
43
|
+
const nonIdentifierParams = Array.from(node.parameters || [])
|
|
44
|
+
.filter((parameter) => !ts.isIdentifier(parameter.name))
|
|
45
|
+
.map((parameter) => nodeText(sourceFile, parameter.name));
|
|
46
|
+
const returnType = node.type ? node.type.getText(sourceFile) : undefined;
|
|
47
|
+
return {
|
|
48
|
+
async: hasModifier(node, ts.SyntaxKind.AsyncKeyword),
|
|
49
|
+
generator: Boolean(node.asteriskToken),
|
|
50
|
+
paramCount: params.length,
|
|
51
|
+
params,
|
|
52
|
+
nonIdentifierParams,
|
|
53
|
+
returnType,
|
|
54
|
+
returnsPromise: Boolean(returnType && /\bPromise\b/.test(returnType))
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function functionKind(node, fallback = 'function') {
|
|
59
|
+
if (ts.isFunctionDeclaration(node)) return 'function-declaration';
|
|
60
|
+
if (ts.isArrowFunction(node)) return fallback === 'inline' ? 'inline-arrow' : 'const-arrow';
|
|
61
|
+
if (ts.isFunctionExpression(node)) return fallback === 'inline' ? 'inline-function-expression' : 'const-function-expression';
|
|
62
|
+
return fallback;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function declarationKindFromList(list) {
|
|
66
|
+
if (!list) return 'var';
|
|
67
|
+
if ((list.flags & ts.NodeFlags.Const) !== 0) return 'const';
|
|
68
|
+
if ((list.flags & ts.NodeFlags.Let) !== 0) return 'let';
|
|
69
|
+
return 'var';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isStringLiteralLike(node) {
|
|
73
|
+
return ts.isStringLiteral(node) || node.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isFunctionLike(node) {
|
|
77
|
+
return ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function sourceKey(loc) {
|
|
81
|
+
if (!loc) return '<unknown>';
|
|
82
|
+
const start = loc.start ? `${loc.start.offset}` : '?';
|
|
83
|
+
return `${loc.file || '<unknown>'}:${start}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function uniqueByKey(items, keyFn) {
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const item of items) {
|
|
90
|
+
const key = keyFn(item);
|
|
91
|
+
if (seen.has(key)) continue;
|
|
92
|
+
seen.add(key);
|
|
93
|
+
out.push(item);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function collectBindingNames(node, names = []) {
|
|
99
|
+
if (!node) return names;
|
|
100
|
+
if (ts.isIdentifier(node)) {
|
|
101
|
+
names.push(node.text);
|
|
102
|
+
return names;
|
|
103
|
+
}
|
|
104
|
+
if (ts.isObjectBindingPattern(node) || ts.isArrayBindingPattern(node)) {
|
|
105
|
+
for (const element of node.elements || []) {
|
|
106
|
+
if (ts.isBindingElement(element)) collectBindingNames(element.name, names);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return names;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function importModuleText(node) {
|
|
113
|
+
return isStringLiteralLike(node.moduleSpecifier) ? node.moduleSpecifier.text : '<dynamic>';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function collectTopLevelSymbols(sourceFile, cwd) {
|
|
117
|
+
const functionsByName = new Map();
|
|
118
|
+
const variablesByName = new Map();
|
|
119
|
+
const importsByName = new Map();
|
|
120
|
+
const inlineFunctionsByOffset = new Map();
|
|
121
|
+
|
|
122
|
+
function addFunction(name, declarationNode, functionNode, kind) {
|
|
123
|
+
const sourceText = nodeText(sourceFile, functionNode);
|
|
124
|
+
functionsByName.set(name, {
|
|
125
|
+
name,
|
|
126
|
+
kind,
|
|
127
|
+
declarationNode,
|
|
128
|
+
node: functionNode,
|
|
129
|
+
body: functionNode.body,
|
|
130
|
+
file: stableFileName(sourceFile.fileName, cwd),
|
|
131
|
+
loc: sourceLoc(sourceFile, declarationNode),
|
|
132
|
+
functionLoc: sourceLoc(sourceFile, functionNode),
|
|
133
|
+
sourceTextHash: sourceTextHash(sourceText),
|
|
134
|
+
signature: describeFunctionSignature(sourceFile, functionNode)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const statement of sourceFile.statements) {
|
|
139
|
+
if (ts.isImportDeclaration(statement) && statement.importClause) {
|
|
140
|
+
const module = importModuleText(statement);
|
|
141
|
+
const clause = statement.importClause;
|
|
142
|
+
if (clause.name) {
|
|
143
|
+
importsByName.set(clause.name.text, {
|
|
144
|
+
name: clause.name.text,
|
|
145
|
+
importedName: 'default',
|
|
146
|
+
kind: 'default',
|
|
147
|
+
module,
|
|
148
|
+
loc: sourceLoc(sourceFile, clause.name)
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const bindings = clause.namedBindings;
|
|
152
|
+
if (bindings && ts.isNamespaceImport(bindings)) {
|
|
153
|
+
importsByName.set(bindings.name.text, {
|
|
154
|
+
name: bindings.name.text,
|
|
155
|
+
importedName: '*',
|
|
156
|
+
kind: 'namespace',
|
|
157
|
+
module,
|
|
158
|
+
loc: sourceLoc(sourceFile, bindings.name)
|
|
159
|
+
});
|
|
160
|
+
} else if (bindings && ts.isNamedImports(bindings)) {
|
|
161
|
+
for (const specifier of bindings.elements) {
|
|
162
|
+
importsByName.set(specifier.name.text, {
|
|
163
|
+
name: specifier.name.text,
|
|
164
|
+
importedName: specifier.propertyName ? specifier.propertyName.text : specifier.name.text,
|
|
165
|
+
kind: 'named',
|
|
166
|
+
module,
|
|
167
|
+
loc: sourceLoc(sourceFile, specifier.name)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (ts.isFunctionDeclaration(statement) && statement.name) {
|
|
175
|
+
addFunction(statement.name.text, statement, statement, 'function-declaration');
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (ts.isVariableStatement(statement)) {
|
|
180
|
+
const declarationKind = declarationKindFromList(statement.declarationList);
|
|
181
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
182
|
+
if (!ts.isIdentifier(declaration.name)) continue;
|
|
183
|
+
const name = declaration.name.text;
|
|
184
|
+
const init = declaration.initializer;
|
|
185
|
+
if (init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
|
|
186
|
+
addFunction(name, declaration, init, ts.isArrowFunction(init) ? 'const-arrow' : 'const-function-expression');
|
|
187
|
+
} else {
|
|
188
|
+
variablesByName.set(name, {
|
|
189
|
+
name,
|
|
190
|
+
declarationKind,
|
|
191
|
+
file: stableFileName(sourceFile.fileName, cwd),
|
|
192
|
+
loc: sourceLoc(sourceFile, declaration),
|
|
193
|
+
hasInitializer: Boolean(init),
|
|
194
|
+
sourceTextHash: init ? sourceTextHash(nodeText(sourceFile, init)) : undefined
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function collectInline(node) {
|
|
202
|
+
if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && !isTopLevelFunctionInitializer(node)) {
|
|
203
|
+
const loc = sourceLoc(sourceFile, node);
|
|
204
|
+
inlineFunctionsByOffset.set(loc.start.offset, {
|
|
205
|
+
name: `<inline:${loc.start.offset}>`,
|
|
206
|
+
kind: functionKind(node, 'inline'),
|
|
207
|
+
declarationNode: node,
|
|
208
|
+
node,
|
|
209
|
+
body: node.body,
|
|
210
|
+
file: stableFileName(sourceFile.fileName, cwd),
|
|
211
|
+
loc,
|
|
212
|
+
functionLoc: loc,
|
|
213
|
+
sourceTextHash: sourceTextHash(nodeText(sourceFile, node)),
|
|
214
|
+
signature: describeFunctionSignature(sourceFile, node)
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
ts.forEachChild(node, collectInline);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isTopLevelFunctionInitializer(node) {
|
|
221
|
+
const parent = node.parent;
|
|
222
|
+
return Boolean(parent && ts.isVariableDeclaration(parent) && parent.initializer === node && parent.parent && parent.parent.parent && ts.isVariableStatement(parent.parent.parent));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
collectInline(sourceFile);
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
functionsByName,
|
|
229
|
+
variablesByName,
|
|
230
|
+
importsByName,
|
|
231
|
+
inlineFunctionsByOffset
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function isIdentifierReference(node) {
|
|
236
|
+
if (!ts.isIdentifier(node)) return false;
|
|
237
|
+
const parent = node.parent;
|
|
238
|
+
if (!parent) return true;
|
|
239
|
+
|
|
240
|
+
if ((ts.isVariableDeclaration(parent) || ts.isParameter(parent) || ts.isFunctionDeclaration(parent) || ts.isFunctionExpression(parent)) && parent.name === node) return false;
|
|
241
|
+
if ((ts.isPropertyAccessExpression(parent) || ts.isPropertyAssignment(parent) || ts.isMethodDeclaration(parent) || ts.isPropertyDeclaration(parent)) && parent.name === node) return false;
|
|
242
|
+
if ((ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent)) && parent.name === node) return false;
|
|
243
|
+
if (ts.isBindingElement(parent) && parent.name === node) return false;
|
|
244
|
+
if (ts.isLabeledStatement(parent) && parent.label === node) return false;
|
|
245
|
+
if (ts.isBreakStatement(parent) && parent.label === node) return false;
|
|
246
|
+
if (ts.isContinueStatement(parent) && parent.label === node) return false;
|
|
247
|
+
if (ts.isTypeReferenceNode(parent)) return false;
|
|
248
|
+
if (ts.isPropertySignature(parent) && parent.name === node) return false;
|
|
249
|
+
if (ts.isEnumMember(parent) && parent.name === node) return false;
|
|
250
|
+
if (ts.isQualifiedName(parent) && parent.right === node) return false;
|
|
251
|
+
if (ts.isJsxAttribute(parent) && parent.name === node) return false;
|
|
252
|
+
|
|
253
|
+
// Shorthand object properties are real references. Everything else above is declaration/name metadata.
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function unsupportedSyntaxForNode(node) {
|
|
258
|
+
if (ts.isAwaitExpression(node)) {
|
|
259
|
+
return ['PULSEWASM_UNSUPPORTED_AWAIT', 'await is not supported in PulseWasm v1 handler bodies.', 'Use explicit continuation style and no hidden suspension.'];
|
|
260
|
+
}
|
|
261
|
+
if (ts.isTryStatement(node)) {
|
|
262
|
+
return ['PULSEWASM_UNSUPPORTED_TRY_CATCH', 'try/catch/finally is not supported in PulseWasm v1 handler bodies.', 'Use explicit Pulse error handlers instead of JavaScript exception control flow.'];
|
|
263
|
+
}
|
|
264
|
+
if (ts.isThrowStatement(node)) {
|
|
265
|
+
return ['PULSEWASM_UNSUPPORTED_THROW', 'throw is not supported in PulseWasm v1 handler bodies.', 'Route errors through the explicit error handler contract.'];
|
|
266
|
+
}
|
|
267
|
+
if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
268
|
+
return ['PULSEWASM_UNSUPPORTED_LOOP', 'loops are not supported in PulseWasm v1 handler bodies.', 'Use a narrower, explicitly lowered helper once the subset expands.'];
|
|
269
|
+
}
|
|
270
|
+
if (ts.isSwitchStatement(node)) {
|
|
271
|
+
return ['PULSEWASM_UNSUPPORTED_SWITCH', 'switch statements are not supported in PulseWasm v1 handler bodies.', 'Use if/else for the v1 subset.'];
|
|
272
|
+
}
|
|
273
|
+
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
|
274
|
+
return ['PULSEWASM_UNSUPPORTED_CLASS', 'classes are not supported in PulseWasm v1 handler bodies.', 'Use top-level functions and explicit data values.'];
|
|
275
|
+
}
|
|
276
|
+
if (ts.isThis(node)) {
|
|
277
|
+
return ['PULSEWASM_UNSUPPORTED_THIS', '`this` is not supported in PulseWasm v1 handler bodies.', 'Use explicit ctx/next parameters only.'];
|
|
278
|
+
}
|
|
279
|
+
if (ts.isNewExpression(node)) {
|
|
280
|
+
return ['PULSEWASM_UNSUPPORTED_NEW', '`new` expressions are not supported in PulseWasm v1 handler bodies.', 'Use allowlisted constructors only after the subset is widened.'];
|
|
281
|
+
}
|
|
282
|
+
if (ts.isSpreadElement(node) || ts.isSpreadAssignment(node)) {
|
|
283
|
+
return ['PULSEWASM_UNSUPPORTED_SPREAD', 'spread syntax is not supported in PulseWasm v1 handler bodies.', 'Use explicit values in the v1 subset.'];
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function makeDiagnostic(code, message, hint, loc, details) {
|
|
289
|
+
const diag = {
|
|
290
|
+
pass: 'handler-eval',
|
|
291
|
+
code,
|
|
292
|
+
severity: 'error',
|
|
293
|
+
message,
|
|
294
|
+
hint,
|
|
295
|
+
loc
|
|
296
|
+
};
|
|
297
|
+
if (details !== undefined) diag.details = details;
|
|
298
|
+
return diag;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function createEvalBase(entry, record, mode) {
|
|
302
|
+
return {
|
|
303
|
+
handlerId: entry.id,
|
|
304
|
+
handlerName: entry.localName || entry.inlineName || entry.name || record?.name || entry.id,
|
|
305
|
+
roles: entry.roles || [],
|
|
306
|
+
mode,
|
|
307
|
+
kind: entry.kind || record?.kind,
|
|
308
|
+
file: entry.file || record?.file,
|
|
309
|
+
loc: entry.loc || record?.loc,
|
|
310
|
+
status: 'ok',
|
|
311
|
+
params: record?.signature?.params || entry.signature?.params || [],
|
|
312
|
+
captures: [],
|
|
313
|
+
calls: [],
|
|
314
|
+
deps: [],
|
|
315
|
+
dependencyChain: [],
|
|
316
|
+
globals: [],
|
|
317
|
+
imports: [],
|
|
318
|
+
fetches: [],
|
|
319
|
+
unsupported: []
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buildHandlerEval(sourceFile, handlerTable, options = {}) {
|
|
324
|
+
const cwd = options.cwd || process.cwd();
|
|
325
|
+
const symbols = collectTopLevelSymbols(sourceFile, cwd);
|
|
326
|
+
const handlersByName = new Map();
|
|
327
|
+
const handlerIdsByName = new Map();
|
|
328
|
+
const diagnostics = [];
|
|
329
|
+
const dependencyEvaluations = [];
|
|
330
|
+
const dependencyCache = new Map();
|
|
331
|
+
const allowedImports = new Set(options.allowedImports || []);
|
|
332
|
+
const allowedImportCalls = new Set(options.allowedImportCalls || []);
|
|
333
|
+
const allowedImportMemberCalls = new Set(options.allowedImportMemberCalls || []);
|
|
334
|
+
const allowedGlobals = new Set([...(options.allowedGlobals || []), ...ALLOWED_LITERAL_GLOBALS]);
|
|
335
|
+
|
|
336
|
+
for (const handler of handlerTable.handlers || []) {
|
|
337
|
+
if (handler.localName) {
|
|
338
|
+
handlersByName.set(handler.localName, handler);
|
|
339
|
+
handlerIdsByName.set(handler.localName, handler.id);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function locateHandlerRecord(handler) {
|
|
344
|
+
if (handler.localName && symbols.functionsByName.has(handler.localName)) return symbols.functionsByName.get(handler.localName);
|
|
345
|
+
const start = handler.loc?.start?.offset;
|
|
346
|
+
if (typeof start === 'number' && symbols.inlineFunctionsByOffset.has(start)) return symbols.inlineFunctionsByOffset.get(start);
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function evaluateRecord(entry, record, mode, stack = []) {
|
|
351
|
+
const cacheKey = entry.id || `local:${entry.name}`;
|
|
352
|
+
const evaluation = createEvalBase(entry, record, mode);
|
|
353
|
+
const unsupportedKeys = new Set();
|
|
354
|
+
|
|
355
|
+
function addUnsupported(code, message, hint, loc, details) {
|
|
356
|
+
const key = `${code}:${sourceKey(loc)}:${message}`;
|
|
357
|
+
if (unsupportedKeys.has(key)) return;
|
|
358
|
+
unsupportedKeys.add(key);
|
|
359
|
+
const item = { code, message, hint, loc };
|
|
360
|
+
if (details !== undefined) item.details = details;
|
|
361
|
+
evaluation.unsupported.push(item);
|
|
362
|
+
diagnostics.push(makeDiagnostic(code, `${evaluation.handlerName}: ${message}`, hint, loc || evaluation.loc, details));
|
|
363
|
+
evaluation.status = 'error';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!record || !record.node) {
|
|
367
|
+
addUnsupported(
|
|
368
|
+
'PULSEWASM_HANDLER_NODE_NOT_FOUND',
|
|
369
|
+
'Unable to find the AST node for this handler.',
|
|
370
|
+
'Handlers must be top-level declarations or direct inline function expressions in the analyzed entry file.',
|
|
371
|
+
entry.loc
|
|
372
|
+
);
|
|
373
|
+
return evaluation;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const rootFunction = record.node;
|
|
377
|
+
const rootBody = rootFunction.body;
|
|
378
|
+
const rootParams = Array.from(rootFunction.parameters || []);
|
|
379
|
+
const scopes = [new Map()];
|
|
380
|
+
|
|
381
|
+
function currentScope() {
|
|
382
|
+
return scopes[scopes.length - 1];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function pushScope() {
|
|
386
|
+
scopes.push(new Map());
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function popScope() {
|
|
390
|
+
scopes.pop();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function addLocal(name, kind, loc) {
|
|
394
|
+
currentScope().set(name, { name, kind, loc });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const parameter of rootParams) {
|
|
398
|
+
if (ts.isIdentifier(parameter.name)) addLocal(parameter.name.text, 'param', sourceLoc(sourceFile, parameter.name));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function resolveName(name) {
|
|
402
|
+
for (let i = scopes.length - 1; i >= 0; i -= 1) {
|
|
403
|
+
const found = scopes[i].get(name);
|
|
404
|
+
if (found) return { ...found, scope: 'local' };
|
|
405
|
+
}
|
|
406
|
+
if (symbols.functionsByName.has(name)) return { scope: 'top-level-function', ...symbols.functionsByName.get(name) };
|
|
407
|
+
if (symbols.variablesByName.has(name)) return { scope: 'top-level-variable', ...symbols.variablesByName.get(name) };
|
|
408
|
+
if (symbols.importsByName.has(name)) return { scope: 'import', ...symbols.importsByName.get(name) };
|
|
409
|
+
if (allowedGlobals.has(name)) return { scope: 'allowed-global', name };
|
|
410
|
+
return { scope: 'global', name };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function addCapture(name, resolution, loc) {
|
|
414
|
+
const capture = {
|
|
415
|
+
name,
|
|
416
|
+
kind: resolution.scope === 'top-level-variable' ? resolution.declarationKind : resolution.scope,
|
|
417
|
+
mutable: resolution.scope === 'top-level-variable' && resolution.declarationKind !== 'const',
|
|
418
|
+
loc
|
|
419
|
+
};
|
|
420
|
+
evaluation.captures.push(capture);
|
|
421
|
+
addUnsupported(
|
|
422
|
+
capture.mutable ? 'PULSEWASM_UNSUPPORTED_MUTABLE_CAPTURE' : 'PULSEWASM_UNSUPPORTED_CLOSURE_CAPTURE',
|
|
423
|
+
`Closure capture of outer ${capture.mutable ? 'mutable ' : ''}symbol "${name}" is not supported.`,
|
|
424
|
+
'Pass values through ctx or a future explicit binding surface instead of closing over module scope.',
|
|
425
|
+
loc,
|
|
426
|
+
{ name, capture }
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function addImportUse(name, resolution, loc) {
|
|
431
|
+
const importUse = {
|
|
432
|
+
name,
|
|
433
|
+
importedName: resolution.importedName,
|
|
434
|
+
kind: resolution.kind,
|
|
435
|
+
module: resolution.module,
|
|
436
|
+
allowed: allowedImports.has(resolution.module),
|
|
437
|
+
loc
|
|
438
|
+
};
|
|
439
|
+
evaluation.imports.push(importUse);
|
|
440
|
+
if (!importUse.allowed) {
|
|
441
|
+
const network = NETWORK_IMPORT_MODULES.has(resolution.module) || NETWORK_IMPORT_MODULES.has(String(resolution.module).replace(/^node:/, 'node:'));
|
|
442
|
+
if (network) {
|
|
443
|
+
addUnsupported(
|
|
444
|
+
'PULSEWASM_FORBIDDEN_NETWORK_IMPORT',
|
|
445
|
+
`Network dependency "${resolution.module}" is not allowed in PulseWasm handler code.`,
|
|
446
|
+
'Use the declared backend capability surface and ctx.fetch("backendKey", { ... }) instead.',
|
|
447
|
+
loc,
|
|
448
|
+
importUse
|
|
449
|
+
);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
addUnsupported(
|
|
453
|
+
'PULSEWASM_FORBIDDEN_IMPORT',
|
|
454
|
+
`Imported dependency "${name}" from "${resolution.module}" is not allowed in PulseWasm v1 handler evaluation.`,
|
|
455
|
+
'Inline the logic into the supported subset or add a future explicit allowlisted import resolver.',
|
|
456
|
+
loc,
|
|
457
|
+
importUse
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function addGlobalUse(name, loc) {
|
|
463
|
+
if (allowedGlobals.has(name)) return;
|
|
464
|
+
const globalUse = { name, loc };
|
|
465
|
+
evaluation.globals.push(globalUse);
|
|
466
|
+
addUnsupported(
|
|
467
|
+
'PULSEWASM_UNSUPPORTED_GLOBAL',
|
|
468
|
+
`Global "${name}" is not supported in PulseWasm v1 handler bodies.`,
|
|
469
|
+
'Use only ctx, next, local variables, and direct same-file helper calls in the v1 subset.',
|
|
470
|
+
loc,
|
|
471
|
+
globalUse
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function isAllowedImportCall(resolution, localName) {
|
|
476
|
+
if (!resolution || resolution.scope !== 'import') return false;
|
|
477
|
+
return allowedImportCalls.has(`${resolution.module}:${resolution.importedName}`) || allowedImportCalls.has(`${resolution.module}:${localName}`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function isAllowedImportMemberCall(resolution, memberName) {
|
|
481
|
+
if (!resolution || resolution.scope !== 'import') return false;
|
|
482
|
+
return allowedImportMemberCalls.has(`${resolution.module}:${resolution.importedName}.${memberName}`) || allowedImportMemberCalls.has(`${resolution.module}:${resolution.name}.${memberName}`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function addDependency(name, resolution, loc) {
|
|
486
|
+
const handlerId = handlerIdsByName.get(name);
|
|
487
|
+
const dep = {
|
|
488
|
+
name,
|
|
489
|
+
kind: 'local-function',
|
|
490
|
+
resolved: true,
|
|
491
|
+
handlerId,
|
|
492
|
+
sourceTextHash: resolution.sourceTextHash,
|
|
493
|
+
loc
|
|
494
|
+
};
|
|
495
|
+
evaluation.deps.push(dep);
|
|
496
|
+
return dep;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function evaluateDependency(name, resolution, loc) {
|
|
500
|
+
const depKey = `local:${name}:${resolution.sourceTextHash || ''}`;
|
|
501
|
+
if (stack.includes(name)) {
|
|
502
|
+
addUnsupported(
|
|
503
|
+
'PULSEWASM_UNSUPPORTED_RECURSION',
|
|
504
|
+
`Recursive helper dependency detected: ${[...stack, name].join(' -> ')}.`,
|
|
505
|
+
'PulseWasm v1 requires an acyclic direct helper dependency chain.',
|
|
506
|
+
loc,
|
|
507
|
+
{ chain: [...stack, name] }
|
|
508
|
+
);
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
if (!dependencyCache.has(depKey)) {
|
|
512
|
+
const depEntry = {
|
|
513
|
+
id: handlerIdsByName.get(name) || `local_${sourceTextHash(`${resolution.file}:${name}:${resolution.sourceTextHash}`)}`,
|
|
514
|
+
name,
|
|
515
|
+
localName: name,
|
|
516
|
+
roles: handlerIdsByName.has(name) ? ['handler-dependency'] : ['local-dependency'],
|
|
517
|
+
kind: resolution.kind,
|
|
518
|
+
file: resolution.file,
|
|
519
|
+
loc: resolution.loc,
|
|
520
|
+
signature: resolution.signature
|
|
521
|
+
};
|
|
522
|
+
const depEval = evaluateRecord(depEntry, resolution, 'dependency', [...stack, name]);
|
|
523
|
+
dependencyCache.set(depKey, depEval);
|
|
524
|
+
dependencyEvaluations.push(depEval);
|
|
525
|
+
}
|
|
526
|
+
const depEval = dependencyCache.get(depKey);
|
|
527
|
+
evaluation.dependencyChain.push({
|
|
528
|
+
name,
|
|
529
|
+
handlerId: handlerIdsByName.get(name),
|
|
530
|
+
status: depEval.status,
|
|
531
|
+
deps: depEval.deps.map((dep) => dep.name),
|
|
532
|
+
unsupported: depEval.unsupported.map((item) => item.code)
|
|
533
|
+
});
|
|
534
|
+
if (depEval.status !== 'ok') evaluation.status = 'error';
|
|
535
|
+
return depEval;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function handleIdentifierReference(node) {
|
|
539
|
+
if (!isIdentifierReference(node)) return;
|
|
540
|
+
const name = node.text;
|
|
541
|
+
const loc = sourceLoc(sourceFile, node);
|
|
542
|
+
const resolution = resolveName(name);
|
|
543
|
+
if (resolution.scope === 'local' || resolution.scope === 'allowed-global') return;
|
|
544
|
+
if (resolution.scope === 'top-level-function') {
|
|
545
|
+
addUnsupported(
|
|
546
|
+
'PULSEWASM_UNSUPPORTED_FUNCTION_REFERENCE',
|
|
547
|
+
`Function "${name}" is referenced as a value instead of called directly.`,
|
|
548
|
+
'Only direct same-file helper calls are supported in handler bodies.',
|
|
549
|
+
loc,
|
|
550
|
+
{ name }
|
|
551
|
+
);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (resolution.scope === 'top-level-variable') {
|
|
555
|
+
addCapture(name, resolution, loc);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (resolution.scope === 'import') {
|
|
559
|
+
addImportUse(name, resolution, loc);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
addGlobalUse(name, loc);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
function propertyValue(objectNode, key) {
|
|
567
|
+
if (!objectNode || !ts.isObjectLiteralExpression(objectNode)) return undefined;
|
|
568
|
+
for (const prop of objectNode.properties) {
|
|
569
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
570
|
+
const name = prop.name;
|
|
571
|
+
if ((ts.isIdentifier(name) || isStringLiteralLike(name)) && name.text === key) return prop.initializer;
|
|
572
|
+
}
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
function propertyAccessChain(node) {
|
|
579
|
+
const parts = [];
|
|
580
|
+
let current = node;
|
|
581
|
+
while (current && ts.isPropertyAccessExpression(current)) {
|
|
582
|
+
parts.unshift(current.name.text);
|
|
583
|
+
current = current.expression;
|
|
584
|
+
}
|
|
585
|
+
if (current && ts.isIdentifier(current)) parts.unshift(current.text);
|
|
586
|
+
else return undefined;
|
|
587
|
+
return parts;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function isTrustedCtxDslCall(node, chain) {
|
|
591
|
+
if (!chain || chain.length < 2) return false;
|
|
592
|
+
const objectName = chain[0];
|
|
593
|
+
const objectResolution = resolveName(objectName);
|
|
594
|
+
if (!(objectResolution.scope === 'local' && objectResolution.kind === 'param')) return false;
|
|
595
|
+
const suffix = chain.slice(1).join('.');
|
|
596
|
+
return new Set([
|
|
597
|
+
'param',
|
|
598
|
+
'paramI32',
|
|
599
|
+
'state.get',
|
|
600
|
+
'state.set',
|
|
601
|
+
'request.method',
|
|
602
|
+
'request.path',
|
|
603
|
+
'request.header.first',
|
|
604
|
+
'request.header.count',
|
|
605
|
+
'request.header.at',
|
|
606
|
+
'req.json',
|
|
607
|
+
'request.json',
|
|
608
|
+
'req.parse',
|
|
609
|
+
'response.header.set',
|
|
610
|
+
'response.header.append',
|
|
611
|
+
'response.header.delete',
|
|
612
|
+
'result.text',
|
|
613
|
+
'result.empty',
|
|
614
|
+
'result.jsonText',
|
|
615
|
+
'result.json',
|
|
616
|
+
'schema.encode',
|
|
617
|
+
'resolved',
|
|
618
|
+
'error'
|
|
619
|
+
]).has(suffix);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function parseStaticFetchRequest(node, objectName) {
|
|
623
|
+
const loc = sourceLoc(sourceFile, node);
|
|
624
|
+
const usage = {
|
|
625
|
+
target: `${objectName}.fetch`,
|
|
626
|
+
object: objectName,
|
|
627
|
+
loc,
|
|
628
|
+
static: false,
|
|
629
|
+
backend: undefined,
|
|
630
|
+
method: undefined,
|
|
631
|
+
path: undefined,
|
|
632
|
+
diagnostics: []
|
|
633
|
+
};
|
|
634
|
+
if (node.arguments.length !== 2) {
|
|
635
|
+
addUnsupported(
|
|
636
|
+
'PULSEWASM_DYNAMIC_FETCH_REQUEST',
|
|
637
|
+
`${objectName}.fetch expects exactly a backend key and a static request object in PulseWasm v1.`,
|
|
638
|
+
'Use ctx.fetch("backendKey", { method: "GET", path: "/path" }).',
|
|
639
|
+
loc,
|
|
640
|
+
{ argCount: node.arguments.length }
|
|
641
|
+
);
|
|
642
|
+
return usage;
|
|
643
|
+
}
|
|
644
|
+
const backendArg = node.arguments[0];
|
|
645
|
+
if (!isStringLiteralLike(backendArg)) {
|
|
646
|
+
addUnsupported(
|
|
647
|
+
'PULSEWASM_DYNAMIC_BACKEND_KEY',
|
|
648
|
+
'ctx.fetch backend key must be a string literal in PulseWasm v1.',
|
|
649
|
+
'Declare the backend in defineConfig.backends and call ctx.fetch("backendKey", ...).',
|
|
650
|
+
sourceLoc(sourceFile, backendArg),
|
|
651
|
+
{ target: nodeText(sourceFile, backendArg) }
|
|
652
|
+
);
|
|
653
|
+
} else {
|
|
654
|
+
usage.backend = backendArg.text;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const requestArg = node.arguments[1];
|
|
658
|
+
if (!ts.isObjectLiteralExpression(requestArg)) {
|
|
659
|
+
addUnsupported(
|
|
660
|
+
'PULSEWASM_DYNAMIC_FETCH_REQUEST',
|
|
661
|
+
'ctx.fetch request must be an object literal in PulseWasm v1.',
|
|
662
|
+
'Use ctx.fetch("backendKey", { method: "GET", path: "/path" }).',
|
|
663
|
+
sourceLoc(sourceFile, requestArg),
|
|
664
|
+
{ target: nodeText(sourceFile, requestArg) }
|
|
665
|
+
);
|
|
666
|
+
return usage;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const methodNode = propertyValue(requestArg, 'method');
|
|
670
|
+
const pathNode = propertyValue(requestArg, 'path');
|
|
671
|
+
if (!methodNode || !isStringLiteralLike(methodNode)) {
|
|
672
|
+
addUnsupported(
|
|
673
|
+
'PULSEWASM_DYNAMIC_FETCH_REQUEST',
|
|
674
|
+
'ctx.fetch method must be a string literal in PulseWasm v1.',
|
|
675
|
+
'Use method: "GET" or another method explicitly allowed by the backend declaration.',
|
|
676
|
+
methodNode ? sourceLoc(sourceFile, methodNode) : sourceLoc(sourceFile, requestArg),
|
|
677
|
+
{ field: 'method' }
|
|
678
|
+
);
|
|
679
|
+
} else {
|
|
680
|
+
usage.method = methodNode.text.toUpperCase();
|
|
681
|
+
}
|
|
682
|
+
if (!pathNode) {
|
|
683
|
+
addUnsupported(
|
|
684
|
+
'PULSEWASM_DYNAMIC_FETCH_REQUEST',
|
|
685
|
+
'ctx.fetch path is required in PulseWasm v1.',
|
|
686
|
+
'Use path: "/path" or a Pass 30 route-effect lowerable expression with literals, +, and ctx.param(...).',
|
|
687
|
+
sourceLoc(sourceFile, requestArg),
|
|
688
|
+
{ field: 'path' }
|
|
689
|
+
);
|
|
690
|
+
} else {
|
|
691
|
+
usage.path = isStringLiteralLike(pathNode) ? pathNode.text : nodeText(sourceFile, pathNode);
|
|
692
|
+
usage.pathLowering = isStringLiteralLike(pathNode) ? 'literal' : 'route-handler-effect-plan';
|
|
693
|
+
}
|
|
694
|
+
usage.static = Boolean(usage.backend && usage.method && usage.path);
|
|
695
|
+
return usage;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function handleCall(node) {
|
|
699
|
+
const callee = node.expression;
|
|
700
|
+
const loc = sourceLoc(sourceFile, node);
|
|
701
|
+
if (ts.isIdentifier(callee)) {
|
|
702
|
+
const name = callee.text;
|
|
703
|
+
const targetLoc = sourceLoc(sourceFile, callee);
|
|
704
|
+
const resolution = resolveName(name);
|
|
705
|
+
if (name === 'fetch') {
|
|
706
|
+
evaluation.calls.push({ target: 'fetch', resolved: false, kind: 'global-fetch', loc });
|
|
707
|
+
addUnsupported(
|
|
708
|
+
'PULSEWASM_FORBIDDEN_GLOBAL_FETCH',
|
|
709
|
+
'Global fetch() is not allowed in PulseWasm handler code.',
|
|
710
|
+
'Declare a backend in defineConfig.backends and use ctx.fetch("backendKey", { ... }).',
|
|
711
|
+
targetLoc,
|
|
712
|
+
{ target: 'fetch' }
|
|
713
|
+
);
|
|
714
|
+
} else if (resolution.scope === 'local' && resolution.kind === 'param' && name === 'next') {
|
|
715
|
+
evaluation.calls.push({
|
|
716
|
+
target: name,
|
|
717
|
+
resolved: true,
|
|
718
|
+
kind: node.arguments.length > 0 ? 'continuation-error' : 'continuation',
|
|
719
|
+
argCount: node.arguments.length,
|
|
720
|
+
transition: node.arguments.length > 0 ? 'error' : 'normal',
|
|
721
|
+
loc
|
|
722
|
+
});
|
|
723
|
+
} else if (resolution.scope === 'top-level-function') {
|
|
724
|
+
const dep = addDependency(name, resolution, targetLoc);
|
|
725
|
+
evaluation.calls.push({ target: name, resolved: true, kind: 'local-function', handlerId: dep.handlerId, loc });
|
|
726
|
+
evaluateDependency(name, resolution, targetLoc);
|
|
727
|
+
} else if (resolution.scope === 'import') {
|
|
728
|
+
addImportUse(name, resolution, targetLoc);
|
|
729
|
+
if (isAllowedImportCall(resolution, name)) {
|
|
730
|
+
evaluation.calls.push({
|
|
731
|
+
target: name,
|
|
732
|
+
resolved: true,
|
|
733
|
+
kind: 'lowerable-import',
|
|
734
|
+
module: resolution.module,
|
|
735
|
+
importedName: resolution.importedName,
|
|
736
|
+
loc
|
|
737
|
+
});
|
|
738
|
+
} else {
|
|
739
|
+
evaluation.calls.push({ target: name, resolved: false, kind: 'import', module: resolution.module, loc });
|
|
740
|
+
addUnsupported(
|
|
741
|
+
'PULSEWASM_UNRESOLVED_HANDLER_DEPENDENCY',
|
|
742
|
+
`Imported call target "${name}" cannot be resolved by the v1 same-file evaluator.`,
|
|
743
|
+
'Use direct same-file helpers or wait for an explicit import resolver phase.',
|
|
744
|
+
targetLoc,
|
|
745
|
+
{ name, module: resolution.module }
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
} else if (resolution.scope === 'top-level-variable') {
|
|
749
|
+
evaluation.calls.push({ target: name, resolved: false, kind: 'captured-variable', loc });
|
|
750
|
+
addCapture(name, resolution, targetLoc);
|
|
751
|
+
addUnsupported(
|
|
752
|
+
'PULSEWASM_UNSUPPORTED_DYNAMIC_CALL',
|
|
753
|
+
`Call target "${name}" resolves to a top-level variable, not a direct function declaration.`,
|
|
754
|
+
'Use a top-level function declaration or const arrow/function expression helper.',
|
|
755
|
+
targetLoc,
|
|
756
|
+
{ name }
|
|
757
|
+
);
|
|
758
|
+
} else if (resolution.scope === 'local') {
|
|
759
|
+
evaluation.calls.push({ target: name, resolved: false, kind: 'local-value', loc });
|
|
760
|
+
addUnsupported(
|
|
761
|
+
'PULSEWASM_UNSUPPORTED_DYNAMIC_CALL',
|
|
762
|
+
`Local value "${name}" is called dynamically.`,
|
|
763
|
+
'PulseWasm v1 only allows next() and direct same-file helper calls.',
|
|
764
|
+
targetLoc,
|
|
765
|
+
{ name }
|
|
766
|
+
);
|
|
767
|
+
} else {
|
|
768
|
+
evaluation.calls.push({ target: name, resolved: false, kind: resolution.scope, loc });
|
|
769
|
+
addGlobalUse(name, targetLoc);
|
|
770
|
+
addUnsupported(
|
|
771
|
+
'PULSEWASM_UNRESOLVED_HANDLER_DEPENDENCY',
|
|
772
|
+
`Call target "${name}" could not be statically resolved.`,
|
|
773
|
+
'Use next() or a direct same-file helper function.',
|
|
774
|
+
targetLoc,
|
|
775
|
+
{ name }
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
for (const arg of node.arguments) walk(arg);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
783
|
+
const target = nodeText(sourceFile, callee);
|
|
784
|
+
const chain = propertyAccessChain(callee);
|
|
785
|
+
if (isTrustedCtxDslCall(node, chain)) {
|
|
786
|
+
evaluation.calls.push({ target, resolved: true, kind: 'trusted-ctx-dsl', loc });
|
|
787
|
+
for (const arg of node.arguments) walk(arg);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const suffix = chain && chain.length >= 2 ? chain.slice(1).join('.') : undefined;
|
|
791
|
+
if (suffix === 'resolve') {
|
|
792
|
+
const objectName = chain[0];
|
|
793
|
+
const objectResolution = resolveName(objectName);
|
|
794
|
+
if (objectResolution.scope === 'local' && objectResolution.kind === 'param') {
|
|
795
|
+
evaluation.calls.push({
|
|
796
|
+
target,
|
|
797
|
+
resolved: true,
|
|
798
|
+
kind: 'ctx-resolve',
|
|
799
|
+
argCount: node.arguments.length,
|
|
800
|
+
loc
|
|
801
|
+
});
|
|
802
|
+
if (node.arguments[0]) walk(node.arguments[0]);
|
|
803
|
+
// The continuation argument is intentionally not walked as a normal
|
|
804
|
+
// function-value reference; route-handler-effect-plan validates that
|
|
805
|
+
// it is a top-level named continuation.
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
const memberName = callee.name.text;
|
|
810
|
+
if (ts.isIdentifier(callee.expression)) {
|
|
811
|
+
const objectResolution = resolveName(callee.expression.text);
|
|
812
|
+
if (isAllowedImportMemberCall(objectResolution, memberName)) {
|
|
813
|
+
addImportUse(callee.expression.text, objectResolution, sourceLoc(sourceFile, callee.expression));
|
|
814
|
+
evaluation.calls.push({
|
|
815
|
+
target,
|
|
816
|
+
resolved: true,
|
|
817
|
+
kind: 'lowerable-import-member',
|
|
818
|
+
module: objectResolution.module,
|
|
819
|
+
importedName: objectResolution.importedName,
|
|
820
|
+
member: memberName,
|
|
821
|
+
loc
|
|
822
|
+
});
|
|
823
|
+
for (const arg of node.arguments) walk(arg);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (memberName === 'fetch' && ts.isIdentifier(callee.expression)) {
|
|
828
|
+
const objectName = callee.expression.text;
|
|
829
|
+
const objectResolution = resolveName(objectName);
|
|
830
|
+
if (objectResolution.scope === 'local' && objectResolution.kind === 'param') {
|
|
831
|
+
const fetchUsage = parseStaticFetchRequest(node, objectName);
|
|
832
|
+
evaluation.fetches.push(fetchUsage);
|
|
833
|
+
evaluation.calls.push({
|
|
834
|
+
target,
|
|
835
|
+
resolved: fetchUsage.static,
|
|
836
|
+
kind: 'ctx-fetch',
|
|
837
|
+
backend: fetchUsage.backend,
|
|
838
|
+
method: fetchUsage.method,
|
|
839
|
+
path: fetchUsage.path,
|
|
840
|
+
static: fetchUsage.static,
|
|
841
|
+
loc
|
|
842
|
+
});
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (ts.isIdentifier(callee.expression) && ['ok', 'errorText', 'jsonText', 'json', 'getString', 'getI32', 'getU32', 'getF64', 'getBool', 'has'].includes(memberName)) {
|
|
847
|
+
const objectResolution = resolveName(callee.expression.text);
|
|
848
|
+
if (objectResolution.scope === 'local') {
|
|
849
|
+
evaluation.calls.push({ target, resolved: true, kind: 'request-json-body-accessor', loc });
|
|
850
|
+
for (const arg of node.arguments) walk(arg);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (target === 'globalThis.fetch') {
|
|
855
|
+
evaluation.calls.push({ target, resolved: false, kind: 'global-fetch', loc });
|
|
856
|
+
addUnsupported(
|
|
857
|
+
'PULSEWASM_FORBIDDEN_GLOBAL_FETCH',
|
|
858
|
+
'globalThis.fetch() is not allowed in PulseWasm handler code.',
|
|
859
|
+
'Declare a backend in defineConfig.backends and use ctx.fetch("backendKey", { ... }).',
|
|
860
|
+
sourceLoc(sourceFile, callee),
|
|
861
|
+
{ target }
|
|
862
|
+
);
|
|
863
|
+
for (const arg of node.arguments) walk(arg);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (ts.isIdentifier(callee.expression)) {
|
|
867
|
+
const objectResolution = resolveName(callee.expression.text);
|
|
868
|
+
if (objectResolution.scope === 'import' && NETWORK_IMPORT_MODULES.has(objectResolution.module)) {
|
|
869
|
+
evaluation.calls.push({ target, resolved: false, kind: 'network-import', module: objectResolution.module, loc });
|
|
870
|
+
addImportUse(callee.expression.text, objectResolution, sourceLoc(sourceFile, callee.expression));
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
evaluation.calls.push({
|
|
875
|
+
target,
|
|
876
|
+
resolved: false,
|
|
877
|
+
kind: 'member',
|
|
878
|
+
loc
|
|
879
|
+
});
|
|
880
|
+
addUnsupported(
|
|
881
|
+
'PULSEWASM_UNSUPPORTED_MEMBER_CALL',
|
|
882
|
+
`Member call "${target}" is not supported in PulseWasm v1 handler evaluation.`,
|
|
883
|
+
'Use direct allowlisted helper calls only. Builder/ctx methods need an explicit future lowering contract.',
|
|
884
|
+
sourceLoc(sourceFile, callee),
|
|
885
|
+
{ target }
|
|
886
|
+
);
|
|
887
|
+
walk(callee.expression);
|
|
888
|
+
for (const arg of node.arguments) walk(arg);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (ts.isElementAccessExpression(callee)) {
|
|
893
|
+
evaluation.calls.push({ target: nodeText(sourceFile, callee), resolved: false, kind: 'computed', loc });
|
|
894
|
+
addUnsupported(
|
|
895
|
+
'PULSEWASM_UNSUPPORTED_COMPUTED_ACCESS',
|
|
896
|
+
`Computed call target "${nodeText(sourceFile, callee)}" is not supported.`,
|
|
897
|
+
'Use direct identifier calls only in the v1 subset.',
|
|
898
|
+
sourceLoc(sourceFile, callee),
|
|
899
|
+
{ target: nodeText(sourceFile, callee) }
|
|
900
|
+
);
|
|
901
|
+
walk(callee.expression);
|
|
902
|
+
walk(callee.argumentExpression);
|
|
903
|
+
for (const arg of node.arguments) walk(arg);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
evaluation.calls.push({ target: nodeText(sourceFile, callee), resolved: false, kind: 'dynamic', loc });
|
|
908
|
+
addUnsupported(
|
|
909
|
+
'PULSEWASM_UNSUPPORTED_DYNAMIC_CALL',
|
|
910
|
+
`Dynamic call target "${nodeText(sourceFile, callee)}" is not supported.`,
|
|
911
|
+
'Use next() or a direct same-file helper function.',
|
|
912
|
+
sourceLoc(sourceFile, callee),
|
|
913
|
+
{ target: nodeText(sourceFile, callee) }
|
|
914
|
+
);
|
|
915
|
+
walk(callee);
|
|
916
|
+
for (const arg of node.arguments) walk(arg);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function handleAssignment(node) {
|
|
920
|
+
const loc = sourceLoc(sourceFile, node);
|
|
921
|
+
const left = node.left;
|
|
922
|
+
if (ts.isIdentifier(left)) {
|
|
923
|
+
const name = left.text;
|
|
924
|
+
const resolution = resolveName(name);
|
|
925
|
+
if (resolution.scope === 'local' && resolution.kind !== 'param') {
|
|
926
|
+
walk(node.right);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
addUnsupported(
|
|
930
|
+
resolution.scope === 'top-level-variable' ? 'PULSEWASM_UNSUPPORTED_OUTER_MUTATION' : 'PULSEWASM_UNSUPPORTED_ASSIGNMENT_TARGET',
|
|
931
|
+
`Assignment to "${name}" is not supported in PulseWasm v1 handler bodies.`,
|
|
932
|
+
'Only local non-parameter variables may be assigned after declaration in the v1 subset.',
|
|
933
|
+
loc,
|
|
934
|
+
{ name, resolution: resolution.scope }
|
|
935
|
+
);
|
|
936
|
+
walk(node.right);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
addUnsupported(
|
|
940
|
+
'PULSEWASM_UNSUPPORTED_ASSIGNMENT_TARGET',
|
|
941
|
+
`Assignment target "${nodeText(sourceFile, left)}" is not supported in PulseWasm v1 handler bodies.`,
|
|
942
|
+
'Avoid object/property mutation until the lowering contract explicitly supports it.',
|
|
943
|
+
sourceLoc(sourceFile, left),
|
|
944
|
+
{ target: nodeText(sourceFile, left) }
|
|
945
|
+
);
|
|
946
|
+
walk(left);
|
|
947
|
+
walk(node.right);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function handleVariableDeclaration(node) {
|
|
951
|
+
if (!ts.isIdentifier(node.name)) {
|
|
952
|
+
addUnsupported(
|
|
953
|
+
'PULSEWASM_UNSUPPORTED_BINDING_PATTERN',
|
|
954
|
+
`Binding pattern "${nodeText(sourceFile, node.name)}" is not supported in PulseWasm v1 handler bodies.`,
|
|
955
|
+
'Use a simple identifier declaration.',
|
|
956
|
+
sourceLoc(sourceFile, node.name),
|
|
957
|
+
{ target: nodeText(sourceFile, node.name) }
|
|
958
|
+
);
|
|
959
|
+
if (node.initializer) walk(node.initializer);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if (node.initializer && ts.isIdentifier(node.initializer) && node.initializer.text === 'next') {
|
|
963
|
+
addUnsupported(
|
|
964
|
+
'PULSEWASM_UNSUPPORTED_NEXT_ESCAPE',
|
|
965
|
+
'Aliasing next is not supported in PulseWasm v1 handler bodies.',
|
|
966
|
+
'Call next() or next(err) directly so the continuation remains statically visible.',
|
|
967
|
+
sourceLoc(sourceFile, node.initializer),
|
|
968
|
+
{ target: node.name.text }
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
if (node.initializer) walk(node.initializer);
|
|
972
|
+
addLocal(node.name.text, 'local', sourceLoc(sourceFile, node.name));
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function walk(node) {
|
|
976
|
+
if (!node) return;
|
|
977
|
+
|
|
978
|
+
const unsupported = unsupportedSyntaxForNode(node);
|
|
979
|
+
if (unsupported) {
|
|
980
|
+
addUnsupported(unsupported[0], unsupported[1], unsupported[2], sourceLoc(sourceFile, node));
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (isFunctionLike(node)) {
|
|
984
|
+
addUnsupported(
|
|
985
|
+
'PULSEWASM_UNSUPPORTED_NESTED_FUNCTION',
|
|
986
|
+
'Nested function declarations/expressions are not supported in PulseWasm v1 handler bodies.',
|
|
987
|
+
'Use top-level helper functions only.',
|
|
988
|
+
sourceLoc(sourceFile, node),
|
|
989
|
+
{ syntaxKind: ts.SyntaxKind[node.kind] }
|
|
990
|
+
);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (ts.isBlock(node)) {
|
|
995
|
+
pushScope();
|
|
996
|
+
for (const statement of node.statements) walk(statement);
|
|
997
|
+
popScope();
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (ts.isVariableDeclaration(node)) {
|
|
1002
|
+
handleVariableDeclaration(node);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (ts.isVariableStatement(node)) {
|
|
1007
|
+
for (const declaration of node.declarationList.declarations) walk(declaration);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (ts.isCallExpression(node)) {
|
|
1012
|
+
handleCall(node);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
1017
|
+
walk(node.expression);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (ts.isElementAccessExpression(node)) {
|
|
1022
|
+
addUnsupported(
|
|
1023
|
+
'PULSEWASM_UNSUPPORTED_COMPUTED_ACCESS',
|
|
1024
|
+
`Computed property access "${nodeText(sourceFile, node)}" is not supported in PulseWasm v1 handler bodies.`,
|
|
1025
|
+
'Use direct property access only, and only where the lowering contract allows it.',
|
|
1026
|
+
sourceLoc(sourceFile, node),
|
|
1027
|
+
{ target: nodeText(sourceFile, node) }
|
|
1028
|
+
);
|
|
1029
|
+
walk(node.expression);
|
|
1030
|
+
walk(node.argumentExpression);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (ts.isBinaryExpression(node) && ASSIGNMENT_OPERATORS.has(node.operatorToken.kind)) {
|
|
1035
|
+
handleAssignment(node);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node)) {
|
|
1040
|
+
if (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken) {
|
|
1041
|
+
addUnsupported(
|
|
1042
|
+
'PULSEWASM_UNSUPPORTED_MUTATION',
|
|
1043
|
+
`Update expression "${nodeText(sourceFile, node)}" is not supported in PulseWasm v1 handler bodies.`,
|
|
1044
|
+
'Use explicit local assignment only if needed.',
|
|
1045
|
+
sourceLoc(sourceFile, node),
|
|
1046
|
+
{ target: nodeText(sourceFile, node) }
|
|
1047
|
+
);
|
|
1048
|
+
walk(node.operand);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (ts.isPropertyAssignment(node)) {
|
|
1054
|
+
if (node.name && ts.isComputedPropertyName(node.name)) walk(node.name.expression);
|
|
1055
|
+
walk(node.initializer);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (ts.isShorthandPropertyAssignment(node)) {
|
|
1060
|
+
handleIdentifierReference(node.name);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (ts.isIdentifier(node)) {
|
|
1065
|
+
handleIdentifierReference(node);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
ts.forEachChild(node, walk);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (!rootBody) {
|
|
1073
|
+
addUnsupported(
|
|
1074
|
+
'PULSEWASM_HANDLER_BODY_REQUIRED',
|
|
1075
|
+
'Handler has no body to evaluate.',
|
|
1076
|
+
'Declare handlers with an explicit function body in the v1 subset.',
|
|
1077
|
+
record.functionLoc || entry.loc
|
|
1078
|
+
);
|
|
1079
|
+
} else if (ts.isBlock(rootBody)) {
|
|
1080
|
+
for (const statement of rootBody.statements) walk(statement);
|
|
1081
|
+
} else {
|
|
1082
|
+
walk(rootBody);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
evaluation.captures = uniqueByKey(evaluation.captures, (item) => `${item.name}:${sourceKey(item.loc)}`);
|
|
1086
|
+
evaluation.calls = uniqueByKey(evaluation.calls, (item) => `${item.kind}:${item.target}:${sourceKey(item.loc)}`);
|
|
1087
|
+
evaluation.deps = uniqueByKey(evaluation.deps, (item) => `${item.name}:${item.handlerId || ''}`);
|
|
1088
|
+
evaluation.globals = uniqueByKey(evaluation.globals, (item) => `${item.name}:${sourceKey(item.loc)}`);
|
|
1089
|
+
evaluation.imports = uniqueByKey(evaluation.imports, (item) => `${item.name}:${item.module}:${sourceKey(item.loc)}`);
|
|
1090
|
+
evaluation.dependencyChain = uniqueByKey(evaluation.dependencyChain, (item) => `${item.name}:${item.handlerId || ''}`);
|
|
1091
|
+
evaluation.summary = {
|
|
1092
|
+
captures: evaluation.captures.length,
|
|
1093
|
+
calls: evaluation.calls.length,
|
|
1094
|
+
deps: evaluation.deps.length,
|
|
1095
|
+
dependencyChain: evaluation.dependencyChain.length,
|
|
1096
|
+
globals: evaluation.globals.length,
|
|
1097
|
+
imports: evaluation.imports.length,
|
|
1098
|
+
fetches: evaluation.fetches.length,
|
|
1099
|
+
unsupported: evaluation.unsupported.length
|
|
1100
|
+
};
|
|
1101
|
+
return evaluation;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const handlerEvaluations = [];
|
|
1105
|
+
for (const handler of handlerTable.handlers || []) {
|
|
1106
|
+
const record = locateHandlerRecord(handler);
|
|
1107
|
+
handlerEvaluations.push(evaluateRecord(handler, record, 'handler', [handler.localName || handler.id]));
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const uniqueDependencyEvaluations = uniqueByKey(dependencyEvaluations, (item) => `${item.handlerId}:${item.handlerName}`);
|
|
1111
|
+
|
|
1112
|
+
const artifact = normalizeArtifact({
|
|
1113
|
+
version: 'pulsewasm.handler-eval.v1',
|
|
1114
|
+
generatedBy: PACKAGE_VERSION,
|
|
1115
|
+
source: stableFileName(sourceFile.fileName, cwd),
|
|
1116
|
+
policy: {
|
|
1117
|
+
model: 'shallow-static-validator',
|
|
1118
|
+
contract: 'validate handler dependency shape; do not interpret arbitrary JavaScript',
|
|
1119
|
+
allowedCalls: ['next()', 'next(err)', 'direct same-file top-level helper calls', 'ctx.fetch("declaredBackend", staticRequest)', 'package-owned lowerable facade imports supplied by compiler orchestration'],
|
|
1120
|
+
rejected: [
|
|
1121
|
+
'async/await and Promise semantics',
|
|
1122
|
+
'loops',
|
|
1123
|
+
'try/catch/throw',
|
|
1124
|
+
'member-call dispatch except ctx.fetch capability usage',
|
|
1125
|
+
'computed access',
|
|
1126
|
+
'forbidden imports',
|
|
1127
|
+
'unsupported globals',
|
|
1128
|
+
'closure captures',
|
|
1129
|
+
'nested functions',
|
|
1130
|
+
'recursion'
|
|
1131
|
+
]
|
|
1132
|
+
},
|
|
1133
|
+
handlers: handlerEvaluations,
|
|
1134
|
+
dependencyEvaluations: uniqueDependencyEvaluations,
|
|
1135
|
+
summary: {
|
|
1136
|
+
handlers: handlerEvaluations.length,
|
|
1137
|
+
ok: handlerEvaluations.filter((item) => item.status === 'ok').length,
|
|
1138
|
+
error: handlerEvaluations.filter((item) => item.status !== 'ok').length,
|
|
1139
|
+
dependencies: uniqueDependencyEvaluations.length,
|
|
1140
|
+
diagnostics: diagnostics.length,
|
|
1141
|
+
unsupported: handlerEvaluations.reduce((sum, item) => sum + item.unsupported.length, 0) + uniqueDependencyEvaluations.reduce((sum, item) => sum + item.unsupported.length, 0)
|
|
1142
|
+
}
|
|
1143
|
+
}, cwd);
|
|
1144
|
+
|
|
1145
|
+
return {
|
|
1146
|
+
handlerEval: artifact,
|
|
1147
|
+
diagnostics
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
module.exports = {
|
|
1152
|
+
buildHandlerEval,
|
|
1153
|
+
collectTopLevelSymbols
|
|
1154
|
+
};
|