@generaltranslation/vue-extractor 0.0.0 → 0.1.0-iris.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/LICENSE.md +18 -102
- package/dist/detect.d.ts +8 -0
- package/dist/detect.js +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/inspect.d.ts +9 -0
- package/dist/inspect.js +2 -0
- package/dist/integration.d.ts +69 -0
- package/dist/integration.js +162 -0
- package/dist/integration.js.map +1 -0
- package/dist/internal/config/configFiles.d.ts +6 -0
- package/dist/internal/config/configFiles.js +34 -0
- package/dist/internal/config/configFiles.js.map +1 -0
- package/dist/internal/config/resolveVueCompilerOptions.d.ts +6 -0
- package/dist/internal/config/resolveVueCompilerOptions.js +74 -56
- package/dist/internal/config/resolveVueCompilerOptions.js.map +1 -1
- package/dist/internal/extractFromVueSource.d.ts +4 -3
- package/dist/internal/extractFromVueSource.js +255 -12
- package/dist/internal/extractFromVueSource.js.map +1 -1
- package/dist/internal/project/consumerUsage.d.ts +34 -0
- package/dist/internal/project/consumerUsage.js +1220 -0
- package/dist/internal/project/consumerUsage.js.map +1 -0
- package/dist/internal/project/detectVueProject.d.ts +13 -0
- package/dist/internal/project/detectVueProject.js +65 -0
- package/dist/internal/project/detectVueProject.js.map +1 -0
- package/dist/internal/project/extractFromVueProject.d.ts +10 -0
- package/dist/internal/project/extractFromVueProject.js +579 -0
- package/dist/internal/project/extractFromVueProject.js.map +1 -0
- package/dist/internal/project/inspectVueProject.d.ts +48 -0
- package/dist/internal/project/inspectVueProject.js +143 -0
- package/dist/internal/project/inspectVueProject.js.map +1 -0
- package/dist/internal/project/manifest.d.ts +64 -0
- package/dist/internal/project/manifest.js +270 -0
- package/dist/internal/project/manifest.js.map +1 -0
- package/dist/internal/project/mergeVueProjectExtraction.d.ts +18 -0
- package/dist/internal/project/mergeVueProjectExtraction.js +79 -0
- package/dist/internal/project/mergeVueProjectExtraction.js.map +1 -0
- package/dist/internal/project/moduleResolver.d.ts +18 -0
- package/dist/internal/project/moduleResolver.js +325 -0
- package/dist/internal/project/moduleResolver.js.map +1 -0
- package/dist/internal/project/scopes.d.ts +31 -0
- package/dist/internal/project/scopes.js +182 -0
- package/dist/internal/project/scopes.js.map +1 -0
- package/dist/internal/project/sourcePatterns.d.ts +4 -0
- package/dist/internal/project/sourcePatterns.js +32 -0
- package/dist/internal/project/sourcePatterns.js.map +1 -0
- package/dist/internal/project/viteAliases.d.ts +42 -0
- package/dist/internal/project/viteAliases.js +1020 -0
- package/dist/internal/project/viteAliases.js.map +1 -0
- package/dist/internal/project/vueSourceClassification.d.ts +13 -0
- package/dist/internal/project/vueSourceClassification.js +146 -0
- package/dist/internal/project/vueSourceClassification.js.map +1 -0
- package/dist/internal/project/workspaces.d.ts +31 -0
- package/dist/internal/project/workspaces.js +326 -0
- package/dist/internal/project/workspaces.js.map +1 -0
- package/dist/internal/project/wrapperProvenance.d.ts +21 -0
- package/dist/internal/project/wrapperProvenance.js +287 -0
- package/dist/internal/project/wrapperProvenance.js.map +1 -0
- package/dist/internal/script/analyze.js +252 -85
- package/dist/internal/script/analyze.js.map +1 -1
- package/dist/internal/script/jsx.d.ts +7 -4
- package/dist/internal/script/jsx.js +351 -79
- package/dist/internal/script/jsx.js.map +1 -1
- package/dist/internal/script/localModules.d.ts +23 -2
- package/dist/internal/script/localModules.js +533 -20
- package/dist/internal/script/localModules.js.map +1 -1
- package/dist/internal/script/model.d.ts +9 -2
- package/dist/internal/script/parser.js +2 -1
- package/dist/internal/script/parser.js.map +1 -1
- package/dist/internal/script/runtimeModules.d.ts +10 -0
- package/dist/internal/script/runtimeModules.js +27 -0
- package/dist/internal/script/runtimeModules.js.map +1 -0
- package/dist/internal/stringCalls.js +1 -1
- package/dist/internal/stringCalls.js.map +1 -1
- package/dist/internal/template.js +263 -141
- package/dist/internal/template.js.map +1 -1
- package/dist/internal/types.d.ts +4 -1
- package/dist/internal/utils.d.ts +14 -3
- package/dist/internal/utils.js +63 -3
- package/dist/internal/utils.js.map +1 -1
- package/dist/internal/vueCompiler.d.ts +9 -1
- package/dist/internal/vueCompiler.js +37 -14
- package/dist/internal/vueCompiler.js.map +1 -1
- package/dist/project.d.ts +11 -0
- package/dist/project.js +3 -0
- package/dist/types.d.ts +95 -4
- package/package.json +30 -4
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { addVueError, babelLocation, createInlineMetadata, unwrapExpression } from "../utils.js";
|
|
1
|
+
import { addVueError, applyStaticTMetadataValue, babelLocation, createInlineMetadata, normalizeTMetadataKey, unwrapExpression } from "../utils.js";
|
|
2
2
|
import { isAcceptedPluralForm } from "generaltranslation/internal";
|
|
3
3
|
import * as babel from "@babel/types";
|
|
4
4
|
import { HTML_CONTENT_PROPS } from "@generaltranslation/format/types";
|
|
5
5
|
import { isHTMLTag, isSVGTag } from "@vue/shared";
|
|
6
6
|
//#region src/internal/script/jsx.ts
|
|
7
|
+
/** Prevents source strings on opposite sides of a JSX comment from merging. */
|
|
8
|
+
const jsxTextBoundary = Symbol("gt-vue-jsx-text-boundary");
|
|
7
9
|
const RESERVED_T_PROPS = new Set(["key", "ref"]);
|
|
8
10
|
const FORMAT_COMPONENTS = new Set([
|
|
9
11
|
"Currency",
|
|
@@ -28,10 +30,13 @@ const NON_BRANCH_PROP_NAMES = new Set([
|
|
|
28
30
|
/**
|
|
29
31
|
* Extracts one statically identified gt-vue `T` JSX element.
|
|
30
32
|
*
|
|
31
|
-
* This serializer follows the VNodes emitted by `@vue/babel-plugin-jsx
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* This serializer follows the VNodes emitted by `@vue/babel-plugin-jsx` while
|
|
34
|
+
* preserving React's authoritative rich-source contract: authored Fragments
|
|
35
|
+
* are semantic elements, explicit arrays retain array cardinality, JSX text
|
|
36
|
+
* uses the plugin's normalization, and static custom-component default slots
|
|
37
|
+
* participate in the same source tree as React component children. Runtime
|
|
38
|
+
* values remain explicit variable boundaries and named component slots remain
|
|
39
|
+
* outside the outer translation.
|
|
35
40
|
*/
|
|
36
41
|
function extractVueJSXTranslation(path, context, analysis) {
|
|
37
42
|
if (analysis.customPragma) {
|
|
@@ -39,13 +44,13 @@ function extractVueJSXTranslation(path, context, analysis) {
|
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
41
46
|
const errorCount = context.errors.length;
|
|
42
|
-
const
|
|
43
|
-
const source =
|
|
47
|
+
const translationMetadata = readTranslationMetadata(path.node.openingElement, path.scope, context, analysis);
|
|
48
|
+
const source = collapseRootChildren(serializeChildren(path.node.children, { value: 0 }, path.scope, context, analysis));
|
|
44
49
|
if (context.errors.length !== errorCount) return;
|
|
45
50
|
context.results.push({
|
|
46
51
|
dataFormat: "JSX",
|
|
47
52
|
source,
|
|
48
|
-
metadata: createInlineMetadata(context, babelLocation(path.node.loc),
|
|
53
|
+
metadata: createInlineMetadata(context, babelLocation(path.node.loc), translationMetadata)
|
|
49
54
|
});
|
|
50
55
|
}
|
|
51
56
|
/** Returns whether the visitor is already covered by an outer serialized T. */
|
|
@@ -56,13 +61,26 @@ function isNestedInVueJSXTranslation(path, analysis) {
|
|
|
56
61
|
const element = current.node;
|
|
57
62
|
const identity = resolveElementIdentity(element.openingElement.name, current.scope, analysis);
|
|
58
63
|
const containingAttribute = findContainingJSXAttribute(path, current);
|
|
59
|
-
if (containingAttribute
|
|
64
|
+
if (containingAttribute) {
|
|
65
|
+
if (!isSlotsAttribute(containingAttribute.node)) return false;
|
|
66
|
+
if (identity?.type === "component" && (identity.name === "Branch" || identity.name === "Plural")) {
|
|
67
|
+
current = current.parentPath;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (identity?.type === "component" && (identity.name === "Var" || FORMAT_COMPONENTS.has(identity.name))) return false;
|
|
71
|
+
const slotName = readContainingJSXSlotName(path, containingAttribute, analysis);
|
|
72
|
+
if (slotName !== "default") return slotName === void 0;
|
|
73
|
+
}
|
|
74
|
+
if (!containingAttribute && identity?.type !== "component" && (identity?.type !== "vue-builtin" || usesFragmentComponentSlots(element.openingElement.name, identity))) {
|
|
75
|
+
if ((identity ? { component: true } : resolveOrdinaryElementShape(element.openingElement.name, current.scope, analysis, /* @__PURE__ */ new Set()))?.component) {
|
|
76
|
+
const objectSlot = readContainingJSXObjectSlotName(path, current, analysis);
|
|
77
|
+
if (objectSlot.present && objectSlot.name !== "default") return objectSlot.name === void 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
60
80
|
if (identity?.type === "component") {
|
|
61
81
|
if (identity.name === "T") return true;
|
|
62
82
|
if (identity.name === "Var" || FORMAT_COMPONENTS.has(identity.name)) return false;
|
|
63
|
-
} else if (identity?.type === "vue-builtin" && (identity.name !== "Fragment" ||
|
|
64
|
-
if (identity.name === "Suspense" && containingAttribute && isSlotsAttribute(containingAttribute.node)) return false;
|
|
65
|
-
} else if (!isLiteralFragment(element.openingElement.name) && !isNativeElement(element.openingElement.name)) return false;
|
|
83
|
+
} else if (identity?.type === "vue-builtin" && (identity.name !== "Fragment" || isAuthoredFragment(element.openingElement.name, identity))) {}
|
|
66
84
|
}
|
|
67
85
|
current = current.parentPath;
|
|
68
86
|
}
|
|
@@ -80,6 +98,33 @@ function isSlotsAttribute(attribute) {
|
|
|
80
98
|
const name = readAttributeName(attribute.name);
|
|
81
99
|
return name === "v-slots" || name === "vSlots";
|
|
82
100
|
}
|
|
101
|
+
/** Returns the direct static slot key containing one nested JSX element. */
|
|
102
|
+
function readContainingJSXSlotName(path, attribute, analysis) {
|
|
103
|
+
const expression = attribute.node.value?.type === "JSXExpressionContainer" && attribute.node.value.expression.type !== "JSXEmptyExpression" ? unwrapExpression(attribute.node.value.expression) : void 0;
|
|
104
|
+
if (expression?.type !== "ObjectExpression") return void 0;
|
|
105
|
+
let current = path.parentPath;
|
|
106
|
+
while (current && current.node !== expression) {
|
|
107
|
+
if (current.parentPath?.node === expression && (current.isObjectMethod() || current.isObjectProperty())) return readStaticObjectKey(current.node, attribute.scope, analysis);
|
|
108
|
+
current = current.parentPath;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Classifies a nested element inside Vue's object-child slot spelling. */
|
|
112
|
+
function readContainingJSXObjectSlotName(path, element, analysis) {
|
|
113
|
+
const meaningfulChildren = element.node.children.filter(isMeaningfulJSXChild);
|
|
114
|
+
const child = meaningfulChildren[0];
|
|
115
|
+
if (meaningfulChildren.length !== 1 || child?.type !== "JSXExpressionContainer" || child.expression.type === "JSXEmptyExpression") return { present: false };
|
|
116
|
+
const object = unwrapExpression(child.expression);
|
|
117
|
+
if (object?.type !== "ObjectExpression") return { present: false };
|
|
118
|
+
let current = path.parentPath;
|
|
119
|
+
while (current && current.node !== object) {
|
|
120
|
+
if (current.parentPath?.node === object && (current.isObjectMethod() || current.isObjectProperty())) return {
|
|
121
|
+
name: readStaticObjectKey(current.node, element.scope, analysis),
|
|
122
|
+
present: true
|
|
123
|
+
};
|
|
124
|
+
current = current.parentPath;
|
|
125
|
+
}
|
|
126
|
+
return { present: false };
|
|
127
|
+
}
|
|
83
128
|
/** Resolves a JSX tag to the identity tracked by the script analyzer. */
|
|
84
129
|
function resolveVueJSXElementIdentity(name, scope, analysis) {
|
|
85
130
|
return resolveElementIdentity(name, scope, analysis);
|
|
@@ -88,63 +133,81 @@ function resolveVueJSXElementIdentity(name, scope, analysis) {
|
|
|
88
133
|
function validateVueJSXVariableComponent(element, component, context) {
|
|
89
134
|
validateVariableElement(element, component, context);
|
|
90
135
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
136
|
+
function readTranslationMetadata(element, scope, context, analysis) {
|
|
137
|
+
const metadata = {};
|
|
138
|
+
const seen = /* @__PURE__ */ new Set();
|
|
94
139
|
for (const attribute of element.attributes) {
|
|
95
140
|
if (attribute.type === "JSXSpreadAttribute") {
|
|
96
|
-
addVueError(context, babelLocation(attribute.loc), "Found a spread prop on a gt-vue <T> component in JSX", "Pass
|
|
141
|
+
addVueError(context, babelLocation(attribute.loc), "Found a spread prop on a gt-vue <T> component in JSX", "Pass each metadata value through an explicit static prop");
|
|
97
142
|
continue;
|
|
98
143
|
}
|
|
99
144
|
const name = readAttributeName(attribute.name);
|
|
100
145
|
if (RESERVED_T_PROPS.has(name)) continue;
|
|
101
|
-
|
|
102
|
-
|
|
146
|
+
const key = normalizeTMetadataKey(name);
|
|
147
|
+
if (!key) {
|
|
148
|
+
addVueError(context, babelLocation(attribute.loc), `Found unsupported prop "${name}" on a gt-vue <T> component`, "Use only static context, id, maxChars, or requiresReview metadata");
|
|
103
149
|
continue;
|
|
104
150
|
}
|
|
105
|
-
if (
|
|
106
|
-
addVueError(context, babelLocation(attribute.loc), "Found duplicate context props on a gt-vue <T> component", "Pass only one context prop");
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
hasContext = true;
|
|
151
|
+
if (!registerJSXTMetadataKey(key, seen, babelLocation(attribute.loc), context)) continue;
|
|
110
152
|
const contextExpression = attribute.value?.type === "JSXExpressionContainer" && attribute.value.expression.type !== "JSXEmptyExpression" ? unwrapExpression(attribute.value.expression) : void 0;
|
|
111
|
-
if (contextExpression?.type === "ConditionalExpression" || contextExpression?.type === "LogicalExpression") {
|
|
153
|
+
if (key === "context" && (contextExpression?.type === "ConditionalExpression" || contextExpression?.type === "LogicalExpression")) {
|
|
112
154
|
addVueError(context, babelLocation(attribute.loc), "Found conditional context on a gt-vue <T> component", "Use one invariant static context string for this translation");
|
|
113
155
|
continue;
|
|
114
156
|
}
|
|
115
157
|
const value = readJSXAttributePrimitive(attribute, scope, analysis);
|
|
116
|
-
if (!value.ok ||
|
|
117
|
-
|
|
158
|
+
if (!value.ok || !applyStaticTMetadataValue(metadata, key, value.value)) {
|
|
159
|
+
addInvalidJSXTMetadataValueError(key, babelLocation(attribute.loc), context);
|
|
118
160
|
continue;
|
|
119
161
|
}
|
|
120
|
-
translationContext = value.value;
|
|
121
162
|
}
|
|
122
|
-
return
|
|
163
|
+
return metadata;
|
|
164
|
+
}
|
|
165
|
+
function registerJSXTMetadataKey(key, seen, location, context) {
|
|
166
|
+
if (!seen.has(key)) {
|
|
167
|
+
seen.add(key);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
addVueError(context, location, `Found duplicate ${key} props on a gt-vue <T> component`, `Pass only one ${key} prop or its $-prefixed alias`);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
function addInvalidJSXTMetadataValueError(key, location, context) {
|
|
174
|
+
const expected = key === "context" || key === "id" ? "a static string" : key === "maxChars" ? "a static integer expression" : "a static boolean or a bare requiresReview prop";
|
|
175
|
+
addVueError(context, location, `Found an invalid or dynamic ${key} on a gt-vue <T> component`, `Use ${expected}`);
|
|
123
176
|
}
|
|
124
177
|
function serializeChildren(children, counter, scope, context, analysis) {
|
|
125
178
|
const result = [];
|
|
126
|
-
|
|
127
|
-
|
|
179
|
+
const runtimeArgumentCount = children.filter(isJSXRuntimeChildArgument).length;
|
|
180
|
+
const preserveScalarWireLiteral = runtimeArgumentCount === 1;
|
|
181
|
+
for (const child of children) for (const serialized of serializeChild(child, counter, scope, context, analysis, preserveScalarWireLiteral)) appendSerializedChild(result, serialized);
|
|
182
|
+
return runtimeArgumentCount > 1 ? [{
|
|
183
|
+
kind: "array",
|
|
184
|
+
children: result
|
|
185
|
+
}] : result;
|
|
186
|
+
}
|
|
187
|
+
/** Whether the Vue JSX transform emits this authored node as a child value. */
|
|
188
|
+
function isJSXRuntimeChildArgument(child) {
|
|
189
|
+
if (child.type === "JSXText") return normalizeJSXText(child.value) !== "";
|
|
190
|
+
return child.type !== "JSXExpressionContainer" || child.expression.type !== "JSXEmptyExpression";
|
|
128
191
|
}
|
|
129
|
-
function serializeChild(child, counter, scope, context, analysis) {
|
|
192
|
+
function serializeChild(child, counter, scope, context, analysis, preserveScalarWireLiteral) {
|
|
130
193
|
if (child.type === "JSXText") {
|
|
131
194
|
const text = normalizeJSXText(child.value);
|
|
132
195
|
return text ? [text] : [];
|
|
133
196
|
}
|
|
134
197
|
if (child.type === "JSXElement") return serializeElement(child, counter, scope, context, analysis);
|
|
135
|
-
if (child.type === "JSXFragment") return
|
|
198
|
+
if (child.type === "JSXFragment") return [serializeJSXFragment(child, counter, scope, context, analysis)];
|
|
136
199
|
if (child.type === "JSXSpreadChild") {
|
|
137
200
|
addVueError(context, babelLocation(child.loc), "Found a spread child inside a gt-vue <T> component in JSX", "Use static JSX children and wrap one runtime value in a gt-vue variable component");
|
|
138
201
|
return [];
|
|
139
202
|
}
|
|
140
|
-
if (child.expression.type === "JSXEmptyExpression") return [];
|
|
141
|
-
return serializeExpression(child.expression, counter, scope, context, analysis);
|
|
203
|
+
if (child.expression.type === "JSXEmptyExpression") return [jsxTextBoundary];
|
|
204
|
+
return serializeExpression(child.expression, counter, scope, context, analysis, preserveScalarWireLiteral);
|
|
142
205
|
}
|
|
143
|
-
function serializeExpression(input, counter, scope, context, analysis) {
|
|
206
|
+
function serializeExpression(input, counter, scope, context, analysis, preserveScalarWireLiteral = true) {
|
|
144
207
|
const expression = unwrapExpression(input);
|
|
145
208
|
if (!expression) return [];
|
|
146
209
|
if (expression.type === "JSXElement") return serializeElement(expression, counter, scope, context, analysis);
|
|
147
|
-
if (expression.type === "JSXFragment") return
|
|
210
|
+
if (expression.type === "JSXFragment") return [serializeJSXFragment(expression, counter, scope, context, analysis)];
|
|
148
211
|
if (expression.type === "ArrayExpression") {
|
|
149
212
|
const result = [];
|
|
150
213
|
for (const item of expression.elements) {
|
|
@@ -153,9 +216,12 @@ function serializeExpression(input, counter, scope, context, analysis) {
|
|
|
153
216
|
addVueError(context, babelLocation(item.loc), "Found a spread array child inside a gt-vue <T> component in JSX", "List each static child explicitly");
|
|
154
217
|
continue;
|
|
155
218
|
}
|
|
156
|
-
for (const serialized of serializeExpression(item, counter, scope, context, analysis)) appendSerializedChild(result, serialized);
|
|
219
|
+
for (const serialized of serializeExpression(item, counter, scope, context, analysis, false)) appendSerializedChild(result, serialized);
|
|
157
220
|
}
|
|
158
|
-
return
|
|
221
|
+
return [{
|
|
222
|
+
kind: "array",
|
|
223
|
+
children: result
|
|
224
|
+
}];
|
|
159
225
|
}
|
|
160
226
|
if (expression.type === "ConditionalExpression" || expression.type === "LogicalExpression") {
|
|
161
227
|
addVueError(context, babelLocation(expression.loc), "Found conditional JSX content inside a gt-vue <T> component", "Move conditional content outside <T>, or use a static Branch or Plural component");
|
|
@@ -166,12 +232,13 @@ function serializeExpression(input, counter, scope, context, analysis) {
|
|
|
166
232
|
addVueError(context, babelLocation(expression.loc), "Found dynamic JSX content inside a gt-vue <T> component", "Wrap runtime values in <Var>, <Num>, <DateTime>, or <Currency>");
|
|
167
233
|
return [];
|
|
168
234
|
}
|
|
169
|
-
|
|
235
|
+
if (value.value === null || typeof value.value === "boolean") return preserveScalarWireLiteral ? [value.value] : [];
|
|
236
|
+
return value.value === void 0 ? [] : [String(value.value)];
|
|
170
237
|
}
|
|
171
238
|
function serializeElement(element, counter, scope, context, analysis) {
|
|
172
239
|
const name = element.openingElement.name;
|
|
173
240
|
const identity = resolveElementIdentity(name, scope, analysis);
|
|
174
|
-
if (
|
|
241
|
+
if (isAuthoredFragment(name, identity)) return serializeSemanticFragment(element, counter, scope, context, analysis, usesFragmentComponentSlots(name, identity));
|
|
175
242
|
counter.value += 1;
|
|
176
243
|
const id = counter.value;
|
|
177
244
|
const component = identity?.type === "component" ? identity.name : void 0;
|
|
@@ -203,19 +270,11 @@ function serializeElement(element, counter, scope, context, analysis) {
|
|
|
203
270
|
}
|
|
204
271
|
if (component === "Branch" || component === "Plural") return [serializeBranchElement(element, id, component, counter, scope, context, analysis)];
|
|
205
272
|
if (identity?.type === "vue-builtin" && identity.name === "Suspense") return [serializeSuspenseElement(element, id, counter, scope, context, analysis)];
|
|
206
|
-
if (identity?.type === "vue-builtin" && identity.name === "Fragment") {
|
|
207
|
-
addVueError(context, babelLocation(element.loc), "Found a renamed Vue Fragment binding inside a gt-vue JSX translation", "Use <>, literal <Fragment>, or a Vue namespace member such as <Vue.Fragment>; the Vue JSX transform compiles renamed Fragment bindings as component slots");
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
210
|
-
if (identity?.type === "vue-builtin") {
|
|
211
|
-
addVueError(context, babelLocation(element.loc), `Found unsupported Vue <${identity.name}> content inside a gt-vue JSX translation`, "Move this Vue builtin outside <T>");
|
|
212
|
-
return [];
|
|
213
|
-
}
|
|
214
273
|
if (!identity && isUnboundNonNativeElement(name, scope)) {
|
|
215
274
|
addVueError(context, babelLocation(element.loc), `Could not determine whether JSX tag <${readElementDisplayName(name) ?? "unknown"}> is a component or custom element`, "Import or locally bind the component, or move the configured custom element outside <T>");
|
|
216
275
|
return [];
|
|
217
276
|
}
|
|
218
|
-
const ordinaryShape = identity ?
|
|
277
|
+
const ordinaryShape = identity ? { component: true } : resolveOrdinaryElementShape(name, scope, analysis, /* @__PURE__ */ new Set());
|
|
219
278
|
if (!identity && !ordinaryShape) {
|
|
220
279
|
addVueError(context, babelLocation(element.loc), `Could not statically resolve whether JSX tag <${readElementDisplayName(name) ?? "unknown"}> renders an element or component`, "Use a native tag, imported component, defineComponent result, or an immutable tag expression that cannot switch between element and component shapes");
|
|
221
280
|
return [];
|
|
@@ -226,19 +285,115 @@ function serializeElement(element, counter, scope, context, analysis) {
|
|
|
226
285
|
addVueError(context, babelLocation(element.loc), "Found unsupported namespaced JSX syntax inside a gt-vue <T> component", "Use a native element or a statically imported component");
|
|
227
286
|
return [];
|
|
228
287
|
}
|
|
229
|
-
|
|
288
|
+
const childResult = ordinaryShape?.component ? serializeOrdinaryComponentDefault(element, counter, scope, context, analysis) : { children: serializeChildren(element.children, counter, scope, context, analysis) };
|
|
289
|
+
return [{
|
|
230
290
|
t: tag,
|
|
231
291
|
i: id,
|
|
232
|
-
...Object.keys(data).length > 0 && { d: data }
|
|
292
|
+
...Object.keys(data).length > 0 && { d: data },
|
|
293
|
+
...shouldIncludeElementChildren(element, childResult.children, scope, analysis, childResult.slotTruthy) && { c: collapseChildren(childResult.children) }
|
|
233
294
|
}];
|
|
234
|
-
|
|
295
|
+
}
|
|
296
|
+
/** Serializes the shorthand `<>...</>` spelling as React.Fragment semantics. */
|
|
297
|
+
function serializeJSXFragment(fragment, counter, scope, context, analysis) {
|
|
298
|
+
counter.value += 1;
|
|
299
|
+
const id = counter.value;
|
|
300
|
+
const children = serializeChildren(fragment.children, counter, scope, context, analysis);
|
|
301
|
+
return {
|
|
302
|
+
t: `C${id}`,
|
|
303
|
+
i: id,
|
|
304
|
+
...hasTruthyJSXChildren(fragment.children, scope, analysis) && { c: collapseChildren(children) }
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** Serializes a Fragment alias that Vue JSX represents as a component slot. */
|
|
308
|
+
function serializeSemanticFragment(element, counter, scope, context, analysis, usesComponentSlots) {
|
|
309
|
+
counter.value += 1;
|
|
310
|
+
const id = counter.value;
|
|
311
|
+
validateElementAttributes(element.openingElement, scope, context, analysis);
|
|
312
|
+
const data = readContentProps(element.openingElement, scope, context, analysis);
|
|
313
|
+
const childResult = usesComponentSlots ? serializeOrdinaryComponentDefault(element, counter, scope, context, analysis) : { children: serializeChildren(element.children, counter, scope, context, analysis) };
|
|
235
314
|
return [{
|
|
236
|
-
t:
|
|
315
|
+
t: `C${id}`,
|
|
237
316
|
i: id,
|
|
238
317
|
...Object.keys(data).length > 0 && { d: data },
|
|
239
|
-
...children
|
|
318
|
+
...shouldIncludeElementChildren(element, childResult.children, scope, analysis, childResult.slotTruthy) && { c: collapseChildren(childResult.children) }
|
|
240
319
|
}];
|
|
241
320
|
}
|
|
321
|
+
/** Serializes the authored default slot of an ordinary component VNode. */
|
|
322
|
+
function serializeOrdinaryComponentDefault(element, counter, scope, context, analysis) {
|
|
323
|
+
const slotAttributes = element.openingElement.attributes.filter((attribute) => attribute.type === "JSXAttribute" && isSlotsAttribute(attribute));
|
|
324
|
+
if (slotAttributes.length > 1) addVueError(context, babelLocation(slotAttributes[1].loc), "Found more than one v-slots prop on a component inside a gt-vue JSX translation", "Pass one static slots object");
|
|
325
|
+
const meaningfulChildren = element.children.filter(isMeaningfulJSXChild);
|
|
326
|
+
const objectSlotChild = slotAttributes.length === 0 && meaningfulChildren.length === 1 && meaningfulChildren[0]?.type === "JSXExpressionContainer" && meaningfulChildren[0].expression.type !== "JSXEmptyExpression" ? analysis.resolveStaticObject(meaningfulChildren[0].expression, scope) : void 0;
|
|
327
|
+
const slotsObject = slotAttributes[0] ? readSlotsAttributeObject(slotAttributes[0], scope, context, analysis, "a component inside a gt-vue JSX translation") : objectSlotChild;
|
|
328
|
+
const defaultSlot = slotsObject ? readOrdinaryDefaultSlot(slotsObject, context, analysis) : { present: false };
|
|
329
|
+
if (defaultSlot.present) return defaultSlot.slot ? serializeSlotFunction(defaultSlot.slot, counter, context, analysis) : {
|
|
330
|
+
children: [],
|
|
331
|
+
slotTruthy: false
|
|
332
|
+
};
|
|
333
|
+
const directSlot = readDirectOrdinaryDefaultSlot(element, scope, context, analysis);
|
|
334
|
+
if (directSlot.present) return directSlot.slot ? serializeSlotFunction(directSlot.slot, counter, context, analysis) : {
|
|
335
|
+
children: [],
|
|
336
|
+
slotTruthy: false
|
|
337
|
+
};
|
|
338
|
+
if (objectSlotChild) return {
|
|
339
|
+
children: [],
|
|
340
|
+
slotTruthy: false
|
|
341
|
+
};
|
|
342
|
+
return { children: serializeChildren(element.children, counter, scope, context, analysis) };
|
|
343
|
+
}
|
|
344
|
+
/** Reads the one-function child Vue JSX compiles as a direct default slot. */
|
|
345
|
+
function readDirectOrdinaryDefaultSlot(element, scope, context, analysis) {
|
|
346
|
+
const children = element.children.filter(isMeaningfulJSXChild);
|
|
347
|
+
if (children.length !== 1 || children[0]?.type !== "JSXExpressionContainer" || children[0].expression.type === "JSXEmptyExpression") return { present: false };
|
|
348
|
+
const value = unwrapExpression(children[0].expression);
|
|
349
|
+
if (!value || value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression") return { present: false };
|
|
350
|
+
if (value.params.length > 0 || value.async || value.generator) {
|
|
351
|
+
addVueError(context, babelLocation(value.loc), "Found a dynamic or scoped direct default slot on a component inside a gt-vue JSX translation", "Use a synchronous zero-argument function with static returned content");
|
|
352
|
+
return { present: true };
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
present: true,
|
|
356
|
+
slot: {
|
|
357
|
+
node: value,
|
|
358
|
+
scope: analysis.scopeForNode(value, scope)
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/** Reads only the default entry; named slots remain runtime-owned and opaque. */
|
|
363
|
+
function readOrdinaryDefaultSlot(object, context, analysis) {
|
|
364
|
+
let present = false;
|
|
365
|
+
let slot;
|
|
366
|
+
for (const property of object.node.properties) {
|
|
367
|
+
if (property.type === "SpreadElement") {
|
|
368
|
+
addVueError(context, babelLocation(property.loc), "Found a spread in a component slots object inside a gt-vue JSX translation", "List the static default slot explicitly so a spread cannot add or replace it");
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const name = readStaticObjectKey(property, object.scope, analysis);
|
|
372
|
+
if (name === void 0) {
|
|
373
|
+
addVueError(context, babelLocation(property.loc), "Found a dynamic slot name on a component inside a gt-vue JSX translation", "Use a static named slot or an explicit default key");
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (name !== "default") continue;
|
|
377
|
+
if (present) {
|
|
378
|
+
addVueError(context, babelLocation(property.loc), "Found duplicate default slots on a component inside a gt-vue JSX translation", "Define the default slot once");
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
present = true;
|
|
382
|
+
const value = property.type === "ObjectMethod" && property.kind === "method" ? property : property.type === "ObjectProperty" ? unwrapExpression(property.value) : void 0;
|
|
383
|
+
if (!value || value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression" && value.type !== "ObjectMethod" || value.params.length > 0 || value.async || value.generator) {
|
|
384
|
+
addVueError(context, babelLocation(property.loc), "Found a dynamic or scoped default slot on a component inside a gt-vue JSX translation", "Use a synchronous zero-argument function with static returned content");
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
slot = {
|
|
388
|
+
node: value,
|
|
389
|
+
scope: analysis.scopeForNode(value, object.scope)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
present,
|
|
394
|
+
...slot && { slot }
|
|
395
|
+
};
|
|
396
|
+
}
|
|
242
397
|
/**
|
|
243
398
|
* Serializes Vue Suspense's normalized default content without evaluating its
|
|
244
399
|
* fallback. Vue requires that default slot to resolve to one VNode root; an
|
|
@@ -257,13 +412,13 @@ function serializeSuspenseElement(element, id, counter, scope, context, analysis
|
|
|
257
412
|
}
|
|
258
413
|
if (slots.defaultSlot) validateSuspenseSlotRoot(slots.defaultSlot, element, context);
|
|
259
414
|
else validateSuspenseImplicitRoot(slots.defaultChildren, element, context);
|
|
260
|
-
const
|
|
415
|
+
const childResult = slots.defaultSlot ? serializeSlotFunction(slots.defaultSlot, counter, context, analysis) : { children: serializeChildren(slots.defaultChildren, counter, scope, context, analysis) };
|
|
261
416
|
const data = readContentProps(element.openingElement, scope, context, analysis);
|
|
262
417
|
return {
|
|
263
418
|
t: "Suspense",
|
|
264
419
|
i: id,
|
|
265
420
|
...Object.keys(data).length > 0 && { d: data },
|
|
266
|
-
...children
|
|
421
|
+
...shouldIncludeElementChildren(element, childResult.children, scope, analysis, childResult.slotTruthy) && { c: collapseChildren(childResult.children) }
|
|
267
422
|
};
|
|
268
423
|
}
|
|
269
424
|
/** Reads the unambiguous one-function JSX spelling for a default slot. */
|
|
@@ -314,12 +469,14 @@ function addSuspenseRootError(element, context) {
|
|
|
314
469
|
function serializeBranchElement(element, id, component, counter, scope, context, analysis) {
|
|
315
470
|
const slots = readJSXSlotLayout(element, scope, context, analysis);
|
|
316
471
|
if (component === "Plural" && !hasExplicitAttribute(element.openingElement, "n")) addVueError(context, babelLocation(element.openingElement.loc), "Found a gt-vue <Plural> component without an n prop", "Pass the numeric selector through <Plural n={count} />");
|
|
317
|
-
const
|
|
472
|
+
const childResult = slots.defaultSlot ? serializeSlotFunction(slots.defaultSlot, counter, context, analysis) : { children: serializeChildren(slots.defaultChildren, counter, scope, context, analysis) };
|
|
318
473
|
const branches = {};
|
|
319
474
|
for (const [name, slot] of slots.namedSlots) {
|
|
320
475
|
if (name.startsWith("_")) continue;
|
|
321
476
|
if (component === "Plural" && !isAcceptedPluralForm(name)) continue;
|
|
322
|
-
|
|
477
|
+
const result = serializeSlotFunction(slot, { value: id }, context, analysis);
|
|
478
|
+
if (result.slotAbsent) continue;
|
|
479
|
+
branches[name] = collapseChildren(result.children);
|
|
323
480
|
}
|
|
324
481
|
readBranchAttributeSources(element.openingElement, component, branches, scope, context, analysis);
|
|
325
482
|
const data = readContentProps(element.openingElement, scope, context, analysis);
|
|
@@ -331,7 +488,7 @@ function serializeBranchElement(element, id, component, counter, scope, context,
|
|
|
331
488
|
t: component,
|
|
332
489
|
i: id,
|
|
333
490
|
...Object.keys(data).length > 0 && { d: data },
|
|
334
|
-
...children
|
|
491
|
+
...shouldIncludeElementChildren(element, childResult.children, scope, analysis, childResult.slotTruthy) && { c: collapseChildren(childResult.children) }
|
|
335
492
|
};
|
|
336
493
|
}
|
|
337
494
|
/** Reconstructs the slots object emitted by the Vue JSX transform. */
|
|
@@ -392,9 +549,53 @@ function serializeSlotFunction(slot, counter, context, analysis) {
|
|
|
392
549
|
const returned = readStaticSlotReturn(slot);
|
|
393
550
|
if (!returned) {
|
|
394
551
|
addVueError(context, babelLocation(slot.node.loc), "Found a JSX slot with a dynamic function body in a gt-vue translation", "Return static slot content directly from the zero-argument function");
|
|
395
|
-
return
|
|
552
|
+
return {
|
|
553
|
+
children: [],
|
|
554
|
+
slotTruthy: false
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const value = unwrapExpression(returned);
|
|
558
|
+
if (!value) return {
|
|
559
|
+
children: [],
|
|
560
|
+
slotTruthy: false
|
|
561
|
+
};
|
|
562
|
+
if (value.type !== "ArrayExpression") return {
|
|
563
|
+
children: serializeExpression(value, counter, slot.scope, context, analysis, true),
|
|
564
|
+
...isUndefinedSlotChild(value, slot.scope, analysis) && { slotAbsent: true },
|
|
565
|
+
slotTruthy: isTruthySlotChild(value, slot.scope, analysis)
|
|
566
|
+
};
|
|
567
|
+
const children = [];
|
|
568
|
+
const entries = value.elements.filter((entry) => entry !== null);
|
|
569
|
+
for (const entry of entries) {
|
|
570
|
+
if (entry.type === "SpreadElement") {
|
|
571
|
+
addVueError(context, babelLocation(entry.loc), "Found a spread in a JSX slot return inside a gt-vue translation", "Return a static list of slot children");
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
for (const child of serializeExpression(entry, counter, slot.scope, context, analysis, entries.length === 1)) appendSerializedChild(children, child);
|
|
396
575
|
}
|
|
397
|
-
return
|
|
576
|
+
return {
|
|
577
|
+
children: entries.length > 1 ? [{
|
|
578
|
+
kind: "array",
|
|
579
|
+
children
|
|
580
|
+
}] : children,
|
|
581
|
+
...entries.length === 1 && entries[0].type !== "SpreadElement" && isUndefinedSlotChild(entries[0], slot.scope, analysis) && { slotAbsent: true },
|
|
582
|
+
slotTruthy: entries.length > 1 || entries.length === 1 && entries[0].type !== "SpreadElement" && isTruthySlotChild(entries[0], slot.scope, analysis)
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/** Matches Vue's absent named-slot result without collapsing an authored array. */
|
|
586
|
+
function isUndefinedSlotChild(input, scope, analysis) {
|
|
587
|
+
const expression = unwrapExpression(input);
|
|
588
|
+
if (!expression || expression.type === "ArrayExpression") return false;
|
|
589
|
+
const primitive = analysis.readStaticPrimitive(expression, scope);
|
|
590
|
+
return primitive.ok && primitive.value === void 0;
|
|
591
|
+
}
|
|
592
|
+
/** Mirrors React's `props.children` truthiness for one normalized slot entry. */
|
|
593
|
+
function isTruthySlotChild(input, scope, analysis) {
|
|
594
|
+
const expression = unwrapExpression(input);
|
|
595
|
+
if (!expression) return false;
|
|
596
|
+
if (expression.type === "ArrayExpression" || expression.type === "JSXElement" || expression.type === "JSXFragment") return true;
|
|
597
|
+
const primitive = analysis.readStaticPrimitive(expression, scope);
|
|
598
|
+
return primitive.ok ? Boolean(primitive.value) : true;
|
|
398
599
|
}
|
|
399
600
|
/** Returns the expression from a slot body that contains only one return. */
|
|
400
601
|
function readStaticSlotReturn(slot) {
|
|
@@ -430,9 +631,23 @@ function readBranchAttributeSources(element, component, branches, scope, context
|
|
|
430
631
|
addVueError(context, babelLocation(attribute.loc), `Found dynamic branch prop "${name}" on a gt-vue <${component}> component`, "Use a static primitive branch prop or a static named slot");
|
|
431
632
|
continue;
|
|
432
633
|
}
|
|
433
|
-
|
|
634
|
+
if (value.value === void 0) continue;
|
|
635
|
+
branches[name] = serializeBranchAttributePrimitive(value.value);
|
|
434
636
|
}
|
|
435
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Serializes one direct branch prop without applying Vue child normalization.
|
|
640
|
+
*
|
|
641
|
+
* Vue renders boolean and null VNode children empty, but React and gt-vue
|
|
642
|
+
* preserve those direct prop values in the persisted branch wire format. The
|
|
643
|
+
* shared `JsxChildren` type predates those values, so the cast stays confined
|
|
644
|
+
* to this exact catalog boundary. Other primitives retain the established
|
|
645
|
+
* string representation.
|
|
646
|
+
*/
|
|
647
|
+
function serializeBranchAttributePrimitive(value) {
|
|
648
|
+
if (value === null || typeof value === "boolean") return value;
|
|
649
|
+
return String(value);
|
|
650
|
+
}
|
|
436
651
|
function isBranchPropName(name) {
|
|
437
652
|
return !(NON_BRANCH_PROP_NAMES.has(name) || name.startsWith("aria-") || name.startsWith("data-") || /^on[^a-z]/.test(name));
|
|
438
653
|
}
|
|
@@ -465,6 +680,7 @@ function validateVariableElement(element, component, context) {
|
|
|
465
680
|
return;
|
|
466
681
|
}
|
|
467
682
|
if (!explicitProps.has("value")) addVueError(context, babelLocation(element.openingElement.loc), `Found a gt-vue <${component}> component without a value prop`, `Pass the runtime value through <${component} value={value} />`);
|
|
683
|
+
if (explicitProps.has("name")) addVueError(context, babelLocation(element.openingElement.loc), `Found unsupported name prop on a gt-vue <${component}> component`, "Remove name; named variables are not supported by gt-vue yet");
|
|
468
684
|
if (hasMeaningfulChildren(element.children)) addVueError(context, babelLocation(element.loc), `Found children on a gt-vue <${component}> component`, `Use only the required value prop: <${component} value={value} />`);
|
|
469
685
|
}
|
|
470
686
|
function hasMeaningfulChildren(children) {
|
|
@@ -528,6 +744,7 @@ function readJSXAttributePrimitive(attribute, scope, analysis) {
|
|
|
528
744
|
return analysis.readStaticPrimitive(attribute.value.expression, scope);
|
|
529
745
|
}
|
|
530
746
|
function resolveElementIdentity(name, scope, analysis) {
|
|
747
|
+
if (isNativeElement(name)) return void 0;
|
|
531
748
|
const expression = jsxNameToExpression(name);
|
|
532
749
|
return expression ? analysis.resolveKnownValue(expression, scope) : void 0;
|
|
533
750
|
}
|
|
@@ -545,9 +762,13 @@ function jsxMemberObjectToExpression(object) {
|
|
|
545
762
|
function isLiteralFragment(name) {
|
|
546
763
|
return name.type === "JSXIdentifier" && name.name === "Fragment";
|
|
547
764
|
}
|
|
548
|
-
/** Matches
|
|
549
|
-
function
|
|
550
|
-
return isLiteralFragment(name) || identity?.type === "vue-builtin" && identity.name === "Fragment"
|
|
765
|
+
/** Matches Fragment spellings that create a semantic authored boundary. */
|
|
766
|
+
function isAuthoredFragment(name, identity) {
|
|
767
|
+
return isLiteralFragment(name) || identity?.type === "vue-builtin" && identity.name === "Fragment";
|
|
768
|
+
}
|
|
769
|
+
/** Detects Fragment aliases the JSX transform compiles through component slots. */
|
|
770
|
+
function usesFragmentComponentSlots(name, identity) {
|
|
771
|
+
return identity?.type === "vue-builtin" && identity.name === "Fragment" && !isLiteralFragment(name) && !(name.type === "JSXMemberExpression" && name.property.name === "Fragment");
|
|
551
772
|
}
|
|
552
773
|
/** Detects tags whose VNode type can change with plugin `isCustomElement`. */
|
|
553
774
|
function isUnboundNonNativeElement(name, scope) {
|
|
@@ -560,10 +781,10 @@ function isUnboundNonNativeElement(name, scope) {
|
|
|
560
781
|
function isNativeElement(name) {
|
|
561
782
|
return name.type === "JSXIdentifier" && (isHTMLTag(name.name) || isSVGTag(name.name));
|
|
562
783
|
}
|
|
563
|
-
/** Proves whether a non-GT JSX tag is
|
|
784
|
+
/** Proves whether a non-GT JSX tag is an element or component VNode. */
|
|
564
785
|
function resolveOrdinaryElementShape(name, scope, analysis, seen) {
|
|
565
786
|
if (isNativeElement(name)) return {
|
|
566
|
-
|
|
787
|
+
component: false,
|
|
567
788
|
tag: name.name
|
|
568
789
|
};
|
|
569
790
|
if (name.type === "JSXNamespacedName") return void 0;
|
|
@@ -571,11 +792,11 @@ function resolveOrdinaryElementShape(name, scope, analysis, seen) {
|
|
|
571
792
|
let root = name.object;
|
|
572
793
|
while (root.type === "JSXMemberExpression") root = root.object;
|
|
573
794
|
const binding = scope.getBinding(root.name);
|
|
574
|
-
return binding?.path.isImportSpecifier() || binding?.path.isImportDefaultSpecifier() || binding?.path.isImportNamespaceSpecifier() ? {
|
|
795
|
+
return binding?.path.isImportSpecifier() || binding?.path.isImportDefaultSpecifier() || binding?.path.isImportNamespaceSpecifier() ? { component: true } : void 0;
|
|
575
796
|
}
|
|
576
797
|
const binding = scope.getBinding(name.name);
|
|
577
798
|
if (!binding) return void 0;
|
|
578
|
-
if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier() || binding.path.isImportNamespaceSpecifier() || binding.path.isFunctionDeclaration() || binding.path.isClassDeclaration()) return {
|
|
799
|
+
if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier() || binding.path.isImportNamespaceSpecifier() || binding.path.isFunctionDeclaration() || binding.path.isClassDeclaration()) return { component: true };
|
|
579
800
|
const declaration = binding.path.node;
|
|
580
801
|
return declaration.type === "VariableDeclarator" && declaration.init ? resolveOrdinaryElementExpression(declaration.init, binding.path.scope, analysis, seen) : void 0;
|
|
581
802
|
}
|
|
@@ -587,21 +808,21 @@ function resolveOrdinaryElementExpression(input, scope, analysis, seen) {
|
|
|
587
808
|
nextSeen.add(expression);
|
|
588
809
|
const primitive = analysis.readStaticPrimitive(expression, scope);
|
|
589
810
|
if (primitive.ok) return typeof primitive.value === "string" ? {
|
|
590
|
-
|
|
811
|
+
component: false,
|
|
591
812
|
tag: primitive.value
|
|
592
813
|
} : void 0;
|
|
593
|
-
if (expression.type === "ArrowFunctionExpression" || expression.type === "FunctionExpression" || expression.type === "ClassExpression" || expression.type === "ObjectExpression") return {
|
|
814
|
+
if (expression.type === "ArrowFunctionExpression" || expression.type === "FunctionExpression" || expression.type === "ClassExpression" || expression.type === "ObjectExpression") return { component: true };
|
|
594
815
|
if (expression.type === "Identifier") {
|
|
595
816
|
const binding = scope.getBinding(expression.name);
|
|
596
817
|
if (!binding) return void 0;
|
|
597
|
-
if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier() || binding.path.isImportNamespaceSpecifier() || binding.path.isFunctionDeclaration() || binding.path.isClassDeclaration()) return {
|
|
818
|
+
if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier() || binding.path.isImportNamespaceSpecifier() || binding.path.isFunctionDeclaration() || binding.path.isClassDeclaration()) return { component: true };
|
|
598
819
|
const declaration = binding.path.node;
|
|
599
820
|
return declaration.type === "VariableDeclarator" && declaration.init ? resolveOrdinaryElementExpression(declaration.init, binding.path.scope, analysis, nextSeen) : void 0;
|
|
600
821
|
}
|
|
601
822
|
if (expression.type === "CallExpression" || expression.type === "OptionalCallExpression") {
|
|
602
823
|
if (expression.callee.type === "V8IntrinsicIdentifier") return void 0;
|
|
603
824
|
const callee = analysis.resolveKnownValue(expression.callee, scope);
|
|
604
|
-
if (callee?.type === "defineComponent") return {
|
|
825
|
+
if (callee?.type === "defineComponent") return { component: true };
|
|
605
826
|
const argument = expression.arguments[0];
|
|
606
827
|
return callee?.type === "identity" && expression.arguments.length === 1 && argument && argument.type !== "SpreadElement" && argument.type !== "ArgumentPlaceholder" ? resolveOrdinaryElementExpression(argument, scope, analysis, nextSeen) : void 0;
|
|
607
828
|
}
|
|
@@ -614,9 +835,9 @@ function resolveOrdinaryElementExpression(input, scope, analysis, seen) {
|
|
|
614
835
|
}
|
|
615
836
|
/** Combines alternate tags only when they preserve traversal semantics. */
|
|
616
837
|
function mergeOrdinaryElementShapes(left, right) {
|
|
617
|
-
if (!left || !right || left.
|
|
838
|
+
if (!left || !right || left.component !== right.component) return void 0;
|
|
618
839
|
return {
|
|
619
|
-
|
|
840
|
+
component: left.component,
|
|
620
841
|
...left.tag === right.tag && left.tag !== void 0 && { tag: left.tag }
|
|
621
842
|
};
|
|
622
843
|
}
|
|
@@ -654,12 +875,63 @@ function normalizeJSXText(text) {
|
|
|
654
875
|
return result;
|
|
655
876
|
}
|
|
656
877
|
function appendSerializedChild(result, child) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
878
|
+
if (child === jsxTextBoundary) {
|
|
879
|
+
if (result.at(-1) !== jsxTextBoundary) result.push(child);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
result.push(child);
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Mirrors the truthiness guard in React's `addGTIdentifier()`.
|
|
886
|
+
*
|
|
887
|
+
* JSX emits a scalar for one authored child and an array for multiple children.
|
|
888
|
+
* Arrays are truthy even when React.Children later removes all of their values;
|
|
889
|
+
* a sole primitive instead controls the guard with its original runtime value.
|
|
890
|
+
*/
|
|
891
|
+
function hasTruthyJSXChildren(children, scope, analysis) {
|
|
892
|
+
const runtimeChildren = children.filter(isJSXRuntimeChildArgument);
|
|
893
|
+
if (runtimeChildren.length !== 1) return runtimeChildren.length > 1;
|
|
894
|
+
const child = runtimeChildren[0];
|
|
895
|
+
if (child.type === "JSXText") return Boolean(normalizeJSXText(child.value));
|
|
896
|
+
if (child.type === "JSXElement" || child.type === "JSXFragment" || child.type === "JSXSpreadChild") return true;
|
|
897
|
+
if (child.expression.type === "JSXEmptyExpression") return false;
|
|
898
|
+
const expression = unwrapExpression(child.expression);
|
|
899
|
+
if (!expression) return false;
|
|
900
|
+
if (expression.type === "ArrayExpression") return true;
|
|
901
|
+
if (expression.type === "JSXElement" || expression.type === "JSXFragment") return true;
|
|
902
|
+
const primitive = analysis.readStaticPrimitive(expression, scope);
|
|
903
|
+
return primitive.ok ? Boolean(primitive.value) : true;
|
|
904
|
+
}
|
|
905
|
+
/** Includes direct children or a statically selected Vue default slot. */
|
|
906
|
+
function shouldIncludeElementChildren(element, children, scope, analysis, slotTruthy) {
|
|
907
|
+
if (slotTruthy !== void 0) return slotTruthy;
|
|
908
|
+
return element.children.some(isJSXRuntimeChildArgument) ? hasTruthyJSXChildren(element.children, scope, analysis) : children.some(hasSerializedValue);
|
|
909
|
+
}
|
|
910
|
+
function hasSerializedValue(child) {
|
|
911
|
+
return child !== jsxTextBoundary;
|
|
912
|
+
}
|
|
913
|
+
function isSerializedJSXArray(child) {
|
|
914
|
+
return child !== jsxTextBoundary && typeof child === "object" && child !== null && "kind" in child && child.kind === "array";
|
|
915
|
+
}
|
|
916
|
+
/** Flattens explicit nested arrays exactly as React.Children.map does. */
|
|
917
|
+
function flattenSerializedChildren(children) {
|
|
918
|
+
const flattened = [];
|
|
919
|
+
for (const child of children) {
|
|
920
|
+
if (child === jsxTextBoundary) continue;
|
|
921
|
+
if (isSerializedJSXArray(child)) flattened.push(...flattenSerializedChildren(child.children));
|
|
922
|
+
else flattened.push(child);
|
|
923
|
+
}
|
|
924
|
+
return flattened;
|
|
660
925
|
}
|
|
661
926
|
function collapseChildren(children) {
|
|
662
|
-
|
|
927
|
+
const structuralChildren = children.filter((child) => child !== jsxTextBoundary);
|
|
928
|
+
if (structuralChildren.length === 1 && isSerializedJSXArray(structuralChildren[0])) return flattenSerializedChildren(structuralChildren[0].children);
|
|
929
|
+
const visibleChildren = flattenSerializedChildren(children);
|
|
930
|
+
return visibleChildren.length === 1 ? visibleChildren[0] : visibleChildren;
|
|
931
|
+
}
|
|
932
|
+
/** Distinguishes a missing T slot from an explicitly authored empty array. */
|
|
933
|
+
function collapseRootChildren(children) {
|
|
934
|
+
return children.some(hasSerializedValue) ? collapseChildren(children) : void 0;
|
|
663
935
|
}
|
|
664
936
|
//#endregion
|
|
665
937
|
export { extractVueJSXTranslation, isNestedInVueJSXTranslation, resolveVueJSXElementIdentity, validateVueJSXVariableComponent };
|