@samva/markup 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -0
- package/dist/{ast-63I-VLQx.d.ts → ast-BrDtApwS.d.ts} +34 -60
- package/dist/ast.d.ts +2 -2
- package/dist/ast.js +4 -52
- package/dist/{catalog-Ck1tAGwO.d.ts → catalog-60eaYC7b.d.ts} +2 -2
- package/dist/{checks-DXuERO4B.js → checks-DBweSIPN.js} +18 -20
- package/dist/checks.d.ts +1 -1
- package/dist/checks.js +1 -1
- package/dist/classes.d.ts +1 -1
- package/dist/compile-sms.d.ts +3 -3
- package/dist/compile-whatsapp.d.ts +5 -5
- package/dist/compile-whatsapp.js +3 -3
- package/dist/compile.d.ts +5 -5
- package/dist/compile.js +5 -5
- package/dist/compiler.d.ts +8 -5
- package/dist/compiler.js +45 -45
- package/dist/components.d.ts +4 -4
- package/dist/conditionals.js +56 -24
- package/dist/corpus-BjYMqOoY.d.ts +106 -0
- package/dist/{css-ar2lU3NC.d.ts → css-COcFOT1y.d.ts} +4 -5
- package/dist/diagnostics-BMYtBCQk.d.ts +46 -0
- package/dist/diagnostics-OcQKQYSY.d.ts +49 -0
- package/dist/diagnostics.d.ts +2 -2
- package/dist/diagnostics.js +24 -33
- package/dist/{elements-DtwKlFVH.d.ts → elements-BzjjnoCS.d.ts} +40 -161
- package/dist/email/components.d.ts +1 -1
- package/dist/email/components.js +40 -10
- package/dist/email/diagnostics.d.ts +1 -1
- package/dist/email/diagnostics.js +8 -43
- package/dist/email/elements.d.ts +1 -1
- package/dist/email/html.d.ts +2 -2
- package/dist/email/html.js +11 -10
- package/dist/email/jsx-dev-runtime.d.ts +3 -21
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/jsx-runtime.js +29 -3
- package/dist/email/render.d.ts +4 -4
- package/dist/email/render.js +1 -1
- package/dist/email/selection.d.ts +20 -31
- package/dist/email/selection.js +1 -53
- package/dist/email/source-edit.d.ts +1 -1
- package/dist/email/source-edit.js +2 -2
- package/dist/email/stylesheets.d.ts +1 -1
- package/dist/email/text.d.ts +1 -1
- package/dist/email/text.js +3 -3
- package/dist/emit.d.ts +2 -2
- package/dist/emit.js +5 -5
- package/dist/footer.js +7 -4
- package/dist/guards-eV1qLbf2.js +9 -0
- package/dist/{input-schema-BIfzI63B.d.ts → input-schema-7dPFVvNE.d.ts} +2 -2
- package/dist/input-schema-zjkgrXn2.js +455 -0
- package/dist/input-schema.d.ts +1 -1
- package/dist/input-schema.js +1 -169
- package/dist/interpolate.d.ts +9 -12
- package/dist/interpolate.js +1 -19
- package/dist/jsx-dev-runtime.d.ts +2 -2
- package/dist/{jsx-runtime-C5k_Zq0A.d.ts → jsx-runtime-I-1nCtl2.d.ts} +3 -3
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +3 -3
- package/dist/lexer.js +17 -5
- package/dist/model.d.ts +18 -33
- package/dist/model.js +4 -17
- package/dist/ops-2aiiYO7m.js +630 -0
- package/dist/ops.d.ts +78 -172
- package/dist/ops.js +2 -546
- package/dist/{parser-CGBS73Gz.d.ts → parser-CQcASEyy.d.ts} +5 -5
- package/dist/parser.d.ts +1 -1
- package/dist/parser.js +8 -8
- package/dist/{predicate-CvkzT-a2.js → predicate-BI6uRRM-.js} +24 -14
- package/dist/{predicate-G2SeSvoN.d.ts → predicate-M-05Nd7R.d.ts} +4 -4
- package/dist/predicate.d.ts +1 -1
- package/dist/predicate.js +1 -1
- package/dist/presets/category-mapping.d.ts +1 -1
- package/dist/presets/corpus.d.ts +1 -1
- package/dist/presets/corpus.js +74 -117
- package/dist/presets/public-gallery.d.ts +2 -2
- package/dist/presets/public-gallery.js +17 -9
- package/dist/presets/public-handoff.d.ts +4 -4
- package/dist/presets/public-handoff.js +10 -9
- package/dist/presets/public-preview-artifacts.d.ts +7 -5
- package/dist/presets/public-preview-artifacts.js +10 -9
- package/dist/presets/showcase.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/printer.d.ts +1 -1
- package/dist/{render-ykQhtMs_.js → render-aaJBnsXK.js} +44 -27
- package/dist/render-template.d.ts +7 -7
- package/dist/render-template.js +6 -6
- package/dist/{showcase-C_xgl6hz.d.ts → showcase-DGZAWn4Z.d.ts} +2 -2
- package/dist/{source-locations-BCJQnIfU.d.ts → source-locations-DrCM82Ym.d.ts} +4 -26
- package/dist/source-locations.d.ts +2 -2
- package/dist/source-locations.js +3 -26
- package/dist/{template-Dy5yOPyp.d.ts → template-DWftqh4x.d.ts} +4 -4
- package/dist/template.d.ts +1 -1
- package/dist/template.js +3 -3
- package/dist/theme-css.d.ts +3 -3
- package/dist/theme-css.js +3 -3
- package/dist/theme.js +25 -2
- package/dist/tsx-source-instrumentation.d.ts +1 -3
- package/dist/tsx-source-instrumentation.js +6 -6
- package/dist/tsx-tracer.d.ts +7 -11
- package/dist/tsx-tracer.js +165 -165
- package/dist/{variables-if1VrV34.d.ts → variables-J_L3qxRa.d.ts} +2 -2
- package/dist/variables.d.ts +1 -1
- package/dist/variables.js +4 -4
- package/package.json +4 -3
- package/dist/corpus-U2EeALvS.d.ts +0 -234
- package/dist/diagnostics-Djd9ckLg.d.ts +0 -90
- package/dist/diagnostics-oveE-laV.d.ts +0 -79
package/dist/tsx-tracer.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
1
2
|
import { buildLineMap } from "./diagnostics.js";
|
|
2
3
|
import { SML_TAGS, componentNameForSmlTag, smlTagForComponentName } from "./ast.js";
|
|
3
4
|
import { decodeEntities } from "./lexer.js";
|
|
4
5
|
import { parseSml } from "./parser.js";
|
|
5
|
-
import { applyOps } from "./ops.js";
|
|
6
|
-
import {
|
|
6
|
+
import { r as applyOps } from "./ops-2aiiYO7m.js";
|
|
7
|
+
import { Result, TaggedError } from "better-result";
|
|
7
8
|
import { parse } from "@babel/parser";
|
|
8
9
|
//#region src/tsx-tracer.ts
|
|
9
|
-
var TsxHeadingTraceError = class extends
|
|
10
|
+
var TsxHeadingTraceError = class extends TaggedError("TsxHeadingTraceError") {};
|
|
10
11
|
const ZERO_SPAN = {
|
|
11
12
|
start: 0,
|
|
12
13
|
end: 0
|
|
@@ -16,9 +17,8 @@ const diagnostic = (code, message, span = ZERO_SPAN) => ({
|
|
|
16
17
|
message,
|
|
17
18
|
span
|
|
18
19
|
});
|
|
19
|
-
const failure = (...diagnostics) => Result.
|
|
20
|
-
const
|
|
21
|
-
const isAstNode = (value) => isRecord(value) && typeof value.type === "string" && typeof value.start === "number" && typeof value.end === "number";
|
|
20
|
+
const failure = (...diagnostics) => Result.err(new TsxHeadingTraceError({ diagnostics }));
|
|
21
|
+
const isAstNode = (value) => isObject(value) && typeof value.type === "string" && typeof value.start === "number" && typeof value.end === "number";
|
|
22
22
|
const childNode = (node, key) => {
|
|
23
23
|
const value = node[key];
|
|
24
24
|
return isAstNode(value) ? value : null;
|
|
@@ -36,7 +36,7 @@ const walkAst = (value, visit) => {
|
|
|
36
36
|
for (const item of value) walkAst(item, visit);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
if (!
|
|
39
|
+
if (!isObject(value)) return;
|
|
40
40
|
if (isAstNode(value)) visit(value);
|
|
41
41
|
for (const [key, child] of Object.entries(value)) {
|
|
42
42
|
if (key === "loc" || key === "extra" || key === "comments" || key === "tokens") continue;
|
|
@@ -73,7 +73,7 @@ const staticStringLiteralText = (node) => {
|
|
|
73
73
|
if (Array.isArray(expressions) && expressions.length > 0) return null;
|
|
74
74
|
const quasis = Array.isArray(expression.quasis) ? expression.quasis.filter(isAstNode) : [];
|
|
75
75
|
if (quasis.length !== 1) return null;
|
|
76
|
-
const cooked =
|
|
76
|
+
const cooked = isObject(quasis[0].value) ? quasis[0].value.cooked : void 0;
|
|
77
77
|
return typeof cooked === "string" ? cooked : null;
|
|
78
78
|
};
|
|
79
79
|
/** True for bare JSX text that carries no visible characters. */
|
|
@@ -142,9 +142,9 @@ const parseAuthored = (source) => {
|
|
|
142
142
|
plugins: ["typescript", "jsx"],
|
|
143
143
|
sourceType: "module"
|
|
144
144
|
});
|
|
145
|
-
return Result.
|
|
145
|
+
return Result.ok(parsed.program);
|
|
146
146
|
} catch (error) {
|
|
147
|
-
return Result.
|
|
147
|
+
return Result.err(diagnostic("tsx-invalid-authored-source", error instanceof Error ? error.message : "The authored TSX could not be parsed."));
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
const isSpanLocator = (locator) => "span" in locator;
|
|
@@ -167,8 +167,8 @@ const locatorSpan = (locator) => locator !== void 0 && isSpanLocator(locator) ?
|
|
|
167
167
|
/** Resolve one rendered Heading to one direct-literal JSX text range. */
|
|
168
168
|
const traceTsxHeadingTarget = (input) => {
|
|
169
169
|
const parsed = parseSml(input.emittedSource);
|
|
170
|
-
if (Result.
|
|
171
|
-
const target = elementById(parsed.
|
|
170
|
+
if (Result.isError(parsed)) return failure(...parsed.error.diagnostics.map(({ message, span }) => diagnostic("tsx-invalid-emitted-source", message, span)));
|
|
171
|
+
const target = elementById(parsed.value.root, input.nodeId);
|
|
172
172
|
if (target === null) return failure(diagnostic("tsx-target-not-found", `rendered node "${input.nodeId}" was not found in the emitted SML source`));
|
|
173
173
|
if (target.tag !== "Heading") return failure(diagnostic("tsx-unsupported-target", `rendered node "${input.nodeId}" is <${target.tag}>; only a Heading can be edited`, target.span));
|
|
174
174
|
const renderedText = headingText(target);
|
|
@@ -180,11 +180,11 @@ const traceTsxHeadingTarget = (input) => {
|
|
|
180
180
|
if (!Number.isInteger(locator.span.start) || !Number.isInteger(locator.span.end) || locator.span.start < 0 || locator.span.end <= locator.span.start || locator.span.end > input.authoredSource.length) return failure(diagnostic("tsx-unsupported-target", "the source locator span is outside the authored TSX source", locator.span));
|
|
181
181
|
} else if (locatorOffset(input.authoredSource, locator) === null) return failure(diagnostic("tsx-unsupported-target", "the source locator does not identify a position in the authored TSX source"));
|
|
182
182
|
const authored = parseAuthored(input.authoredSource);
|
|
183
|
-
if (Result.
|
|
183
|
+
if (Result.isError(authored)) return failure(authored.error);
|
|
184
184
|
const candidates = [];
|
|
185
185
|
const dynamicSpans = [];
|
|
186
186
|
const unsupportedSpans = [];
|
|
187
|
-
walkAst(authored.
|
|
187
|
+
walkAst(authored.value, (node) => {
|
|
188
188
|
if (node.type !== "JSXElement") return;
|
|
189
189
|
const name = jsxOpeningName(node);
|
|
190
190
|
if (name.kind === "dynamic") {
|
|
@@ -208,7 +208,7 @@ const traceTsxHeadingTarget = (input) => {
|
|
|
208
208
|
if (mappedCandidates.length !== 1) return failure(diagnostic("tsx-ambiguous-target", `the source locator identifies ${mappedCandidates.length} authored Heading elements; refusing to guess a source range`, mappedCandidates[0].nodeSpan));
|
|
209
209
|
const candidate = mappedCandidates[0];
|
|
210
210
|
if (normalizeText(renderedText) !== normalizeText(candidate.text)) return failure(diagnostic("tsx-ambiguous-target", "the selected emitted Heading does not match the located authored literal; refusing to edit stale source", candidate.nodeSpan));
|
|
211
|
-
return Result.
|
|
211
|
+
return Result.ok({
|
|
212
212
|
nodeId: input.nodeId,
|
|
213
213
|
text: candidate.text,
|
|
214
214
|
span: candidate.span,
|
|
@@ -218,18 +218,18 @@ const traceTsxHeadingTarget = (input) => {
|
|
|
218
218
|
/** Replace only the mapped literal bytes; all other authored TSX stays untouched. */
|
|
219
219
|
const replaceTsxHeadingText = (input) => {
|
|
220
220
|
const traced = traceTsxHeadingTarget(input);
|
|
221
|
-
if (Result.
|
|
222
|
-
const { start, end } = traced.
|
|
223
|
-
const replacement = serializeStaticTextChild(traced.
|
|
224
|
-
return Result.
|
|
221
|
+
if (Result.isError(traced)) return Result.err(traced.error);
|
|
222
|
+
const { start, end } = traced.value.span;
|
|
223
|
+
const replacement = serializeStaticTextChild(traced.value.form, input.text);
|
|
224
|
+
return Result.ok(`${input.authoredSource.slice(0, start)}${replacement}${input.authoredSource.slice(end)}`);
|
|
225
225
|
};
|
|
226
|
-
var TsxSmlOpsError = class extends
|
|
226
|
+
var TsxSmlOpsError = class extends TaggedError("TsxSmlOpsError") {};
|
|
227
227
|
const sourceDiagnostic = (code, message, span = ZERO_SPAN) => ({
|
|
228
228
|
code,
|
|
229
229
|
message,
|
|
230
230
|
span
|
|
231
231
|
});
|
|
232
|
-
const sourceFailure = (...diagnostics) => Result.
|
|
232
|
+
const sourceFailure = (...diagnostics) => Result.err(new TsxSmlOpsError({ diagnostics }));
|
|
233
233
|
const astNodes = (value) => {
|
|
234
234
|
if (!Array.isArray(value)) return [];
|
|
235
235
|
return value.filter(isAstNode);
|
|
@@ -454,25 +454,25 @@ const sourceStaticAttributes = (element) => {
|
|
|
454
454
|
const values = /* @__PURE__ */ new Map();
|
|
455
455
|
for (const attribute of element.attributes) {
|
|
456
456
|
const value = sourceAttributeValue(attribute);
|
|
457
|
-
if (attribute.kind === "dynamic" || value === void 0) return Result.
|
|
457
|
+
if (attribute.kind === "dynamic" || value === void 0) return Result.err(sourceDiagnostic("tsx-dynamic-target", `authored <${sourceTagName(element)}> has a dynamic or spread attribute; refusing to rewrite it`, {
|
|
458
458
|
start: attribute.start,
|
|
459
459
|
end: attribute.end
|
|
460
460
|
}));
|
|
461
|
-
if (values.has(attribute.name)) return Result.
|
|
461
|
+
if (values.has(attribute.name)) return Result.err(sourceDiagnostic("tsx-ambiguous-target", `authored <${sourceTagName(element)}> repeats attribute "${attribute.name}"; refusing to choose one`, {
|
|
462
462
|
start: attribute.start,
|
|
463
463
|
end: attribute.end
|
|
464
464
|
}));
|
|
465
465
|
values.set(attribute.name, value);
|
|
466
466
|
}
|
|
467
|
-
return Result.
|
|
467
|
+
return Result.ok(values);
|
|
468
468
|
};
|
|
469
469
|
const currentSourceAttributes = (element) => {
|
|
470
470
|
const original = sourceStaticAttributes(element);
|
|
471
|
-
if (Result.
|
|
472
|
-
const values = new Map(original.
|
|
471
|
+
if (Result.isError(original)) return original;
|
|
472
|
+
const values = new Map(original.value);
|
|
473
473
|
for (const [name, value] of element.propEdits) if (value === null || value.kind === "string" && value.value === "") values.delete(name);
|
|
474
474
|
else values.set(name, value);
|
|
475
|
-
return Result.
|
|
475
|
+
return Result.ok(values);
|
|
476
476
|
};
|
|
477
477
|
const emittedAttributes = (element) => new Map(element.attributes.map((attribute) => [attribute.name, attribute.value]));
|
|
478
478
|
const attributesMatch = (source, emitted) => {
|
|
@@ -481,34 +481,34 @@ const attributesMatch = (source, emitted) => {
|
|
|
481
481
|
return true;
|
|
482
482
|
};
|
|
483
483
|
const validateMappedElement = (source, emitted, root, authoredSource, expectedAttributes, validateAttributes = true) => {
|
|
484
|
-
if (!sourceIsAttached(source, root)) return Result.
|
|
485
|
-
if (!isSmlTag(source.tag)) return Result.
|
|
486
|
-
if (source.tag !== emitted.tag) return Result.
|
|
484
|
+
if (!sourceIsAttached(source, root)) return Result.err(sourceDiagnostic("tsx-target-not-found", `the authored JSX node for emitted "${emitted.id}" is no longer in the active source tree`, sourceSpan(source)));
|
|
485
|
+
if (!isSmlTag(source.tag)) return Result.err(sourceDiagnostic("tsx-unsupported-target", `authored ${sourceTagName(source)} is not a recognized Samva primitive`, sourceSpan(source)));
|
|
486
|
+
if (source.tag !== emitted.tag) return Result.err(sourceDiagnostic("tsx-ambiguous-target", `emitted node "${emitted.id}" is <${emitted.tag}> but its locator identifies authored <${sourceTagName(source)}>`, sourceSpan(source)));
|
|
487
487
|
if (validateAttributes) {
|
|
488
488
|
const sourceValues = currentSourceAttributes(source);
|
|
489
|
-
if (Result.
|
|
489
|
+
if (Result.isError(sourceValues)) return Result.err(sourceValues.error);
|
|
490
490
|
const emittedValues = expectedAttributes ?? emittedAttributes(emitted);
|
|
491
|
-
if (!attributesMatch(sourceValues.
|
|
491
|
+
if (!attributesMatch(sourceValues.value, emittedValues)) return Result.err(sourceDiagnostic("tsx-ambiguous-target", `emitted <${emitted.tag}> does not match the located authored JSX attributes; refusing to edit stale source`, sourceSpan(source)));
|
|
492
492
|
}
|
|
493
|
-
if (source.end > authoredSource.length || source.start < 0) return Result.
|
|
494
|
-
return Result.
|
|
493
|
+
if (source.end > authoredSource.length || source.start < 0) return Result.err(sourceDiagnostic("tsx-unsupported-target", "the mapped JSX element lies outside the authored source", sourceSpan(source)));
|
|
494
|
+
return Result.ok(source);
|
|
495
495
|
};
|
|
496
496
|
const sourceElementForLocator = (sourceElements, authoredSource, authoredFile, locator) => {
|
|
497
|
-
if (locator.fileName !== authoredFile) return Result.
|
|
498
|
-
if (!sourceLocatorIsValid(authoredSource, locator)) return Result.
|
|
497
|
+
if (locator.fileName !== authoredFile) return Result.err(sourceDiagnostic("tsx-unsupported-target", `the source locator points to "${locator.fileName}", not the authored file "${authoredFile}"`, locatorSpan(locator)));
|
|
498
|
+
if (!sourceLocatorIsValid(authoredSource, locator)) return Result.err(sourceDiagnostic("tsx-unsupported-target", "the source locator does not identify a position in the authored TSX source", locatorSpan(locator)));
|
|
499
499
|
const candidates = sourceElements.filter((element) => locatorMatches(authoredSource, authoredFile, locator, sourceSpan(element)));
|
|
500
|
-
if (candidates.length === 0) return Result.
|
|
501
|
-
if (candidates.length !== 1) return Result.
|
|
502
|
-
return Result.
|
|
500
|
+
if (candidates.length === 0) return Result.err(sourceDiagnostic("tsx-target-not-found", "the source locator does not identify a JSX element in the authored TSX source", locatorSpan(locator)));
|
|
501
|
+
if (candidates.length !== 1) return Result.err(sourceDiagnostic("tsx-ambiguous-target", `the source locator identifies ${candidates.length} JSX elements; refusing to guess a source range`, sourceSpan(candidates[0])));
|
|
502
|
+
return Result.ok(candidates[0]);
|
|
503
503
|
};
|
|
504
504
|
const sourceElementForId = (id, emittedRoot, sourceElementsById, sourceMapErrors) => {
|
|
505
505
|
const emitted = elementById(emittedRoot, id);
|
|
506
|
-
if (emitted === null) return Result.
|
|
506
|
+
if (emitted === null) return Result.err(sourceDiagnostic("tsx-target-not-found", `emitted node "${id}" was not found in the SML source`));
|
|
507
507
|
const mapError = sourceMapErrors.get(id);
|
|
508
|
-
if (mapError !== void 0) return Result.
|
|
508
|
+
if (mapError !== void 0) return Result.err(mapError);
|
|
509
509
|
const source = sourceElementsById.get(id);
|
|
510
|
-
if (source === void 0) return Result.
|
|
511
|
-
return Result.
|
|
510
|
+
if (source === void 0) return Result.err(sourceDiagnostic("tsx-unsupported-target", `emitted node "${id}" has no exact authored TSX source locator; refusing to guess a range`, emitted.span));
|
|
511
|
+
return Result.ok({
|
|
512
512
|
source,
|
|
513
513
|
emitted
|
|
514
514
|
});
|
|
@@ -624,29 +624,29 @@ const attachSourceElement = (element, parent, index) => {
|
|
|
624
624
|
};
|
|
625
625
|
const escapedStaticText = (value) => escapeJsxText(value);
|
|
626
626
|
const renderStaticSmlNode = (node) => {
|
|
627
|
-
if (node.kind === "interpolation") return Result.
|
|
628
|
-
if (node.kind === "text") return Result.
|
|
629
|
-
if (!isSmlTag(node.tag)) return Result.
|
|
627
|
+
if (node.kind === "interpolation") return Result.err(sourceDiagnostic("tsx-dynamic-target", `cannot safely print interpolation {{${node.name}}} into a TSX child list without a variable binding`, node.span));
|
|
628
|
+
if (node.kind === "text") return Result.ok(escapedStaticText(node.value));
|
|
629
|
+
if (!isSmlTag(node.tag)) return Result.err(sourceDiagnostic("tsx-unsupported-target", `cannot print unknown SML tag <${node.tag}>`, node.span));
|
|
630
630
|
const attributes = node.attributes.map((attribute) => renderStaticAttribute(attribute.name, attribute.value)).join(" ");
|
|
631
631
|
const name = componentNameForSmlTag(node.tag);
|
|
632
632
|
const open = attributes.length === 0 ? `<${name}` : `<${name} ${attributes}`;
|
|
633
|
-
if (node.children.length === 0) return Result.
|
|
633
|
+
if (node.children.length === 0) return Result.ok(`${open} />`);
|
|
634
634
|
const children = [];
|
|
635
635
|
for (const child of node.children) {
|
|
636
636
|
const rendered = renderStaticSmlNode(child);
|
|
637
|
-
if (Result.
|
|
638
|
-
children.push(rendered.
|
|
637
|
+
if (Result.isError(rendered)) return rendered;
|
|
638
|
+
children.push(rendered.value);
|
|
639
639
|
}
|
|
640
|
-
return Result.
|
|
640
|
+
return Result.ok(`${open}>${children.join("")}</${name}>`);
|
|
641
641
|
};
|
|
642
642
|
const renderStaticSmlChildren = (children) => {
|
|
643
643
|
const rendered = [];
|
|
644
644
|
for (const child of children) {
|
|
645
645
|
const value = renderStaticSmlNode(child);
|
|
646
|
-
if (Result.
|
|
647
|
-
rendered.push(value.
|
|
646
|
+
if (Result.isError(value)) return value;
|
|
647
|
+
rendered.push(value.value);
|
|
648
648
|
}
|
|
649
|
-
return Result.
|
|
649
|
+
return Result.ok(rendered.join(""));
|
|
650
650
|
};
|
|
651
651
|
/**
|
|
652
652
|
* The one static text child an inline edit may rewrite. A formatter puts a
|
|
@@ -659,22 +659,22 @@ const directTextPart = (element, source) => {
|
|
|
659
659
|
return parts.length === 1 ? parts[0] : null;
|
|
660
660
|
};
|
|
661
661
|
const replaceDirectText = (element, emitted, source, text) => {
|
|
662
|
-
if (emitted.children.length !== 1 || emitted.children[0]?.kind !== "text") return Result.
|
|
662
|
+
if (emitted.children.length !== 1 || emitted.children[0]?.kind !== "text") return Result.ok(false);
|
|
663
663
|
const part = directTextPart(element, source);
|
|
664
|
-
if (part === null) return Result.
|
|
664
|
+
if (part === null) return Result.ok(false);
|
|
665
665
|
const current = sourceTextValue(part, source);
|
|
666
|
-
if (normalizeText(current) !== normalizeText(emitted.children[0].value)) return Result.
|
|
666
|
+
if (normalizeText(current) !== normalizeText(emitted.children[0].value)) return Result.err(sourceDiagnostic("tsx-ambiguous-target", `emitted <${emitted.tag}> text does not match the located authored literal; refusing to edit stale source`, sourceSpan(element)));
|
|
667
667
|
if (part.form === "string-literal") {
|
|
668
668
|
part.replacement = serializeStaticTextChild("string-literal", text);
|
|
669
|
-
return Result.
|
|
669
|
+
return Result.ok(true);
|
|
670
670
|
}
|
|
671
671
|
const raw = source.slice(part.start, part.end);
|
|
672
672
|
const leading = raw.search(/\S/u);
|
|
673
|
-
if (leading < 0) return Result.
|
|
673
|
+
if (leading < 0) return Result.ok(false);
|
|
674
674
|
let trailing = raw.length;
|
|
675
675
|
while (trailing > leading && /\s/u.test(raw[trailing - 1])) trailing -= 1;
|
|
676
676
|
part.replacement = `${raw.slice(0, leading)}${escapedStaticText(text)}${raw.slice(trailing)}`;
|
|
677
|
-
return Result.
|
|
677
|
+
return Result.ok(true);
|
|
678
678
|
};
|
|
679
679
|
const validateStaticSnippet = (source, snippet) => {
|
|
680
680
|
let parsed;
|
|
@@ -684,14 +684,14 @@ const validateStaticSnippet = (source, snippet) => {
|
|
|
684
684
|
sourceType: "module"
|
|
685
685
|
}).program;
|
|
686
686
|
} catch (error) {
|
|
687
|
-
return Result.
|
|
687
|
+
return Result.err(sourceDiagnostic("tsx-invalid-snippet", error instanceof Error ? error.message : "the insert snippet is not valid TSX"));
|
|
688
688
|
}
|
|
689
689
|
const fragments = [];
|
|
690
690
|
walkAst(parsed, (node) => {
|
|
691
691
|
if (node.type === "JSXFragment") fragments.push(node);
|
|
692
692
|
});
|
|
693
693
|
const fragment = fragments[0];
|
|
694
|
-
if (fragment === void 0) return Result.
|
|
694
|
+
if (fragment === void 0) return Result.err(sourceDiagnostic("tsx-invalid-snippet", "the insert snippet has no JSX fragment"));
|
|
695
695
|
const visitElement = (node) => {
|
|
696
696
|
const tag = sourceElementTag(node);
|
|
697
697
|
if (!isSmlTag(tag)) return sourceDiagnostic("tsx-unsupported-target", `insert snippet contains ${tag === null ? "a dynamic JSX component" : `<${tag}>`}, not a recognized Samva primitive`, astNodeSpan(node));
|
|
@@ -714,15 +714,15 @@ const validateStaticSnippet = (source, snippet) => {
|
|
|
714
714
|
let elementCount = 0;
|
|
715
715
|
for (const child of directChildren) {
|
|
716
716
|
if (child.type === "JSXText") {
|
|
717
|
-
if (source.slice(child.start, child.end).trim().length > 0) return Result.
|
|
717
|
+
if (source.slice(child.start, child.end).trim().length > 0) return Result.err(sourceDiagnostic("tsx-invalid-snippet", "insert snippet must contain only JSX elements", astNodeSpan(child)));
|
|
718
718
|
continue;
|
|
719
719
|
}
|
|
720
|
-
if (child.type !== "JSXElement") return Result.
|
|
720
|
+
if (child.type !== "JSXElement") return Result.err(sourceDiagnostic("tsx-dynamic-target", "insert snippet contains an opaque JSX child", astNodeSpan(child)));
|
|
721
721
|
elementCount++;
|
|
722
722
|
const childFailure = visitElement(child);
|
|
723
|
-
if (childFailure !== null) return Result.
|
|
723
|
+
if (childFailure !== null) return Result.err(childFailure);
|
|
724
724
|
}
|
|
725
|
-
return elementCount === 0 ? Result.
|
|
725
|
+
return elementCount === 0 ? Result.err(sourceDiagnostic("tsx-invalid-snippet", "insert snippet contains no elements")) : Result.ok(void 0);
|
|
726
726
|
};
|
|
727
727
|
const allEmittedElements = (root) => {
|
|
728
728
|
const elements = [];
|
|
@@ -734,16 +734,16 @@ const allEmittedElements = (root) => {
|
|
|
734
734
|
return elements;
|
|
735
735
|
};
|
|
736
736
|
const sourceTargetChildrenAreStatic = (target) => {
|
|
737
|
-
for (const child of target.children) if (child.kind === "opaque" || child.kind === "raw") return Result.
|
|
737
|
+
for (const child of target.children) if (child.kind === "opaque" || child.kind === "raw") return Result.err(sourceDiagnostic("tsx-dynamic-target", `authored <${sourceTagName(target)}> has a dynamic or opaque child; refusing to replace its complete child list`, child.kind === "raw" ? sourceSpan(target) : {
|
|
738
738
|
start: child.start,
|
|
739
739
|
end: child.end
|
|
740
740
|
}));
|
|
741
|
-
return Result.
|
|
741
|
+
return Result.ok(void 0);
|
|
742
742
|
};
|
|
743
743
|
const staticSourceTreeMatches = (source, emitted, authoredSource) => {
|
|
744
744
|
if (source.tag !== emitted.tag) return false;
|
|
745
745
|
const attributes = sourceStaticAttributes(source);
|
|
746
|
-
if (Result.
|
|
746
|
+
if (Result.isError(attributes) || !attributesMatch(attributes.value, emittedAttributes(emitted))) return false;
|
|
747
747
|
const sourceChildren = source.children.filter((child) => child.kind !== "text" || normalizeText(sourceTextValue(child, authoredSource)) !== "");
|
|
748
748
|
const emittedChildren = emitted.children.filter((child) => child.kind !== "text" || normalizeText(child.value) !== "");
|
|
749
749
|
if (sourceChildren.length !== emittedChildren.length) return false;
|
|
@@ -773,187 +773,187 @@ const removeEmittedSubtree = (values, node) => {
|
|
|
773
773
|
*/
|
|
774
774
|
const applyTsxSmlOps = (input) => {
|
|
775
775
|
const emitted = parseSml(input.emittedSource);
|
|
776
|
-
if (Result.
|
|
776
|
+
if (Result.isError(emitted)) return sourceFailure(...emitted.error.diagnostics.map(({ message, span }) => sourceDiagnostic("tsx-invalid-emitted-source", message, span)));
|
|
777
777
|
const authored = parseAuthored(input.authoredSource);
|
|
778
|
-
if (Result.
|
|
779
|
-
if (input.ops.length === 0) return Result.
|
|
778
|
+
if (Result.isError(authored)) return sourceFailure(authored.error);
|
|
779
|
+
if (input.ops.length === 0) return Result.ok(input.authoredSource);
|
|
780
780
|
const preflight = applyOps(input.emittedSource, input.ops);
|
|
781
|
-
if (Result.
|
|
782
|
-
const failedOperation = preflight.
|
|
781
|
+
if (Result.isError(preflight)) return sourceFailure(...preflight.error.diagnostics.map(({ message, span, code }) => sourceDiagnostic(code === "op-target-not-found" ? "tsx-target-not-found" : "tsx-unsupported-target", message, span)));
|
|
782
|
+
const failedOperation = preflight.value.results.find((result) => result.status === "failed");
|
|
783
783
|
if (failedOperation !== void 0) return sourceFailure(...failedOperation.diagnostics.map(({ message, span, code }) => sourceDiagnostic(code === "op-target-not-found" ? "tsx-target-not-found" : "tsx-unsupported-target", message, span)));
|
|
784
|
-
const sourceElements = collectSourceElements(authored.
|
|
784
|
+
const sourceElements = collectSourceElements(authored.value, input.authoredSource);
|
|
785
785
|
const sourceElementsById = /* @__PURE__ */ new Map();
|
|
786
786
|
const sourceIdByElement = /* @__PURE__ */ new Map();
|
|
787
787
|
const sourceMapErrors = /* @__PURE__ */ new Map();
|
|
788
788
|
const firstIdBySourceElement = /* @__PURE__ */ new Map();
|
|
789
789
|
for (const [id, locator] of Object.entries(input.sourceLocations.byNodeId)) {
|
|
790
790
|
const resolved = sourceElementForLocator(sourceElements, input.authoredSource, input.authoredFile, locator);
|
|
791
|
-
if (Result.
|
|
792
|
-
sourceMapErrors.set(id, resolved.
|
|
791
|
+
if (Result.isError(resolved)) {
|
|
792
|
+
sourceMapErrors.set(id, resolved.error);
|
|
793
793
|
continue;
|
|
794
794
|
}
|
|
795
|
-
const existingId = firstIdBySourceElement.get(resolved.
|
|
795
|
+
const existingId = firstIdBySourceElement.get(resolved.value);
|
|
796
796
|
if (existingId !== void 0) {
|
|
797
|
-
const duplicate = sourceDiagnostic("tsx-ambiguous-target", `source locators for emitted nodes "${existingId}" and "${id}" identify the same authored JSX element`, sourceSpan(resolved.
|
|
797
|
+
const duplicate = sourceDiagnostic("tsx-ambiguous-target", `source locators for emitted nodes "${existingId}" and "${id}" identify the same authored JSX element`, sourceSpan(resolved.value));
|
|
798
798
|
sourceMapErrors.set(existingId, duplicate);
|
|
799
799
|
sourceMapErrors.set(id, duplicate);
|
|
800
800
|
continue;
|
|
801
801
|
}
|
|
802
|
-
firstIdBySourceElement.set(resolved.
|
|
803
|
-
sourceElementsById.set(id, resolved.
|
|
804
|
-
sourceIdByElement.set(resolved.
|
|
802
|
+
firstIdBySourceElement.set(resolved.value, id);
|
|
803
|
+
sourceElementsById.set(id, resolved.value);
|
|
804
|
+
sourceIdByElement.set(resolved.value, id);
|
|
805
805
|
}
|
|
806
|
-
const rootId = emitted.
|
|
807
|
-
const mappedRoot = sourceElementForId(rootId, emitted.
|
|
808
|
-
if (Result.
|
|
809
|
-
if (mappedRoot.
|
|
810
|
-
const root = mappedRoot.
|
|
811
|
-
const rootValidation = validateMappedElement(root, mappedRoot.
|
|
812
|
-
if (Result.
|
|
806
|
+
const rootId = emitted.value.root.id;
|
|
807
|
+
const mappedRoot = sourceElementForId(rootId, emitted.value.root, sourceElementsById, sourceMapErrors);
|
|
808
|
+
if (Result.isError(mappedRoot)) return sourceFailure(mappedRoot.error);
|
|
809
|
+
if (mappedRoot.value.source.parent !== null) return sourceFailure(sourceDiagnostic("tsx-unsupported-target", "the emitted root locator identifies a nested JSX element, not the template root", sourceSpan(mappedRoot.value.source)));
|
|
810
|
+
const root = mappedRoot.value.source;
|
|
811
|
+
const rootValidation = validateMappedElement(root, mappedRoot.value.emitted, root, input.authoredSource);
|
|
812
|
+
if (Result.isError(rootValidation)) return sourceFailure(rootValidation.error);
|
|
813
813
|
for (const element of sourceElements) markSourceSubtree(element, false);
|
|
814
814
|
markSourceSubtree(root, true);
|
|
815
|
-
const virtualAttributes = new Map(allEmittedElements(emitted.
|
|
815
|
+
const virtualAttributes = new Map(allEmittedElements(emitted.value.root).map((element) => [element.id, new Map(emittedAttributes(element))]));
|
|
816
816
|
const resolveTarget = (id) => {
|
|
817
|
-
const resolved = sourceElementForId(id, emitted.
|
|
818
|
-
if (Result.
|
|
819
|
-
const validated = validateMappedElement(resolved.
|
|
820
|
-
if (Result.
|
|
821
|
-
return Result.
|
|
817
|
+
const resolved = sourceElementForId(id, emitted.value.root, sourceElementsById, sourceMapErrors);
|
|
818
|
+
if (Result.isError(resolved)) return Result.err(resolved.error);
|
|
819
|
+
const validated = validateMappedElement(resolved.value.source, resolved.value.emitted, root, input.authoredSource, virtualAttributes.get(id));
|
|
820
|
+
if (Result.isError(validated)) return Result.err(validated.error);
|
|
821
|
+
return Result.ok(resolved.value);
|
|
822
822
|
};
|
|
823
823
|
const resolveParentFor = (source) => {
|
|
824
|
-
if (source.parent === null) return Result.
|
|
824
|
+
if (source.parent === null) return Result.err(sourceDiagnostic("tsx-target-not-found", `authored <${sourceTagName(source)}> has no active JSX parent`, sourceSpan(source)));
|
|
825
825
|
const id = sourceIdByElement.get(source.parent);
|
|
826
|
-
if (id === void 0) return Result.
|
|
826
|
+
if (id === void 0) return Result.err(sourceDiagnostic("tsx-unsupported-target", `authored parent <${sourceTagName(source.parent)}> has no exact source-map entry`, sourceSpan(source.parent)));
|
|
827
827
|
const resolved = resolveTarget(id);
|
|
828
|
-
if (Result.
|
|
829
|
-
return Result.
|
|
828
|
+
if (Result.isError(resolved)) return Result.err(resolved.error);
|
|
829
|
+
return Result.ok(source.parent);
|
|
830
830
|
};
|
|
831
831
|
const resolveRootTarget = () => {
|
|
832
832
|
const validated = resolveTarget(rootId);
|
|
833
|
-
if (Result.
|
|
834
|
-
return Result.
|
|
833
|
+
if (Result.isError(validated)) return Result.err(validated.error);
|
|
834
|
+
return Result.ok(validated.value.source);
|
|
835
835
|
};
|
|
836
836
|
const applySetProp = (op) => {
|
|
837
|
-
if (op.name === "src:dark") return Result.
|
|
837
|
+
if (op.name === "src:dark") return Result.err(sourceDiagnostic("tsx-unsupported-operation", "src:dark is a derived SML source and has no direct TSX attribute splice"));
|
|
838
838
|
const resolved = resolveTarget(op.nodeId);
|
|
839
|
-
if (Result.
|
|
839
|
+
if (Result.isError(resolved)) return Result.err(resolved.error);
|
|
840
840
|
const value = op.value?.kind === "string" && op.value.value === "" ? null : op.value;
|
|
841
|
-
resolved.
|
|
841
|
+
resolved.value.source.propEdits.set(op.name, value);
|
|
842
842
|
const attributes = virtualAttributes.get(op.nodeId);
|
|
843
|
-
if (attributes === void 0) return Result.
|
|
843
|
+
if (attributes === void 0) return Result.err(sourceDiagnostic("tsx-target-not-found", `emitted node "${op.nodeId}" was not found in the SML attribute map`));
|
|
844
844
|
if (value === null) attributes.delete(op.name);
|
|
845
845
|
else attributes.set(op.name, value);
|
|
846
|
-
return Result.
|
|
846
|
+
return Result.ok(void 0);
|
|
847
847
|
};
|
|
848
848
|
const applySetChildren = (op) => {
|
|
849
|
-
const mapped = sourceElementForId(op.nodeId, emitted.
|
|
850
|
-
if (Result.
|
|
851
|
-
const mappedIdentity = validateMappedElement(mapped.
|
|
852
|
-
if (Result.
|
|
849
|
+
const mapped = sourceElementForId(op.nodeId, emitted.value.root, sourceElementsById, sourceMapErrors);
|
|
850
|
+
if (Result.isError(mapped)) return Result.err(mapped.error);
|
|
851
|
+
const mappedIdentity = validateMappedElement(mapped.value.source, mapped.value.emitted, root, input.authoredSource, void 0, false);
|
|
852
|
+
if (Result.isError(mappedIdentity)) return Result.err(mappedIdentity.error);
|
|
853
853
|
const rendered = renderStaticSmlChildren(op.children);
|
|
854
|
-
if (Result.
|
|
854
|
+
if (Result.isError(rendered)) return Result.err(rendered.error);
|
|
855
855
|
if (op.children.length === 1 && op.children[0]?.kind === "text") {
|
|
856
|
-
const direct = replaceDirectText(mapped.
|
|
857
|
-
if (Result.
|
|
858
|
-
if (direct.
|
|
856
|
+
const direct = replaceDirectText(mapped.value.source, mapped.value.emitted, input.authoredSource, op.children[0].value);
|
|
857
|
+
if (Result.isError(direct)) return Result.err(direct.error);
|
|
858
|
+
if (direct.value) return Result.ok(void 0);
|
|
859
859
|
}
|
|
860
860
|
const resolved = resolveTarget(op.nodeId);
|
|
861
|
-
if (Result.
|
|
862
|
-
const { source, emitted: emittedElement } = resolved.
|
|
861
|
+
if (Result.isError(resolved)) return Result.err(resolved.error);
|
|
862
|
+
const { source, emitted: emittedElement } = resolved.value;
|
|
863
863
|
const staticChildren = sourceTargetChildrenAreStatic(source);
|
|
864
|
-
if (Result.
|
|
865
|
-
if (!staticSourceTreeMatches(source, emittedElement, input.authoredSource)) return Result.
|
|
864
|
+
if (Result.isError(staticChildren)) return staticChildren;
|
|
865
|
+
if (!staticSourceTreeMatches(source, emittedElement, input.authoredSource)) return Result.err(sourceDiagnostic("tsx-ambiguous-target", `emitted <${emittedElement.tag}> does not match the located static authored JSX subtree`, sourceSpan(source)));
|
|
866
866
|
for (const child of sourceElementChildren(source)) {
|
|
867
867
|
child.parent = null;
|
|
868
868
|
markSourceSubtree(child, false);
|
|
869
869
|
}
|
|
870
|
-
source.children = rendered.
|
|
870
|
+
source.children = rendered.value.length === 0 ? [] : [{
|
|
871
871
|
kind: "raw",
|
|
872
|
-
source: rendered.
|
|
872
|
+
source: rendered.value
|
|
873
873
|
}];
|
|
874
|
-
return Result.
|
|
874
|
+
return Result.ok(void 0);
|
|
875
875
|
};
|
|
876
876
|
const applyInsertSnippet = (op) => {
|
|
877
877
|
const validSnippet = validateStaticSnippet(input.authoredSource, op.snippet);
|
|
878
|
-
if (Result.
|
|
878
|
+
if (Result.isError(validSnippet)) return validSnippet;
|
|
879
879
|
let parent;
|
|
880
880
|
let insertionIndex;
|
|
881
881
|
if (op.referenceId === null || op.referenceId === rootId) {
|
|
882
882
|
const resolvedRoot = resolveRootTarget();
|
|
883
|
-
if (Result.
|
|
884
|
-
parent = resolvedRoot.
|
|
883
|
+
if (Result.isError(resolvedRoot)) return Result.err(resolvedRoot.error);
|
|
884
|
+
parent = resolvedRoot.value;
|
|
885
885
|
insertionIndex = op.position === "before" ? 0 : parent.children.length;
|
|
886
886
|
} else {
|
|
887
887
|
const reference = resolveTarget(op.referenceId);
|
|
888
|
-
if (Result.
|
|
888
|
+
if (Result.isError(reference)) return Result.err(reference.error);
|
|
889
889
|
if (op.position === "inside") {
|
|
890
|
-
parent = reference.
|
|
890
|
+
parent = reference.value.source;
|
|
891
891
|
insertionIndex = parent.children.length;
|
|
892
892
|
} else {
|
|
893
|
-
const resolvedParent = resolveParentFor(reference.
|
|
894
|
-
if (Result.
|
|
895
|
-
parent = resolvedParent.
|
|
896
|
-
const referenceIndex = sourceElementParentIndex(parent, reference.
|
|
897
|
-
if (referenceIndex < 0) return Result.
|
|
893
|
+
const resolvedParent = resolveParentFor(reference.value.source);
|
|
894
|
+
if (Result.isError(resolvedParent)) return Result.err(resolvedParent.error);
|
|
895
|
+
parent = resolvedParent.value;
|
|
896
|
+
const referenceIndex = sourceElementParentIndex(parent, reference.value.source);
|
|
897
|
+
if (referenceIndex < 0) return Result.err(sourceDiagnostic("tsx-target-not-found", `reference node "${op.referenceId}" is no longer attached`));
|
|
898
898
|
insertionIndex = referenceIndex + (op.position === "after" ? 1 : 0);
|
|
899
899
|
}
|
|
900
900
|
}
|
|
901
901
|
const parentId = sourceIdByElement.get(parent);
|
|
902
902
|
if (parentId !== void 0) {
|
|
903
903
|
const resolvedParent = resolveTarget(parentId);
|
|
904
|
-
if (Result.
|
|
904
|
+
if (Result.isError(resolvedParent)) return Result.err(resolvedParent.error);
|
|
905
905
|
}
|
|
906
906
|
parent.children.splice(insertionIndex, 0, {
|
|
907
907
|
kind: "raw",
|
|
908
908
|
source: op.snippet
|
|
909
909
|
});
|
|
910
|
-
return Result.
|
|
910
|
+
return Result.ok(void 0);
|
|
911
911
|
};
|
|
912
912
|
const applyDuplicate = (op) => {
|
|
913
913
|
const resolved = resolveTarget(op.nodeId);
|
|
914
|
-
if (Result.
|
|
915
|
-
if (resolved.
|
|
916
|
-
const parent = resolveParentFor(resolved.
|
|
917
|
-
if (Result.
|
|
918
|
-
const index = sourceElementParentIndex(parent.
|
|
919
|
-
if (index < 0) return Result.
|
|
920
|
-
parent.
|
|
914
|
+
if (Result.isError(resolved)) return Result.err(resolved.error);
|
|
915
|
+
if (resolved.value.source === root) return Result.err(sourceDiagnostic("tsx-unsupported-operation", "the document root cannot be duplicated"));
|
|
916
|
+
const parent = resolveParentFor(resolved.value.source);
|
|
917
|
+
if (Result.isError(parent)) return Result.err(parent.error);
|
|
918
|
+
const index = sourceElementParentIndex(parent.value, resolved.value.source);
|
|
919
|
+
if (index < 0) return Result.err(sourceDiagnostic("tsx-target-not-found", "the node is no longer attached"));
|
|
920
|
+
parent.value.children.splice(index + 1, 0, {
|
|
921
921
|
kind: "raw",
|
|
922
|
-
source: renderSourceElement(resolved.
|
|
922
|
+
source: renderSourceElement(resolved.value.source, input.authoredSource)
|
|
923
923
|
});
|
|
924
|
-
return Result.
|
|
924
|
+
return Result.ok(void 0);
|
|
925
925
|
};
|
|
926
926
|
const applyRemove = (op) => {
|
|
927
927
|
const resolved = resolveTarget(op.nodeId);
|
|
928
|
-
if (Result.
|
|
929
|
-
if (resolved.
|
|
930
|
-
const parent = resolveParentFor(resolved.
|
|
931
|
-
if (Result.
|
|
932
|
-
if (!detachSourceElement(resolved.
|
|
933
|
-
const emittedNode = resolved.
|
|
928
|
+
if (Result.isError(resolved)) return Result.err(resolved.error);
|
|
929
|
+
if (resolved.value.source === root) return Result.err(sourceDiagnostic("tsx-unsupported-operation", "the document root cannot be removed"));
|
|
930
|
+
const parent = resolveParentFor(resolved.value.source);
|
|
931
|
+
if (Result.isError(parent)) return Result.err(parent.error);
|
|
932
|
+
if (!detachSourceElement(resolved.value.source)) return Result.err(sourceDiagnostic("tsx-target-not-found", `node "${op.nodeId}" is no longer attached`));
|
|
933
|
+
const emittedNode = resolved.value.emitted;
|
|
934
934
|
removeEmittedSubtree(virtualAttributes, emittedNode);
|
|
935
|
-
return Result.
|
|
935
|
+
return Result.ok(void 0);
|
|
936
936
|
};
|
|
937
937
|
const applyMove = (op) => {
|
|
938
938
|
const moved = resolveTarget(op.nodeId);
|
|
939
|
-
if (Result.
|
|
939
|
+
if (Result.isError(moved)) return Result.err(moved.error);
|
|
940
940
|
const parent = resolveTarget(op.targetParentId);
|
|
941
|
-
if (Result.
|
|
942
|
-
if (moved.
|
|
943
|
-
if (moved.
|
|
944
|
-
const oldParent = resolveParentFor(moved.
|
|
945
|
-
if (Result.
|
|
946
|
-
const oldIndex = sourceElementParentIndex(oldParent.
|
|
947
|
-
if (oldIndex < 0) return Result.
|
|
948
|
-
oldParent.
|
|
949
|
-
moved.
|
|
950
|
-
const insertionIndex = elementChildInsertionIndex(parent.
|
|
951
|
-
attachSourceElement(moved.
|
|
952
|
-
return Result.
|
|
941
|
+
if (Result.isError(parent)) return Result.err(parent.error);
|
|
942
|
+
if (moved.value.source === root) return Result.err(sourceDiagnostic("tsx-unsupported-operation", "the document root cannot be moved"));
|
|
943
|
+
if (moved.value.source === parent.value.source || activeDescendant(moved.value.source, parent.value.source)) return Result.err(sourceDiagnostic("tsx-unsupported-operation", "a node cannot be moved into itself or its descendants"));
|
|
944
|
+
const oldParent = resolveParentFor(moved.value.source);
|
|
945
|
+
if (Result.isError(oldParent)) return Result.err(oldParent.error);
|
|
946
|
+
const oldIndex = sourceElementParentIndex(oldParent.value, moved.value.source);
|
|
947
|
+
if (oldIndex < 0) return Result.err(sourceDiagnostic("tsx-target-not-found", `node "${op.nodeId}" is no longer attached`));
|
|
948
|
+
oldParent.value.children.splice(oldIndex, 1);
|
|
949
|
+
moved.value.source.parent = null;
|
|
950
|
+
const insertionIndex = elementChildInsertionIndex(parent.value.source.children, op.insertionIndex);
|
|
951
|
+
attachSourceElement(moved.value.source, parent.value.source, insertionIndex);
|
|
952
|
+
return Result.ok(void 0);
|
|
953
953
|
};
|
|
954
954
|
for (const op of input.ops) {
|
|
955
955
|
let result;
|
|
956
|
-
switch (op.
|
|
956
|
+
switch (op.type) {
|
|
957
957
|
case "setProp":
|
|
958
958
|
result = applySetProp(op);
|
|
959
959
|
break;
|
|
@@ -972,12 +972,12 @@ const applyTsxSmlOps = (input) => {
|
|
|
972
972
|
case "moveNode":
|
|
973
973
|
result = applyMove(op);
|
|
974
974
|
break;
|
|
975
|
-
case "insertBlock": result = Result.
|
|
975
|
+
case "insertBlock": result = Result.err(sourceDiagnostic("tsx-unsupported-operation", "insertBlock has no authored TSX fragment; use insertSnippet with an explicit JSX fragment"));
|
|
976
976
|
}
|
|
977
|
-
if (Result.
|
|
977
|
+
if (Result.isError(result)) return sourceFailure(result.error);
|
|
978
978
|
}
|
|
979
979
|
const renderedRoot = renderSourceElement(root, input.authoredSource);
|
|
980
|
-
return Result.
|
|
980
|
+
return Result.ok(`${input.authoredSource.slice(0, root.start)}${renderedRoot}${input.authoredSource.slice(root.end)}`);
|
|
981
981
|
};
|
|
982
982
|
//#endregion
|
|
983
983
|
export { TsxHeadingTraceError, TsxSmlOpsError, applyTsxSmlOps, replaceTsxHeadingText, traceTsxHeadingTarget };
|