@statelyai/sdk 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -15
- package/dist/assetStorage.d.mts +1 -1
- package/dist/cli.d.mts +32 -9
- package/dist/cli.mjs +374 -26
- package/dist/embed.d.mts +1 -1
- package/dist/embed.mjs +1 -1
- package/dist/graph.d.mts +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +1 -1
- package/dist/{inspect-BMIJcsFh.d.mts → inspect-YoEwfiKb.d.mts} +1 -1
- package/dist/inspect.d.mts +2 -2
- package/dist/inspect.mjs +1 -1
- package/dist/{protocol-CDoCcaIP.d.mts → protocol-s9zwsiCW.d.mts} +1 -2
- package/dist/studio.d.mts +7 -1
- package/dist/studio.mjs +18 -3
- package/dist/sync-DLkTmSyA.mjs +2513 -0
- package/dist/sync.d.mts +20 -2
- package/dist/sync.mjs +4 -376
- package/dist/{transport-C0eTgNNu.mjs → transport-C8UTS3Fa.mjs} +1 -1
- package/package.json +5 -2
- package/schemas/statelyai.schema.json +1 -1
- package/schemas/xstate-json.schema.json +214 -0
- /package/dist/{graph-DpBGHZwl.d.mts → graph-zuNj3kfa.d.mts} +0 -0
|
@@ -0,0 +1,2513 @@
|
|
|
1
|
+
import { createStatelyClient } from "./studio.mjs";
|
|
2
|
+
import { d as upsertStatelyPragma, l as findStatelyPragmaAttachments, t as graphToXStateTS, u as getStatelyPragma } from "./graphToXStateTS-Gzh0ZqbN.mjs";
|
|
3
|
+
import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
|
|
4
|
+
import * as ts$1 from "typescript";
|
|
5
|
+
import { getDiff, isEmptyDiff } from "@statelyai/graph";
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
import * as vm from "node:vm";
|
|
11
|
+
import * as esbuild from "esbuild";
|
|
12
|
+
import * as fs$1 from "fs";
|
|
13
|
+
import * as path$1 from "path";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
15
|
+
|
|
16
|
+
//#region ../editor-sync/src/fs.ts
|
|
17
|
+
function createTextDocument(fileName, text, uri = path$1.isAbsolute(fileName) ? pathToFileURL(fileName).toString() : fileName) {
|
|
18
|
+
const lineStarts = getLineStarts(text);
|
|
19
|
+
return {
|
|
20
|
+
fileName,
|
|
21
|
+
uri,
|
|
22
|
+
getText() {
|
|
23
|
+
return text;
|
|
24
|
+
},
|
|
25
|
+
positionAt(offset) {
|
|
26
|
+
const clampedOffset = Math.max(0, Math.min(offset, text.length));
|
|
27
|
+
let low = 0;
|
|
28
|
+
let high = lineStarts.length - 1;
|
|
29
|
+
while (low <= high) {
|
|
30
|
+
const mid = Math.floor((low + high) / 2);
|
|
31
|
+
const start = lineStarts[mid];
|
|
32
|
+
const nextStart = mid + 1 < lineStarts.length ? lineStarts[mid + 1] : text.length + 1;
|
|
33
|
+
if (clampedOffset < start) {
|
|
34
|
+
high = mid - 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (clampedOffset >= nextStart) {
|
|
38
|
+
low = mid + 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
line: mid,
|
|
43
|
+
character: clampedOffset - start
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
line: lineStarts.length - 1,
|
|
48
|
+
character: clampedOffset - lineStarts[lineStarts.length - 1]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function getLineStarts(text) {
|
|
54
|
+
const lineStarts = [0];
|
|
55
|
+
for (let index = 0; index < text.length; index += 1) if (text.charCodeAt(index) === 10) lineStarts.push(index + 1);
|
|
56
|
+
return lineStarts;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region ../editor-sync/src/internal/sourceLocations.ts
|
|
61
|
+
function collectXStateSourceLocations(sourceText, document) {
|
|
62
|
+
const sourceFile = ts$1.createSourceFile(document.fileName, sourceText, ts$1.ScriptTarget.Latest, true, getScriptKind$1(document.fileName));
|
|
63
|
+
const uri = document.uri?.toString() ?? document.fileName;
|
|
64
|
+
const bindings = collectBindings(sourceFile, uri, document.fileName);
|
|
65
|
+
const staticValues = collectStaticValueBindings(sourceFile);
|
|
66
|
+
const root = findMachineConfigObjectLiteral$1(sourceFile, bindings);
|
|
67
|
+
if (!root) return;
|
|
68
|
+
const states = [];
|
|
69
|
+
collectStateLocations(root, [], states, bindings, staticValues, root);
|
|
70
|
+
return {
|
|
71
|
+
version: 1,
|
|
72
|
+
root: {
|
|
73
|
+
uri,
|
|
74
|
+
range: toSourceRange(sourceFile, root)
|
|
75
|
+
},
|
|
76
|
+
states,
|
|
77
|
+
staticValues: collectStaticValueReferences(sourceFile, uri, root, staticValues)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function collectStaticValueBindings(sourceFile) {
|
|
81
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const statement of sourceFile.statements) {
|
|
83
|
+
if (ts$1.isVariableStatement(statement)) {
|
|
84
|
+
if (!((statement.declarationList.flags & ts$1.NodeFlags.Const) === ts$1.NodeFlags.Const)) continue;
|
|
85
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
86
|
+
if (!ts$1.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
87
|
+
const value = getStaticStringValue(declaration.initializer);
|
|
88
|
+
if (value === null) continue;
|
|
89
|
+
bindings.set(declaration.name.text, {
|
|
90
|
+
kind: "const",
|
|
91
|
+
symbol: declaration.name.text,
|
|
92
|
+
value,
|
|
93
|
+
valueRange: toSourceRange(sourceFile, declaration.initializer)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (ts$1.isEnumDeclaration(statement)) for (const member of statement.members) {
|
|
99
|
+
const memberName = getEnumMemberName(member.name);
|
|
100
|
+
if (!memberName || !member.initializer) continue;
|
|
101
|
+
const value = getStaticStringValue(member.initializer);
|
|
102
|
+
if (value === null) continue;
|
|
103
|
+
const symbol = `${statement.name.text}.${memberName}`;
|
|
104
|
+
bindings.set(symbol, {
|
|
105
|
+
kind: "enumMember",
|
|
106
|
+
symbol,
|
|
107
|
+
value,
|
|
108
|
+
valueRange: toSourceRange(sourceFile, member.initializer)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return bindings;
|
|
113
|
+
}
|
|
114
|
+
function collectBindings(sourceFile, uri, fileName) {
|
|
115
|
+
const bindings = collectTopLevelBindings(sourceFile, uri);
|
|
116
|
+
addImportedBindings(bindings, sourceFile, fileName);
|
|
117
|
+
return bindings;
|
|
118
|
+
}
|
|
119
|
+
function collectTopLevelBindings(sourceFile, uri) {
|
|
120
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
121
|
+
for (const statement of sourceFile.statements) {
|
|
122
|
+
if (!ts$1.isVariableStatement(statement)) continue;
|
|
123
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
124
|
+
if (!ts$1.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
125
|
+
const objectLiteral = unwrapObjectLiteralExpression$1(declaration.initializer);
|
|
126
|
+
if (objectLiteral) {
|
|
127
|
+
bindings.set(declaration.name.text, {
|
|
128
|
+
kind: "objectReference",
|
|
129
|
+
node: objectLiteral,
|
|
130
|
+
sourceFile,
|
|
131
|
+
uri,
|
|
132
|
+
bindings
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const stateConfig = unwrapCreateConfigArgument(declaration.initializer);
|
|
137
|
+
if (stateConfig) bindings.set(declaration.name.text, {
|
|
138
|
+
kind: "createStateConfig",
|
|
139
|
+
node: stateConfig,
|
|
140
|
+
sourceFile,
|
|
141
|
+
uri,
|
|
142
|
+
bindings
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return bindings;
|
|
147
|
+
}
|
|
148
|
+
function addImportedBindings(bindings, sourceFile, fileName) {
|
|
149
|
+
for (const statement of sourceFile.statements) {
|
|
150
|
+
if (!ts$1.isImportDeclaration(statement) || !ts$1.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
151
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
152
|
+
if (!namedBindings || !ts$1.isNamedImports(namedBindings)) continue;
|
|
153
|
+
const importedFileName = resolveImportPath(fileName, statement.moduleSpecifier.text);
|
|
154
|
+
if (!importedFileName) continue;
|
|
155
|
+
const importedText = readSourceFile(importedFileName);
|
|
156
|
+
if (importedText === null) continue;
|
|
157
|
+
const importedBindings = collectBindings(ts$1.createSourceFile(importedFileName, importedText, ts$1.ScriptTarget.Latest, true, getScriptKind$1(importedFileName)), pathToFileURL(importedFileName).toString(), importedFileName);
|
|
158
|
+
for (const element of namedBindings.elements) {
|
|
159
|
+
const importedName = element.propertyName?.text ?? element.name.text;
|
|
160
|
+
const binding = importedBindings.get(importedName);
|
|
161
|
+
if (binding) bindings.set(element.name.text, binding);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function findMachineConfigObjectLiteral$1(sourceFile, bindings) {
|
|
166
|
+
let found = null;
|
|
167
|
+
const visit = (node) => {
|
|
168
|
+
if (found) return;
|
|
169
|
+
if (ts$1.isCallExpression(node) && isCreateMachineExpression(node.expression)) {
|
|
170
|
+
const [firstArgument] = node.arguments;
|
|
171
|
+
if (!firstArgument || !ts$1.isExpression(firstArgument)) return;
|
|
172
|
+
found = unwrapObjectLiteralExpression$1(firstArgument) ?? resolveIdentifierObject(firstArgument, bindings);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
ts$1.forEachChild(node, visit);
|
|
176
|
+
};
|
|
177
|
+
visit(sourceFile);
|
|
178
|
+
return found;
|
|
179
|
+
}
|
|
180
|
+
function collectStateLocations(stateObject, parentPath, locations, bindings, staticValues, root) {
|
|
181
|
+
const statesObject = getObjectLiteralProperty$1(stateObject, "states");
|
|
182
|
+
if (!statesObject) return;
|
|
183
|
+
for (const property of statesObject.properties) {
|
|
184
|
+
const resolved = resolveStateProperty(property, bindings, staticValues, root);
|
|
185
|
+
if (!resolved) continue;
|
|
186
|
+
const path = [...parentPath, resolved.key];
|
|
187
|
+
locations.push({
|
|
188
|
+
uri: resolved.uri,
|
|
189
|
+
path,
|
|
190
|
+
kind: resolved.kind,
|
|
191
|
+
symbol: resolved.symbol,
|
|
192
|
+
keyRange: resolved.keyRange,
|
|
193
|
+
referenceRange: resolved.referenceRange,
|
|
194
|
+
keySource: resolved.keySource,
|
|
195
|
+
keyReplacementText: resolved.keyReplacementText,
|
|
196
|
+
range: toSourceRange(resolved.sourceFile, resolved.stateObject)
|
|
197
|
+
});
|
|
198
|
+
collectStateLocations(resolved.stateObject, path, locations, resolved.bindings, staticValues, root);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function resolveStateProperty(property, bindings, staticValues, root) {
|
|
202
|
+
if (ts$1.isShorthandPropertyAssignment(property)) {
|
|
203
|
+
const binding = bindings.get(property.name.text);
|
|
204
|
+
if (!binding) return null;
|
|
205
|
+
return {
|
|
206
|
+
key: property.name.text,
|
|
207
|
+
kind: binding.kind,
|
|
208
|
+
symbol: property.name.text,
|
|
209
|
+
keyRange: toSourceRange(property.getSourceFile(), property.name),
|
|
210
|
+
referenceRange: toSourceRange(property.getSourceFile(), property.name),
|
|
211
|
+
stateObject: binding.node,
|
|
212
|
+
sourceFile: binding.sourceFile,
|
|
213
|
+
uri: binding.uri,
|
|
214
|
+
bindings: binding.bindings
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (!ts$1.isPropertyAssignment(property)) return null;
|
|
218
|
+
const resolvedKey = resolvePropertyKey(property.name, staticValues, root);
|
|
219
|
+
if (!resolvedKey) return null;
|
|
220
|
+
const inlineObject = unwrapObjectLiteralExpression$1(property.initializer);
|
|
221
|
+
if (inlineObject) return {
|
|
222
|
+
key: resolvedKey.key,
|
|
223
|
+
kind: "inline",
|
|
224
|
+
keyRange: toSourceRange(property.getSourceFile(), property.name),
|
|
225
|
+
keySource: resolvedKey.keySource,
|
|
226
|
+
keyReplacementText: resolvedKey.keyReplacementText,
|
|
227
|
+
stateObject: inlineObject,
|
|
228
|
+
sourceFile: property.getSourceFile(),
|
|
229
|
+
uri: getSourceFileUri(property.getSourceFile()),
|
|
230
|
+
bindings
|
|
231
|
+
};
|
|
232
|
+
if (ts$1.isIdentifier(property.initializer)) {
|
|
233
|
+
const binding = bindings.get(property.initializer.text);
|
|
234
|
+
if (!binding) return null;
|
|
235
|
+
return {
|
|
236
|
+
key: resolvedKey.key,
|
|
237
|
+
kind: binding.kind,
|
|
238
|
+
symbol: property.initializer.text,
|
|
239
|
+
keyRange: toSourceRange(property.getSourceFile(), property.name),
|
|
240
|
+
referenceRange: toSourceRange(property.getSourceFile(), property.initializer),
|
|
241
|
+
keySource: resolvedKey.keySource,
|
|
242
|
+
keyReplacementText: resolvedKey.keyReplacementText,
|
|
243
|
+
stateObject: binding.node,
|
|
244
|
+
sourceFile: binding.sourceFile,
|
|
245
|
+
uri: binding.uri,
|
|
246
|
+
bindings: binding.bindings
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
function resolveImportPath(fromFileName, specifier) {
|
|
252
|
+
if (!specifier.startsWith(".")) return null;
|
|
253
|
+
const basePath = path$1.resolve(path$1.dirname(fromFileName), specifier);
|
|
254
|
+
return [
|
|
255
|
+
basePath,
|
|
256
|
+
`${basePath}.ts`,
|
|
257
|
+
`${basePath}.tsx`,
|
|
258
|
+
`${basePath}.js`,
|
|
259
|
+
`${basePath}.jsx`,
|
|
260
|
+
path$1.join(basePath, "index.ts"),
|
|
261
|
+
path$1.join(basePath, "index.tsx"),
|
|
262
|
+
path$1.join(basePath, "index.js"),
|
|
263
|
+
path$1.join(basePath, "index.jsx")
|
|
264
|
+
].find((candidate) => isReadableFile(candidate)) ?? null;
|
|
265
|
+
}
|
|
266
|
+
function isReadableFile(fileName) {
|
|
267
|
+
try {
|
|
268
|
+
return fs$1.statSync(fileName).isFile();
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function readSourceFile(fileName) {
|
|
274
|
+
try {
|
|
275
|
+
return fs$1.readFileSync(fileName, "utf8");
|
|
276
|
+
} catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function getSourceFileUri(sourceFile) {
|
|
281
|
+
return path$1.isAbsolute(sourceFile.fileName) ? pathToFileURL(sourceFile.fileName).toString() : sourceFile.fileName;
|
|
282
|
+
}
|
|
283
|
+
function unwrapCreateConfigArgument(expression) {
|
|
284
|
+
const unwrapped = unwrapExpression(expression);
|
|
285
|
+
if (!ts$1.isCallExpression(unwrapped)) return null;
|
|
286
|
+
const callee = unwrapped.expression;
|
|
287
|
+
if (!ts$1.isPropertyAccessExpression(callee) || !isSetupCreateConfigName(callee.name.text)) return null;
|
|
288
|
+
const [firstArgument] = unwrapped.arguments;
|
|
289
|
+
if (!firstArgument || !ts$1.isExpression(firstArgument)) return null;
|
|
290
|
+
return unwrapObjectLiteralExpression$1(firstArgument);
|
|
291
|
+
}
|
|
292
|
+
function isSetupCreateConfigName(name) {
|
|
293
|
+
return /^create[A-Z_$][\w$]*Config$/.test(name);
|
|
294
|
+
}
|
|
295
|
+
function resolveIdentifierObject(expression, bindings) {
|
|
296
|
+
const unwrapped = unwrapExpression(expression);
|
|
297
|
+
if (!ts$1.isIdentifier(unwrapped)) return null;
|
|
298
|
+
const binding = bindings.get(unwrapped.text);
|
|
299
|
+
return binding?.kind === "objectReference" ? binding.node : null;
|
|
300
|
+
}
|
|
301
|
+
function getObjectLiteralProperty$1(objectLiteral, propertyName) {
|
|
302
|
+
for (const property of objectLiteral.properties) {
|
|
303
|
+
if (!ts$1.isPropertyAssignment(property)) continue;
|
|
304
|
+
if (getPropertyNameText$1(property.name) !== propertyName) continue;
|
|
305
|
+
return unwrapObjectLiteralExpression$1(property.initializer);
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
function getPropertyNameText$1(name) {
|
|
310
|
+
if (ts$1.isIdentifier(name) || ts$1.isStringLiteral(name) || ts$1.isNumericLiteral(name)) return name.text;
|
|
311
|
+
if (ts$1.isComputedPropertyName(name)) {
|
|
312
|
+
const expression = name.expression;
|
|
313
|
+
if (ts$1.isStringLiteral(expression) || ts$1.isNoSubstitutionTemplateLiteral(expression) || ts$1.isNumericLiteral(expression)) return expression.text;
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function resolvePropertyKey(name, staticValues, root) {
|
|
318
|
+
const literalKey = getPropertyNameText$1(name);
|
|
319
|
+
if (literalKey !== null) {
|
|
320
|
+
if (ts$1.isComputedPropertyName(name)) return {
|
|
321
|
+
key: literalKey,
|
|
322
|
+
keySource: { kind: "computedLiteral" },
|
|
323
|
+
keyReplacementText: JSON.stringify(literalKey)
|
|
324
|
+
};
|
|
325
|
+
return { key: literalKey };
|
|
326
|
+
}
|
|
327
|
+
if (!ts$1.isComputedPropertyName(name)) return null;
|
|
328
|
+
const binding = getStaticValueBindingForExpression(name.expression, staticValues);
|
|
329
|
+
if (!binding) return null;
|
|
330
|
+
return {
|
|
331
|
+
key: binding.value,
|
|
332
|
+
keySource: {
|
|
333
|
+
kind: binding.kind,
|
|
334
|
+
symbol: binding.symbol,
|
|
335
|
+
valueRange: binding.valueRange,
|
|
336
|
+
machineLocal: isStaticValueMachineLocal(name.getSourceFile(), binding, root)
|
|
337
|
+
},
|
|
338
|
+
keyReplacementText: JSON.stringify(binding.value)
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function getStaticValueBindingForExpression(expression, staticValues) {
|
|
342
|
+
const unwrapped = unwrapExpression(expression);
|
|
343
|
+
if (ts$1.isIdentifier(unwrapped)) return staticValues.get(unwrapped.text) ?? null;
|
|
344
|
+
if (ts$1.isPropertyAccessExpression(unwrapped) && ts$1.isIdentifier(unwrapped.expression)) return staticValues.get(`${unwrapped.expression.text}.${unwrapped.name.text}`) ?? null;
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
function collectStaticValueReferences(sourceFile, uri, root, staticValues) {
|
|
348
|
+
const references = [];
|
|
349
|
+
if (staticValues.size === 0) return references;
|
|
350
|
+
const rootStart = root.getStart(sourceFile);
|
|
351
|
+
const rootEnd = root.getEnd();
|
|
352
|
+
const visit = (node) => {
|
|
353
|
+
if (ts$1.isComputedPropertyName(node)) {
|
|
354
|
+
const binding = getStaticValueBindingForExpression(node.expression, staticValues);
|
|
355
|
+
if (binding && !isStateKeyComputedPropertyName(node)) references.push({
|
|
356
|
+
uri,
|
|
357
|
+
range: toSourceRange(sourceFile, node),
|
|
358
|
+
value: binding.value
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (!(node.getStart(sourceFile) >= rootStart && node.getEnd() <= rootEnd)) {
|
|
363
|
+
ts$1.forEachChild(node, visit);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const binding = getStaticValueReferenceBinding(node, staticValues);
|
|
367
|
+
if (binding) {
|
|
368
|
+
references.push({
|
|
369
|
+
uri,
|
|
370
|
+
range: toSourceRange(sourceFile, node),
|
|
371
|
+
value: binding.value
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
ts$1.forEachChild(node, visit);
|
|
376
|
+
};
|
|
377
|
+
visit(root);
|
|
378
|
+
return references;
|
|
379
|
+
}
|
|
380
|
+
function getStaticValueReferenceBinding(node, staticValues) {
|
|
381
|
+
if (ts$1.isIdentifier(node)) {
|
|
382
|
+
if (ts$1.isVariableDeclaration(node.parent) && node.parent.name === node) return null;
|
|
383
|
+
if (ts$1.isPropertyAssignment(node.parent) && node.parent.name === node) return null;
|
|
384
|
+
if (ts$1.isPropertyAccessExpression(node.parent)) return null;
|
|
385
|
+
return staticValues.get(node.text) ?? null;
|
|
386
|
+
}
|
|
387
|
+
if (ts$1.isPropertyAccessExpression(node) && ts$1.isIdentifier(node.expression)) {
|
|
388
|
+
if (ts$1.isComputedPropertyName(node.parent) && ts$1.isEnumMember(node.parent.parent)) return null;
|
|
389
|
+
return staticValues.get(`${node.expression.text}.${node.name.text}`) ?? null;
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function isStaticValueMachineLocal(sourceFile, binding, root) {
|
|
394
|
+
const rootStart = root.getStart(sourceFile);
|
|
395
|
+
const rootEnd = root.getEnd();
|
|
396
|
+
let referenceCount = 0;
|
|
397
|
+
let hasOutsideReference = false;
|
|
398
|
+
const visit = (node) => {
|
|
399
|
+
if (getStaticValueReferenceBinding(node, new Map([[binding.symbol, binding]]))) {
|
|
400
|
+
referenceCount += 1;
|
|
401
|
+
if (node.getStart(sourceFile) < rootStart || node.getEnd() > rootEnd) hasOutsideReference = true;
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
ts$1.forEachChild(node, visit);
|
|
405
|
+
};
|
|
406
|
+
visit(sourceFile);
|
|
407
|
+
return referenceCount > 0 && !hasOutsideReference;
|
|
408
|
+
}
|
|
409
|
+
function isStateKeyComputedPropertyName(name) {
|
|
410
|
+
const property = name.parent;
|
|
411
|
+
if (!ts$1.isPropertyAssignment(property)) return false;
|
|
412
|
+
const objectLiteral = property.parent;
|
|
413
|
+
if (!ts$1.isObjectLiteralExpression(objectLiteral)) return false;
|
|
414
|
+
const parentProperty = objectLiteral.parent;
|
|
415
|
+
if (!ts$1.isPropertyAssignment(parentProperty)) return false;
|
|
416
|
+
return getPropertyNameText$1(parentProperty.name) === "states";
|
|
417
|
+
}
|
|
418
|
+
function getStaticStringValue(expression) {
|
|
419
|
+
const unwrapped = unwrapExpression(expression);
|
|
420
|
+
if (ts$1.isStringLiteral(unwrapped) || ts$1.isNoSubstitutionTemplateLiteral(unwrapped) || ts$1.isNumericLiteral(unwrapped)) return unwrapped.text;
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
function getEnumMemberName(name) {
|
|
424
|
+
if (ts$1.isIdentifier(name) || ts$1.isStringLiteral(name) || ts$1.isNumericLiteral(name)) return name.text;
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
function isCreateMachineExpression(expression) {
|
|
428
|
+
return ts$1.isIdentifier(expression) && expression.text === "createMachine" || ts$1.isPropertyAccessExpression(expression) && expression.name.text === "createMachine";
|
|
429
|
+
}
|
|
430
|
+
function unwrapObjectLiteralExpression$1(expression) {
|
|
431
|
+
const unwrapped = unwrapExpression(expression);
|
|
432
|
+
return ts$1.isObjectLiteralExpression(unwrapped) ? unwrapped : null;
|
|
433
|
+
}
|
|
434
|
+
function unwrapExpression(expression) {
|
|
435
|
+
let current = expression;
|
|
436
|
+
for (;;) {
|
|
437
|
+
if (ts$1.isParenthesizedExpression(current)) {
|
|
438
|
+
current = current.expression;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (ts$1.isAsExpression(current) || ts$1.isSatisfiesExpression(current)) {
|
|
442
|
+
current = current.expression;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (ts$1.isTypeAssertionExpression(current) || ts$1.isNonNullExpression(current)) {
|
|
446
|
+
current = current.expression;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
return current;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function toSourceRange(sourceFile, node) {
|
|
453
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
454
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
455
|
+
return {
|
|
456
|
+
startLine: start.line,
|
|
457
|
+
startChar: start.character,
|
|
458
|
+
endLine: end.line,
|
|
459
|
+
endChar: end.character
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function getScriptKind$1(fileName) {
|
|
463
|
+
if (fileName.endsWith(".tsx")) return ts$1.ScriptKind.TSX;
|
|
464
|
+
if (fileName.endsWith(".jsx")) return ts$1.ScriptKind.JSX;
|
|
465
|
+
if (fileName.endsWith(".js")) return ts$1.ScriptKind.JS;
|
|
466
|
+
return ts$1.ScriptKind.TS;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region ../editor-sync/src/internal/codeToGraph.ts
|
|
471
|
+
/**
|
|
472
|
+
* Returns source text for the embed to parse.
|
|
473
|
+
*
|
|
474
|
+
* The returned text is still a temporary parse payload, not the source we write
|
|
475
|
+
* back to. Directly imported modular states are hydrated into the root
|
|
476
|
+
* `states` object so Viz can see their transition bodies even though it only
|
|
477
|
+
* receives one string.
|
|
478
|
+
*/
|
|
479
|
+
function parseSourceToMachine(text, document) {
|
|
480
|
+
const sourceLocations = collectXStateSourceLocations(text, document);
|
|
481
|
+
if (!sourceLocations) return text;
|
|
482
|
+
const staticValueReplacements = (sourceLocations.staticValues ?? []).map((location) => ({
|
|
483
|
+
range: location.range,
|
|
484
|
+
newText: JSON.stringify(location.value)
|
|
485
|
+
}));
|
|
486
|
+
const stateReplacements = sourceLocations.states.filter((location) => location.path.length === 1 && location.keyRange).flatMap((location) => {
|
|
487
|
+
const keyRange = location.keyRange;
|
|
488
|
+
const keyReplacement = location.keyReplacementText ? [{
|
|
489
|
+
range: keyRange,
|
|
490
|
+
newText: location.keyReplacementText
|
|
491
|
+
}] : [];
|
|
492
|
+
const stateText = readSourceRange(location.uri, location.range);
|
|
493
|
+
if (!stateText) return keyReplacement;
|
|
494
|
+
if (location.referenceRange && !areSourceRangesEqual(location.referenceRange, keyRange)) return [...keyReplacement, {
|
|
495
|
+
range: location.referenceRange,
|
|
496
|
+
newText: stateText
|
|
497
|
+
}];
|
|
498
|
+
if (!canHydrateShorthandState(text, keyRange)) return keyReplacement;
|
|
499
|
+
return [...keyReplacement, {
|
|
500
|
+
range: keyRange,
|
|
501
|
+
newText: `${location.path[0]}: ${stateText}`
|
|
502
|
+
}];
|
|
503
|
+
});
|
|
504
|
+
return applySourceRangeReplacements(text, [...staticValueReplacements, ...stateReplacements].filter(removeOverlappingRanges).sort(compareSourceRangesDescending));
|
|
505
|
+
}
|
|
506
|
+
function removeOverlappingRanges(replacement, index, replacements) {
|
|
507
|
+
return !replacements.some((candidate, candidateIndex) => {
|
|
508
|
+
if (candidateIndex === index) return false;
|
|
509
|
+
return compareSourceRangesDescending(candidate, replacement) < 0 && rangesOverlap(candidate.range, replacement.range);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function rangesOverlap(left, right) {
|
|
513
|
+
const leftStart = rangePositionKey(left.startLine, left.startChar);
|
|
514
|
+
const leftEnd = rangePositionKey(left.endLine, left.endChar);
|
|
515
|
+
const rightStart = rangePositionKey(right.startLine, right.startChar);
|
|
516
|
+
return leftStart < rangePositionKey(right.endLine, right.endChar) && rightStart < leftEnd;
|
|
517
|
+
}
|
|
518
|
+
function rangePositionKey(line, character) {
|
|
519
|
+
return line * 1e6 + character;
|
|
520
|
+
}
|
|
521
|
+
function areSourceRangesEqual(left, right) {
|
|
522
|
+
return left.startLine === right.startLine && left.startChar === right.startChar && left.endLine === right.endLine && left.endChar === right.endChar;
|
|
523
|
+
}
|
|
524
|
+
function canHydrateShorthandState(text, range) {
|
|
525
|
+
const end = offsetAtRangePosition$1(text, range.endLine, range.endChar);
|
|
526
|
+
return text.slice(end).match(/\S/)?.[0] !== ":";
|
|
527
|
+
}
|
|
528
|
+
function readSourceRange(uri, range) {
|
|
529
|
+
const fileName = sourceUriToFileName(uri);
|
|
530
|
+
if (!fileName) return null;
|
|
531
|
+
try {
|
|
532
|
+
const text = fs$1.readFileSync(fileName, "utf8");
|
|
533
|
+
const start = offsetAtRangePosition$1(text, range.startLine, range.startChar);
|
|
534
|
+
const end = offsetAtRangePosition$1(text, range.endLine, range.endChar);
|
|
535
|
+
return text.slice(start, end);
|
|
536
|
+
} catch {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function sourceUriToFileName(uri) {
|
|
541
|
+
if (uri.startsWith("file://")) return fileURLToPath(uri);
|
|
542
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(uri)) return null;
|
|
543
|
+
return uri;
|
|
544
|
+
}
|
|
545
|
+
function applySourceRangeReplacements(text, replacements) {
|
|
546
|
+
let updated = text;
|
|
547
|
+
for (const replacement of replacements) {
|
|
548
|
+
const start = offsetAtRangePosition$1(updated, replacement.range.startLine, replacement.range.startChar);
|
|
549
|
+
const end = offsetAtRangePosition$1(updated, replacement.range.endLine, replacement.range.endChar);
|
|
550
|
+
updated = `${updated.slice(0, start)}${replacement.newText}${updated.slice(end)}`;
|
|
551
|
+
}
|
|
552
|
+
return updated;
|
|
553
|
+
}
|
|
554
|
+
function compareSourceRangesDescending(left, right) {
|
|
555
|
+
if (left.range.startLine !== right.range.startLine) return right.range.startLine - left.range.startLine;
|
|
556
|
+
return right.range.startChar - left.range.startChar;
|
|
557
|
+
}
|
|
558
|
+
function offsetAtRangePosition$1(text, line, character) {
|
|
559
|
+
if (line <= 0) return character;
|
|
560
|
+
let currentLine = 0;
|
|
561
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
562
|
+
if (text.charCodeAt(index) !== 10) continue;
|
|
563
|
+
currentLine += 1;
|
|
564
|
+
if (currentLine === line) return index + 1 + character;
|
|
565
|
+
}
|
|
566
|
+
return text.length;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region ../editor-sync/src/internal/graphToCode.ts
|
|
571
|
+
async function applyMachineToSourceDocuments(machineConfig, document, options = {}) {
|
|
572
|
+
if (!isMachineConfig(machineConfig)) return [];
|
|
573
|
+
const sourceFile = createSourceFile(document, document.getText());
|
|
574
|
+
const machineConfigNode = getMappedRootObjectLiteral(document, sourceFile, options.sourceLocations) ?? findMachineConfigObjectLiteral(sourceFile);
|
|
575
|
+
if (!machineConfigNode) return [];
|
|
576
|
+
const parsedPatches = parseGraphPatches(options.patches);
|
|
577
|
+
const prevGraph = parseGraph(options.prevGraph);
|
|
578
|
+
const nextGraph = parseGraph(options.nextGraph);
|
|
579
|
+
if (!parsedPatches || !prevGraph || !nextGraph || parsedPatches.length === 0) {
|
|
580
|
+
if (wouldDeModularizeRootReplacement(options.sourceLocations)) return [];
|
|
581
|
+
return [createReplacement(document, sourceFile, machineConfigNode, machineConfig)];
|
|
582
|
+
}
|
|
583
|
+
const semanticSplit = await tryCreateSemanticPatchSplitForDocuments(document, sourceFile, machineConfigNode, parsedPatches, prevGraph, nextGraph, options.sourceLocations, options.openTextDocument);
|
|
584
|
+
if (semanticSplit && semanticSplit.remainingPatches.length === 0) return semanticSplit.replacements.sort(compareReplacementRangesDescending);
|
|
585
|
+
const activePatches = semanticSplit?.remainingPatches ?? parsedPatches;
|
|
586
|
+
const activeSourceLocations = semanticSplit?.sourceLocations ?? options.sourceLocations;
|
|
587
|
+
let semanticReplacements = semanticSplit?.replacements ?? [];
|
|
588
|
+
const implementationSplit = tryCreateImplementationPatchSplit(document, sourceFile, activePatches, nextGraph);
|
|
589
|
+
if (implementationSplit && implementationSplit.remainingPatches.length === 0) return [...semanticReplacements, ...implementationSplit.replacements].sort(compareReplacementRangesDescending);
|
|
590
|
+
if (implementationSplit) semanticReplacements = [...semanticReplacements, ...implementationSplit.replacements];
|
|
591
|
+
const patchesAfterImplementationSplit = implementationSplit?.remainingPatches ?? activePatches;
|
|
592
|
+
const stateMembershipSplit = await tryCreateStateMembershipPatchSplitForDocuments(document, sourceFile, machineConfigNode, machineConfig, patchesAfterImplementationSplit, prevGraph, nextGraph, activeSourceLocations, options.openTextDocument);
|
|
593
|
+
if (stateMembershipSplit) {
|
|
594
|
+
if (stateMembershipSplit.remainingPatches.length === 0) return [...semanticReplacements, ...stateMembershipSplit.replacements].sort(compareReplacementRangesDescending);
|
|
595
|
+
const remainingPlan = deriveReplacementPlan(stateMembershipSplit.remainingPatches, prevGraph, nextGraph);
|
|
596
|
+
if (remainingPlan.type === "root") {
|
|
597
|
+
if (wouldDeModularizeRootReplacement(options.sourceLocations)) return [];
|
|
598
|
+
return [createReplacement(document, sourceFile, machineConfigNode, machineConfig)];
|
|
599
|
+
}
|
|
600
|
+
const replacements = [...semanticReplacements, ...stateMembershipSplit.replacements];
|
|
601
|
+
for (const path of remainingPlan.paths) {
|
|
602
|
+
const nextValue = getStateConfigByPath(machineConfig, path);
|
|
603
|
+
if (!isMachineConfig(nextValue)) return fallbackRootReplacement(document, sourceFile, machineConfigNode, machineConfig, activeSourceLocations);
|
|
604
|
+
const mappedTarget = await getMappedStateReplacementTarget(document, sourceFile, activeSourceLocations, path, options.openTextDocument);
|
|
605
|
+
const currentNode = mappedTarget?.node ?? getStateObjectLiteralByPath(machineConfigNode, path);
|
|
606
|
+
const targetDocument = mappedTarget?.document ?? document;
|
|
607
|
+
const targetSourceFile = mappedTarget?.sourceFile ?? sourceFile;
|
|
608
|
+
if (!currentNode) return fallbackRootReplacement(document, sourceFile, machineConfigNode, machineConfig, activeSourceLocations);
|
|
609
|
+
replacements.push(createReplacement(targetDocument, targetSourceFile, currentNode, nextValue));
|
|
610
|
+
}
|
|
611
|
+
return replacements.sort(compareReplacementRangesDescending);
|
|
612
|
+
}
|
|
613
|
+
const plan = deriveReplacementPlan(patchesAfterImplementationSplit, prevGraph, nextGraph);
|
|
614
|
+
if (plan.type === "root") {
|
|
615
|
+
if (wouldDeModularizeRootReplacement(options.sourceLocations)) return [];
|
|
616
|
+
return [createReplacement(document, sourceFile, machineConfigNode, machineConfig)];
|
|
617
|
+
}
|
|
618
|
+
const replacements = [...semanticReplacements];
|
|
619
|
+
for (const path of plan.paths) {
|
|
620
|
+
const nextValue = getStateConfigByPath(machineConfig, path);
|
|
621
|
+
if (!isMachineConfig(nextValue)) return fallbackRootReplacement(document, sourceFile, machineConfigNode, machineConfig, activeSourceLocations);
|
|
622
|
+
const mappedTarget = await getMappedStateReplacementTarget(document, sourceFile, activeSourceLocations, path, options.openTextDocument);
|
|
623
|
+
const currentNode = mappedTarget?.node ?? getStateObjectLiteralByPath(machineConfigNode, path);
|
|
624
|
+
const targetDocument = mappedTarget?.document ?? document;
|
|
625
|
+
const targetSourceFile = mappedTarget?.sourceFile ?? sourceFile;
|
|
626
|
+
if (!currentNode) return fallbackRootReplacement(document, sourceFile, machineConfigNode, machineConfig, activeSourceLocations);
|
|
627
|
+
replacements.push(createReplacement(targetDocument, targetSourceFile, currentNode, nextValue));
|
|
628
|
+
}
|
|
629
|
+
return replacements.sort(compareReplacementRangesDescending);
|
|
630
|
+
}
|
|
631
|
+
function fallbackRootReplacement(document, sourceFile, machineConfigNode, machineConfig, sourceLocations) {
|
|
632
|
+
if (wouldDeModularizeRootReplacement(sourceLocations)) return [];
|
|
633
|
+
return [createReplacement(document, sourceFile, machineConfigNode, machineConfig)];
|
|
634
|
+
}
|
|
635
|
+
function tryCreateImplementationPatchSplit(document, sourceFile, patches, nextGraph) {
|
|
636
|
+
const replacements = [];
|
|
637
|
+
const remainingPatches = [];
|
|
638
|
+
for (const patch of patches) {
|
|
639
|
+
if (!isImplementationPatch(patch)) {
|
|
640
|
+
remainingPatches.push(patch);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
const patchReplacements = createImplementationPatchReplacements(document, sourceFile, patch, nextGraph);
|
|
644
|
+
if (!patchReplacements) {
|
|
645
|
+
remainingPatches.push(patch);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
replacements.push(...patchReplacements);
|
|
649
|
+
}
|
|
650
|
+
return replacements.length > 0 ? {
|
|
651
|
+
replacements: replacements.sort(compareReplacementRangesDescending),
|
|
652
|
+
remainingPatches
|
|
653
|
+
} : null;
|
|
654
|
+
}
|
|
655
|
+
function isImplementationPatch(patch) {
|
|
656
|
+
return patch.op === "createImplementation" || patch.op === "updateImplementation" || patch.op === "deleteImplementation";
|
|
657
|
+
}
|
|
658
|
+
function createImplementationPatchReplacements(document, sourceFile, patch, nextGraph) {
|
|
659
|
+
const implementationRoot = findSetupObjectLiteral(sourceFile) ?? findCreateMachineImplementationObjectLiteral(sourceFile);
|
|
660
|
+
if (!implementationRoot) return null;
|
|
661
|
+
const categoryName = implementationSourceTypeToCategoryName(patch.sourceType);
|
|
662
|
+
const categoryObject = getObjectLiteralProperty(implementationRoot, categoryName);
|
|
663
|
+
if (patch.op === "createImplementation") return createImplementationCreateReplacements(document, sourceFile, implementationRoot, categoryName, getGraphImplementation(nextGraph, patch.sourceType, patch.implementation.id) ?? patch.implementation);
|
|
664
|
+
if (!categoryObject) return null;
|
|
665
|
+
const implementationProperty = findObjectLiteralProperty(categoryObject, patch.implementationId);
|
|
666
|
+
if (!implementationProperty) return null;
|
|
667
|
+
if (patch.op === "deleteImplementation") {
|
|
668
|
+
const deletionRange = getObjectLiteralPropertyDeletionRange(sourceFile, categoryObject, implementationProperty);
|
|
669
|
+
return [createOffsetReplacement(document, deletionRange.start, deletionRange.end, "")];
|
|
670
|
+
}
|
|
671
|
+
const nextImplementation = getGraphImplementation(nextGraph, patch.sourceType, patch.implementationId);
|
|
672
|
+
const nextName = patch.data.name ?? nextImplementation?.name;
|
|
673
|
+
const nextCode = patch.data.code ?? nextImplementation?.code?.body;
|
|
674
|
+
const replacements = [];
|
|
675
|
+
if (nextName && nextName !== patch.implementationId) {
|
|
676
|
+
const nameNode = getObjectLiteralElementName(implementationProperty);
|
|
677
|
+
if (!nameNode) return null;
|
|
678
|
+
replacements.push(createOffsetReplacement(document, nameNode.getStart(sourceFile), nameNode.getEnd(), propertyNameTextForSource(nextName)));
|
|
679
|
+
}
|
|
680
|
+
if (nextCode !== void 0) {
|
|
681
|
+
if (!ts$1.isPropertyAssignment(implementationProperty)) return null;
|
|
682
|
+
replacements.push(createOffsetReplacement(document, implementationProperty.initializer.getStart(sourceFile), implementationProperty.initializer.getEnd(), nextCode));
|
|
683
|
+
}
|
|
684
|
+
return replacements.length > 0 ? replacements : null;
|
|
685
|
+
}
|
|
686
|
+
function createImplementationCreateReplacements(document, sourceFile, implementationRoot, categoryName, implementation) {
|
|
687
|
+
const implementationCode = implementation.code?.body;
|
|
688
|
+
if (!implementation.id || !implementationCode) return null;
|
|
689
|
+
const categoryObject = getObjectLiteralProperty(implementationRoot, categoryName);
|
|
690
|
+
if (!categoryObject) {
|
|
691
|
+
const text = objectPropertyTextForInsertion(implementationRoot, `${propertyNameTextForSource(categoryName)}: {\n${getIndentForNewObjectProperty(sourceFile, implementationRoot)}${propertyNameTextForSource(implementation.id)}: ${implementationCode},\n${getIndentForObjectClose(sourceFile, implementationRoot)}}`);
|
|
692
|
+
return [createOffsetReplacement(document, implementationRoot.properties.end, implementationRoot.properties.end, text)];
|
|
693
|
+
}
|
|
694
|
+
const text = objectPropertyTextForInsertion(categoryObject, `${propertyNameTextForSource(implementation.id)}: ${implementationCode}`);
|
|
695
|
+
return [createOffsetReplacement(document, categoryObject.properties.end, categoryObject.properties.end, text)];
|
|
696
|
+
}
|
|
697
|
+
function objectPropertyTextForInsertion(objectLiteral, propertyText) {
|
|
698
|
+
return `${objectLiteral.properties.length > 0 ? "," : ""}${getLineBreak(objectLiteral.getSourceFile())}${getIndentForNewObjectProperty(objectLiteral.getSourceFile(), objectLiteral)}${propertyText}`;
|
|
699
|
+
}
|
|
700
|
+
function getLineBreak(sourceFile) {
|
|
701
|
+
return sourceFile.text.includes("\r\n") ? "\r\n" : "\n";
|
|
702
|
+
}
|
|
703
|
+
function getIndentForNewObjectProperty(sourceFile, objectLiteral) {
|
|
704
|
+
const closeIndent = getIndentForObjectClose(sourceFile, objectLiteral);
|
|
705
|
+
const existingProperty = objectLiteral.properties[0];
|
|
706
|
+
if (existingProperty) {
|
|
707
|
+
const propertyLineStart = getLineStartOffset(sourceFile.text, existingProperty.getStart(sourceFile));
|
|
708
|
+
return sourceFile.text.slice(propertyLineStart, existingProperty.getStart(sourceFile)).match(/^[ \t]*/)?.[0] ?? `${closeIndent} `;
|
|
709
|
+
}
|
|
710
|
+
return `${closeIndent} `;
|
|
711
|
+
}
|
|
712
|
+
function getIndentForObjectClose(sourceFile, objectLiteral) {
|
|
713
|
+
const objectStart = objectLiteral.getStart(sourceFile);
|
|
714
|
+
const lineStart = getLineStartOffset(sourceFile.text, objectStart);
|
|
715
|
+
return sourceFile.text.slice(lineStart, objectStart).match(/^[ \t]*/)?.[0] ?? "";
|
|
716
|
+
}
|
|
717
|
+
function createOffsetReplacement(document, startOffset, endOffset, newText) {
|
|
718
|
+
const start = document.positionAt(startOffset);
|
|
719
|
+
const end = document.positionAt(endOffset);
|
|
720
|
+
return {
|
|
721
|
+
uri: getDocumentUri(document),
|
|
722
|
+
range: {
|
|
723
|
+
startLine: start.line,
|
|
724
|
+
startChar: start.character,
|
|
725
|
+
endLine: end.line,
|
|
726
|
+
endChar: end.character
|
|
727
|
+
},
|
|
728
|
+
newText
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function propertyNameTextForSource(name) {
|
|
732
|
+
return isIdentifierText(name) ? name : JSON.stringify(name);
|
|
733
|
+
}
|
|
734
|
+
function getGraphImplementation(graph, sourceType, implementationId) {
|
|
735
|
+
if (!implementationId) return;
|
|
736
|
+
return graph.data?.implementations?.[implementationSourceTypeToCategoryName(sourceType)]?.find((implementation) => implementation.id === implementationId);
|
|
737
|
+
}
|
|
738
|
+
function implementationSourceTypeToCategoryName(sourceType) {
|
|
739
|
+
switch (sourceType) {
|
|
740
|
+
case "action": return "actions";
|
|
741
|
+
case "guard": return "guards";
|
|
742
|
+
case "actor": return "actors";
|
|
743
|
+
case "delay": return "delays";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async function tryCreateStateMembershipPatchSplitForDocuments(document, sourceFile, machineConfigNode, machineConfig, patches, prevGraph, nextGraph, sourceLocations, openTextDocument) {
|
|
747
|
+
const split = splitStateMembershipPatches(patches, prevGraph, nextGraph, sourceLocations);
|
|
748
|
+
if (!split) return null;
|
|
749
|
+
const replacements = await createStateMembershipReplacementsForDocuments(document, sourceFile, machineConfigNode, machineConfig, split.changes, sourceLocations, openTextDocument);
|
|
750
|
+
if (!replacements) return null;
|
|
751
|
+
return {
|
|
752
|
+
replacements,
|
|
753
|
+
remainingPatches: split.remainingPatches
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function splitStateMembershipPatches(patches, prevGraph, nextGraph, sourceLocations) {
|
|
757
|
+
const createdById = /* @__PURE__ */ new Map();
|
|
758
|
+
const changesByParent = /* @__PURE__ */ new Map();
|
|
759
|
+
const remainingPatches = [];
|
|
760
|
+
const getChange = (parentPath) => {
|
|
761
|
+
const key = parentPath.join(".");
|
|
762
|
+
const existing = changesByParent.get(key);
|
|
763
|
+
if (existing) return existing;
|
|
764
|
+
const change = {
|
|
765
|
+
parentPath,
|
|
766
|
+
createdPaths: [],
|
|
767
|
+
deletedPaths: [],
|
|
768
|
+
patches: []
|
|
769
|
+
};
|
|
770
|
+
changesByParent.set(key, change);
|
|
771
|
+
return change;
|
|
772
|
+
};
|
|
773
|
+
for (const patch of patches) switch (patch.op) {
|
|
774
|
+
case "createState": {
|
|
775
|
+
if (!patch.id) {
|
|
776
|
+
remainingPatches.push(patch);
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
const parentPath = resolveStatePath(nextGraph, patch.parentId);
|
|
780
|
+
const nextPath = resolveStatePath(nextGraph, patch.id);
|
|
781
|
+
if (!parentPath || !nextPath || nextPath.length === 0) {
|
|
782
|
+
remainingPatches.push(patch);
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
const change = getChange(parentPath);
|
|
786
|
+
createdById.set(patch.id, {
|
|
787
|
+
parentPath,
|
|
788
|
+
path: nextPath
|
|
789
|
+
});
|
|
790
|
+
change.createdPaths.push(nextPath);
|
|
791
|
+
change.patches.push(patch);
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
case "updateState": {
|
|
795
|
+
const created = createdById.get(patch.stateId);
|
|
796
|
+
const nextPath = resolveStatePath(nextGraph, patch.stateId);
|
|
797
|
+
if (created && nextPath && arePathsEqual(created.path, nextPath)) {
|
|
798
|
+
getChange(created.parentPath).patches.push(patch);
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
remainingPatches.push(patch);
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
case "deleteState": {
|
|
805
|
+
const prevPath = resolveStatePath(prevGraph, patch.stateId);
|
|
806
|
+
if (!prevPath || prevPath.length === 0) {
|
|
807
|
+
remainingPatches.push(patch);
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
const change = getChange(prevPath.slice(0, -1));
|
|
811
|
+
change.deletedPaths.push(prevPath);
|
|
812
|
+
change.patches.push(patch);
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
default: remainingPatches.push(patch);
|
|
816
|
+
}
|
|
817
|
+
const deletedPaths = [...changesByParent.values()].flatMap((change) => change.deletedPaths);
|
|
818
|
+
const remainingPatchesWithSurvivingOwners = remainingPatches.filter((patch) => {
|
|
819
|
+
const owner = resolvePatchOwner(patch, prevGraph, nextGraph);
|
|
820
|
+
return owner.type !== "covered" && !isPatchOwnerCoveredByDeletedPaths(owner, deletedPaths);
|
|
821
|
+
});
|
|
822
|
+
const split = coalesceMembershipChangesWithRemainingPatches([...changesByParent.values()], remainingPatchesWithSurvivingOwners, prevGraph, nextGraph, sourceLocations);
|
|
823
|
+
return split.changes.length > 0 ? split : null;
|
|
824
|
+
}
|
|
825
|
+
function isPatchOwnerCoveredByDeletedPaths(owner, deletedPaths) {
|
|
826
|
+
if (owner.type !== "state" || deletedPaths.length === 0) return false;
|
|
827
|
+
return deletedPaths.some((deletedPath) => isPathEqualOrDescendant(owner.path, deletedPath));
|
|
828
|
+
}
|
|
829
|
+
function coalesceMembershipChangesWithRemainingPatches(changes, remainingPatches, prevGraph, nextGraph, sourceLocations) {
|
|
830
|
+
if (remainingPatches.length === 0) return {
|
|
831
|
+
changes,
|
|
832
|
+
remainingPatches
|
|
833
|
+
};
|
|
834
|
+
const remainingPlan = deriveReplacementPlan(remainingPatches, prevGraph, nextGraph);
|
|
835
|
+
const activeChanges = [];
|
|
836
|
+
const activeRemainingPatches = [...remainingPatches];
|
|
837
|
+
for (const change of changes) {
|
|
838
|
+
if (shouldCoalesceMembershipChangeWithRemainingPlan(change, remainingPlan, sourceLocations)) {
|
|
839
|
+
activeRemainingPatches.push(...change.patches);
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
activeChanges.push(change);
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
changes: activeChanges,
|
|
846
|
+
remainingPatches: activeRemainingPatches
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function shouldCoalesceMembershipChangeWithRemainingPlan(change, remainingPlan, sourceLocations) {
|
|
850
|
+
if (change.parentPath.length === 0 && wouldDeModularizeRootReplacement(sourceLocations)) return false;
|
|
851
|
+
if (remainingPlan.type === "root") return true;
|
|
852
|
+
return remainingPlan.paths.some((path) => isPathEqualOrDescendant(path, change.parentPath) || isPathEqualOrDescendant(change.parentPath, path));
|
|
853
|
+
}
|
|
854
|
+
function isPathEqualOrDescendant(path, candidateParentPath) {
|
|
855
|
+
return candidateParentPath.every((segment, index) => path[index] === segment);
|
|
856
|
+
}
|
|
857
|
+
async function createStateMembershipReplacementsForDocuments(document, sourceFile, machineConfigNode, machineConfig, changes, sourceLocations, openTextDocument) {
|
|
858
|
+
const replacements = [];
|
|
859
|
+
for (const change of changes) {
|
|
860
|
+
const target = await getStateMembershipParentTargetForDocuments(document, sourceFile, machineConfigNode, sourceLocations, change.parentPath, openTextDocument);
|
|
861
|
+
if (!target) return null;
|
|
862
|
+
const changeReplacements = createStateMembershipReplacementsForTarget(target.document, target.sourceFile, target.node, machineConfig, change);
|
|
863
|
+
if (!changeReplacements) return null;
|
|
864
|
+
replacements.push(...changeReplacements);
|
|
865
|
+
}
|
|
866
|
+
return replacements.sort(compareReplacementRangesDescending);
|
|
867
|
+
}
|
|
868
|
+
function createStateMembershipReplacementsForTarget(document, sourceFile, parentObject, machineConfig, change) {
|
|
869
|
+
const statesObject = getObjectLiteralProperty(parentObject, "states");
|
|
870
|
+
if (!statesObject) return null;
|
|
871
|
+
const stateEntries = [];
|
|
872
|
+
for (const path of change.createdPaths) {
|
|
873
|
+
const key = path[path.length - 1];
|
|
874
|
+
const stateConfig = getStateConfigByPath(machineConfig, path);
|
|
875
|
+
if (!key || !isMachineConfig(stateConfig)) return null;
|
|
876
|
+
stateEntries.push({
|
|
877
|
+
key,
|
|
878
|
+
config: stateConfig
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
const deletionReplacements = [];
|
|
882
|
+
for (const path of change.deletedPaths) {
|
|
883
|
+
const key = path[path.length - 1];
|
|
884
|
+
const property = key ? findObjectLiteralProperty(statesObject, key) : null;
|
|
885
|
+
if (!property) return null;
|
|
886
|
+
deletionReplacements.push(createObjectLiteralPropertyDeletion(document, sourceFile, statesObject, property));
|
|
887
|
+
}
|
|
888
|
+
return [...stateEntries.length > 0 ? [createObjectLiteralPropertiesInsertion(document, sourceFile, statesObject, stateEntries)] : [], ...deletionReplacements].sort(compareReplacementRangesDescending);
|
|
889
|
+
}
|
|
890
|
+
async function getStateMembershipParentTargetForDocuments(document, sourceFile, machineConfigNode, sourceLocations, parentPath, openTextDocument) {
|
|
891
|
+
if (parentPath.length === 0) return {
|
|
892
|
+
document,
|
|
893
|
+
sourceFile,
|
|
894
|
+
node: machineConfigNode
|
|
895
|
+
};
|
|
896
|
+
const mappedTarget = await getMappedStateReplacementTarget(document, sourceFile, sourceLocations, parentPath, openTextDocument);
|
|
897
|
+
if (mappedTarget) return mappedTarget;
|
|
898
|
+
const node = getStateObjectLiteralByPath(machineConfigNode, parentPath);
|
|
899
|
+
return node ? {
|
|
900
|
+
document,
|
|
901
|
+
sourceFile,
|
|
902
|
+
node
|
|
903
|
+
} : null;
|
|
904
|
+
}
|
|
905
|
+
function createObjectLiteralPropertiesInsertion(document, sourceFile, objectLiteral, entries) {
|
|
906
|
+
const formatting = getFormattingContext(document, sourceFile, objectLiteral);
|
|
907
|
+
const lineSeparator = formatting.newLineKind === ts$1.NewLineKind.CarriageReturnLineFeed ? "\r\n" : "\n";
|
|
908
|
+
const propertyTexts = entries.map((entry) => printObjectLiteralProperty(entry.key, entry.config, sourceFile, formatting));
|
|
909
|
+
const existingProperties = objectLiteral.properties;
|
|
910
|
+
const closeBraceOffset = objectLiteral.getEnd() - 1;
|
|
911
|
+
const startOffset = existingProperties.length === 0 ? closeBraceOffset : existingProperties[existingProperties.length - 1].getEnd();
|
|
912
|
+
const start = document.positionAt(startOffset);
|
|
913
|
+
const end = document.positionAt(closeBraceOffset);
|
|
914
|
+
const prefix = existingProperties.length === 0 ? lineSeparator : `,${lineSeparator}`;
|
|
915
|
+
const suffix = `${lineSeparator}${formatting.baseIndent}`;
|
|
916
|
+
return {
|
|
917
|
+
uri: getDocumentUri(document),
|
|
918
|
+
range: {
|
|
919
|
+
startLine: start.line,
|
|
920
|
+
startChar: start.character,
|
|
921
|
+
endLine: end.line,
|
|
922
|
+
endChar: end.character
|
|
923
|
+
},
|
|
924
|
+
newText: `${prefix}${propertyTexts.join(`,${lineSeparator}`)}${suffix}`
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function createObjectLiteralPropertyDeletion(document, sourceFile, objectLiteral, property) {
|
|
928
|
+
const range = getObjectLiteralPropertyDeletionRange(sourceFile, objectLiteral, property);
|
|
929
|
+
const start = document.positionAt(range.start);
|
|
930
|
+
const end = document.positionAt(range.end);
|
|
931
|
+
return {
|
|
932
|
+
uri: getDocumentUri(document),
|
|
933
|
+
range: {
|
|
934
|
+
startLine: start.line,
|
|
935
|
+
startChar: start.character,
|
|
936
|
+
endLine: end.line,
|
|
937
|
+
endChar: end.character
|
|
938
|
+
},
|
|
939
|
+
newText: ""
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function getObjectLiteralPropertyDeletionRange(sourceFile, objectLiteral, property) {
|
|
943
|
+
const properties = [...objectLiteral.properties];
|
|
944
|
+
const propertyIndex = properties.indexOf(property);
|
|
945
|
+
const propertyStart = property.getFullStart();
|
|
946
|
+
const propertyEnd = property.getEnd();
|
|
947
|
+
if (properties.length === 1) return {
|
|
948
|
+
start: propertyStart,
|
|
949
|
+
end: propertyEnd
|
|
950
|
+
};
|
|
951
|
+
if (propertyIndex === properties.length - 1) {
|
|
952
|
+
const previousProperty = properties[propertyIndex - 1];
|
|
953
|
+
const commaOffset = sourceFile.text.lastIndexOf(",", propertyStart - 1);
|
|
954
|
+
return {
|
|
955
|
+
start: commaOffset >= previousProperty.getEnd() && commaOffset < propertyStart ? commaOffset : propertyStart,
|
|
956
|
+
end: propertyEnd
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const nextProperty = properties[propertyIndex + 1];
|
|
960
|
+
const commaOffset = sourceFile.text.indexOf(",", propertyEnd);
|
|
961
|
+
return {
|
|
962
|
+
start: propertyStart,
|
|
963
|
+
end: commaOffset >= propertyEnd && commaOffset < nextProperty.getFullStart() ? commaOffset + 1 : propertyEnd
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
function findObjectLiteralProperty(objectLiteral, propertyName) {
|
|
967
|
+
for (const property of objectLiteral.properties) {
|
|
968
|
+
if (ts$1.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property;
|
|
969
|
+
if (ts$1.isPropertyAssignment(property)) {
|
|
970
|
+
if (getPropertyNameText(property.name) === propertyName) return property;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
function findObjectLiteralPropertyByRange(sourceFile, objectLiteral, range) {
|
|
976
|
+
if (!range) return null;
|
|
977
|
+
const start = offsetAtRangePosition(sourceFile.text, range.startLine, range.startChar);
|
|
978
|
+
const end = offsetAtRangePosition(sourceFile.text, range.endLine, range.endChar);
|
|
979
|
+
for (const property of objectLiteral.properties) {
|
|
980
|
+
const propertyName = getObjectLiteralElementName(property);
|
|
981
|
+
if (propertyName && propertyName.getStart(sourceFile) === start && propertyName.getEnd() === end) return property;
|
|
982
|
+
}
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
function getObjectLiteralElementName(property) {
|
|
986
|
+
if (ts$1.isPropertyAssignment(property) || ts$1.isShorthandPropertyAssignment(property) || ts$1.isMethodDeclaration(property) || ts$1.isGetAccessorDeclaration(property) || ts$1.isSetAccessorDeclaration(property)) return property.name;
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
function getStatePropertyKeyText(property) {
|
|
990
|
+
if (ts$1.isShorthandPropertyAssignment(property)) return property.name.text;
|
|
991
|
+
if (ts$1.isPropertyAssignment(property)) return getPropertyNameText(property.name);
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
function printObjectLiteralProperty(key, value, sourceFile, formatting) {
|
|
995
|
+
const objectText = printExpression(valueToExpression({ [key]: value }), sourceFile, formatting);
|
|
996
|
+
const lineSeparator = formatting.newLineKind === ts$1.NewLineKind.CarriageReturnLineFeed ? "\r\n" : "\n";
|
|
997
|
+
const lines = objectText.split(/\r?\n/);
|
|
998
|
+
if (lines.length <= 2) return objectText.replace(/^\{\s*/, "").replace(/\s*\}$/, "");
|
|
999
|
+
return lines.slice(1, -1).join(lineSeparator);
|
|
1000
|
+
}
|
|
1001
|
+
function wouldDeModularizeRootReplacement(sourceLocations) {
|
|
1002
|
+
return sourceLocations?.states.some((location) => location.kind !== "inline") ?? false;
|
|
1003
|
+
}
|
|
1004
|
+
function parseGraphPatches(value) {
|
|
1005
|
+
if (!Array.isArray(value)) return null;
|
|
1006
|
+
return value.filter(isGraphPatch);
|
|
1007
|
+
}
|
|
1008
|
+
function parseGraph(value) {
|
|
1009
|
+
if (!value || typeof value !== "object") return null;
|
|
1010
|
+
const candidate = value;
|
|
1011
|
+
if (!Array.isArray(candidate.nodes) || !Array.isArray(candidate.edges)) return null;
|
|
1012
|
+
return candidate;
|
|
1013
|
+
}
|
|
1014
|
+
function isGraphPatch(value) {
|
|
1015
|
+
return Boolean(value && typeof value === "object" && "op" in value && typeof value.op === "string");
|
|
1016
|
+
}
|
|
1017
|
+
function isMachineConfig(value) {
|
|
1018
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1019
|
+
}
|
|
1020
|
+
function getScriptKind(fileName) {
|
|
1021
|
+
if (fileName.endsWith(".tsx")) return ts$1.ScriptKind.TSX;
|
|
1022
|
+
if (fileName.endsWith(".jsx")) return ts$1.ScriptKind.JSX;
|
|
1023
|
+
if (fileName.endsWith(".js")) return ts$1.ScriptKind.JS;
|
|
1024
|
+
return ts$1.ScriptKind.TS;
|
|
1025
|
+
}
|
|
1026
|
+
function createSourceFile(document, sourceText) {
|
|
1027
|
+
return ts$1.createSourceFile(document.fileName, sourceText, ts$1.ScriptTarget.Latest, true, getScriptKind(document.fileName));
|
|
1028
|
+
}
|
|
1029
|
+
function findMachineConfigObjectLiteral(sourceFile) {
|
|
1030
|
+
let found = null;
|
|
1031
|
+
const visit = (node) => {
|
|
1032
|
+
if (found) return;
|
|
1033
|
+
if (ts$1.isCallExpression(node)) {
|
|
1034
|
+
const expression = node.expression;
|
|
1035
|
+
if (ts$1.isIdentifier(expression) && expression.text === "createMachine" || ts$1.isPropertyAccessExpression(expression) && expression.name.text === "createMachine") {
|
|
1036
|
+
const [firstArgument] = node.arguments;
|
|
1037
|
+
const objectLiteral = firstArgument && unwrapObjectLiteralExpression(firstArgument);
|
|
1038
|
+
if (objectLiteral) {
|
|
1039
|
+
found = objectLiteral;
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
ts$1.forEachChild(node, visit);
|
|
1045
|
+
};
|
|
1046
|
+
visit(sourceFile);
|
|
1047
|
+
return found;
|
|
1048
|
+
}
|
|
1049
|
+
function findSetupObjectLiteral(sourceFile) {
|
|
1050
|
+
let found = null;
|
|
1051
|
+
const visit = (node) => {
|
|
1052
|
+
if (found) return;
|
|
1053
|
+
if (ts$1.isCallExpression(node) && isSetupCallExpression(node)) {
|
|
1054
|
+
const [firstArgument] = node.arguments;
|
|
1055
|
+
if (firstArgument && ts$1.isExpression(firstArgument)) {
|
|
1056
|
+
found = unwrapObjectLiteralExpression(firstArgument);
|
|
1057
|
+
if (found) return;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
ts$1.forEachChild(node, visit);
|
|
1061
|
+
};
|
|
1062
|
+
visit(sourceFile);
|
|
1063
|
+
return found;
|
|
1064
|
+
}
|
|
1065
|
+
function findCreateMachineImplementationObjectLiteral(sourceFile) {
|
|
1066
|
+
let found = null;
|
|
1067
|
+
const visit = (node) => {
|
|
1068
|
+
if (found) return;
|
|
1069
|
+
if (ts$1.isCallExpression(node) && isCreateMachineCallExpression(node)) {
|
|
1070
|
+
const secondArgument = node.arguments[1];
|
|
1071
|
+
if (secondArgument && ts$1.isExpression(secondArgument)) {
|
|
1072
|
+
found = unwrapObjectLiteralExpression(secondArgument);
|
|
1073
|
+
if (found) return;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
ts$1.forEachChild(node, visit);
|
|
1077
|
+
};
|
|
1078
|
+
visit(sourceFile);
|
|
1079
|
+
return found;
|
|
1080
|
+
}
|
|
1081
|
+
function isCreateMachineCallExpression(node) {
|
|
1082
|
+
const expression = node.expression;
|
|
1083
|
+
return ts$1.isIdentifier(expression) && expression.text === "createMachine" || ts$1.isPropertyAccessExpression(expression) && expression.name.text === "createMachine";
|
|
1084
|
+
}
|
|
1085
|
+
function isSetupCallExpression(node) {
|
|
1086
|
+
const expression = node.expression;
|
|
1087
|
+
return ts$1.isIdentifier(expression) && expression.text === "setup" || ts$1.isPropertyAccessExpression(expression) && expression.name.text === "setup";
|
|
1088
|
+
}
|
|
1089
|
+
function unwrapObjectLiteralExpression(expression) {
|
|
1090
|
+
let current = expression;
|
|
1091
|
+
for (;;) {
|
|
1092
|
+
if (ts$1.isParenthesizedExpression(current)) {
|
|
1093
|
+
current = current.expression;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (ts$1.isAsExpression(current) || ts$1.isSatisfiesExpression(current)) {
|
|
1097
|
+
current = current.expression;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (ts$1.isTypeAssertionExpression(current)) {
|
|
1101
|
+
current = current.expression;
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
return ts$1.isObjectLiteralExpression(current) ? current : null;
|
|
1107
|
+
}
|
|
1108
|
+
function resolveStatePath(graph, nodeId) {
|
|
1109
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1110
|
+
const segments = [];
|
|
1111
|
+
let current = nodeById.get(nodeId);
|
|
1112
|
+
if (!current) return null;
|
|
1113
|
+
while (current && current.parentId !== null) {
|
|
1114
|
+
segments.push(current.data.key);
|
|
1115
|
+
current = nodeById.get(current.parentId);
|
|
1116
|
+
}
|
|
1117
|
+
return segments.reverse();
|
|
1118
|
+
}
|
|
1119
|
+
function getEdgeSourceId(graph, edgeId) {
|
|
1120
|
+
return graph.edges.find((edge) => edge.id === edgeId)?.sourceId ?? null;
|
|
1121
|
+
}
|
|
1122
|
+
function resolveEdgeSourcePath(graph, edgeId) {
|
|
1123
|
+
const sourceId = getEdgeSourceId(graph, edgeId);
|
|
1124
|
+
return sourceId ? resolveStatePath(graph, sourceId) : null;
|
|
1125
|
+
}
|
|
1126
|
+
function resolvePatchOwner(patch, prevGraph, nextGraph) {
|
|
1127
|
+
switch (patch.op) {
|
|
1128
|
+
case "createState": {
|
|
1129
|
+
const parentPath = resolveStatePath(nextGraph, patch.parentId);
|
|
1130
|
+
return parentPath ? {
|
|
1131
|
+
type: "state",
|
|
1132
|
+
path: parentPath
|
|
1133
|
+
} : { type: "covered" };
|
|
1134
|
+
}
|
|
1135
|
+
case "updateState": {
|
|
1136
|
+
const nextPath = resolveStatePath(nextGraph, patch.stateId);
|
|
1137
|
+
const prevPath = resolveStatePath(prevGraph, patch.stateId);
|
|
1138
|
+
if (patch.key !== void 0 && prevPath && nextPath) return { type: "root" };
|
|
1139
|
+
const path = nextPath ?? prevPath;
|
|
1140
|
+
return path ? {
|
|
1141
|
+
type: "state",
|
|
1142
|
+
path
|
|
1143
|
+
} : { type: "covered" };
|
|
1144
|
+
}
|
|
1145
|
+
case "deleteState": {
|
|
1146
|
+
const node = prevGraph.nodes.find((item) => item.id === patch.stateId);
|
|
1147
|
+
if (!node) return { type: "covered" };
|
|
1148
|
+
if (!node.parentId) return { type: "root" };
|
|
1149
|
+
const parentPath = resolveStatePath(prevGraph, node.parentId);
|
|
1150
|
+
return parentPath ? {
|
|
1151
|
+
type: "state",
|
|
1152
|
+
path: parentPath
|
|
1153
|
+
} : { type: "root" };
|
|
1154
|
+
}
|
|
1155
|
+
case "createTransition": {
|
|
1156
|
+
const path = resolveStatePath(nextGraph, patch.sourceId);
|
|
1157
|
+
return path ? {
|
|
1158
|
+
type: "state",
|
|
1159
|
+
path
|
|
1160
|
+
} : { type: "covered" };
|
|
1161
|
+
}
|
|
1162
|
+
case "updateTransition": {
|
|
1163
|
+
const path = resolveEdgeSourcePath(nextGraph, patch.transitionId) ?? resolveEdgeSourcePath(prevGraph, patch.transitionId);
|
|
1164
|
+
return path ? {
|
|
1165
|
+
type: "state",
|
|
1166
|
+
path
|
|
1167
|
+
} : { type: "covered" };
|
|
1168
|
+
}
|
|
1169
|
+
case "deleteTransition":
|
|
1170
|
+
case "setGuard":
|
|
1171
|
+
case "deleteGuard": {
|
|
1172
|
+
const edgeId = "transitionId" in patch ? patch.transitionId : patch.edgeId;
|
|
1173
|
+
const path = resolveEdgeSourcePath(nextGraph, edgeId) ?? resolveEdgeSourcePath(prevGraph, edgeId);
|
|
1174
|
+
return path ? {
|
|
1175
|
+
type: "state",
|
|
1176
|
+
path
|
|
1177
|
+
} : { type: "covered" };
|
|
1178
|
+
}
|
|
1179
|
+
case "createAction":
|
|
1180
|
+
case "updateAction":
|
|
1181
|
+
case "deleteAction": {
|
|
1182
|
+
if ("nodeId" in patch.location) {
|
|
1183
|
+
const path = resolveStatePath(nextGraph, patch.location.nodeId) ?? resolveStatePath(prevGraph, patch.location.nodeId);
|
|
1184
|
+
return path ? {
|
|
1185
|
+
type: "state",
|
|
1186
|
+
path
|
|
1187
|
+
} : { type: "covered" };
|
|
1188
|
+
}
|
|
1189
|
+
const path = resolveEdgeSourcePath(nextGraph, patch.location.edgeId) ?? resolveEdgeSourcePath(prevGraph, patch.location.edgeId);
|
|
1190
|
+
return path ? {
|
|
1191
|
+
type: "state",
|
|
1192
|
+
path
|
|
1193
|
+
} : { type: "covered" };
|
|
1194
|
+
}
|
|
1195
|
+
case "createInvoke":
|
|
1196
|
+
case "updateInvoke":
|
|
1197
|
+
case "deleteInvoke":
|
|
1198
|
+
case "createTag":
|
|
1199
|
+
case "deleteTag": {
|
|
1200
|
+
const path = resolveStatePath(nextGraph, patch.nodeId) ?? resolveStatePath(prevGraph, patch.nodeId);
|
|
1201
|
+
return path ? {
|
|
1202
|
+
type: "state",
|
|
1203
|
+
path
|
|
1204
|
+
} : { type: "covered" };
|
|
1205
|
+
}
|
|
1206
|
+
case "updateSchemas": return { type: "root" };
|
|
1207
|
+
default: return { type: "root" };
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
function deriveReplacementPlan(patches, prevGraph, nextGraph) {
|
|
1211
|
+
const rawPaths = [];
|
|
1212
|
+
for (const patch of patches) {
|
|
1213
|
+
const owner = resolvePatchOwner(patch, prevGraph, nextGraph);
|
|
1214
|
+
if (owner.type === "root") return { type: "root" };
|
|
1215
|
+
if (owner.type === "covered") continue;
|
|
1216
|
+
rawPaths.push(owner.path);
|
|
1217
|
+
}
|
|
1218
|
+
const reducedPaths = reducePaths(rawPaths);
|
|
1219
|
+
if (reducedPaths.length === 0 || reducedPaths.some((path) => path.length === 0)) return { type: "root" };
|
|
1220
|
+
return {
|
|
1221
|
+
type: "states",
|
|
1222
|
+
paths: reducedPaths
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
function reducePaths(paths) {
|
|
1226
|
+
const uniquePaths = Array.from(new Set(paths.map((path) => path.join(".")))).map((value) => value === "" ? [] : value.split(".")).sort((a, b) => a.length - b.length);
|
|
1227
|
+
return uniquePaths.filter((path, index) => {
|
|
1228
|
+
return !uniquePaths.some((candidate, candidateIndex) => {
|
|
1229
|
+
if (candidateIndex === index || candidate.length >= path.length) return false;
|
|
1230
|
+
return candidate.every((segment, segmentIndex) => segment === path[segmentIndex]);
|
|
1231
|
+
});
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
function getStateConfigByPath(machineConfig, path) {
|
|
1235
|
+
let current = machineConfig;
|
|
1236
|
+
for (const segment of path) {
|
|
1237
|
+
const states = current?.states;
|
|
1238
|
+
if (!isMachineConfig(states)) return;
|
|
1239
|
+
const next = states[segment];
|
|
1240
|
+
if (!isMachineConfig(next)) return;
|
|
1241
|
+
current = next;
|
|
1242
|
+
}
|
|
1243
|
+
return current;
|
|
1244
|
+
}
|
|
1245
|
+
function getStateObjectLiteralByPath(root, path) {
|
|
1246
|
+
let current = root;
|
|
1247
|
+
for (const segment of path) {
|
|
1248
|
+
if (!current) return null;
|
|
1249
|
+
const statesProperty = getObjectLiteralProperty(current, "states");
|
|
1250
|
+
current = statesProperty && getObjectLiteralProperty(statesProperty, segment);
|
|
1251
|
+
}
|
|
1252
|
+
return current;
|
|
1253
|
+
}
|
|
1254
|
+
function getMappedRootObjectLiteral(document, sourceFile, sourceLocations) {
|
|
1255
|
+
if (!sourceLocations?.root || !isSourceLocationForDocument(sourceLocations.root.uri, document)) return null;
|
|
1256
|
+
return findObjectLiteralByRange(sourceFile, sourceLocations.root.range);
|
|
1257
|
+
}
|
|
1258
|
+
async function getMappedStateReplacementTarget(rootDocument, rootSourceFile, sourceLocations, path, openTextDocument) {
|
|
1259
|
+
if (!sourceLocations) return null;
|
|
1260
|
+
const location = sourceLocations.states.find((item) => arePathsEqual(item.path, path));
|
|
1261
|
+
if (!location) return null;
|
|
1262
|
+
const targetDocument = isSourceLocationForDocument(location.uri, rootDocument) ? rootDocument : await openDocumentBySourceUri(location.uri, openTextDocument);
|
|
1263
|
+
if (!targetDocument) return null;
|
|
1264
|
+
const targetSourceFile = isSourceLocationForDocument(location.uri, rootDocument) ? rootSourceFile : createSourceFile(targetDocument, targetDocument.getText());
|
|
1265
|
+
const node = findObjectLiteralByRange(targetSourceFile, location.range);
|
|
1266
|
+
if (!node) return null;
|
|
1267
|
+
return {
|
|
1268
|
+
document: targetDocument,
|
|
1269
|
+
sourceFile: targetSourceFile,
|
|
1270
|
+
node
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
async function openDocumentBySourceUri(uri, openTextDocument) {
|
|
1274
|
+
if (!openTextDocument) return null;
|
|
1275
|
+
try {
|
|
1276
|
+
return await openTextDocument(uri);
|
|
1277
|
+
} catch {
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
function isSourceLocationForDocument(uri, document) {
|
|
1282
|
+
return uri === (typeof document.uri === "string" ? document.uri : document.uri?.toString()) || uri === document.fileName;
|
|
1283
|
+
}
|
|
1284
|
+
function findObjectLiteralByRange(sourceFile, range) {
|
|
1285
|
+
const start = offsetAtRangePosition(sourceFile.text, range.startLine, range.startChar);
|
|
1286
|
+
const end = offsetAtRangePosition(sourceFile.text, range.endLine, range.endChar);
|
|
1287
|
+
let found = null;
|
|
1288
|
+
const visit = (node) => {
|
|
1289
|
+
if (found) return;
|
|
1290
|
+
if (ts$1.isObjectLiteralExpression(node) && node.getStart(sourceFile) === start && node.getEnd() === end) {
|
|
1291
|
+
found = node;
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
ts$1.forEachChild(node, visit);
|
|
1295
|
+
};
|
|
1296
|
+
visit(sourceFile);
|
|
1297
|
+
return found;
|
|
1298
|
+
}
|
|
1299
|
+
function offsetAtRangePosition(sourceText, line, character) {
|
|
1300
|
+
if (line <= 0) return character;
|
|
1301
|
+
let currentLine = 0;
|
|
1302
|
+
for (let index = 0; index < sourceText.length; index += 1) {
|
|
1303
|
+
if (sourceText.charCodeAt(index) !== 10) continue;
|
|
1304
|
+
currentLine += 1;
|
|
1305
|
+
if (currentLine === line) return index + 1 + character;
|
|
1306
|
+
}
|
|
1307
|
+
return sourceText.length;
|
|
1308
|
+
}
|
|
1309
|
+
function getObjectLiteralProperty(objectLiteral, propertyName) {
|
|
1310
|
+
for (const property of objectLiteral.properties) {
|
|
1311
|
+
if (!ts$1.isPropertyAssignment(property)) continue;
|
|
1312
|
+
if (getPropertyNameText(property.name) !== propertyName) continue;
|
|
1313
|
+
return unwrapObjectLiteralExpression(property.initializer) ?? null;
|
|
1314
|
+
}
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
function getPropertyNameText(name) {
|
|
1318
|
+
if (ts$1.isIdentifier(name) || ts$1.isStringLiteral(name) || ts$1.isNumericLiteral(name)) return name.text;
|
|
1319
|
+
if (ts$1.isComputedPropertyName(name)) {
|
|
1320
|
+
const expression = name.expression;
|
|
1321
|
+
if (ts$1.isStringLiteral(expression) || ts$1.isNoSubstitutionTemplateLiteral(expression) || ts$1.isNumericLiteral(expression)) return expression.text;
|
|
1322
|
+
}
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
function createReplacement(document, sourceFile, node, value) {
|
|
1326
|
+
const start = document.positionAt(node.getStart(sourceFile));
|
|
1327
|
+
const end = document.positionAt(node.getEnd());
|
|
1328
|
+
const formatting = getFormattingContext(document, sourceFile, node);
|
|
1329
|
+
return {
|
|
1330
|
+
uri: getDocumentUri(document),
|
|
1331
|
+
range: {
|
|
1332
|
+
startLine: start.line,
|
|
1333
|
+
startChar: start.character,
|
|
1334
|
+
endLine: end.line,
|
|
1335
|
+
endChar: end.character
|
|
1336
|
+
},
|
|
1337
|
+
newText: printExpression(valueToExpression(value, node), sourceFile, formatting)
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
function getDocumentUri(document) {
|
|
1341
|
+
return (typeof document.uri === "string" ? document.uri : document.uri?.toString()) ?? document.fileName;
|
|
1342
|
+
}
|
|
1343
|
+
async function tryCreateSemanticPatchSplitForDocuments(document, sourceFile, machineConfigNode, patches, prevGraph, nextGraph, sourceLocations, openTextDocument) {
|
|
1344
|
+
const split = getSemanticStateRenamePatchSplit(patches, prevGraph, nextGraph);
|
|
1345
|
+
if (!split) return null;
|
|
1346
|
+
const { rename } = split;
|
|
1347
|
+
const parentTarget = await getStateMembershipParentTargetForDocuments(document, sourceFile, machineConfigNode, sourceLocations, rename.parentPath, openTextDocument);
|
|
1348
|
+
if (!parentTarget) return null;
|
|
1349
|
+
const targetLiterals = await collectMappedTargetStringLiterals(document, sourceFile, machineConfigNode, sourceLocations, openTextDocument);
|
|
1350
|
+
const replacements = createStateKeyRenameReplacementsForTarget(parentTarget.document, parentTarget.sourceFile, parentTarget.node, rename, sourceLocations, targetLiterals);
|
|
1351
|
+
if (!replacements) return null;
|
|
1352
|
+
return {
|
|
1353
|
+
replacements: replacements.sort(compareReplacementRangesDescending),
|
|
1354
|
+
remainingPatches: split.remainingPatches,
|
|
1355
|
+
sourceLocations: createRenamedSourceLocations(sourceLocations, rename)
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
function getSemanticStateRenameFromPatch(patch, prevGraph, nextGraph) {
|
|
1359
|
+
if (patch.op !== "updateState" || patch.key === void 0) return null;
|
|
1360
|
+
const prevPath = resolveStatePath(prevGraph, patch.stateId);
|
|
1361
|
+
const nextPath = resolveStatePath(nextGraph, patch.stateId);
|
|
1362
|
+
if (!prevPath || !nextPath || prevPath.length !== nextPath.length || prevPath.length === 0) return null;
|
|
1363
|
+
const oldKey = prevPath[prevPath.length - 1];
|
|
1364
|
+
const newKey = nextPath[nextPath.length - 1];
|
|
1365
|
+
const parentPath = prevPath.slice(0, -1);
|
|
1366
|
+
if (oldKey === newKey) return null;
|
|
1367
|
+
return {
|
|
1368
|
+
oldKey,
|
|
1369
|
+
newKey,
|
|
1370
|
+
oldPath: prevPath,
|
|
1371
|
+
newPath: nextPath,
|
|
1372
|
+
parentPath
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
function getSemanticStateRenamePatchSplit(patches, prevGraph, nextGraph) {
|
|
1376
|
+
let rename = null;
|
|
1377
|
+
const remainingPatches = [];
|
|
1378
|
+
for (const patch of patches) {
|
|
1379
|
+
const patchRename = getSemanticStateRenameFromPatch(patch, prevGraph, nextGraph);
|
|
1380
|
+
if (patchRename) {
|
|
1381
|
+
if (rename) return null;
|
|
1382
|
+
rename = patchRename;
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
remainingPatches.push(patch);
|
|
1386
|
+
}
|
|
1387
|
+
return rename ? {
|
|
1388
|
+
rename,
|
|
1389
|
+
remainingPatches
|
|
1390
|
+
} : null;
|
|
1391
|
+
}
|
|
1392
|
+
function createRenamedSourceLocations(sourceLocations, rename) {
|
|
1393
|
+
if (!sourceLocations) return;
|
|
1394
|
+
return {
|
|
1395
|
+
...sourceLocations,
|
|
1396
|
+
states: sourceLocations.states.map((location) => {
|
|
1397
|
+
if (!isPathEqualOrDescendant(location.path, rename.oldPath)) return location;
|
|
1398
|
+
return {
|
|
1399
|
+
...location,
|
|
1400
|
+
path: [...rename.newPath, ...location.path.slice(rename.oldPath.length)]
|
|
1401
|
+
};
|
|
1402
|
+
})
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
function createStateKeyRenameReplacementsForTarget(document, sourceFile, parentObject, rename, sourceLocations, targetLiterals) {
|
|
1406
|
+
const statesObject = getObjectLiteralProperty(parentObject, "states");
|
|
1407
|
+
const location = sourceLocations?.states.find((item) => rename.oldPath && arePathsEqual(item.path, rename.oldPath));
|
|
1408
|
+
const stateProperty = statesObject && (findObjectLiteralProperty(statesObject, rename.oldKey) ?? findObjectLiteralPropertyByRange(sourceFile, statesObject, location?.keyRange));
|
|
1409
|
+
if (!statesObject || !stateProperty) return null;
|
|
1410
|
+
const keyReplacement = createStatePropertyRenameReplacement(document, sourceFile, stateProperty, rename.oldKey, rename.newKey, location?.keySource);
|
|
1411
|
+
if (!keyReplacement) return null;
|
|
1412
|
+
const replacements = [keyReplacement];
|
|
1413
|
+
const initialProperty = getPropertyAssignment(parentObject, "initial");
|
|
1414
|
+
if (initialProperty) {
|
|
1415
|
+
const initialReplacement = createStringInitializerRenameReplacement(document, sourceFile, initialProperty.initializer, rename.oldKey, rename.newKey);
|
|
1416
|
+
if (initialReplacement) replacements.push(initialReplacement);
|
|
1417
|
+
}
|
|
1418
|
+
const supportedScope = new Set([rename.parentPath.join("."), ...getDirectChildStatePaths(parentObject, rename.parentPath).map((path) => path.join("."))]);
|
|
1419
|
+
for (const target of targetLiterals) {
|
|
1420
|
+
if (!referencesRenamedSibling(target.node.text, rename.oldKey)) continue;
|
|
1421
|
+
const sourceKey = target.sourcePath.join(".");
|
|
1422
|
+
if (!(target.sourcePath.length === rename.parentPath.length || arePathsEqual(target.sourcePath.slice(0, -1), rename.parentPath)) || !supportedScope.has(sourceKey)) return null;
|
|
1423
|
+
const replacement = createStringLiteralReplacement(target.document, target.sourceFile, target.node, renameSiblingReference(target.node.text, rename.oldKey, rename.newKey));
|
|
1424
|
+
replacements.push(replacement);
|
|
1425
|
+
}
|
|
1426
|
+
return replacements;
|
|
1427
|
+
}
|
|
1428
|
+
function getDirectChildStatePaths(stateObject, parentPath) {
|
|
1429
|
+
const statesObject = getObjectLiteralProperty(stateObject, "states");
|
|
1430
|
+
if (!statesObject) return [];
|
|
1431
|
+
return statesObject.properties.flatMap((property) => {
|
|
1432
|
+
const key = getStatePropertyKeyText(property);
|
|
1433
|
+
if (!key) return [];
|
|
1434
|
+
return [[...parentPath, key]];
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
function collectTargetStringLiterals(machineConfigNode) {
|
|
1438
|
+
const literals = [];
|
|
1439
|
+
const visitStateObject = (stateObject, sourcePath) => {
|
|
1440
|
+
for (const propertyName of [
|
|
1441
|
+
"always",
|
|
1442
|
+
"after",
|
|
1443
|
+
"on",
|
|
1444
|
+
"onDone"
|
|
1445
|
+
]) {
|
|
1446
|
+
const property = getPropertyAssignment(stateObject, propertyName);
|
|
1447
|
+
if (!property) continue;
|
|
1448
|
+
collectTargetStringLiteralsFromExpression(property.initializer, sourcePath, literals);
|
|
1449
|
+
}
|
|
1450
|
+
const statesObject = getObjectLiteralProperty(stateObject, "states");
|
|
1451
|
+
if (!statesObject) return;
|
|
1452
|
+
for (const property of statesObject.properties) {
|
|
1453
|
+
if (!ts$1.isPropertyAssignment(property)) continue;
|
|
1454
|
+
const key = getPropertyNameText(property.name);
|
|
1455
|
+
const childState = unwrapObjectLiteralExpression(property.initializer);
|
|
1456
|
+
if (!key || !childState) continue;
|
|
1457
|
+
visitStateObject(childState, [...sourcePath, key]);
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
visitStateObject(machineConfigNode, []);
|
|
1461
|
+
return literals;
|
|
1462
|
+
}
|
|
1463
|
+
async function collectMappedTargetStringLiterals(rootDocument, rootSourceFile, machineConfigNode, sourceLocations, openTextDocument) {
|
|
1464
|
+
const literals = collectTargetStringLiterals(machineConfigNode).map((target) => ({
|
|
1465
|
+
...target,
|
|
1466
|
+
document: rootDocument,
|
|
1467
|
+
sourceFile: rootSourceFile
|
|
1468
|
+
}));
|
|
1469
|
+
if (!sourceLocations) return literals;
|
|
1470
|
+
const seen = new Set(literals.map((target) => getTargetLiteralReplacementKey(rootDocument, rootSourceFile, target.node)));
|
|
1471
|
+
for (const location of sourceLocations.states) {
|
|
1472
|
+
const targetDocument = isSourceLocationForDocument(location.uri, rootDocument) ? rootDocument : await openDocumentBySourceUri(location.uri, openTextDocument);
|
|
1473
|
+
if (!targetDocument) continue;
|
|
1474
|
+
const targetSourceFile = isSourceLocationForDocument(location.uri, rootDocument) ? rootSourceFile : createSourceFile(targetDocument, targetDocument.getText());
|
|
1475
|
+
const stateObject = findObjectLiteralByRange(targetSourceFile, location.range);
|
|
1476
|
+
if (!stateObject) continue;
|
|
1477
|
+
const stateLiterals = [];
|
|
1478
|
+
collectTargetStringLiteralsFromStateObject(stateObject, location.path, stateLiterals);
|
|
1479
|
+
for (const target of stateLiterals) {
|
|
1480
|
+
const key = getTargetLiteralReplacementKey(targetDocument, targetSourceFile, target.node);
|
|
1481
|
+
if (seen.has(key)) continue;
|
|
1482
|
+
seen.add(key);
|
|
1483
|
+
literals.push({
|
|
1484
|
+
...target,
|
|
1485
|
+
document: targetDocument,
|
|
1486
|
+
sourceFile: targetSourceFile
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return literals;
|
|
1491
|
+
}
|
|
1492
|
+
function getTargetLiteralReplacementKey(document, sourceFile, node) {
|
|
1493
|
+
return `${getDocumentUri(document)}:${node.getStart(sourceFile)}:${node.getEnd()}`;
|
|
1494
|
+
}
|
|
1495
|
+
function collectTargetStringLiteralsFromStateObject(stateObject, sourcePath, literals) {
|
|
1496
|
+
for (const propertyName of [
|
|
1497
|
+
"always",
|
|
1498
|
+
"after",
|
|
1499
|
+
"on",
|
|
1500
|
+
"onDone"
|
|
1501
|
+
]) {
|
|
1502
|
+
const property = getPropertyAssignment(stateObject, propertyName);
|
|
1503
|
+
if (!property) continue;
|
|
1504
|
+
collectTargetStringLiteralsFromExpression(property.initializer, sourcePath, literals);
|
|
1505
|
+
}
|
|
1506
|
+
const statesObject = getObjectLiteralProperty(stateObject, "states");
|
|
1507
|
+
if (!statesObject) return;
|
|
1508
|
+
for (const property of statesObject.properties) {
|
|
1509
|
+
if (!ts$1.isPropertyAssignment(property)) continue;
|
|
1510
|
+
const key = getPropertyNameText(property.name);
|
|
1511
|
+
const childState = unwrapObjectLiteralExpression(property.initializer);
|
|
1512
|
+
if (!key || !childState) continue;
|
|
1513
|
+
collectTargetStringLiteralsFromStateObject(childState, [...sourcePath, key], literals);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function collectTargetStringLiteralsFromExpression(expression, sourcePath, literals) {
|
|
1517
|
+
if (ts$1.isStringLiteralLike(expression)) {
|
|
1518
|
+
literals.push({
|
|
1519
|
+
sourcePath,
|
|
1520
|
+
node: expression
|
|
1521
|
+
});
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
if (ts$1.isArrayLiteralExpression(expression)) {
|
|
1525
|
+
for (const element of expression.elements) if (ts$1.isExpression(element)) collectTargetStringLiteralsFromExpression(element, sourcePath, literals);
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (!ts$1.isObjectLiteralExpression(expression)) return;
|
|
1529
|
+
const targetProperty = getPropertyAssignment(expression, "target");
|
|
1530
|
+
if (targetProperty) {
|
|
1531
|
+
collectTargetStringLiteralsFromExpression(targetProperty.initializer, sourcePath, literals);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
for (const property of expression.properties) {
|
|
1535
|
+
if (!ts$1.isPropertyAssignment(property)) continue;
|
|
1536
|
+
collectTargetStringLiteralsFromExpression(property.initializer, sourcePath, literals);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function referencesRenamedSibling(value, oldKey) {
|
|
1540
|
+
return value === oldKey || value.startsWith(`${oldKey}.`);
|
|
1541
|
+
}
|
|
1542
|
+
function renameSiblingReference(value, oldKey, newKey) {
|
|
1543
|
+
if (value === oldKey) return newKey;
|
|
1544
|
+
return value.startsWith(`${oldKey}.`) ? `${newKey}${value.slice(oldKey.length)}` : value;
|
|
1545
|
+
}
|
|
1546
|
+
function arePathsEqual(left, right) {
|
|
1547
|
+
return left.length === right.length && left.every((segment, index) => segment === right[index]);
|
|
1548
|
+
}
|
|
1549
|
+
function createPropertyNameReplacement(document, sourceFile, propertyName, nextName) {
|
|
1550
|
+
if (!isSupportedPropertyName(propertyName)) return null;
|
|
1551
|
+
const start = document.positionAt(propertyName.getStart(sourceFile));
|
|
1552
|
+
const end = document.positionAt(propertyName.getEnd());
|
|
1553
|
+
const sourceText = sourceFile.text.slice(propertyName.getStart(sourceFile), propertyName.getEnd());
|
|
1554
|
+
return {
|
|
1555
|
+
range: {
|
|
1556
|
+
startLine: start.line,
|
|
1557
|
+
startChar: start.character,
|
|
1558
|
+
endLine: end.line,
|
|
1559
|
+
endChar: end.character
|
|
1560
|
+
},
|
|
1561
|
+
uri: getDocumentUri(document),
|
|
1562
|
+
newText: formatPropertyNameText(propertyName, nextName, sourceText)
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function createStatePropertyRenameReplacement(document, sourceFile, property, oldKey, newKey, keySource) {
|
|
1566
|
+
if (keySource?.machineLocal && keySource.valueRange && isSourceRangeInDocument(sourceFile, keySource.valueRange)) return createSourceRangeReplacement(document, sourceFile, keySource.valueRange, formatStringLiteralText(sourceFile.text.slice(offsetAtRangePosition(sourceFile.text, keySource.valueRange.startLine, keySource.valueRange.startChar), offsetAtRangePosition(sourceFile.text, keySource.valueRange.endLine, keySource.valueRange.endChar)), newKey));
|
|
1567
|
+
if (ts$1.isPropertyAssignment(property)) {
|
|
1568
|
+
if (ts$1.isComputedPropertyName(property.name)) return createPropertyNameLiteralReplacement(document, sourceFile, property.name, newKey);
|
|
1569
|
+
return createPropertyNameReplacement(document, sourceFile, property.name, newKey);
|
|
1570
|
+
}
|
|
1571
|
+
if (!ts$1.isShorthandPropertyAssignment(property)) return null;
|
|
1572
|
+
const start = document.positionAt(property.name.getStart(sourceFile));
|
|
1573
|
+
const end = document.positionAt(property.name.getEnd());
|
|
1574
|
+
const sourceText = sourceFile.text.slice(property.name.getStart(sourceFile), property.name.getEnd());
|
|
1575
|
+
const nextPropertyName = formatPropertyNameText(property.name, newKey, sourceText);
|
|
1576
|
+
return {
|
|
1577
|
+
range: {
|
|
1578
|
+
startLine: start.line,
|
|
1579
|
+
startChar: start.character,
|
|
1580
|
+
endLine: end.line,
|
|
1581
|
+
endChar: end.character
|
|
1582
|
+
},
|
|
1583
|
+
uri: getDocumentUri(document),
|
|
1584
|
+
newText: `${nextPropertyName}: ${oldKey}`
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function createPropertyNameLiteralReplacement(document, sourceFile, propertyName, nextName) {
|
|
1588
|
+
const start = document.positionAt(propertyName.getStart(sourceFile));
|
|
1589
|
+
const end = document.positionAt(propertyName.getEnd());
|
|
1590
|
+
return {
|
|
1591
|
+
range: {
|
|
1592
|
+
startLine: start.line,
|
|
1593
|
+
startChar: start.character,
|
|
1594
|
+
endLine: end.line,
|
|
1595
|
+
endChar: end.character
|
|
1596
|
+
},
|
|
1597
|
+
uri: getDocumentUri(document),
|
|
1598
|
+
newText: isIdentifierText(nextName) ? nextName : JSON.stringify(nextName)
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function createStringInitializerRenameReplacement(document, sourceFile, expression, oldValue, newValue) {
|
|
1602
|
+
if (!ts$1.isStringLiteralLike(expression) || expression.text !== oldValue) return null;
|
|
1603
|
+
return createStringLiteralReplacement(document, sourceFile, expression, newValue);
|
|
1604
|
+
}
|
|
1605
|
+
function createStringLiteralReplacement(document, sourceFile, expression, nextValue) {
|
|
1606
|
+
const start = document.positionAt(expression.getStart(sourceFile));
|
|
1607
|
+
const end = document.positionAt(expression.getEnd());
|
|
1608
|
+
const sourceText = sourceFile.text.slice(expression.getStart(sourceFile), expression.getEnd());
|
|
1609
|
+
return {
|
|
1610
|
+
range: {
|
|
1611
|
+
startLine: start.line,
|
|
1612
|
+
startChar: start.character,
|
|
1613
|
+
endLine: end.line,
|
|
1614
|
+
endChar: end.character
|
|
1615
|
+
},
|
|
1616
|
+
uri: getDocumentUri(document),
|
|
1617
|
+
newText: formatStringLiteralText(sourceText, nextValue)
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
function createSourceRangeReplacement(document, sourceFile, range, newText) {
|
|
1621
|
+
const startOffset = offsetAtRangePosition(sourceFile.text, range.startLine, range.startChar);
|
|
1622
|
+
const endOffset = offsetAtRangePosition(sourceFile.text, range.endLine, range.endChar);
|
|
1623
|
+
const start = document.positionAt(startOffset);
|
|
1624
|
+
const end = document.positionAt(endOffset);
|
|
1625
|
+
return {
|
|
1626
|
+
range: {
|
|
1627
|
+
startLine: start.line,
|
|
1628
|
+
startChar: start.character,
|
|
1629
|
+
endLine: end.line,
|
|
1630
|
+
endChar: end.character
|
|
1631
|
+
},
|
|
1632
|
+
uri: getDocumentUri(document),
|
|
1633
|
+
newText
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function isSourceRangeInDocument(sourceFile, range) {
|
|
1637
|
+
return offsetAtRangePosition(sourceFile.text, range.startLine, range.startChar) <= sourceFile.text.length;
|
|
1638
|
+
}
|
|
1639
|
+
function isSupportedPropertyName(propertyName) {
|
|
1640
|
+
if (ts$1.isIdentifier(propertyName) || ts$1.isStringLiteral(propertyName) || ts$1.isNumericLiteral(propertyName)) return true;
|
|
1641
|
+
if (ts$1.isComputedPropertyName(propertyName)) {
|
|
1642
|
+
const expression = propertyName.expression;
|
|
1643
|
+
return ts$1.isStringLiteral(expression) || ts$1.isNoSubstitutionTemplateLiteral(expression) || ts$1.isNumericLiteral(expression);
|
|
1644
|
+
}
|
|
1645
|
+
return false;
|
|
1646
|
+
}
|
|
1647
|
+
function formatPropertyNameText(propertyName, nextName, sourceText) {
|
|
1648
|
+
if (ts$1.isIdentifier(propertyName) && isIdentifierText(nextName)) return nextName;
|
|
1649
|
+
if (ts$1.isComputedPropertyName(propertyName)) return `[${formatStringLiteralText(sourceText.slice(1, -1), nextName)}]`;
|
|
1650
|
+
return formatStringLiteralText(sourceText, nextName);
|
|
1651
|
+
}
|
|
1652
|
+
function formatStringLiteralText(sourceText, nextValue) {
|
|
1653
|
+
const quote = sourceText.startsWith("'") ? "'" : sourceText.startsWith("`") ? "`" : "\"";
|
|
1654
|
+
return `${quote}${quote === "'" ? nextValue.replace(/\\/g, "\\\\").replace(/'/g, "\\'") : quote === "\"" ? nextValue.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") : nextValue.replace(/\\/g, "\\\\").replace(/`/g, "\\`")}${quote}`;
|
|
1655
|
+
}
|
|
1656
|
+
function compareReplacementRangesDescending(a, b) {
|
|
1657
|
+
if (a.range.startLine !== b.range.startLine) return b.range.startLine - a.range.startLine;
|
|
1658
|
+
return b.range.startChar - a.range.startChar;
|
|
1659
|
+
}
|
|
1660
|
+
function printExpression(expression, sourceFile, formatting) {
|
|
1661
|
+
return reindentPrintedExpression(ts$1.createPrinter({ newLine: formatting.newLineKind }).printNode(ts$1.EmitHint.Expression, expression, sourceFile), formatting);
|
|
1662
|
+
}
|
|
1663
|
+
function reindentPrintedExpression(printed, formatting) {
|
|
1664
|
+
const lineSeparator = formatting.newLineKind === ts$1.NewLineKind.CarriageReturnLineFeed ? "\r\n" : "\n";
|
|
1665
|
+
const lines = printed.split(/\r?\n/);
|
|
1666
|
+
if (lines.length <= 1) return printed;
|
|
1667
|
+
return lines.map((line, index) => {
|
|
1668
|
+
if (index === 0) return line;
|
|
1669
|
+
if (line.trim() === "") return "";
|
|
1670
|
+
const leadingSpaces = line.match(/^ */)?.[0].length ?? 0;
|
|
1671
|
+
const indentLevel = Math.floor(leadingSpaces / 4);
|
|
1672
|
+
return `${formatting.baseIndent}${formatting.indentUnit.repeat(indentLevel)}${line.slice(leadingSpaces)}`;
|
|
1673
|
+
}).join(lineSeparator);
|
|
1674
|
+
}
|
|
1675
|
+
function getFormattingContext(document, sourceFile, node) {
|
|
1676
|
+
const sourceText = sourceFile.text;
|
|
1677
|
+
const lineStart = getLineStartOffset(sourceText, node.getStart(sourceFile));
|
|
1678
|
+
const lineIndent = sourceText.slice(lineStart).match(/^[ \t]*/)?.[0] ?? "";
|
|
1679
|
+
const editorIndentUnit = getEditorIndentUnit(document);
|
|
1680
|
+
const inferredIndentUnit = inferIndentUnitFromNode(sourceText, node, sourceFile, lineIndent) ?? inferIndentUnitFromSource(sourceText);
|
|
1681
|
+
return {
|
|
1682
|
+
baseIndent: lineIndent,
|
|
1683
|
+
indentUnit: editorIndentUnit ?? inferredIndentUnit ?? " ",
|
|
1684
|
+
newLineKind: sourceText.includes("\r\n") ? ts$1.NewLineKind.CarriageReturnLineFeed : ts$1.NewLineKind.LineFeed
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function getEditorIndentUnit(document) {
|
|
1688
|
+
return document.editorIndentUnit ?? null;
|
|
1689
|
+
}
|
|
1690
|
+
function inferIndentUnitFromNode(sourceText, node, sourceFile, baseIndent) {
|
|
1691
|
+
return getShortestIndent(sourceText.slice(node.getStart(sourceFile), node.getEnd()).split(/\r?\n/).slice(1).map((line) => line.match(/^[ \t]*/)?.[0] ?? "").filter((indent) => indent.startsWith(baseIndent)).map((indent) => indent.slice(baseIndent.length)).filter((indent) => indent.length > 0));
|
|
1692
|
+
}
|
|
1693
|
+
function inferIndentUnitFromSource(sourceText) {
|
|
1694
|
+
return getShortestIndent(sourceText.split(/\r?\n/).map((line) => line.match(/^[ \t]+(?=\S)/)?.[0] ?? "").filter(Boolean));
|
|
1695
|
+
}
|
|
1696
|
+
function getShortestIndent(indents) {
|
|
1697
|
+
if (indents.length === 0) return null;
|
|
1698
|
+
return indents.reduce((shortest, current) => current.length < shortest.length ? current : shortest);
|
|
1699
|
+
}
|
|
1700
|
+
function getLineStartOffset(sourceText, offset) {
|
|
1701
|
+
const lastNewline = sourceText.lastIndexOf("\n", offset - 1);
|
|
1702
|
+
if (lastNewline === -1) return 0;
|
|
1703
|
+
return lastNewline + 1;
|
|
1704
|
+
}
|
|
1705
|
+
function valueToExpression(value, existingExpression) {
|
|
1706
|
+
const inlineExpression = inlineExpressionDirectiveToExpression(value);
|
|
1707
|
+
if (inlineExpression) return inlineExpression;
|
|
1708
|
+
if (value === null) return ts$1.factory.createNull();
|
|
1709
|
+
if (typeof value === "string") return ts$1.factory.createStringLiteral(value);
|
|
1710
|
+
if (typeof value === "number") {
|
|
1711
|
+
if (value < 0) return ts$1.factory.createPrefixUnaryExpression(ts$1.SyntaxKind.MinusToken, ts$1.factory.createNumericLiteral(Math.abs(value)));
|
|
1712
|
+
return ts$1.factory.createNumericLiteral(value);
|
|
1713
|
+
}
|
|
1714
|
+
if (typeof value === "boolean") return value ? ts$1.factory.createTrue() : ts$1.factory.createFalse();
|
|
1715
|
+
if (Array.isArray(value)) {
|
|
1716
|
+
const existingArray = existingExpression && ts$1.isArrayLiteralExpression(existingExpression) ? existingExpression : void 0;
|
|
1717
|
+
const elements = value.map((item, index) => valueToExpression(item, existingArray && ts$1.isExpression(existingArray.elements[index]) ? existingArray.elements[index] : void 0));
|
|
1718
|
+
if (existingArray) {
|
|
1719
|
+
const updatedArray = ts$1.factory.createArrayLiteralExpression(elements, true);
|
|
1720
|
+
ts$1.setOriginalNode(updatedArray, existingArray);
|
|
1721
|
+
ts$1.setTextRange(updatedArray, existingArray);
|
|
1722
|
+
return updatedArray;
|
|
1723
|
+
}
|
|
1724
|
+
return ts$1.factory.createArrayLiteralExpression(elements, true);
|
|
1725
|
+
}
|
|
1726
|
+
if (isMachineConfig(value)) {
|
|
1727
|
+
const existingObject = existingExpression && ts$1.isObjectLiteralExpression(existingExpression) ? existingExpression : void 0;
|
|
1728
|
+
const properties = Object.entries(value).filter(([, propertyValue]) => propertyValue !== void 0).map(([key, propertyValue]) => {
|
|
1729
|
+
const existingProperty = existingObject ? getPropertyAssignment(existingObject, key) : null;
|
|
1730
|
+
const nextInitializer = getPreservedPropertyInitializer(key, propertyValue, existingProperty?.initializer) ?? valueToExpression(propertyValue, existingProperty?.initializer);
|
|
1731
|
+
if (existingProperty) return ts$1.factory.updatePropertyAssignment(existingProperty, existingProperty.name, nextInitializer);
|
|
1732
|
+
return ts$1.factory.createPropertyAssignment(toPropertyName(key), nextInitializer);
|
|
1733
|
+
});
|
|
1734
|
+
if (existingObject) {
|
|
1735
|
+
const updatedObject = ts$1.factory.createObjectLiteralExpression(properties, true);
|
|
1736
|
+
ts$1.setOriginalNode(updatedObject, existingObject);
|
|
1737
|
+
ts$1.setTextRange(updatedObject, existingObject);
|
|
1738
|
+
return updatedObject;
|
|
1739
|
+
}
|
|
1740
|
+
return ts$1.factory.createObjectLiteralExpression(properties, true);
|
|
1741
|
+
}
|
|
1742
|
+
return ts$1.factory.createIdentifier("undefined");
|
|
1743
|
+
}
|
|
1744
|
+
function inlineExpressionDirectiveToExpression(value) {
|
|
1745
|
+
if (!isInlineExpressionDirective(value)) return null;
|
|
1746
|
+
return ts$1.factory.createIdentifier(value.expr);
|
|
1747
|
+
}
|
|
1748
|
+
function isInlineExpressionDirective(value) {
|
|
1749
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1750
|
+
const directive = value;
|
|
1751
|
+
return directive["@type"] === "code" && typeof directive.expr === "string";
|
|
1752
|
+
}
|
|
1753
|
+
function getPreservedPropertyInitializer(propertyName, nextValue, existingInitializer) {
|
|
1754
|
+
if (propertyName !== "description" || typeof nextValue !== "string" || !existingInitializer) return null;
|
|
1755
|
+
if (ts$1.isStringLiteralLike(existingInitializer)) return existingInitializer.text === nextValue ? existingInitializer : null;
|
|
1756
|
+
return existingInitializer;
|
|
1757
|
+
}
|
|
1758
|
+
function getPropertyAssignment(objectLiteral, propertyName) {
|
|
1759
|
+
for (const property of objectLiteral.properties) {
|
|
1760
|
+
if (!ts$1.isPropertyAssignment(property)) continue;
|
|
1761
|
+
if (getPropertyNameText(property.name) === propertyName) return property;
|
|
1762
|
+
}
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
function toPropertyName(name) {
|
|
1766
|
+
return isIdentifierText(name) ? ts$1.factory.createIdentifier(name) : ts$1.factory.createStringLiteral(name);
|
|
1767
|
+
}
|
|
1768
|
+
function isIdentifierText(value) {
|
|
1769
|
+
return /^[$A-Z_][0-9A-Z_$]*$/i.test(value);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
//#endregion
|
|
1773
|
+
//#region ../editor-sync/src/internal/statelyMetadata.ts
|
|
1774
|
+
const STATELY_METADATA_PREFIX = "@statelyai.";
|
|
1775
|
+
function isPlainObject(value) {
|
|
1776
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1777
|
+
}
|
|
1778
|
+
function stripStatelyMetadata(value) {
|
|
1779
|
+
if (Array.isArray(value)) return value.map((item) => stripStatelyMetadata(item));
|
|
1780
|
+
if (!isPlainObject(value)) return value;
|
|
1781
|
+
const result = {};
|
|
1782
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1783
|
+
if (key.startsWith(STATELY_METADATA_PREFIX)) continue;
|
|
1784
|
+
const nextChild = stripStatelyMetadata(child);
|
|
1785
|
+
if (isPlainObject(nextChild) && Object.keys(nextChild).length === 0 && isPlainObject(child)) continue;
|
|
1786
|
+
result[key] = nextChild;
|
|
1787
|
+
}
|
|
1788
|
+
return result;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
//#endregion
|
|
1792
|
+
//#region ../editor-sync/src/internal/adapters.ts
|
|
1793
|
+
const xstateAdapter = {
|
|
1794
|
+
id: "xstate",
|
|
1795
|
+
format: "xstate",
|
|
1796
|
+
parseSourceToMachine(text, document) {
|
|
1797
|
+
return parseSourceToMachine(text, document);
|
|
1798
|
+
},
|
|
1799
|
+
collectSourceLocations(text, document) {
|
|
1800
|
+
return collectXStateSourceLocations(text, document);
|
|
1801
|
+
},
|
|
1802
|
+
async applyMachineToDocument(machineConfig, document, options) {
|
|
1803
|
+
return applyMachineToSourceDocuments(options?.preserveStatelyMetadata === false ? stripStatelyMetadata(machineConfig) : machineConfig, document, options);
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
function createSerializedExportAdapter(format) {
|
|
1807
|
+
return {
|
|
1808
|
+
id: format,
|
|
1809
|
+
format,
|
|
1810
|
+
parseSourceToMachine(text) {
|
|
1811
|
+
return text;
|
|
1812
|
+
},
|
|
1813
|
+
async applyMachineToDocument(_machineConfig, document, options) {
|
|
1814
|
+
const serialized = await options?.requestSerialized?.(format);
|
|
1815
|
+
if (typeof serialized !== "string") return [];
|
|
1816
|
+
const fullText = document.getText();
|
|
1817
|
+
const end = document.positionAt(fullText.length);
|
|
1818
|
+
return [{
|
|
1819
|
+
range: {
|
|
1820
|
+
startLine: 0,
|
|
1821
|
+
startChar: 0,
|
|
1822
|
+
endLine: end.line,
|
|
1823
|
+
endChar: end.character
|
|
1824
|
+
},
|
|
1825
|
+
newText: serialized
|
|
1826
|
+
}];
|
|
1827
|
+
}
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
const xstateJsonAdapter = createSerializedExportAdapter("xstate-json");
|
|
1831
|
+
const xgraphAdapter = createSerializedExportAdapter("xgraph");
|
|
1832
|
+
const mermaidAdapter = createSerializedExportAdapter("mermaid");
|
|
1833
|
+
const aslJsonAdapter = createSerializedExportAdapter("asl-json");
|
|
1834
|
+
const aslYamlAdapter = createSerializedExportAdapter("asl-yaml");
|
|
1835
|
+
const reduxAdapter = createSerializedExportAdapter("redux");
|
|
1836
|
+
const zustandAdapter = createSerializedExportAdapter("zustand");
|
|
1837
|
+
function isJavaScriptOrTypeScriptFile(fileName) {
|
|
1838
|
+
return /\.(?:c|m)?(?:jsx?|tsx?)$/i.test(fileName);
|
|
1839
|
+
}
|
|
1840
|
+
function isYamlFile(fileName) {
|
|
1841
|
+
return /\.(?:asl\.)?ya?ml$/i.test(fileName);
|
|
1842
|
+
}
|
|
1843
|
+
function hasAslExtension(fileName) {
|
|
1844
|
+
return /\.asl\.(?:jsonc?|ya?ml)$/i.test(fileName);
|
|
1845
|
+
}
|
|
1846
|
+
function isJsonFile(fileName) {
|
|
1847
|
+
return /\.jsonc?$/i.test(fileName);
|
|
1848
|
+
}
|
|
1849
|
+
function isMermaidFile(fileName) {
|
|
1850
|
+
return /\.(?:mmd|mermaid)$/i.test(fileName);
|
|
1851
|
+
}
|
|
1852
|
+
function stripMermaidFrontmatter(text) {
|
|
1853
|
+
return text.replace(/^\s*---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
1854
|
+
}
|
|
1855
|
+
function looksLikeMermaid(text) {
|
|
1856
|
+
return /^\s*(?:stateDiagram(?:-v2)?|flowchart(?:\s+(?:TB|TD|BT|LR|RL))?|graph\s+(?:TB|TD|BT|LR|RL))\b/.test(stripMermaidFrontmatter(text));
|
|
1857
|
+
}
|
|
1858
|
+
function looksLikeAmazonStatesLanguage(text) {
|
|
1859
|
+
return /(^|\s|["'])StartAt["']?\s*:/m.test(text) && /(^|\s|["'])States["']?\s*:/m.test(text);
|
|
1860
|
+
}
|
|
1861
|
+
function looksLikeXStateJson(text) {
|
|
1862
|
+
try {
|
|
1863
|
+
const value = JSON.parse(text);
|
|
1864
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "states" in value);
|
|
1865
|
+
} catch {
|
|
1866
|
+
return false;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
function looksLikeXGraphJson(text) {
|
|
1870
|
+
try {
|
|
1871
|
+
const value = JSON.parse(text);
|
|
1872
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && Array.isArray(value.nodes) && Array.isArray(value.edges));
|
|
1873
|
+
} catch {
|
|
1874
|
+
return false;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
function looksLikeReduxToolkit(text) {
|
|
1878
|
+
return /\bcreateSlice\s*\(/.test(text) && /@reduxjs\/toolkit/.test(text);
|
|
1879
|
+
}
|
|
1880
|
+
function looksLikeZustand(text) {
|
|
1881
|
+
return /\bcreate\s*\(\s*\(/.test(text) && /from\s+["']zustand["']/.test(text);
|
|
1882
|
+
}
|
|
1883
|
+
function getSyncAdapter(document, text = "") {
|
|
1884
|
+
const { fileName } = document;
|
|
1885
|
+
const resolvedText = text || document.getText?.() || "";
|
|
1886
|
+
if (isJavaScriptOrTypeScriptFile(fileName)) {
|
|
1887
|
+
if (looksLikeReduxToolkit(resolvedText)) return reduxAdapter;
|
|
1888
|
+
if (looksLikeZustand(resolvedText)) return zustandAdapter;
|
|
1889
|
+
return xstateAdapter;
|
|
1890
|
+
}
|
|
1891
|
+
if (hasAslExtension(fileName) || looksLikeAmazonStatesLanguage(resolvedText)) {
|
|
1892
|
+
if (isYamlFile(fileName)) return aslYamlAdapter;
|
|
1893
|
+
return aslJsonAdapter;
|
|
1894
|
+
}
|
|
1895
|
+
if (isMermaidFile(fileName) || looksLikeMermaid(resolvedText)) return mermaidAdapter;
|
|
1896
|
+
if (isJsonFile(fileName) && looksLikeXGraphJson(resolvedText)) return xgraphAdapter;
|
|
1897
|
+
if (isJsonFile(fileName) && looksLikeXStateJson(resolvedText)) return xstateJsonAdapter;
|
|
1898
|
+
throw new Error(`Unsupported document for Stately sync: ${fileName}`);
|
|
1899
|
+
}
|
|
1900
|
+
function parseDocumentToMachine(document) {
|
|
1901
|
+
const text = document.getText();
|
|
1902
|
+
const adapter = getSyncAdapter(document, text);
|
|
1903
|
+
return {
|
|
1904
|
+
adapter,
|
|
1905
|
+
format: adapter.format,
|
|
1906
|
+
machine: adapter.parseSourceToMachine(text, document),
|
|
1907
|
+
sourceLocations: adapter.collectSourceLocations?.(text, document)
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
//#endregion
|
|
1912
|
+
//#region src/sync.ts
|
|
1913
|
+
const execFileAsync = promisify(execFile);
|
|
1914
|
+
const LOCAL_EXTRACTION_TIMEOUT_MS = 5e3;
|
|
1915
|
+
const MAX_CAPTURED_LOCAL_MACHINES = 100;
|
|
1916
|
+
function createLocalCapturePlugin() {
|
|
1917
|
+
return {
|
|
1918
|
+
name: "statelyai-xstate-capture",
|
|
1919
|
+
setup(build) {
|
|
1920
|
+
build.onResolve({ filter: /^xstate(?:\/.*)?$/ }, (args) => ({
|
|
1921
|
+
path: args.path,
|
|
1922
|
+
namespace: "statelyai-xstate-capture"
|
|
1923
|
+
}));
|
|
1924
|
+
build.onLoad({
|
|
1925
|
+
filter: /^xstate(?:\/.*)?$/,
|
|
1926
|
+
namespace: "statelyai-xstate-capture"
|
|
1927
|
+
}, async (args) => {
|
|
1928
|
+
const xstateModule = await import(args.path);
|
|
1929
|
+
return {
|
|
1930
|
+
contents: `
|
|
1931
|
+
const __xstate = globalThis.__statelyaiXState;
|
|
1932
|
+
const __captured = globalThis.__capturedMachines ?? (globalThis.__capturedMachines = []);
|
|
1933
|
+
const __maxCaptured = ${MAX_CAPTURED_LOCAL_MACHINES};
|
|
1934
|
+
|
|
1935
|
+
function __capture(machine, type) {
|
|
1936
|
+
if (__captured.length >= __maxCaptured || !machine?.config) {
|
|
1937
|
+
return machine;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
try {
|
|
1941
|
+
__captured.push(JSON.stringify({
|
|
1942
|
+
config: machine.config,
|
|
1943
|
+
_type: type,
|
|
1944
|
+
}));
|
|
1945
|
+
} catch {}
|
|
1946
|
+
|
|
1947
|
+
return machine;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
${Object.keys(xstateModule).filter((key) => key !== "createMachine" && key !== "setup").filter((key) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)).sort().map((key) => `export const ${key} = __xstate.${key};`).join("\n")}
|
|
1951
|
+
export const createMachine = (...args) => __capture(__xstate.createMachine(...args), 'createMachine');
|
|
1952
|
+
export const setup = (...args) => {
|
|
1953
|
+
const setupResult = __xstate.setup(...args);
|
|
1954
|
+
return {
|
|
1955
|
+
...setupResult,
|
|
1956
|
+
createMachine: (...machineArgs) =>
|
|
1957
|
+
__capture(setupResult.createMachine(...machineArgs), 'setup.createMachine'),
|
|
1958
|
+
};
|
|
1959
|
+
};
|
|
1960
|
+
`,
|
|
1961
|
+
loader: "js"
|
|
1962
|
+
};
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
function getEsbuildLoader(sourcePath) {
|
|
1968
|
+
switch (path.extname(sourcePath).toLowerCase()) {
|
|
1969
|
+
case ".tsx": return "tsx";
|
|
1970
|
+
case ".jsx": return "jsx";
|
|
1971
|
+
case ".js":
|
|
1972
|
+
case ".mjs":
|
|
1973
|
+
case ".cjs": return "js";
|
|
1974
|
+
default: return "ts";
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
async function extractMachinesFromLocalSource(sourcePath, contents) {
|
|
1978
|
+
try {
|
|
1979
|
+
const parsed = parseDocumentToMachine(createTextDocument(sourcePath, contents));
|
|
1980
|
+
if (parsed.format !== "xstate" || typeof parsed.machine !== "string") return {
|
|
1981
|
+
machines: [],
|
|
1982
|
+
error: `Unsupported local source format: ${parsed.format}`
|
|
1983
|
+
};
|
|
1984
|
+
const bundledCode = (await esbuild.build({
|
|
1985
|
+
stdin: {
|
|
1986
|
+
contents: parsed.machine,
|
|
1987
|
+
sourcefile: sourcePath,
|
|
1988
|
+
loader: getEsbuildLoader(sourcePath),
|
|
1989
|
+
resolveDir: path.dirname(sourcePath)
|
|
1990
|
+
},
|
|
1991
|
+
absWorkingDir: path.dirname(sourcePath),
|
|
1992
|
+
bundle: true,
|
|
1993
|
+
write: false,
|
|
1994
|
+
format: "iife",
|
|
1995
|
+
target: "es2020",
|
|
1996
|
+
platform: "neutral",
|
|
1997
|
+
logLevel: "silent",
|
|
1998
|
+
plugins: [createLocalCapturePlugin()]
|
|
1999
|
+
})).outputFiles[0]?.text;
|
|
2000
|
+
if (!bundledCode) return {
|
|
2001
|
+
machines: [],
|
|
2002
|
+
error: "Failed to bundle source for local machine extraction."
|
|
2003
|
+
};
|
|
2004
|
+
const sandbox = {
|
|
2005
|
+
__statelyaiXState: await import("xstate"),
|
|
2006
|
+
__capturedMachines: [],
|
|
2007
|
+
console: {
|
|
2008
|
+
log: () => {},
|
|
2009
|
+
error: () => {},
|
|
2010
|
+
warn: () => {},
|
|
2011
|
+
info: () => {},
|
|
2012
|
+
debug: () => {}
|
|
2013
|
+
},
|
|
2014
|
+
setTimeout: () => 0,
|
|
2015
|
+
clearTimeout: () => {},
|
|
2016
|
+
setInterval: () => 0,
|
|
2017
|
+
clearInterval: () => {},
|
|
2018
|
+
queueMicrotask: () => {}
|
|
2019
|
+
};
|
|
2020
|
+
const context = vm.createContext(sandbox);
|
|
2021
|
+
new vm.Script(bundledCode, { filename: sourcePath }).runInContext(context, { timeout: LOCAL_EXTRACTION_TIMEOUT_MS });
|
|
2022
|
+
const captured = sandbox.__capturedMachines;
|
|
2023
|
+
if (!Array.isArray(captured)) return { machines: [] };
|
|
2024
|
+
return { machines: captured.map((entry) => typeof entry === "string" ? JSON.parse(entry) : entry).filter((entry) => typeof entry === "object" && entry !== null && "config" in entry) };
|
|
2025
|
+
} catch (error) {
|
|
2026
|
+
return {
|
|
2027
|
+
machines: [],
|
|
2028
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
function isUrl(value) {
|
|
2033
|
+
try {
|
|
2034
|
+
const url = new URL(value);
|
|
2035
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2036
|
+
} catch {
|
|
2037
|
+
return false;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
async function fileExists(filePath) {
|
|
2041
|
+
try {
|
|
2042
|
+
await fs.access(filePath);
|
|
2043
|
+
return true;
|
|
2044
|
+
} catch {
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
function parseGitHubRemote(remoteUrl) {
|
|
2049
|
+
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
2050
|
+
if (httpsMatch) {
|
|
2051
|
+
const [, owner, repo] = httpsMatch;
|
|
2052
|
+
if (!owner || !repo) return null;
|
|
2053
|
+
return {
|
|
2054
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
2055
|
+
owner,
|
|
2056
|
+
repo
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
2060
|
+
if (sshMatch) {
|
|
2061
|
+
const [, owner, repo] = sshMatch;
|
|
2062
|
+
if (!owner || !repo) return null;
|
|
2063
|
+
return {
|
|
2064
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
2065
|
+
owner,
|
|
2066
|
+
repo
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
async function inferConnectedRepo(filePath, cwd) {
|
|
2072
|
+
const workingDir = cwd ?? path.dirname(filePath);
|
|
2073
|
+
try {
|
|
2074
|
+
const [{ stdout: repoRootStdout }, { stdout: remoteStdout }, { stdout: branchStdout }, { stdout: treeShaStdout }] = await Promise.all([
|
|
2075
|
+
execFileAsync("git", ["rev-parse", "--show-toplevel"], { cwd: workingDir }),
|
|
2076
|
+
execFileAsync("git", [
|
|
2077
|
+
"remote",
|
|
2078
|
+
"get-url",
|
|
2079
|
+
"origin"
|
|
2080
|
+
], { cwd: workingDir }),
|
|
2081
|
+
execFileAsync("git", ["branch", "--show-current"], { cwd: workingDir }),
|
|
2082
|
+
execFileAsync("git", ["rev-parse", "HEAD"], { cwd: workingDir })
|
|
2083
|
+
]);
|
|
2084
|
+
const repoRoot = repoRootStdout.trim();
|
|
2085
|
+
const remote = parseGitHubRemote(remoteStdout.trim());
|
|
2086
|
+
const branch = branchStdout.trim();
|
|
2087
|
+
const treeSha = treeShaStdout.trim();
|
|
2088
|
+
if (!remote || !branch || !treeSha) return;
|
|
2089
|
+
const relativePath = path.relative(repoRoot, filePath).replace(/\\/g, "/");
|
|
2090
|
+
const relativeDir = path.dirname(relativePath).replace(/\\/g, "/");
|
|
2091
|
+
const selectedPaths = relativePath && relativePath !== "." ? [relativePath] : [];
|
|
2092
|
+
return {
|
|
2093
|
+
...remote,
|
|
2094
|
+
branch,
|
|
2095
|
+
treeSha,
|
|
2096
|
+
autoSync: false,
|
|
2097
|
+
pathForNewFiles: relativeDir && relativeDir !== "." ? relativeDir : "src/stately-studio",
|
|
2098
|
+
selectedPaths
|
|
2099
|
+
};
|
|
2100
|
+
} catch {
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
function normalizeActions(value) {
|
|
2105
|
+
if (value == null) return [];
|
|
2106
|
+
return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
2107
|
+
if (typeof item === "string") return { type: item };
|
|
2108
|
+
if (typeof item === "object" && item !== null && "type" in item) {
|
|
2109
|
+
const type = item.type;
|
|
2110
|
+
const params = "params" in item && typeof item.params === "object" && item.params ? item.params : void 0;
|
|
2111
|
+
return {
|
|
2112
|
+
type,
|
|
2113
|
+
...params ? { params } : {}
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
return { type: String(item) };
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
function normalizeTags(value) {
|
|
2120
|
+
if (!value) return [];
|
|
2121
|
+
if (Array.isArray(value)) return value.map((tag) => String(tag));
|
|
2122
|
+
return [String(value)];
|
|
2123
|
+
}
|
|
2124
|
+
function normalizeInvoke(value) {
|
|
2125
|
+
if (!value) return [];
|
|
2126
|
+
return (Array.isArray(value) ? value : [value]).map((invoke, index) => ({
|
|
2127
|
+
src: invoke.src,
|
|
2128
|
+
id: invoke.id ?? `invoke[${index}]`,
|
|
2129
|
+
...invoke.input ? { input: invoke.input } : {}
|
|
2130
|
+
}));
|
|
2131
|
+
}
|
|
2132
|
+
function resolveTargetId(sourceParentPath, target) {
|
|
2133
|
+
if (!target) return;
|
|
2134
|
+
if (target.startsWith("#")) {
|
|
2135
|
+
const stripped = target.slice(1);
|
|
2136
|
+
const machineIndex = stripped.indexOf(".");
|
|
2137
|
+
if (machineIndex >= 0) return `(machine).${stripped.slice(machineIndex + 1)}`;
|
|
2138
|
+
return `(machine).${stripped}`;
|
|
2139
|
+
}
|
|
2140
|
+
if (target.startsWith("(machine)")) return target;
|
|
2141
|
+
if (target.includes(".")) return `(machine).${target}`;
|
|
2142
|
+
return `${sourceParentPath}.${target}`;
|
|
2143
|
+
}
|
|
2144
|
+
function appendTransitionEdges(edges, sourceId, sourceParentPath, eventType, transition, edgeCounts) {
|
|
2145
|
+
const transitions = Array.isArray(transition) ? transition : [transition];
|
|
2146
|
+
for (const item of transitions) {
|
|
2147
|
+
const normalized = typeof item === "string" ? { target: item } : item;
|
|
2148
|
+
const targetId = resolveTargetId(sourceParentPath, Array.isArray(normalized.target) ? normalized.target[0] : normalized.target);
|
|
2149
|
+
const groupKey = `${sourceId}:${eventType}`;
|
|
2150
|
+
const index = edgeCounts.get(groupKey) ?? 0;
|
|
2151
|
+
edgeCounts.set(groupKey, index + 1);
|
|
2152
|
+
edges.push({
|
|
2153
|
+
type: "edge",
|
|
2154
|
+
id: `${sourceId}#${eventType}[${index}]`,
|
|
2155
|
+
sourceId,
|
|
2156
|
+
targetId: targetId ?? sourceId,
|
|
2157
|
+
label: eventType,
|
|
2158
|
+
data: {
|
|
2159
|
+
eventType,
|
|
2160
|
+
transitionType: normalized.reenter === true ? "reenter" : normalized.internal === true || !targetId ? "targetless" : "normal",
|
|
2161
|
+
...normalized.guard ? { guard: typeof normalized.guard === "string" ? { type: normalized.guard } : {
|
|
2162
|
+
type: normalized.guard.type,
|
|
2163
|
+
...normalized.guard.params ? { params: normalized.guard.params } : {}
|
|
2164
|
+
} } : {},
|
|
2165
|
+
actions: normalizeActions(normalized.actions),
|
|
2166
|
+
...normalized.description ? { description: normalized.description } : {}
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
function fromXStateConfig(config) {
|
|
2172
|
+
const nodes = [];
|
|
2173
|
+
const edges = [];
|
|
2174
|
+
const edgeCounts = /* @__PURE__ */ new Map();
|
|
2175
|
+
function visitNode(key, nodeConfig, parentId, parentPath) {
|
|
2176
|
+
const nodeId = parentPath ? `${parentPath}.${key}` : "(machine)";
|
|
2177
|
+
const currentPath = parentPath ? nodeId : "(machine)";
|
|
2178
|
+
const initialId = nodeConfig.initial ? `${currentPath}.${nodeConfig.initial}` : void 0;
|
|
2179
|
+
nodes.push({
|
|
2180
|
+
type: "node",
|
|
2181
|
+
id: nodeId,
|
|
2182
|
+
parentId,
|
|
2183
|
+
label: key,
|
|
2184
|
+
...initialId ? { initialNodeId: initialId } : {},
|
|
2185
|
+
data: {
|
|
2186
|
+
key,
|
|
2187
|
+
...nodeConfig.type ? { type: nodeConfig.type } : {},
|
|
2188
|
+
...nodeConfig.history ? { history: nodeConfig.history } : {},
|
|
2189
|
+
...initialId ? { initialId } : {},
|
|
2190
|
+
entry: normalizeActions(nodeConfig.entry),
|
|
2191
|
+
exit: normalizeActions(nodeConfig.exit),
|
|
2192
|
+
invokes: normalizeInvoke(nodeConfig.invoke),
|
|
2193
|
+
tags: normalizeTags(nodeConfig.tags),
|
|
2194
|
+
...nodeConfig.description ? { description: nodeConfig.description } : {}
|
|
2195
|
+
}
|
|
2196
|
+
});
|
|
2197
|
+
for (const [eventType, transition] of Object.entries(nodeConfig.on ?? {})) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", eventType, transition, edgeCounts);
|
|
2198
|
+
if (nodeConfig.always) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", "", nodeConfig.always, edgeCounts);
|
|
2199
|
+
for (const [childKey, childConfig] of Object.entries(nodeConfig.states ?? {})) visitNode(childKey, childConfig, nodeId, currentPath);
|
|
2200
|
+
}
|
|
2201
|
+
visitNode("(machine)", config, null, null);
|
|
2202
|
+
return {
|
|
2203
|
+
id: config.id ?? "machine",
|
|
2204
|
+
nodes,
|
|
2205
|
+
edges,
|
|
2206
|
+
data: {}
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
async function pushLocalMachineLinks(options) {
|
|
2210
|
+
const client = options.client ?? createStatelyClient({
|
|
2211
|
+
apiKey: options.apiKey,
|
|
2212
|
+
baseUrl: options.baseUrl,
|
|
2213
|
+
fetch: options.fetch
|
|
2214
|
+
});
|
|
2215
|
+
const sourcePath = path.resolve(options.cwd ?? process.cwd(), options.source);
|
|
2216
|
+
const project = await resolvePushProject(client, sourcePath, options);
|
|
2217
|
+
if (!project.projectVersionId) throw new Error("Resolved project is missing projectVersionId.");
|
|
2218
|
+
const contents = await fs.readFile(sourcePath, "utf8");
|
|
2219
|
+
const attachments = findStatelyPragmaAttachments(contents, sourcePath);
|
|
2220
|
+
const extracted = await extractMachinesFromLocalSource(sourcePath, contents);
|
|
2221
|
+
if (attachments.length === 0 || extracted.machines.length === 0) return {
|
|
2222
|
+
sourcePath,
|
|
2223
|
+
project,
|
|
2224
|
+
created: [],
|
|
2225
|
+
updated: [],
|
|
2226
|
+
skipped: [{
|
|
2227
|
+
machineIndex: 0,
|
|
2228
|
+
reason: extracted.error != null && attachments.length > 0 ? `Local machine extraction failed: ${extracted.error}` : "No local machines were discovered in this file."
|
|
2229
|
+
}]
|
|
2230
|
+
};
|
|
2231
|
+
if (attachments.length !== extracted.machines.length) return {
|
|
2232
|
+
sourcePath,
|
|
2233
|
+
project,
|
|
2234
|
+
created: [],
|
|
2235
|
+
updated: [],
|
|
2236
|
+
skipped: [{
|
|
2237
|
+
machineIndex: 0,
|
|
2238
|
+
reason: "The local machine extractor did not align with the source attachments for this file."
|
|
2239
|
+
}]
|
|
2240
|
+
};
|
|
2241
|
+
let nextContents = contents;
|
|
2242
|
+
let outputPath;
|
|
2243
|
+
const created = [];
|
|
2244
|
+
const updated = [];
|
|
2245
|
+
const skipped = [];
|
|
2246
|
+
for (const [machineIndex, attachment] of attachments.entries()) {
|
|
2247
|
+
const extractedMachine = extracted.machines[machineIndex];
|
|
2248
|
+
if (!extractedMachine?.config) {
|
|
2249
|
+
skipped.push({
|
|
2250
|
+
machineIndex,
|
|
2251
|
+
reason: "No extracted machine config was available for this source."
|
|
2252
|
+
});
|
|
2253
|
+
continue;
|
|
2254
|
+
}
|
|
2255
|
+
if (attachment.pragma?.id) {
|
|
2256
|
+
const updatedMachine = await client.machines.update({
|
|
2257
|
+
id: attachment.pragma.id,
|
|
2258
|
+
definition: toStudioMachine(fromXStateConfig(extractedMachine.config))
|
|
2259
|
+
});
|
|
2260
|
+
updated.push({
|
|
2261
|
+
machineIndex,
|
|
2262
|
+
machine: updatedMachine
|
|
2263
|
+
});
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
const machine = await client.machines.create({
|
|
2267
|
+
projectVersionId: project.projectVersionId,
|
|
2268
|
+
definition: toStudioMachine(fromXStateConfig(extractedMachine.config)),
|
|
2269
|
+
xstateVersion: Math.max(5, options.xstateVersion ?? 5)
|
|
2270
|
+
});
|
|
2271
|
+
nextContents = upsertStatelyPragma(nextContents, machine.id, {
|
|
2272
|
+
fileName: sourcePath,
|
|
2273
|
+
machineIndex
|
|
2274
|
+
});
|
|
2275
|
+
created.push({
|
|
2276
|
+
machineIndex,
|
|
2277
|
+
machine
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
if (nextContents !== contents) {
|
|
2281
|
+
await fs.writeFile(sourcePath, nextContents, "utf8");
|
|
2282
|
+
outputPath = sourcePath;
|
|
2283
|
+
}
|
|
2284
|
+
return {
|
|
2285
|
+
sourcePath,
|
|
2286
|
+
project,
|
|
2287
|
+
created,
|
|
2288
|
+
updated,
|
|
2289
|
+
skipped,
|
|
2290
|
+
...outputPath ? { outputPath } : {}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
async function resolveLocalFile(locator, options) {
|
|
2294
|
+
const filePath = path.resolve(options.cwd ?? process.cwd(), locator);
|
|
2295
|
+
const contents = await fs.readFile(filePath, "utf8");
|
|
2296
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
2297
|
+
if (extension === ".ts" || extension === ".tsx" || extension === ".js" || extension === ".jsx") {
|
|
2298
|
+
const config = (await (options.client ?? createStatelyClient({
|
|
2299
|
+
apiKey: options.apiKey,
|
|
2300
|
+
baseUrl: options.baseUrl,
|
|
2301
|
+
fetch: options.fetch
|
|
2302
|
+
})).code.extractMachines(contents)).machines[0]?.config;
|
|
2303
|
+
if (!config) throw new Error(`No machines extracted from ${filePath}`);
|
|
2304
|
+
return {
|
|
2305
|
+
kind: "local-file",
|
|
2306
|
+
locator: filePath,
|
|
2307
|
+
format: "xstate",
|
|
2308
|
+
graph: fromXStateConfig(config)
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
const parsed = JSON.parse(contents);
|
|
2312
|
+
if ("rootNode" in parsed && "edges" in parsed) return {
|
|
2313
|
+
kind: "local-file",
|
|
2314
|
+
locator: filePath,
|
|
2315
|
+
format: "digraph",
|
|
2316
|
+
graph: fromStudioMachine(parsed)
|
|
2317
|
+
};
|
|
2318
|
+
if ("nodes" in parsed && "edges" in parsed) return {
|
|
2319
|
+
kind: "local-file",
|
|
2320
|
+
locator: filePath,
|
|
2321
|
+
format: "graph",
|
|
2322
|
+
graph: parsed
|
|
2323
|
+
};
|
|
2324
|
+
if ("states" in parsed) return {
|
|
2325
|
+
kind: "local-file",
|
|
2326
|
+
locator: filePath,
|
|
2327
|
+
format: "xstate",
|
|
2328
|
+
graph: fromXStateConfig(parsed)
|
|
2329
|
+
};
|
|
2330
|
+
throw new Error(`Unsupported local sync input: ${filePath}`);
|
|
2331
|
+
}
|
|
2332
|
+
function inferWritableTargetFormat(filePath) {
|
|
2333
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "xstate";
|
|
2334
|
+
if (filePath.endsWith(".digraph.json")) return "digraph";
|
|
2335
|
+
if (filePath.endsWith(".graph.json")) return "graph";
|
|
2336
|
+
return null;
|
|
2337
|
+
}
|
|
2338
|
+
function resolveLocalXStateMachineId(graph) {
|
|
2339
|
+
const rootKey = (graph.nodes.find((node) => node.parentId == null) ?? null)?.data?.key;
|
|
2340
|
+
if (rootKey && rootKey !== "(machine)") return rootKey;
|
|
2341
|
+
return graph.id;
|
|
2342
|
+
}
|
|
2343
|
+
function serializeGraph(graph, format, options = {}) {
|
|
2344
|
+
switch (format) {
|
|
2345
|
+
case "digraph": return `${JSON.stringify(toStudioMachine(graph), null, 2)}\n`;
|
|
2346
|
+
case "graph": return `${JSON.stringify(graph, null, 2)}\n`;
|
|
2347
|
+
case "xstate": {
|
|
2348
|
+
const source = graphToXStateTS({
|
|
2349
|
+
...graph,
|
|
2350
|
+
id: resolveLocalXStateMachineId(graph)
|
|
2351
|
+
});
|
|
2352
|
+
if (!options.remoteMachineId) return source;
|
|
2353
|
+
return upsertStatelyPragma(source, options.remoteMachineId, options.targetPath ? { fileName: options.targetPath } : {});
|
|
2354
|
+
}
|
|
2355
|
+
default: {
|
|
2356
|
+
const exhaustive = format;
|
|
2357
|
+
throw new Error(`Unsupported sync output format: ${exhaustive}`);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
async function resolveRemoteMachine(machineId, baseUrl, kind, locator, options) {
|
|
2362
|
+
const machineResponse = await (options.client ?? createStatelyClient({
|
|
2363
|
+
apiKey: options.apiKey,
|
|
2364
|
+
baseUrl,
|
|
2365
|
+
fetch: options.fetch
|
|
2366
|
+
})).machines.get(machineId);
|
|
2367
|
+
return {
|
|
2368
|
+
kind,
|
|
2369
|
+
locator,
|
|
2370
|
+
format: "digraph",
|
|
2371
|
+
graph: fromStudioMachine(machineResponse && typeof machineResponse === "object" && "definition" in machineResponse && machineResponse.definition ? machineResponse.definition : machineResponse),
|
|
2372
|
+
remoteMachineId: machineId
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
async function resolveSyncInput(locator, options) {
|
|
2376
|
+
if (await fileExists(path.resolve(options.cwd ?? process.cwd(), locator))) return resolveLocalFile(locator, options);
|
|
2377
|
+
if (isUrl(locator)) {
|
|
2378
|
+
const url = new URL(locator);
|
|
2379
|
+
const machineId = url.pathname.split("/").filter(Boolean).at(-1);
|
|
2380
|
+
if (!machineId) throw new Error(`Could not resolve machine ID from URL: ${locator}`);
|
|
2381
|
+
return resolveRemoteMachine(machineId, url.origin, "studio-url", locator, options);
|
|
2382
|
+
}
|
|
2383
|
+
return resolveRemoteMachine(locator, options.baseUrl, "studio-machine-id", locator, options);
|
|
2384
|
+
}
|
|
2385
|
+
function summarizeDiff(diff) {
|
|
2386
|
+
const nodeChanges = diff.nodes.added.length + diff.nodes.removed.length + diff.nodes.updated.length;
|
|
2387
|
+
const edgeChanges = diff.edges.added.length + diff.edges.removed.length + diff.edges.updated.length;
|
|
2388
|
+
return {
|
|
2389
|
+
hasChanges: !isEmptyDiff(diff),
|
|
2390
|
+
nodeChanges,
|
|
2391
|
+
edgeChanges
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
async function planSync(options) {
|
|
2395
|
+
const source = await resolveSyncInput(options.source, options);
|
|
2396
|
+
const target = await resolveSyncInput(options.target, options);
|
|
2397
|
+
const diff = getDiff(source.graph, target.graph);
|
|
2398
|
+
return {
|
|
2399
|
+
source,
|
|
2400
|
+
target,
|
|
2401
|
+
diff,
|
|
2402
|
+
summary: summarizeDiff(diff),
|
|
2403
|
+
warnings: []
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
async function pullSync(options) {
|
|
2407
|
+
const source = await resolveSyncInput(options.source, options);
|
|
2408
|
+
const outputPath = path.resolve(options.cwd ?? process.cwd(), options.target);
|
|
2409
|
+
const fallbackFormat = inferWritableTargetFormat(outputPath);
|
|
2410
|
+
let targetFormat = fallbackFormat;
|
|
2411
|
+
if (await fileExists(outputPath)) try {
|
|
2412
|
+
targetFormat = (await resolveLocalFile(options.target, options)).format;
|
|
2413
|
+
} catch (error) {
|
|
2414
|
+
if (!fallbackFormat) throw error;
|
|
2415
|
+
}
|
|
2416
|
+
if (!targetFormat) throw new Error(`Could not infer a writable target format from ${outputPath}. Use an existing digraph/graph file or a .digraph.json/.graph.json target.`);
|
|
2417
|
+
const serialized = serializeGraph(source.graph, targetFormat, {
|
|
2418
|
+
remoteMachineId: source.remoteMachineId,
|
|
2419
|
+
targetPath: outputPath
|
|
2420
|
+
});
|
|
2421
|
+
await fs.writeFile(outputPath, serialized, "utf8");
|
|
2422
|
+
return {
|
|
2423
|
+
source,
|
|
2424
|
+
target: {
|
|
2425
|
+
kind: "local-file",
|
|
2426
|
+
locator: outputPath,
|
|
2427
|
+
format: targetFormat,
|
|
2428
|
+
graph: source.graph
|
|
2429
|
+
},
|
|
2430
|
+
outputPath
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
function inferDefaultProjectName(sourcePath, repo) {
|
|
2434
|
+
if (repo?.repo) return repo.repo;
|
|
2435
|
+
const parentDir = path.basename(path.dirname(sourcePath));
|
|
2436
|
+
if (parentDir && parentDir !== ".") return parentDir;
|
|
2437
|
+
return path.basename(sourcePath, path.extname(sourcePath));
|
|
2438
|
+
}
|
|
2439
|
+
async function resolvePushProject(client, sourcePath, options) {
|
|
2440
|
+
async function withResolvedProjectVersionId(project, explicitProjectVersionId) {
|
|
2441
|
+
if (project.projectVersionId) return project;
|
|
2442
|
+
if (explicitProjectVersionId) return {
|
|
2443
|
+
...project,
|
|
2444
|
+
projectVersionId: explicitProjectVersionId
|
|
2445
|
+
};
|
|
2446
|
+
const matchedProject = (await client.projects.list()).find((candidate) => {
|
|
2447
|
+
return candidate.projectId === project.projectId || candidate.id != null && project.id != null && candidate.id === project.id;
|
|
2448
|
+
});
|
|
2449
|
+
if (matchedProject?.projectVersionId) return {
|
|
2450
|
+
...project,
|
|
2451
|
+
projectVersionId: matchedProject.projectVersionId
|
|
2452
|
+
};
|
|
2453
|
+
return project;
|
|
2454
|
+
}
|
|
2455
|
+
const explicitProject = options.project;
|
|
2456
|
+
if (explicitProject?.projectVersionId) return withResolvedProjectVersionId(await client.projects.get(explicitProject.projectVersionId), explicitProject.projectVersionId);
|
|
2457
|
+
if (explicitProject?.projectId) return withResolvedProjectVersionId(await client.projects.get(explicitProject.projectId));
|
|
2458
|
+
const inferredRepo = explicitProject?.repo ?? await inferConnectedRepo(sourcePath, options.cwd);
|
|
2459
|
+
const projectInput = {
|
|
2460
|
+
name: explicitProject?.name ?? inferDefaultProjectName(sourcePath, inferredRepo),
|
|
2461
|
+
visibility: explicitProject?.visibility ?? "Private",
|
|
2462
|
+
...explicitProject?.description ? { description: explicitProject.description } : {},
|
|
2463
|
+
...explicitProject?.keywords ? { keywords: explicitProject.keywords } : {},
|
|
2464
|
+
...inferredRepo ? { repo: inferredRepo } : {}
|
|
2465
|
+
};
|
|
2466
|
+
return withResolvedProjectVersionId(await client.projects.ensure(projectInput));
|
|
2467
|
+
}
|
|
2468
|
+
async function pushSync(options) {
|
|
2469
|
+
const client = options.client ?? createStatelyClient({
|
|
2470
|
+
apiKey: options.apiKey,
|
|
2471
|
+
baseUrl: options.baseUrl,
|
|
2472
|
+
fetch: options.fetch
|
|
2473
|
+
});
|
|
2474
|
+
const source = await resolveSyncInput(options.source, {
|
|
2475
|
+
...options,
|
|
2476
|
+
target: options.source
|
|
2477
|
+
});
|
|
2478
|
+
if (source.kind !== "local-file") throw new Error("pushSync currently requires a local source file.");
|
|
2479
|
+
const sourcePath = source.locator;
|
|
2480
|
+
const project = await resolvePushProject(client, sourcePath, options);
|
|
2481
|
+
const existingMachineId = source.format === "xstate" ? getStatelyPragma(await fs.readFile(sourcePath, "utf8"), sourcePath)?.id : void 0;
|
|
2482
|
+
let machine;
|
|
2483
|
+
if (existingMachineId) machine = await client.machines.update({
|
|
2484
|
+
id: existingMachineId,
|
|
2485
|
+
definition: toStudioMachine(source.graph)
|
|
2486
|
+
});
|
|
2487
|
+
else {
|
|
2488
|
+
if (!project.projectVersionId) throw new Error("Resolved project is missing projectVersionId.");
|
|
2489
|
+
machine = await client.machines.create({
|
|
2490
|
+
projectVersionId: project.projectVersionId,
|
|
2491
|
+
definition: toStudioMachine(source.graph),
|
|
2492
|
+
xstateVersion: options.xstateVersion ?? 5
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
let outputPath;
|
|
2496
|
+
if (source.format === "xstate") {
|
|
2497
|
+
const contents = await fs.readFile(sourcePath, "utf8");
|
|
2498
|
+
const nextContents = upsertStatelyPragma(contents, machine.id, { fileName: sourcePath });
|
|
2499
|
+
if (nextContents !== contents) {
|
|
2500
|
+
await fs.writeFile(sourcePath, nextContents, "utf8");
|
|
2501
|
+
outputPath = sourcePath;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return {
|
|
2505
|
+
source,
|
|
2506
|
+
project,
|
|
2507
|
+
machine,
|
|
2508
|
+
...outputPath ? { outputPath } : {}
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
//#endregion
|
|
2513
|
+
export { pushSync as i, pullSync as n, pushLocalMachineLinks as r, planSync as t };
|