@nocobase/plugin-flow-engine 3.0.0-alpha.1 → 3.0.0-alpha.11
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/dist/externalVersion.js +9 -9
- package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
- package/dist/node_modules/acorn/package.json +1 -1
- package/dist/node_modules/acorn-jsx/package.json +1 -1
- package/dist/node_modules/acorn-walk/package.json +1 -1
- package/dist/node_modules/ses/package.json +1 -1
- package/dist/node_modules/zod/package.json +1 -1
- package/dist/server/flow-surfaces/builder.js +5 -0
- package/dist/server/flow-surfaces/catalog.js +27 -8
- package/dist/server/flow-surfaces/configure-options.js +17 -1
- package/dist/server/flow-surfaces/field-binding-registry.js +9 -8
- package/dist/server/flow-surfaces/navigation-targets.js +21 -40
- package/dist/server/flow-surfaces/service-utils.d.ts +1 -0
- package/dist/server/flow-surfaces/service-utils.js +17 -0
- package/dist/server/flow-surfaces/service.js +9 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +7 -0
- package/dist/server/plugin.d.ts +13 -0
- package/dist/server/plugin.js +90 -3
- package/dist/server/repository.d.ts +12 -0
- package/dist/server/repository.js +39 -2
- package/dist/server/template/context-keys.d.ts +14 -0
- package/dist/server/template/context-keys.js +86 -0
- package/dist/server/template/contexts.d.ts +5 -8
- package/dist/server/template/contexts.js +43 -51
- package/dist/server/template/resolver.d.ts +5 -13
- package/dist/server/template/resolver.js +188 -257
- package/dist/server/template/variable-expression.d.ts +72 -0
- package/dist/server/template/variable-expression.js +597 -0
- package/dist/server/variables/allow-list.d.ts +49 -0
- package/dist/server/variables/allow-list.js +492 -0
- package/dist/server/variables/form-item-record-slot-resolvers.d.ts +10 -0
- package/dist/server/variables/form-item-record-slot-resolvers.js +280 -0
- package/dist/server/variables/record-bindings.d.ts +42 -0
- package/dist/server/variables/record-bindings.js +196 -0
- package/dist/server/variables/record-projection.d.ts +10 -0
- package/dist/server/variables/record-projection.js +140 -0
- package/dist/server/variables/record-slot-policy.d.ts +23 -0
- package/dist/server/variables/record-slot-policy.js +100 -0
- package/dist/server/variables/record-slot-resolvers.d.ts +46 -0
- package/dist/server/variables/record-slot-resolvers.js +115 -0
- package/dist/server/variables/records.d.ts +49 -2
- package/dist/server/variables/records.js +309 -18
- package/dist/server/variables/registry.d.ts +33 -10
- package/dist/server/variables/registry.js +197 -362
- package/dist/server/variables/resolve.d.ts +20 -2
- package/dist/server/variables/resolve.js +85 -13
- package/dist/server/variables/runjs-variable-dependencies.d.ts +23 -0
- package/dist/server/variables/runjs-variable-dependencies.js +407 -0
- package/dist/server/variables/selects.js +6 -3
- package/dist/server/variables/utils.d.ts +3 -6
- package/dist/server/variables/utils.js +50 -111
- package/package.json +2 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
export type PathSegment = string | number;
|
|
10
|
+
export type CanonicalPathSegment = string | Readonly<{
|
|
11
|
+
kind: 'index';
|
|
12
|
+
}>;
|
|
13
|
+
export type SourceSpan = Readonly<{
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
}>;
|
|
17
|
+
export type TemplatePathSegment = string | number;
|
|
18
|
+
export type VariablePathRef = Readonly<{
|
|
19
|
+
varName: string;
|
|
20
|
+
runtimeSegments: readonly PathSegment[];
|
|
21
|
+
runtimeKey: string;
|
|
22
|
+
canonicalKey: string;
|
|
23
|
+
span: SourceSpan;
|
|
24
|
+
templatePath: readonly TemplatePathSegment[];
|
|
25
|
+
}>;
|
|
26
|
+
export type ResolvePathPolicy = Readonly<{
|
|
27
|
+
allowAll: boolean;
|
|
28
|
+
allowedPaths: ReadonlySet<string>;
|
|
29
|
+
unrestrictedVariables: ReadonlySet<string>;
|
|
30
|
+
}>;
|
|
31
|
+
export type VariableExpressionErrorCode = 'bare-ctx' | 'ctx-call' | 'ctx-write' | 'dynamic-ctx-path' | 'invalid-expression' | 'reserved-helper' | 'unsafe-execution' | 'unreliable-scope';
|
|
32
|
+
export type VariableExpressionError = Readonly<{
|
|
33
|
+
code: VariableExpressionErrorCode;
|
|
34
|
+
span: SourceSpan;
|
|
35
|
+
templatePath: readonly TemplatePathSegment[];
|
|
36
|
+
}>;
|
|
37
|
+
export type AnalyzedExpression = Readonly<{
|
|
38
|
+
source: string;
|
|
39
|
+
compiled: string;
|
|
40
|
+
helperIdentifier: string;
|
|
41
|
+
expressionSpan: SourceSpan;
|
|
42
|
+
placeholderSpan: SourceSpan;
|
|
43
|
+
paths: readonly VariablePathRef[];
|
|
44
|
+
supported: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
export type AnalyzedTemplateNode = Readonly<{
|
|
47
|
+
kind: 'string';
|
|
48
|
+
source: string;
|
|
49
|
+
expressions: readonly AnalyzedExpression[];
|
|
50
|
+
}> | Readonly<{
|
|
51
|
+
kind: 'array';
|
|
52
|
+
items: readonly AnalyzedTemplateNode[];
|
|
53
|
+
}> | Readonly<{
|
|
54
|
+
kind: 'object';
|
|
55
|
+
entries: Readonly<Record<string, AnalyzedTemplateNode>>;
|
|
56
|
+
}> | Readonly<{
|
|
57
|
+
kind: 'value';
|
|
58
|
+
value: unknown;
|
|
59
|
+
}>;
|
|
60
|
+
export type AnalyzedTemplate = Readonly<{
|
|
61
|
+
value: AnalyzedTemplateNode;
|
|
62
|
+
paths: readonly VariablePathRef[];
|
|
63
|
+
usage: Readonly<Record<string, readonly VariablePathRef[]>>;
|
|
64
|
+
errors: readonly VariableExpressionError[];
|
|
65
|
+
supported: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
export type AnalyzeTemplateMode = 'request' | 'untrusted-request' | 'flow-model';
|
|
68
|
+
export declare function getVariableRuntimeKey(varName: string, runtimeSegments: readonly PathSegment[]): string;
|
|
69
|
+
export declare function getVariableCanonicalKey(varName: string, runtimeSegments: readonly PathSegment[]): string;
|
|
70
|
+
export declare function analyzeVariableTemplate(template: unknown, options?: Readonly<{
|
|
71
|
+
mode?: AnalyzeTemplateMode;
|
|
72
|
+
}>): AnalyzedTemplate;
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var variable_expression_exports = {};
|
|
28
|
+
__export(variable_expression_exports, {
|
|
29
|
+
analyzeVariableTemplate: () => analyzeVariableTemplate,
|
|
30
|
+
getVariableCanonicalKey: () => getVariableCanonicalKey,
|
|
31
|
+
getVariableRuntimeKey: () => getVariableRuntimeKey
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(variable_expression_exports);
|
|
34
|
+
var import_acorn = require("acorn");
|
|
35
|
+
const CANONICAL_INDEX = Object.freeze({ kind: "index" });
|
|
36
|
+
const RESERVED_HELPERS = /* @__PURE__ */ new Set(["__get", "__resolveVariablePath"]);
|
|
37
|
+
const SAFE_MATH_CALLS = /* @__PURE__ */ new Set([
|
|
38
|
+
"abs",
|
|
39
|
+
"acos",
|
|
40
|
+
"acosh",
|
|
41
|
+
"asin",
|
|
42
|
+
"asinh",
|
|
43
|
+
"atan",
|
|
44
|
+
"atan2",
|
|
45
|
+
"atanh",
|
|
46
|
+
"cbrt",
|
|
47
|
+
"ceil",
|
|
48
|
+
"clz32",
|
|
49
|
+
"cos",
|
|
50
|
+
"cosh",
|
|
51
|
+
"exp",
|
|
52
|
+
"expm1",
|
|
53
|
+
"floor",
|
|
54
|
+
"fround",
|
|
55
|
+
"hypot",
|
|
56
|
+
"imul",
|
|
57
|
+
"log",
|
|
58
|
+
"log10",
|
|
59
|
+
"log1p",
|
|
60
|
+
"log2",
|
|
61
|
+
"max",
|
|
62
|
+
"min",
|
|
63
|
+
"pow",
|
|
64
|
+
"random",
|
|
65
|
+
"round",
|
|
66
|
+
"sign",
|
|
67
|
+
"sin",
|
|
68
|
+
"sinh",
|
|
69
|
+
"sqrt",
|
|
70
|
+
"tan",
|
|
71
|
+
"tanh",
|
|
72
|
+
"trunc"
|
|
73
|
+
]);
|
|
74
|
+
const UNSAFE_EXECUTION_NODES = /* @__PURE__ */ new Set([
|
|
75
|
+
"ArrowFunctionExpression",
|
|
76
|
+
"AssignmentExpression",
|
|
77
|
+
"AwaitExpression",
|
|
78
|
+
"ClassDeclaration",
|
|
79
|
+
"ClassExpression",
|
|
80
|
+
"DoWhileStatement",
|
|
81
|
+
"ForInStatement",
|
|
82
|
+
"ForOfStatement",
|
|
83
|
+
"ForStatement",
|
|
84
|
+
"FunctionDeclaration",
|
|
85
|
+
"FunctionExpression",
|
|
86
|
+
"ImportExpression",
|
|
87
|
+
"MetaProperty",
|
|
88
|
+
"NewExpression",
|
|
89
|
+
"SequenceExpression",
|
|
90
|
+
"SpreadElement",
|
|
91
|
+
"StaticBlock",
|
|
92
|
+
"TaggedTemplateExpression",
|
|
93
|
+
"UpdateExpression",
|
|
94
|
+
"WhileStatement",
|
|
95
|
+
"YieldExpression"
|
|
96
|
+
]);
|
|
97
|
+
const ACORN_OPTIONS = { ecmaVersion: "latest", preserveParens: true, sourceType: "script" };
|
|
98
|
+
function getVariableRuntimeKey(varName, runtimeSegments) {
|
|
99
|
+
return JSON.stringify([varName, ...runtimeSegments]);
|
|
100
|
+
}
|
|
101
|
+
function getVariableCanonicalKey(varName, runtimeSegments) {
|
|
102
|
+
const segments = runtimeSegments.map(
|
|
103
|
+
(segment) => typeof segment === "number" ? CANONICAL_INDEX : segment
|
|
104
|
+
);
|
|
105
|
+
return JSON.stringify([varName, ...segments]);
|
|
106
|
+
}
|
|
107
|
+
function analyzeVariableTemplate(template, options = {}) {
|
|
108
|
+
const mode = options.mode ?? "request";
|
|
109
|
+
const paths = [];
|
|
110
|
+
const errors = [];
|
|
111
|
+
const helperIdentifiers = /* @__PURE__ */ new Set();
|
|
112
|
+
const visit = (value2, templatePath) => {
|
|
113
|
+
if (typeof value2 === "string") {
|
|
114
|
+
const expressions = analyzeString(value2, templatePath, paths, errors, helperIdentifiers, mode);
|
|
115
|
+
return { kind: "string", source: value2, expressions };
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(value2)) {
|
|
118
|
+
return { kind: "array", items: value2.map((item, index) => visit(item, [...templatePath, index])) };
|
|
119
|
+
}
|
|
120
|
+
if (value2 && typeof value2 === "object") {
|
|
121
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
122
|
+
for (const [key, item] of Object.entries(value2)) entries[key] = visit(item, [...templatePath, key]);
|
|
123
|
+
return { kind: "object", entries };
|
|
124
|
+
}
|
|
125
|
+
return { kind: "value", value: value2 };
|
|
126
|
+
};
|
|
127
|
+
const value = visit(template, []);
|
|
128
|
+
const usage = /* @__PURE__ */ Object.create(null);
|
|
129
|
+
const seenRuntimeKeys = /* @__PURE__ */ new Map();
|
|
130
|
+
for (const path of paths) {
|
|
131
|
+
const seen = seenRuntimeKeys.get(path.varName) ?? /* @__PURE__ */ new Set();
|
|
132
|
+
seenRuntimeKeys.set(path.varName, seen);
|
|
133
|
+
if (seen.has(path.runtimeKey)) continue;
|
|
134
|
+
seen.add(path.runtimeKey);
|
|
135
|
+
(usage[path.varName] ??= []).push(path);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
value,
|
|
139
|
+
paths,
|
|
140
|
+
usage,
|
|
141
|
+
errors,
|
|
142
|
+
supported: mode === "flow-model" || errors.length === 0
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function analyzeString(source, templatePath, allPaths, allErrors, helperIdentifiers, mode) {
|
|
146
|
+
const expressions = [];
|
|
147
|
+
let cursor = 0;
|
|
148
|
+
while (cursor < source.length) {
|
|
149
|
+
const placeholderStart = source.indexOf("{{", cursor);
|
|
150
|
+
if (placeholderStart < 0) break;
|
|
151
|
+
const parseStart = placeholderStart + 2;
|
|
152
|
+
let ast;
|
|
153
|
+
try {
|
|
154
|
+
ast = (0, import_acorn.parseExpressionAt)(source, parseStart, ACORN_OPTIONS);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const position = getAcornErrorPosition(error, parseStart);
|
|
157
|
+
allErrors.push({ code: "invalid-expression", span: { start: position, end: position }, templatePath });
|
|
158
|
+
cursor = parseStart;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
let placeholderEnd = ast.end;
|
|
162
|
+
while (/\s/.test(source[placeholderEnd] ?? "")) placeholderEnd += 1;
|
|
163
|
+
if (!source.startsWith("}}", placeholderEnd)) {
|
|
164
|
+
allErrors.push({
|
|
165
|
+
code: "invalid-expression",
|
|
166
|
+
span: { start: ast.start, end: ast.end },
|
|
167
|
+
templatePath
|
|
168
|
+
});
|
|
169
|
+
cursor = parseStart;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const expressionEnd = placeholderEnd;
|
|
173
|
+
const expressionErrors = [];
|
|
174
|
+
const helperIdentifier = createHelperIdentifier(ast, helperIdentifiers);
|
|
175
|
+
let expressionPaths = analyzeAst(ast, templatePath, expressionErrors, mode);
|
|
176
|
+
const dashedPath = parseDashedCtxPath(source.slice(ast.start, ast.end));
|
|
177
|
+
if (dashedPath && expressionPaths.length === 1 && expressionErrors.length === 0) {
|
|
178
|
+
expressionPaths = [createPathRef(dashedPath[0], dashedPath.slice(1), ast, templatePath)];
|
|
179
|
+
}
|
|
180
|
+
const supported = expressionErrors.length === 0;
|
|
181
|
+
const retainedPaths = supported ? expressionPaths : [];
|
|
182
|
+
allErrors.push(...expressionErrors);
|
|
183
|
+
allPaths.push(...retainedPaths);
|
|
184
|
+
expressions.push({
|
|
185
|
+
source: source.slice(parseStart, expressionEnd),
|
|
186
|
+
compiled: compileExpression(source, parseStart, expressionEnd, retainedPaths, helperIdentifier),
|
|
187
|
+
helperIdentifier,
|
|
188
|
+
expressionSpan: { start: parseStart, end: expressionEnd },
|
|
189
|
+
placeholderSpan: { start: placeholderStart, end: placeholderEnd + 2 },
|
|
190
|
+
paths: retainedPaths,
|
|
191
|
+
supported
|
|
192
|
+
});
|
|
193
|
+
cursor = placeholderEnd + 2;
|
|
194
|
+
}
|
|
195
|
+
return expressions;
|
|
196
|
+
}
|
|
197
|
+
function analyzeAst(ast, templatePath, errors, mode) {
|
|
198
|
+
const paths = [];
|
|
199
|
+
const pathRanges = [];
|
|
200
|
+
const errorKeys = /* @__PURE__ */ new Set();
|
|
201
|
+
const rootScope = { bindings: /* @__PURE__ */ new Set() };
|
|
202
|
+
const addError = (code, node) => {
|
|
203
|
+
const key = `${code}:${node.start}:${node.end}`;
|
|
204
|
+
if (errorKeys.has(key)) return;
|
|
205
|
+
errorKeys.add(key);
|
|
206
|
+
errors.push({ code, span: { start: node.start, end: node.end }, templatePath });
|
|
207
|
+
};
|
|
208
|
+
const visit = (node, scope, parent, parentKey) => {
|
|
209
|
+
if (mode === "untrusted-request" && isUnsafeExecutionNode(node)) addError("unsafe-execution", node);
|
|
210
|
+
if (RESERVED_HELPERS.has(identifierName(node) ?? "") && isIdentifierReference(parent, parentKey)) {
|
|
211
|
+
addError("reserved-helper", node);
|
|
212
|
+
}
|
|
213
|
+
if (isFunctionNode(node)) {
|
|
214
|
+
visitFunction(node, scope, visit);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (node.type === "BlockStatement" || node.type === "StaticBlock") {
|
|
218
|
+
const blockScope = createScope(scope);
|
|
219
|
+
collectDirectLexicalBindings(node, blockScope.bindings);
|
|
220
|
+
visitNodeFields(node, blockScope, visit, /* @__PURE__ */ new Set(["type", "start", "end"]));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (node.type === "CatchClause") {
|
|
224
|
+
const catchScope = createScope(scope);
|
|
225
|
+
addPatternBindings(asNode(node.param), catchScope.bindings);
|
|
226
|
+
visitPatternExpressions(asNode(node.param), catchScope, visit);
|
|
227
|
+
visitMaybeNode(node.body, catchScope, visit, node, "body");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (node.type === "WithStatement") addError("unreliable-scope", node);
|
|
231
|
+
if (node.type === "CallExpression" && identifierName(unwrapChain(asNode(node.callee) ?? node)) === "eval" && !hasBinding(scope, "eval")) {
|
|
232
|
+
addError("unreliable-scope", node);
|
|
233
|
+
}
|
|
234
|
+
if (node.type === "ForStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement") {
|
|
235
|
+
const loopScope = createScope(scope);
|
|
236
|
+
const declaration = asNode(node.type === "ForStatement" ? node.init : node.left);
|
|
237
|
+
if ((declaration == null ? void 0 : declaration.type) === "VariableDeclaration" && declaration.kind !== "var") {
|
|
238
|
+
for (const item of nodeArray(declaration.declarations)) addPatternBindings(asNode(item.id), loopScope.bindings);
|
|
239
|
+
}
|
|
240
|
+
if (node.type !== "ForStatement" && (declaration == null ? void 0 : declaration.type) !== "VariableDeclaration" && containsCtxDerived(declaration, scope)) {
|
|
241
|
+
addError("ctx-write", declaration);
|
|
242
|
+
}
|
|
243
|
+
visitNodeFields(node, loopScope, visit, /* @__PURE__ */ new Set(["type", "start", "end"]));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (node.type === "SwitchStatement") {
|
|
247
|
+
const switchScope = createScope(scope);
|
|
248
|
+
for (const switchCase of nodeArray(node.cases)) {
|
|
249
|
+
for (const statement of nodeArray(switchCase.consequent)) {
|
|
250
|
+
if (statement.type === "VariableDeclaration" && statement.kind !== "var") {
|
|
251
|
+
for (const declaration of nodeArray(statement.declarations)) {
|
|
252
|
+
addPatternBindings(asNode(declaration.id), switchScope.bindings);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (statement.type === "FunctionDeclaration" || statement.type === "ClassDeclaration") {
|
|
256
|
+
const name = identifierName(asNode(statement.id));
|
|
257
|
+
if (name) switchScope.bindings.add(name);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
visitMaybeNode(node.discriminant, scope, visit, node, "discriminant");
|
|
262
|
+
for (const switchCase of nodeArray(node.cases)) {
|
|
263
|
+
visitMaybeNode(switchCase.test, switchScope, visit, switchCase, "test");
|
|
264
|
+
for (const statement of nodeArray(switchCase.consequent))
|
|
265
|
+
visit(statement, switchScope, switchCase, "consequent");
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (node.type === "ClassExpression" || node.type === "ClassDeclaration") {
|
|
270
|
+
const classScope = createScope(scope);
|
|
271
|
+
const name = identifierName(asNode(node.id));
|
|
272
|
+
if (name) classScope.bindings.add(name);
|
|
273
|
+
visitMaybeNode(node.superClass, classScope, visit, node, "superClass");
|
|
274
|
+
visitMaybeNode(node.body, classScope, visit, node, "body");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (node.type === "VariableDeclaration") {
|
|
278
|
+
for (const declaration of nodeArray(node.declarations)) {
|
|
279
|
+
visitPatternExpressions(asNode(declaration.id), scope, visit);
|
|
280
|
+
visitMaybeNode(declaration.init, scope, visit, declaration, "init");
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (node.type === "Property") {
|
|
285
|
+
if (node.computed === true) visitMaybeNode(node.key, scope, visit, node, "key");
|
|
286
|
+
if (node.shorthand === true) visitMaybeNode(node.value, scope, visit, node, "value");
|
|
287
|
+
else visitMaybeNode(node.value, scope, visit, node, "value");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (node.type === "MemberExpression") {
|
|
291
|
+
const parsed = parseMemberPath(node, scope);
|
|
292
|
+
const covered = pathRanges.some((span) => span.start <= node.start && span.end >= node.end);
|
|
293
|
+
if (!covered && parsed.kind === "static") {
|
|
294
|
+
if (!parsed.segments.length || typeof parsed.segments[0] !== "string") {
|
|
295
|
+
addError("dynamic-ctx-path", node);
|
|
296
|
+
} else {
|
|
297
|
+
const ref = createPathRef(parsed.segments[0], parsed.segments.slice(1), node, templatePath);
|
|
298
|
+
paths.push(ref);
|
|
299
|
+
pathRanges.push(ref.span);
|
|
300
|
+
}
|
|
301
|
+
} else if (!covered && parsed.kind === "dynamic") {
|
|
302
|
+
addError("dynamic-ctx-path", node);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if ((node.type === "CallExpression" || node.type === "NewExpression") && isCtxDerived(asNode(node.callee), scope)) {
|
|
306
|
+
addError("ctx-call", node);
|
|
307
|
+
}
|
|
308
|
+
if (node.type === "TaggedTemplateExpression" && isCtxDerived(asNode(node.tag), scope)) {
|
|
309
|
+
addError("ctx-call", node);
|
|
310
|
+
}
|
|
311
|
+
if (node.type === "AssignmentExpression" && containsCtxDerived(asNode(node.left), scope)) {
|
|
312
|
+
addError("ctx-write", node);
|
|
313
|
+
}
|
|
314
|
+
if (node.type === "UpdateExpression" && isCtxDerived(asNode(node.argument), scope)) {
|
|
315
|
+
addError("ctx-write", node);
|
|
316
|
+
}
|
|
317
|
+
if (node.type === "UnaryExpression" && node.operator === "delete" && isCtxDerived(asNode(node.argument), scope)) {
|
|
318
|
+
addError("ctx-write", node);
|
|
319
|
+
}
|
|
320
|
+
if (node.type === "Identifier" && identifierName(node) === "ctx" && !hasBinding(scope, "ctx") && isIdentifierReference(parent, parentKey) && !((parent == null ? void 0 : parent.type) === "MemberExpression" && parentKey === "object")) {
|
|
321
|
+
addError("bare-ctx", node);
|
|
322
|
+
}
|
|
323
|
+
visitNodeFields(node, scope, visit, /* @__PURE__ */ new Set(["type", "start", "end"]));
|
|
324
|
+
};
|
|
325
|
+
visit(ast, rootScope);
|
|
326
|
+
return paths.sort((left, right) => left.span.start - right.span.start);
|
|
327
|
+
}
|
|
328
|
+
function isUnsafeExecutionNode(node) {
|
|
329
|
+
if (node.type === "CallExpression") return !isSafeMathCall(node);
|
|
330
|
+
if (node.type === "Literal" && (typeof node.value === "bigint" || typeof node.bigint === "string")) return true;
|
|
331
|
+
if (node.type === "UnaryExpression" && node.operator === "delete") return true;
|
|
332
|
+
return UNSAFE_EXECUTION_NODES.has(node.type);
|
|
333
|
+
}
|
|
334
|
+
function isSafeMathCall(node) {
|
|
335
|
+
if (node.optional === true) return false;
|
|
336
|
+
const callee = unwrapChain(asNode(node.callee) ?? node);
|
|
337
|
+
if (callee.type !== "MemberExpression" || callee.computed === true || callee.optional === true) return false;
|
|
338
|
+
return identifierName(asNode(callee.object)) === "Math" && SAFE_MATH_CALLS.has(identifierName(asNode(callee.property)) ?? "");
|
|
339
|
+
}
|
|
340
|
+
function visitFunction(node, outerScope, visit) {
|
|
341
|
+
const scope = createScope(outerScope);
|
|
342
|
+
const name = identifierName(asNode(node.id));
|
|
343
|
+
if (name) scope.bindings.add(name);
|
|
344
|
+
for (const parameter of nodeArray(node.params)) addPatternBindings(parameter, scope.bindings);
|
|
345
|
+
collectFunctionVarBindings(asNode(node.body), scope.bindings);
|
|
346
|
+
for (const parameter of nodeArray(node.params)) visitPatternExpressions(parameter, scope, visit);
|
|
347
|
+
visitMaybeNode(node.body, scope, visit, node, "body");
|
|
348
|
+
}
|
|
349
|
+
function visitPatternExpressions(pattern, scope, visit) {
|
|
350
|
+
if (!pattern) return;
|
|
351
|
+
if (pattern.type === "AssignmentPattern") {
|
|
352
|
+
visitPatternExpressions(asNode(pattern.left), scope, visit);
|
|
353
|
+
visitMaybeNode(pattern.right, scope, visit, pattern, "right");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (pattern.type === "RestElement") {
|
|
357
|
+
visitPatternExpressions(asNode(pattern.argument), scope, visit);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (pattern.type === "ArrayPattern") {
|
|
361
|
+
for (const element of nodeArray(pattern.elements)) visitPatternExpressions(element, scope, visit);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (pattern.type === "ObjectPattern") {
|
|
365
|
+
for (const property of nodeArray(pattern.properties)) {
|
|
366
|
+
if (property.type === "RestElement") {
|
|
367
|
+
visitPatternExpressions(asNode(property.argument), scope, visit);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (property.computed === true) visitMaybeNode(property.key, scope, visit, property, "key");
|
|
371
|
+
visitPatternExpressions(asNode(property.value), scope, visit);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function collectDirectLexicalBindings(node, bindings) {
|
|
376
|
+
for (const statement of nodeArray(node.body)) {
|
|
377
|
+
if (statement.type === "VariableDeclaration" && statement.kind !== "var") {
|
|
378
|
+
for (const declaration of nodeArray(statement.declarations)) addPatternBindings(asNode(declaration.id), bindings);
|
|
379
|
+
}
|
|
380
|
+
if (statement.type === "FunctionDeclaration" || statement.type === "ClassDeclaration") {
|
|
381
|
+
const name = identifierName(asNode(statement.id));
|
|
382
|
+
if (name) bindings.add(name);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function collectFunctionVarBindings(node, bindings) {
|
|
387
|
+
if (!node) return;
|
|
388
|
+
if (isFunctionNode(node)) return;
|
|
389
|
+
if (node.type === "VariableDeclaration" && node.kind === "var") {
|
|
390
|
+
for (const declaration of nodeArray(node.declarations)) addPatternBindings(asNode(declaration.id), bindings);
|
|
391
|
+
}
|
|
392
|
+
if (node.type === "FunctionDeclaration") {
|
|
393
|
+
const name = identifierName(asNode(node.id));
|
|
394
|
+
if (name) bindings.add(name);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
for (const [key, value] of Object.entries(node)) {
|
|
398
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
399
|
+
if (isNode(value)) collectFunctionVarBindings(value, bindings);
|
|
400
|
+
else if (Array.isArray(value)) {
|
|
401
|
+
for (const item of value) if (isNode(item)) collectFunctionVarBindings(item, bindings);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function addPatternBindings(pattern, bindings) {
|
|
406
|
+
if (!pattern) return;
|
|
407
|
+
const name = identifierName(pattern);
|
|
408
|
+
if (name) {
|
|
409
|
+
bindings.add(name);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (pattern.type === "RestElement") {
|
|
413
|
+
addPatternBindings(asNode(pattern.argument), bindings);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (pattern.type === "AssignmentPattern") {
|
|
417
|
+
addPatternBindings(asNode(pattern.left), bindings);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (pattern.type === "ArrayPattern") {
|
|
421
|
+
for (const element of nodeArray(pattern.elements)) addPatternBindings(element, bindings);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (pattern.type === "ObjectPattern") {
|
|
425
|
+
for (const property of nodeArray(pattern.properties)) {
|
|
426
|
+
addPatternBindings(asNode(property.type === "RestElement" ? property.argument : property.value), bindings);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function parseMemberPath(node, scope) {
|
|
431
|
+
const current = unwrapChain(node);
|
|
432
|
+
if (current.type === "Identifier") {
|
|
433
|
+
if (identifierName(current) !== "ctx" || hasBinding(scope, "ctx")) return { kind: "none" };
|
|
434
|
+
return { kind: "bare", root: current };
|
|
435
|
+
}
|
|
436
|
+
if (current.type !== "MemberExpression") return { kind: "none" };
|
|
437
|
+
const base = parseMemberPath(asNode(current.object) ?? current, scope);
|
|
438
|
+
if (base.kind === "none" || base.kind === "dynamic") return base;
|
|
439
|
+
const segment = getStaticMemberSegment(current);
|
|
440
|
+
if (segment === void 0) return { kind: "dynamic", root: base.root };
|
|
441
|
+
return { kind: "static", root: base.root, segments: [...base.kind === "static" ? base.segments : [], segment] };
|
|
442
|
+
}
|
|
443
|
+
function getStaticMemberSegment(node) {
|
|
444
|
+
const property = asNode(node.property);
|
|
445
|
+
if (!property) return void 0;
|
|
446
|
+
if (node.computed !== true) return identifierName(property);
|
|
447
|
+
if (property.type !== "Literal") return void 0;
|
|
448
|
+
return typeof property.value === "string" || typeof property.value === "number" ? property.value : void 0;
|
|
449
|
+
}
|
|
450
|
+
function isCtxDerived(node, scope) {
|
|
451
|
+
if (!node) return false;
|
|
452
|
+
const current = unwrapChain(node);
|
|
453
|
+
if (current.type === "Identifier") return identifierName(current) === "ctx" && !hasBinding(scope, "ctx");
|
|
454
|
+
if (current.type === "MemberExpression") return isCtxDerived(asNode(current.object), scope);
|
|
455
|
+
if (current.type === "CallExpression" || current.type === "NewExpression") {
|
|
456
|
+
return isCtxDerived(asNode(current.callee), scope);
|
|
457
|
+
}
|
|
458
|
+
if (current.type === "AwaitExpression") return isCtxDerived(asNode(current.argument), scope);
|
|
459
|
+
if (current.type === "SequenceExpression") {
|
|
460
|
+
return isCtxDerived(nodeArray(current.expressions).at(-1), scope);
|
|
461
|
+
}
|
|
462
|
+
if (current.type === "ConditionalExpression") {
|
|
463
|
+
return isCtxDerived(asNode(current.consequent), scope) || isCtxDerived(asNode(current.alternate), scope);
|
|
464
|
+
}
|
|
465
|
+
if (current.type === "LogicalExpression") {
|
|
466
|
+
return isCtxDerived(asNode(current.left), scope) || isCtxDerived(asNode(current.right), scope);
|
|
467
|
+
}
|
|
468
|
+
if (current.type === "AssignmentExpression") return isCtxDerived(asNode(current.right), scope);
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
function containsCtxDerived(node, scope) {
|
|
472
|
+
if (!node) return false;
|
|
473
|
+
if (isCtxDerived(node, scope)) return true;
|
|
474
|
+
for (const [key, value] of Object.entries(node)) {
|
|
475
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
476
|
+
if (isNode(value) && containsCtxDerived(value, scope)) return true;
|
|
477
|
+
if (Array.isArray(value) && value.some((item) => isNode(item) && containsCtxDerived(item, scope))) return true;
|
|
478
|
+
}
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
function parseDashedCtxPath(source) {
|
|
482
|
+
if (!source.startsWith("ctx.")) return null;
|
|
483
|
+
const segments = source.slice(4).split(".");
|
|
484
|
+
if (segments.length < 2 || segments.some((segment) => !segment)) return null;
|
|
485
|
+
const varName = parseIdentifierSource(segments[0]);
|
|
486
|
+
if (!varName) return null;
|
|
487
|
+
let hasDash = false;
|
|
488
|
+
const decodedSegments = [];
|
|
489
|
+
for (const segment of segments.slice(1)) {
|
|
490
|
+
const parts = segment.split("-");
|
|
491
|
+
if (parts.length > 1) hasDash = true;
|
|
492
|
+
const decodedParts = parts.map(parseIdentifierSource);
|
|
493
|
+
if (decodedParts.some((part) => !part)) return null;
|
|
494
|
+
decodedSegments.push(decodedParts.join("-"));
|
|
495
|
+
}
|
|
496
|
+
return hasDash ? [varName, ...decodedSegments] : null;
|
|
497
|
+
}
|
|
498
|
+
function parseIdentifierSource(source) {
|
|
499
|
+
try {
|
|
500
|
+
const node = (0, import_acorn.parseExpressionAt)(source, 0, ACORN_OPTIONS);
|
|
501
|
+
return node.type === "Identifier" && node.end === source.length ? identifierName(node) : void 0;
|
|
502
|
+
} catch (_) {
|
|
503
|
+
return void 0;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function createPathRef(varName, runtimeSegments, node, templatePath) {
|
|
507
|
+
return {
|
|
508
|
+
varName,
|
|
509
|
+
runtimeSegments,
|
|
510
|
+
runtimeKey: getVariableRuntimeKey(varName, runtimeSegments),
|
|
511
|
+
canonicalKey: getVariableCanonicalKey(varName, runtimeSegments),
|
|
512
|
+
span: { start: node.start, end: node.end },
|
|
513
|
+
templatePath
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function createHelperIdentifier(ast, used) {
|
|
517
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
518
|
+
const collect = (node) => {
|
|
519
|
+
const name = identifierName(node);
|
|
520
|
+
if (name) identifiers.add(name);
|
|
521
|
+
for (const value of Object.values(node)) {
|
|
522
|
+
if (isNode(value)) collect(value);
|
|
523
|
+
else if (Array.isArray(value)) {
|
|
524
|
+
for (const item of value) if (isNode(item)) collect(item);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
collect(ast);
|
|
529
|
+
let index = used.size;
|
|
530
|
+
let candidate = `__resolveVariablePath${index}`;
|
|
531
|
+
while (identifiers.has(candidate) || used.has(candidate)) candidate = `__resolveVariablePath${++index}`;
|
|
532
|
+
used.add(candidate);
|
|
533
|
+
return candidate;
|
|
534
|
+
}
|
|
535
|
+
function compileExpression(source, start, end, paths, helperIdentifier) {
|
|
536
|
+
let compiled = source.slice(start, end);
|
|
537
|
+
for (const path of [...paths].sort((left, right) => right.span.start - left.span.start)) {
|
|
538
|
+
const replacement = `(await ${helperIdentifier}(${JSON.stringify(path.varName)}, ${JSON.stringify(
|
|
539
|
+
path.runtimeSegments
|
|
540
|
+
)}))`;
|
|
541
|
+
compiled = `${compiled.slice(0, path.span.start - start)}${replacement}${compiled.slice(path.span.end - start)}`;
|
|
542
|
+
}
|
|
543
|
+
return compiled;
|
|
544
|
+
}
|
|
545
|
+
function visitNodeFields(node, scope, visit, skipped) {
|
|
546
|
+
for (const [key, value] of Object.entries(node)) {
|
|
547
|
+
if (skipped.has(key)) continue;
|
|
548
|
+
if (isNode(value)) visit(value, scope, node, key);
|
|
549
|
+
else if (Array.isArray(value)) {
|
|
550
|
+
for (const item of value) if (isNode(item)) visit(item, scope, node, key);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function visitMaybeNode(value, scope, visit, parent, parentKey) {
|
|
555
|
+
if (isNode(value)) visit(value, scope, parent, parentKey);
|
|
556
|
+
}
|
|
557
|
+
function unwrapChain(node) {
|
|
558
|
+
return (node.type === "ChainExpression" || node.type === "ParenthesizedExpression") && isNode(node.expression) ? unwrapChain(node.expression) : node;
|
|
559
|
+
}
|
|
560
|
+
function createScope(parent) {
|
|
561
|
+
return { bindings: /* @__PURE__ */ new Set(), parent };
|
|
562
|
+
}
|
|
563
|
+
function hasBinding(scope, name) {
|
|
564
|
+
return scope.bindings.has(name) || !!scope.parent && hasBinding(scope.parent, name);
|
|
565
|
+
}
|
|
566
|
+
function identifierName(node) {
|
|
567
|
+
return (node == null ? void 0 : node.type) === "Identifier" && typeof node.name === "string" ? node.name : void 0;
|
|
568
|
+
}
|
|
569
|
+
function isIdentifierReference(parent, parentKey) {
|
|
570
|
+
if (!parent) return true;
|
|
571
|
+
if (parent.type === "MemberExpression" && parentKey === "property" && parent.computed !== true) return false;
|
|
572
|
+
if ((parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") && parentKey === "key" && parent.computed !== true) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
return !(parent.type === "LabeledStatement" && parentKey === "label" || (parent.type === "BreakStatement" || parent.type === "ContinueStatement") && parentKey === "label");
|
|
576
|
+
}
|
|
577
|
+
function isFunctionNode(node) {
|
|
578
|
+
return node.type === "FunctionExpression" || node.type === "FunctionDeclaration" || node.type === "ArrowFunctionExpression";
|
|
579
|
+
}
|
|
580
|
+
function isNode(value) {
|
|
581
|
+
return !!value && typeof value === "object" && typeof value.type === "string";
|
|
582
|
+
}
|
|
583
|
+
function asNode(value) {
|
|
584
|
+
return isNode(value) ? value : void 0;
|
|
585
|
+
}
|
|
586
|
+
function nodeArray(value) {
|
|
587
|
+
return Array.isArray(value) ? value.filter(isNode) : [];
|
|
588
|
+
}
|
|
589
|
+
function getAcornErrorPosition(error, fallback) {
|
|
590
|
+
return typeof error === "object" && error && "pos" in error && typeof error.pos === "number" ? error.pos : fallback;
|
|
591
|
+
}
|
|
592
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
593
|
+
0 && (module.exports = {
|
|
594
|
+
analyzeVariableTemplate,
|
|
595
|
+
getVariableCanonicalKey,
|
|
596
|
+
getVariableRuntimeKey
|
|
597
|
+
});
|