@generaltranslation/vue-extractor 0.0.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 +105 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal/compilerAst.d.ts +43 -0
- package/dist/internal/compilerAst.js +77 -0
- package/dist/internal/compilerAst.js.map +1 -0
- package/dist/internal/config/resolveVueCompilerOptions.d.ts +23 -0
- package/dist/internal/config/resolveVueCompilerOptions.js +987 -0
- package/dist/internal/config/resolveVueCompilerOptions.js.map +1 -0
- package/dist/internal/extractFromVueSource.d.ts +9 -0
- package/dist/internal/extractFromVueSource.js +332 -0
- package/dist/internal/extractFromVueSource.js.map +1 -0
- package/dist/internal/script/analyze.d.ts +13 -0
- package/dist/internal/script/analyze.js +4761 -0
- package/dist/internal/script/analyze.js.map +1 -0
- package/dist/internal/script/jsx.d.ts +36 -0
- package/dist/internal/script/jsx.js +667 -0
- package/dist/internal/script/jsx.js.map +1 -0
- package/dist/internal/script/knownValues.d.ts +14 -0
- package/dist/internal/script/knownValues.js +148 -0
- package/dist/internal/script/knownValues.js.map +1 -0
- package/dist/internal/script/localModules.d.ts +80 -0
- package/dist/internal/script/localModules.js +364 -0
- package/dist/internal/script/localModules.js.map +1 -0
- package/dist/internal/script/model.d.ts +317 -0
- package/dist/internal/script/model.js +1 -0
- package/dist/internal/script/parser.d.ts +3 -0
- package/dist/internal/script/parser.js +21 -0
- package/dist/internal/script/parser.js.map +1 -0
- package/dist/internal/script/replay.d.ts +53 -0
- package/dist/internal/script/replay.js +1611 -0
- package/dist/internal/script/replay.js.map +1 -0
- package/dist/internal/script/snapshot.d.ts +25 -0
- package/dist/internal/script/snapshot.js +293 -0
- package/dist/internal/script/snapshot.js.map +1 -0
- package/dist/internal/script/syntax.d.ts +21 -0
- package/dist/internal/script/syntax.js +46 -0
- package/dist/internal/script/syntax.js.map +1 -0
- package/dist/internal/script.d.ts +2 -0
- package/dist/internal/script.js +2 -0
- package/dist/internal/stringCalls.d.ts +7 -0
- package/dist/internal/stringCalls.js +82 -0
- package/dist/internal/stringCalls.js.map +1 -0
- package/dist/internal/template.d.ts +4 -0
- package/dist/internal/template.js +2198 -0
- package/dist/internal/template.js.map +1 -0
- package/dist/internal/templatePath.d.ts +12 -0
- package/dist/internal/templatePath.js +23 -0
- package/dist/internal/templatePath.js.map +1 -0
- package/dist/internal/types.d.ts +69 -0
- package/dist/internal/types.js +1 -0
- package/dist/internal/utils.d.ts +19 -0
- package/dist/internal/utils.js +149 -0
- package/dist/internal/utils.js.map +1 -0
- package/dist/internal/vueCompiler.d.ts +35 -0
- package/dist/internal/vueCompiler.js +320 -0
- package/dist/internal/vueCompiler.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
import { addVueError, babelLocation, createInlineMetadata, unwrapExpression } from "../utils.js";
|
|
2
|
+
import { isAcceptedPluralForm } from "generaltranslation/internal";
|
|
3
|
+
import * as babel from "@babel/types";
|
|
4
|
+
import { HTML_CONTENT_PROPS } from "@generaltranslation/format/types";
|
|
5
|
+
import { isHTMLTag, isSVGTag } from "@vue/shared";
|
|
6
|
+
//#region src/internal/script/jsx.ts
|
|
7
|
+
const RESERVED_T_PROPS = new Set(["key", "ref"]);
|
|
8
|
+
const FORMAT_COMPONENTS = new Set([
|
|
9
|
+
"Currency",
|
|
10
|
+
"DateTime",
|
|
11
|
+
"Num"
|
|
12
|
+
]);
|
|
13
|
+
const NON_BRANCH_PROP_NAMES = new Set([
|
|
14
|
+
"branch",
|
|
15
|
+
"class",
|
|
16
|
+
"key",
|
|
17
|
+
"locales",
|
|
18
|
+
"n",
|
|
19
|
+
"ref",
|
|
20
|
+
"ref-for",
|
|
21
|
+
"ref-key",
|
|
22
|
+
"ref_for",
|
|
23
|
+
"ref_key",
|
|
24
|
+
"style",
|
|
25
|
+
"v-slots",
|
|
26
|
+
"vSlots"
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* Extracts one statically identified gt-vue `T` JSX element.
|
|
30
|
+
*
|
|
31
|
+
* This serializer follows the VNodes emitted by `@vue/babel-plugin-jsx`:
|
|
32
|
+
* fragments are transparent, JSX text uses the plugin's normalization, and
|
|
33
|
+
* arbitrary component slots remain opaque because gt-vue does not execute
|
|
34
|
+
* user slots while calculating a source hash.
|
|
35
|
+
*/
|
|
36
|
+
function extractVueJSXTranslation(path, context, analysis) {
|
|
37
|
+
if (analysis.customPragma) {
|
|
38
|
+
addVueError(context, babelLocation(analysis.customPragma.loc), "Found a custom @jsx pragma in a gt-vue JSX translation file", "Use the default Vue JSX VNode factory for files containing gt-vue <T>");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const errorCount = context.errors.length;
|
|
42
|
+
const translationContext = readTranslationContext(path.node.openingElement, path.scope, context, analysis);
|
|
43
|
+
const source = collapseChildren(serializeChildren(path.node.children, { value: 0 }, path.scope, context, analysis));
|
|
44
|
+
if (context.errors.length !== errorCount) return;
|
|
45
|
+
context.results.push({
|
|
46
|
+
dataFormat: "JSX",
|
|
47
|
+
source,
|
|
48
|
+
metadata: createInlineMetadata(context, babelLocation(path.node.loc), translationContext)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Returns whether the visitor is already covered by an outer serialized T. */
|
|
52
|
+
function isNestedInVueJSXTranslation(path, analysis) {
|
|
53
|
+
let current = path.parentPath;
|
|
54
|
+
while (current) {
|
|
55
|
+
if (current.isJSXElement()) {
|
|
56
|
+
const element = current.node;
|
|
57
|
+
const identity = resolveElementIdentity(element.openingElement.name, current.scope, analysis);
|
|
58
|
+
const containingAttribute = findContainingJSXAttribute(path, current);
|
|
59
|
+
if (containingAttribute && !(identity?.type === "component" && (identity.name === "Branch" || identity.name === "Plural") && isSlotsAttribute(containingAttribute.node))) return false;
|
|
60
|
+
if (identity?.type === "component") {
|
|
61
|
+
if (identity.name === "T") return true;
|
|
62
|
+
if (identity.name === "Var" || FORMAT_COMPONENTS.has(identity.name)) return false;
|
|
63
|
+
} else if (identity?.type === "vue-builtin" && (identity.name !== "Fragment" || isTransparentFragment(element.openingElement.name, identity))) {
|
|
64
|
+
if (identity.name === "Suspense" && containingAttribute && isSlotsAttribute(containingAttribute.node)) return false;
|
|
65
|
+
} else if (!isLiteralFragment(element.openingElement.name) && !isNativeElement(element.openingElement.name)) return false;
|
|
66
|
+
}
|
|
67
|
+
current = current.parentPath;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/** Finds an attribute boundary between a nested JSX node and one ancestor. */
|
|
72
|
+
function findContainingJSXAttribute(path, ancestor) {
|
|
73
|
+
let current = path.parentPath;
|
|
74
|
+
while (current && current !== ancestor) {
|
|
75
|
+
if (current.isJSXAttribute() && current.parentPath?.isJSXOpeningElement() && current.parentPath.parentPath === ancestor) return current;
|
|
76
|
+
current = current.parentPath;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function isSlotsAttribute(attribute) {
|
|
80
|
+
const name = readAttributeName(attribute.name);
|
|
81
|
+
return name === "v-slots" || name === "vSlots";
|
|
82
|
+
}
|
|
83
|
+
/** Resolves a JSX tag to the identity tracked by the script analyzer. */
|
|
84
|
+
function resolveVueJSXElementIdentity(name, scope, analysis) {
|
|
85
|
+
return resolveElementIdentity(name, scope, analysis);
|
|
86
|
+
}
|
|
87
|
+
/** Enforces the one supported public shape for a gt-vue variable component. */
|
|
88
|
+
function validateVueJSXVariableComponent(element, component, context) {
|
|
89
|
+
validateVariableElement(element, component, context);
|
|
90
|
+
}
|
|
91
|
+
function readTranslationContext(element, scope, context, analysis) {
|
|
92
|
+
let translationContext;
|
|
93
|
+
let hasContext = false;
|
|
94
|
+
for (const attribute of element.attributes) {
|
|
95
|
+
if (attribute.type === "JSXSpreadAttribute") {
|
|
96
|
+
addVueError(context, babelLocation(attribute.loc), "Found a spread prop on a gt-vue <T> component in JSX", "Pass context as one explicit static context or $context prop");
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const name = readAttributeName(attribute.name);
|
|
100
|
+
if (RESERVED_T_PROPS.has(name)) continue;
|
|
101
|
+
if (name !== "context" && name !== "$context") {
|
|
102
|
+
addVueError(context, babelLocation(attribute.loc), `Found unsupported prop "${name}" on a gt-vue <T> component`, "gt-vue <T> currently supports only context");
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (hasContext) {
|
|
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;
|
|
110
|
+
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") {
|
|
112
|
+
addVueError(context, babelLocation(attribute.loc), "Found conditional context on a gt-vue <T> component", "Use one invariant static context string for this translation");
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const value = readJSXAttributePrimitive(attribute, scope, analysis);
|
|
116
|
+
if (!value.ok || typeof value.value !== "string") {
|
|
117
|
+
addVueError(context, babelLocation(attribute.loc), "Found a dynamic context on a gt-vue <T> component", "Use a string literal or an immutable static string");
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
translationContext = value.value;
|
|
121
|
+
}
|
|
122
|
+
return translationContext;
|
|
123
|
+
}
|
|
124
|
+
function serializeChildren(children, counter, scope, context, analysis) {
|
|
125
|
+
const result = [];
|
|
126
|
+
for (const child of children) for (const serialized of serializeChild(child, counter, scope, context, analysis)) appendSerializedChild(result, serialized);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
function serializeChild(child, counter, scope, context, analysis) {
|
|
130
|
+
if (child.type === "JSXText") {
|
|
131
|
+
const text = normalizeJSXText(child.value);
|
|
132
|
+
return text ? [text] : [];
|
|
133
|
+
}
|
|
134
|
+
if (child.type === "JSXElement") return serializeElement(child, counter, scope, context, analysis);
|
|
135
|
+
if (child.type === "JSXFragment") return serializeChildren(child.children, counter, scope, context, analysis);
|
|
136
|
+
if (child.type === "JSXSpreadChild") {
|
|
137
|
+
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
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
if (child.expression.type === "JSXEmptyExpression") return [];
|
|
141
|
+
return serializeExpression(child.expression, counter, scope, context, analysis);
|
|
142
|
+
}
|
|
143
|
+
function serializeExpression(input, counter, scope, context, analysis) {
|
|
144
|
+
const expression = unwrapExpression(input);
|
|
145
|
+
if (!expression) return [];
|
|
146
|
+
if (expression.type === "JSXElement") return serializeElement(expression, counter, scope, context, analysis);
|
|
147
|
+
if (expression.type === "JSXFragment") return serializeChildren(expression.children, counter, scope, context, analysis);
|
|
148
|
+
if (expression.type === "ArrayExpression") {
|
|
149
|
+
const result = [];
|
|
150
|
+
for (const item of expression.elements) {
|
|
151
|
+
if (!item) continue;
|
|
152
|
+
if (item.type === "SpreadElement") {
|
|
153
|
+
addVueError(context, babelLocation(item.loc), "Found a spread array child inside a gt-vue <T> component in JSX", "List each static child explicitly");
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
for (const serialized of serializeExpression(item, counter, scope, context, analysis)) appendSerializedChild(result, serialized);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
if (expression.type === "ConditionalExpression" || expression.type === "LogicalExpression") {
|
|
161
|
+
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");
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const value = analysis.readStaticPrimitive(expression, scope);
|
|
165
|
+
if (!value.ok) {
|
|
166
|
+
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
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
return value.value == null || typeof value.value === "boolean" ? [] : [String(value.value)];
|
|
170
|
+
}
|
|
171
|
+
function serializeElement(element, counter, scope, context, analysis) {
|
|
172
|
+
const name = element.openingElement.name;
|
|
173
|
+
const identity = resolveElementIdentity(name, scope, analysis);
|
|
174
|
+
if (isTransparentFragment(name, identity)) return serializeChildren(element.children, counter, scope, context, analysis);
|
|
175
|
+
counter.value += 1;
|
|
176
|
+
const id = counter.value;
|
|
177
|
+
const component = identity?.type === "component" ? identity.name : void 0;
|
|
178
|
+
validateElementAttributes(element.openingElement, scope, context, analysis);
|
|
179
|
+
if (component === "T") {
|
|
180
|
+
addVueError(context, babelLocation(element.loc), "Found a nested gt-vue <T> component inside another <T>", "Split nested translations into sibling <T> components");
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
if (component === "Var" || component === "Num" || component === "DateTime" || component === "Currency") {
|
|
184
|
+
validateVariableElement(element, component, context);
|
|
185
|
+
const variable = component === "Num" ? {
|
|
186
|
+
name: "n",
|
|
187
|
+
type: "n"
|
|
188
|
+
} : component === "DateTime" ? {
|
|
189
|
+
name: "date",
|
|
190
|
+
type: "d"
|
|
191
|
+
} : component === "Currency" ? {
|
|
192
|
+
name: "cost",
|
|
193
|
+
type: "c"
|
|
194
|
+
} : {
|
|
195
|
+
name: "value",
|
|
196
|
+
type: "v"
|
|
197
|
+
};
|
|
198
|
+
return [{
|
|
199
|
+
i: id,
|
|
200
|
+
k: `_gt_${variable.name}_${id}`,
|
|
201
|
+
v: variable.type
|
|
202
|
+
}];
|
|
203
|
+
}
|
|
204
|
+
if (component === "Branch" || component === "Plural") return [serializeBranchElement(element, id, component, counter, scope, context, analysis)];
|
|
205
|
+
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
|
+
if (!identity && isUnboundNonNativeElement(name, scope)) {
|
|
215
|
+
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
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
const ordinaryShape = identity ? void 0 : resolveOrdinaryElementShape(name, scope, analysis, /* @__PURE__ */ new Set());
|
|
219
|
+
if (!identity && !ordinaryShape) {
|
|
220
|
+
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
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
const data = readContentProps(element.openingElement, scope, context, analysis);
|
|
224
|
+
const tag = ordinaryShape?.tag ?? readElementDisplayName(name);
|
|
225
|
+
if (!tag) {
|
|
226
|
+
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
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
if (ordinaryShape?.opaque) return [{
|
|
230
|
+
t: tag,
|
|
231
|
+
i: id,
|
|
232
|
+
...Object.keys(data).length > 0 && { d: data }
|
|
233
|
+
}];
|
|
234
|
+
const children = serializeChildren(element.children, counter, scope, context, analysis);
|
|
235
|
+
return [{
|
|
236
|
+
t: tag,
|
|
237
|
+
i: id,
|
|
238
|
+
...Object.keys(data).length > 0 && { d: data },
|
|
239
|
+
...children.length > 0 && { c: collapseChildren(children) }
|
|
240
|
+
}];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Serializes Vue Suspense's normalized default content without evaluating its
|
|
244
|
+
* fallback. Vue requires that default slot to resolve to one VNode root; an
|
|
245
|
+
* ambiguous or multi-root shape is rejected before it can hash differently.
|
|
246
|
+
*/
|
|
247
|
+
function serializeSuspenseElement(element, id, counter, scope, context, analysis) {
|
|
248
|
+
const slots = readJSXSlotLayout(element, scope, context, analysis, "suspense");
|
|
249
|
+
const directSlot = readDirectSuspenseSlot(element, scope, context, analysis);
|
|
250
|
+
if (directSlot.present) {
|
|
251
|
+
slots.defaultChildren = [];
|
|
252
|
+
slots.defaultSlot = directSlot.slot;
|
|
253
|
+
}
|
|
254
|
+
for (const name of slots.namedSlots.keys()) {
|
|
255
|
+
if (name === "fallback" || name.startsWith("_")) continue;
|
|
256
|
+
addVueError(context, babelLocation(element.loc), `Found unsupported named slot "${name}" on Vue <Suspense> inside a gt-vue <T> component`, "Use only the default and fallback Suspense slots");
|
|
257
|
+
}
|
|
258
|
+
if (slots.defaultSlot) validateSuspenseSlotRoot(slots.defaultSlot, element, context);
|
|
259
|
+
else validateSuspenseImplicitRoot(slots.defaultChildren, element, context);
|
|
260
|
+
const children = slots.defaultSlot ? serializeSlotFunction(slots.defaultSlot, counter, context, analysis) : serializeChildren(slots.defaultChildren, counter, scope, context, analysis);
|
|
261
|
+
const data = readContentProps(element.openingElement, scope, context, analysis);
|
|
262
|
+
return {
|
|
263
|
+
t: "Suspense",
|
|
264
|
+
i: id,
|
|
265
|
+
...Object.keys(data).length > 0 && { d: data },
|
|
266
|
+
...children.length > 0 && { c: collapseChildren(children) }
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/** Reads the unambiguous one-function JSX spelling for a default slot. */
|
|
270
|
+
function readDirectSuspenseSlot(element, scope, context, analysis) {
|
|
271
|
+
if (hasExplicitAttribute(element.openingElement, "v-slots") || hasExplicitAttribute(element.openingElement, "vSlots")) return { present: false };
|
|
272
|
+
const children = element.children.filter(isMeaningfulJSXChild);
|
|
273
|
+
if (children.length !== 1 || children[0]?.type !== "JSXExpressionContainer" || children[0].expression.type === "JSXEmptyExpression") return { present: false };
|
|
274
|
+
const value = unwrapExpression(children[0].expression);
|
|
275
|
+
if (!value || value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression") return { present: false };
|
|
276
|
+
if (value.params.length > 0 || value.async || value.generator) {
|
|
277
|
+
addVueError(context, babelLocation(value.loc), "Found a dynamic or scoped default slot on Vue <Suspense> inside a gt-vue <T> component", "Use a synchronous zero-argument function with static returned content");
|
|
278
|
+
return { present: true };
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
present: true,
|
|
282
|
+
slot: {
|
|
283
|
+
node: value,
|
|
284
|
+
scope: analysis.scopeForNode(value, scope)
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/** Enforces the single-root contract for children wrapped by the JSX plugin. */
|
|
289
|
+
function validateSuspenseImplicitRoot(children, element, context) {
|
|
290
|
+
const roots = children.filter(isMeaningfulJSXChild);
|
|
291
|
+
if (roots.length > 1) {
|
|
292
|
+
addSuspenseRootError(element, context);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const root = roots[0];
|
|
296
|
+
if (root?.type === "JSXExpressionContainer" && root.expression.type !== "JSXEmptyExpression") {
|
|
297
|
+
const expression = unwrapExpression(root.expression);
|
|
298
|
+
if (expression?.type !== "JSXElement" && expression?.type !== "JSXFragment") addVueError(context, babelLocation(root.loc), "Could not statically prove the default root of Vue <Suspense> inside a gt-vue <T> component", "Use one JSX element, Fragment, or a static zero-argument default slot");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/** Enforces the single-root contract for an explicit static slot function. */
|
|
302
|
+
function validateSuspenseSlotRoot(slot, element, context) {
|
|
303
|
+
const returned = readStaticSlotReturn(slot);
|
|
304
|
+
const expression = returned && unwrapExpression(returned);
|
|
305
|
+
if (expression?.type !== "ArrayExpression") return;
|
|
306
|
+
const roots = expression.elements.filter(Boolean);
|
|
307
|
+
if (roots.length > 1 || roots.some((root) => root?.type === "SpreadElement" || root?.type !== "JSXElement" && root?.type !== "JSXFragment")) addSuspenseRootError(element, context);
|
|
308
|
+
}
|
|
309
|
+
/** Reports the stable diagnostic shared by all invalid Suspense root shapes. */
|
|
310
|
+
function addSuspenseRootError(element, context) {
|
|
311
|
+
addVueError(context, babelLocation(element.loc), "Found more than one or an invalid default root inside Vue <Suspense> within a gt-vue <T> component", "Wrap the Suspense default content in one element or Fragment, or move <T> inside <Suspense>");
|
|
312
|
+
}
|
|
313
|
+
/** Serializes GT-owned Branch and Plural slots with independent ID scopes. */
|
|
314
|
+
function serializeBranchElement(element, id, component, counter, scope, context, analysis) {
|
|
315
|
+
const slots = readJSXSlotLayout(element, scope, context, analysis);
|
|
316
|
+
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 children = slots.defaultSlot ? serializeSlotFunction(slots.defaultSlot, counter, context, analysis) : serializeChildren(slots.defaultChildren, counter, scope, context, analysis);
|
|
318
|
+
const branches = {};
|
|
319
|
+
for (const [name, slot] of slots.namedSlots) {
|
|
320
|
+
if (name.startsWith("_")) continue;
|
|
321
|
+
if (component === "Plural" && !isAcceptedPluralForm(name)) continue;
|
|
322
|
+
branches[name] = collapseChildren(serializeSlotFunction(slot, { value: id }, context, analysis));
|
|
323
|
+
}
|
|
324
|
+
readBranchAttributeSources(element.openingElement, component, branches, scope, context, analysis);
|
|
325
|
+
const data = readContentProps(element.openingElement, scope, context, analysis);
|
|
326
|
+
if (Object.keys(branches).length > 0) {
|
|
327
|
+
data.b = branches;
|
|
328
|
+
data.t = component === "Plural" ? "p" : "b";
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
t: component,
|
|
332
|
+
i: id,
|
|
333
|
+
...Object.keys(data).length > 0 && { d: data },
|
|
334
|
+
...children.length > 0 && { c: collapseChildren(children) }
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/** Reconstructs the slots object emitted by the Vue JSX transform. */
|
|
338
|
+
function readJSXSlotLayout(element, scope, context, analysis, owner = "branch") {
|
|
339
|
+
const ownerLabel = owner === "suspense" ? "Vue <Suspense>" : "a gt-vue JSX branch component";
|
|
340
|
+
const vSlots = element.openingElement.attributes.filter((attribute) => attribute.type === "JSXAttribute" && isSlotsAttribute(attribute));
|
|
341
|
+
if (vSlots.length > 1) addVueError(context, babelLocation(vSlots[1].loc), `Found more than one v-slots prop on ${ownerLabel}`, "Pass one static slots object");
|
|
342
|
+
const meaningfulChildren = element.children.filter(isMeaningfulJSXChild);
|
|
343
|
+
const objectSlotChild = vSlots.length === 0 && meaningfulChildren.length === 1 && meaningfulChildren[0]?.type === "JSXExpressionContainer" && meaningfulChildren[0].expression.type !== "JSXEmptyExpression" ? analysis.resolveStaticObject(meaningfulChildren[0].expression, scope) : void 0;
|
|
344
|
+
if (objectSlotChild) addVueError(context, babelLocation(meaningfulChildren[0]?.loc), "Found Vue object-slot child syntax in a gt-vue JSX translation", "Pass the static slots object through v-slots so extraction is independent of the JSX enableObjectSlots compiler option");
|
|
345
|
+
const slotsObject = vSlots[0] ? readSlotsAttributeObject(vSlots[0], scope, context, analysis, ownerLabel) : void 0;
|
|
346
|
+
const namedSlots = /* @__PURE__ */ new Map();
|
|
347
|
+
let defaultSlot;
|
|
348
|
+
if (slotsObject) for (const [name, slot] of readStaticSlotObject(slotsObject, context, analysis)) if (name === "default") defaultSlot = slot;
|
|
349
|
+
else namedSlots.set(name, slot);
|
|
350
|
+
return {
|
|
351
|
+
defaultChildren: objectSlotChild ? [] : element.children,
|
|
352
|
+
...defaultSlot && { defaultSlot },
|
|
353
|
+
namedSlots
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function readSlotsAttributeObject(attribute, scope, context, analysis, ownerLabel) {
|
|
357
|
+
const expression = attribute.value?.type === "JSXExpressionContainer" && attribute.value.expression.type !== "JSXEmptyExpression" ? attribute.value.expression : void 0;
|
|
358
|
+
const object = expression ? analysis.resolveStaticObject(expression, scope) : void 0;
|
|
359
|
+
if (!object) addVueError(context, babelLocation(attribute.loc), `Found dynamic v-slots on ${ownerLabel}`, "Use a retained object literal with statically named zero-argument slot functions");
|
|
360
|
+
return object;
|
|
361
|
+
}
|
|
362
|
+
/** Reads statically named zero-argument functions from one JSX slots object. */
|
|
363
|
+
function readStaticSlotObject(object, context, analysis) {
|
|
364
|
+
const slots = /* @__PURE__ */ new Map();
|
|
365
|
+
for (const property of object.node.properties) {
|
|
366
|
+
if (property.type === "SpreadElement") {
|
|
367
|
+
addVueError(context, babelLocation(property.loc), "Found a spread in a gt-vue JSX slots object", "List every static slot explicitly");
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const name = readStaticObjectKey(property, object.scope, analysis);
|
|
371
|
+
if (name === void 0) {
|
|
372
|
+
addVueError(context, babelLocation(property.loc), "Found a dynamic slot name in a gt-vue JSX slots object", "Use a static string slot name");
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (slots.has(name)) {
|
|
376
|
+
addVueError(context, babelLocation(property.loc), `Found duplicate JSX slot "${name}" in a gt-vue translation`, "Define every slot once");
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const value = property.type === "ObjectMethod" ? property : unwrapExpression(property.value);
|
|
380
|
+
if (!value || value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression" && value.type !== "ObjectMethod" || value.params.length > 0 || value.async || value.generator) {
|
|
381
|
+
addVueError(context, babelLocation(property.loc), `Found a dynamic or scoped JSX slot "${name}" in a gt-vue translation`, "Use a synchronous zero-argument function with static returned content");
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
slots.set(name, {
|
|
385
|
+
node: value,
|
|
386
|
+
scope: analysis.scopeForNode(value, object.scope)
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return slots;
|
|
390
|
+
}
|
|
391
|
+
function serializeSlotFunction(slot, counter, context, analysis) {
|
|
392
|
+
const returned = readStaticSlotReturn(slot);
|
|
393
|
+
if (!returned) {
|
|
394
|
+
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 [];
|
|
396
|
+
}
|
|
397
|
+
return serializeExpression(returned, counter, slot.scope, context, analysis);
|
|
398
|
+
}
|
|
399
|
+
/** Returns the expression from a slot body that contains only one return. */
|
|
400
|
+
function readStaticSlotReturn(slot) {
|
|
401
|
+
const body = slot.node.body;
|
|
402
|
+
if (body.type !== "BlockStatement") return body;
|
|
403
|
+
const statements = body.body.filter((statement) => statement.type !== "EmptyStatement");
|
|
404
|
+
return (statements.length === 1 && statements[0]?.type === "ReturnStatement" ? statements[0].argument : void 0) ?? void 0;
|
|
405
|
+
}
|
|
406
|
+
function readStaticObjectKey(property, scope, analysis) {
|
|
407
|
+
if (!property.computed) {
|
|
408
|
+
if (property.key.type === "Identifier") return property.key.name;
|
|
409
|
+
if (property.key.type === "StringLiteral" || property.key.type === "NumericLiteral") return String(property.key.value);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const key = analysis.readStaticPrimitive(property.key, scope);
|
|
413
|
+
return key.ok && (typeof key.value === "string" || typeof key.value === "number") ? String(key.value) : void 0;
|
|
414
|
+
}
|
|
415
|
+
function readBranchAttributeSources(element, component, branches, scope, context, analysis) {
|
|
416
|
+
const seenBranchProps = /* @__PURE__ */ new Set();
|
|
417
|
+
for (const attribute of element.attributes) {
|
|
418
|
+
if (attribute.type !== "JSXAttribute") continue;
|
|
419
|
+
const name = readAttributeName(attribute.name);
|
|
420
|
+
if (!isBranchPropName(name) || component === "Plural" && !isAcceptedPluralForm(name)) continue;
|
|
421
|
+
if (seenBranchProps.has(name)) {
|
|
422
|
+
addVueError(context, babelLocation(attribute.loc), `Found duplicate branch prop "${name}" on a gt-vue <${component}> component`, "Pass each branch prop once so Vue JSX compiler options cannot change which value is hashed");
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
seenBranchProps.add(name);
|
|
426
|
+
if (Object.prototype.hasOwnProperty.call(branches, name)) continue;
|
|
427
|
+
const value = readJSXAttributePrimitive(attribute, scope, analysis);
|
|
428
|
+
if (!value.ok) {
|
|
429
|
+
if (isStaticallyNonBranchAttribute(attribute, scope)) continue;
|
|
430
|
+
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
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
branches[name] = value.value == null || typeof value.value === "boolean" ? [] : String(value.value);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function isBranchPropName(name) {
|
|
437
|
+
return !(NON_BRANCH_PROP_NAMES.has(name) || name.startsWith("aria-") || name.startsWith("data-") || /^on[^a-z]/.test(name));
|
|
438
|
+
}
|
|
439
|
+
function isStaticallyNonBranchAttribute(attribute, scope) {
|
|
440
|
+
const expression = attribute.value?.type === "JSXExpressionContainer" && attribute.value.expression.type !== "JSXEmptyExpression" ? unwrapExpression(attribute.value.expression) : void 0;
|
|
441
|
+
if (!expression) return false;
|
|
442
|
+
if (expression.type === "Identifier" && expression.name === "undefined") return !scope.hasBinding("undefined");
|
|
443
|
+
return expression.type === "ArrayExpression" || expression.type === "ArrowFunctionExpression" || expression.type === "ClassExpression" || expression.type === "FunctionExpression" || expression.type === "JSXElement" || expression.type === "JSXFragment" || expression.type === "NewExpression" || expression.type === "ObjectExpression" || expression.type === "RegExpLiteral" || expression.type === "UnaryExpression" && expression.operator === "void";
|
|
444
|
+
}
|
|
445
|
+
function hasExplicitAttribute(element, name) {
|
|
446
|
+
return element.attributes.some((attribute) => attribute.type === "JSXAttribute" && readAttributeName(attribute.name) === name);
|
|
447
|
+
}
|
|
448
|
+
function isMeaningfulJSXChild(child) {
|
|
449
|
+
if (child.type === "JSXText") return normalizeJSXText(child.value) !== "";
|
|
450
|
+
return !(child.type === "JSXExpressionContainer" && child.expression.type === "JSXEmptyExpression");
|
|
451
|
+
}
|
|
452
|
+
function validateVariableElement(element, component, context) {
|
|
453
|
+
if (context.validatedVariableComponents.has(element)) return;
|
|
454
|
+
context.validatedVariableComponents.add(element);
|
|
455
|
+
const explicitProps = /* @__PURE__ */ new Set();
|
|
456
|
+
let hasSpread = false;
|
|
457
|
+
for (const attribute of element.openingElement.attributes) if (attribute.type === "JSXSpreadAttribute") hasSpread = true;
|
|
458
|
+
else explicitProps.add(readAttributeName(attribute.name));
|
|
459
|
+
if (hasSpread) addVueError(context, babelLocation(element.openingElement.loc), `Found a spread prop on a gt-vue <${component}> component`, "Pass variable component props explicitly");
|
|
460
|
+
if (component === "Var") {
|
|
461
|
+
for (const prop of ["name", "value"]) {
|
|
462
|
+
if (!explicitProps.has(prop)) continue;
|
|
463
|
+
addVueError(context, babelLocation(element.openingElement.loc), `Found unsupported ${prop} prop on a gt-vue <Var> component`, "Pass the variable as the default child of <Var>");
|
|
464
|
+
}
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
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} />`);
|
|
468
|
+
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
|
+
}
|
|
470
|
+
function hasMeaningfulChildren(children) {
|
|
471
|
+
return children.some((child) => {
|
|
472
|
+
if (child.type === "JSXText") return normalizeJSXText(child.value) !== "";
|
|
473
|
+
return !(child.type === "JSXExpressionContainer" && child.expression.type === "JSXEmptyExpression");
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function validateElementAttributes(element, scope, context, analysis) {
|
|
477
|
+
const seenContentProps = /* @__PURE__ */ new Set();
|
|
478
|
+
for (const attribute of element.attributes) {
|
|
479
|
+
if (attribute.type === "JSXSpreadAttribute") {
|
|
480
|
+
addVueError(context, babelLocation(attribute.loc), "Found a spread prop inside a gt-vue <T> component in JSX", "Pass props by static name so their effect on the translation source is known");
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
const name = readAttributeName(attribute.name);
|
|
484
|
+
if (isVueJSXDirectiveName(name) && !isSlotsAttribute(attribute)) {
|
|
485
|
+
addVueError(context, babelLocation(attribute.loc), `Found Vue JSX directive "${name}" inside a gt-vue <T> component`, "Move the directive outside <T> so the translated VNode source stays static");
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (name === "innerHTML" || name === "textContent") {
|
|
489
|
+
addVueError(context, babelLocation(attribute.loc), `Found source-shaping prop "${name}" inside a gt-vue <T> component`, "Use static JSX children instead");
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (!Object.values(HTML_CONTENT_PROPS).includes(name)) continue;
|
|
493
|
+
if (seenContentProps.has(name)) {
|
|
494
|
+
addVueError(context, babelLocation(attribute.loc), `Found duplicate translatable prop "${name}" inside a gt-vue <T> component`, "Pass each translatable element prop once so Vue JSX compiler options cannot change which value is hashed");
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
seenContentProps.add(name);
|
|
498
|
+
if (!readJSXAttributePrimitive(attribute, scope, analysis).ok) addVueError(context, babelLocation(attribute.loc), `Found dynamic translatable prop "${name}" inside a gt-vue <T> component`, "Use a static string for translatable element props");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function isVueJSXDirectiveName(name) {
|
|
502
|
+
return /^v(?:-|[A-Z])/.test(name);
|
|
503
|
+
}
|
|
504
|
+
function readContentProps(element, scope, context, analysis) {
|
|
505
|
+
const data = {};
|
|
506
|
+
const entries = Object.entries(HTML_CONTENT_PROPS);
|
|
507
|
+
for (const attribute of element.attributes) {
|
|
508
|
+
if (attribute.type !== "JSXAttribute") continue;
|
|
509
|
+
const name = readAttributeName(attribute.name);
|
|
510
|
+
const entry = entries.find(([, propName]) => propName === name);
|
|
511
|
+
if (!entry) continue;
|
|
512
|
+
const value = readJSXAttributePrimitive(attribute, scope, analysis);
|
|
513
|
+
if (value.ok && typeof value.value === "string") data[entry[0]] = value.value;
|
|
514
|
+
else if (!value.ok) {}
|
|
515
|
+
}
|
|
516
|
+
return data;
|
|
517
|
+
}
|
|
518
|
+
function readJSXAttributePrimitive(attribute, scope, analysis) {
|
|
519
|
+
if (!attribute.value) return {
|
|
520
|
+
ok: true,
|
|
521
|
+
value: true
|
|
522
|
+
};
|
|
523
|
+
if (attribute.value.type === "StringLiteral") return {
|
|
524
|
+
ok: true,
|
|
525
|
+
value: normalizeJSXText(attribute.value.value)
|
|
526
|
+
};
|
|
527
|
+
if (attribute.value.type !== "JSXExpressionContainer" || attribute.value.expression.type === "JSXEmptyExpression") return { ok: false };
|
|
528
|
+
return analysis.readStaticPrimitive(attribute.value.expression, scope);
|
|
529
|
+
}
|
|
530
|
+
function resolveElementIdentity(name, scope, analysis) {
|
|
531
|
+
const expression = jsxNameToExpression(name);
|
|
532
|
+
return expression ? analysis.resolveKnownValue(expression, scope) : void 0;
|
|
533
|
+
}
|
|
534
|
+
function jsxNameToExpression(name) {
|
|
535
|
+
if (name.type === "JSXIdentifier") return babel.identifier(name.name);
|
|
536
|
+
if (name.type === "JSXNamespacedName") return void 0;
|
|
537
|
+
const object = jsxMemberObjectToExpression(name.object);
|
|
538
|
+
return object ? babel.memberExpression(object, babel.identifier(name.property.name)) : void 0;
|
|
539
|
+
}
|
|
540
|
+
function jsxMemberObjectToExpression(object) {
|
|
541
|
+
if (object.type === "JSXIdentifier") return babel.identifier(object.name);
|
|
542
|
+
const parent = jsxMemberObjectToExpression(object.object);
|
|
543
|
+
return parent ? babel.memberExpression(parent, babel.identifier(object.property.name)) : void 0;
|
|
544
|
+
}
|
|
545
|
+
function isLiteralFragment(name) {
|
|
546
|
+
return name.type === "JSXIdentifier" && name.name === "Fragment";
|
|
547
|
+
}
|
|
548
|
+
/** Matches only Fragment spellings the Vue JSX transform keeps transparent. */
|
|
549
|
+
function isTransparentFragment(name, identity) {
|
|
550
|
+
return isLiteralFragment(name) || identity?.type === "vue-builtin" && identity.name === "Fragment" && name.type === "JSXMemberExpression" && name.property.name === "Fragment";
|
|
551
|
+
}
|
|
552
|
+
/** Detects tags whose VNode type can change with plugin `isCustomElement`. */
|
|
553
|
+
function isUnboundNonNativeElement(name, scope) {
|
|
554
|
+
if (name.type === "JSXIdentifier") return name.name !== "Fragment" && !isNativeElement(name) && !scope.hasBinding(name.name);
|
|
555
|
+
if (name.type === "JSXNamespacedName") return true;
|
|
556
|
+
let object = name.object;
|
|
557
|
+
while (object.type === "JSXMemberExpression") object = object.object;
|
|
558
|
+
return !scope.hasBinding(object.name);
|
|
559
|
+
}
|
|
560
|
+
function isNativeElement(name) {
|
|
561
|
+
return name.type === "JSXIdentifier" && (isHTMLTag(name.name) || isSVGTag(name.name));
|
|
562
|
+
}
|
|
563
|
+
/** Proves whether a non-GT JSX tag is traversable or slot-opaque. */
|
|
564
|
+
function resolveOrdinaryElementShape(name, scope, analysis, seen) {
|
|
565
|
+
if (isNativeElement(name)) return {
|
|
566
|
+
opaque: false,
|
|
567
|
+
tag: name.name
|
|
568
|
+
};
|
|
569
|
+
if (name.type === "JSXNamespacedName") return void 0;
|
|
570
|
+
if (name.type === "JSXMemberExpression") {
|
|
571
|
+
let root = name.object;
|
|
572
|
+
while (root.type === "JSXMemberExpression") root = root.object;
|
|
573
|
+
const binding = scope.getBinding(root.name);
|
|
574
|
+
return binding?.path.isImportSpecifier() || binding?.path.isImportDefaultSpecifier() || binding?.path.isImportNamespaceSpecifier() ? { opaque: true } : void 0;
|
|
575
|
+
}
|
|
576
|
+
const binding = scope.getBinding(name.name);
|
|
577
|
+
if (!binding) return void 0;
|
|
578
|
+
if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier() || binding.path.isImportNamespaceSpecifier() || binding.path.isFunctionDeclaration() || binding.path.isClassDeclaration()) return { opaque: true };
|
|
579
|
+
const declaration = binding.path.node;
|
|
580
|
+
return declaration.type === "VariableDeclarator" && declaration.init ? resolveOrdinaryElementExpression(declaration.init, binding.path.scope, analysis, seen) : void 0;
|
|
581
|
+
}
|
|
582
|
+
/** Classifies a retained tag expression without executing application code. */
|
|
583
|
+
function resolveOrdinaryElementExpression(input, scope, analysis, seen) {
|
|
584
|
+
const expression = unwrapExpression(input);
|
|
585
|
+
if (!expression || seen.has(expression)) return void 0;
|
|
586
|
+
const nextSeen = new Set(seen);
|
|
587
|
+
nextSeen.add(expression);
|
|
588
|
+
const primitive = analysis.readStaticPrimitive(expression, scope);
|
|
589
|
+
if (primitive.ok) return typeof primitive.value === "string" ? {
|
|
590
|
+
opaque: false,
|
|
591
|
+
tag: primitive.value
|
|
592
|
+
} : void 0;
|
|
593
|
+
if (expression.type === "ArrowFunctionExpression" || expression.type === "FunctionExpression" || expression.type === "ClassExpression" || expression.type === "ObjectExpression") return { opaque: true };
|
|
594
|
+
if (expression.type === "Identifier") {
|
|
595
|
+
const binding = scope.getBinding(expression.name);
|
|
596
|
+
if (!binding) return void 0;
|
|
597
|
+
if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier() || binding.path.isImportNamespaceSpecifier() || binding.path.isFunctionDeclaration() || binding.path.isClassDeclaration()) return { opaque: true };
|
|
598
|
+
const declaration = binding.path.node;
|
|
599
|
+
return declaration.type === "VariableDeclarator" && declaration.init ? resolveOrdinaryElementExpression(declaration.init, binding.path.scope, analysis, nextSeen) : void 0;
|
|
600
|
+
}
|
|
601
|
+
if (expression.type === "CallExpression" || expression.type === "OptionalCallExpression") {
|
|
602
|
+
if (expression.callee.type === "V8IntrinsicIdentifier") return void 0;
|
|
603
|
+
const callee = analysis.resolveKnownValue(expression.callee, scope);
|
|
604
|
+
if (callee?.type === "defineComponent") return { opaque: true };
|
|
605
|
+
const argument = expression.arguments[0];
|
|
606
|
+
return callee?.type === "identity" && expression.arguments.length === 1 && argument && argument.type !== "SpreadElement" && argument.type !== "ArgumentPlaceholder" ? resolveOrdinaryElementExpression(argument, scope, analysis, nextSeen) : void 0;
|
|
607
|
+
}
|
|
608
|
+
if (expression.type === "ConditionalExpression") return mergeOrdinaryElementShapes(resolveOrdinaryElementExpression(expression.consequent, scope, analysis, nextSeen), resolveOrdinaryElementExpression(expression.alternate, scope, analysis, nextSeen));
|
|
609
|
+
if (expression.type === "LogicalExpression") return mergeOrdinaryElementShapes(resolveOrdinaryElementExpression(expression.left, scope, analysis, nextSeen), resolveOrdinaryElementExpression(expression.right, scope, analysis, nextSeen));
|
|
610
|
+
if (expression.type === "SequenceExpression") {
|
|
611
|
+
const last = expression.expressions.at(-1);
|
|
612
|
+
return last ? resolveOrdinaryElementExpression(last, scope, analysis, nextSeen) : void 0;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/** Combines alternate tags only when they preserve traversal semantics. */
|
|
616
|
+
function mergeOrdinaryElementShapes(left, right) {
|
|
617
|
+
if (!left || !right || left.opaque !== right.opaque) return void 0;
|
|
618
|
+
return {
|
|
619
|
+
opaque: left.opaque,
|
|
620
|
+
...left.tag === right.tag && left.tag !== void 0 && { tag: left.tag }
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function readElementDisplayName(name) {
|
|
624
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
625
|
+
if (name.type === "JSXNamespacedName") return void 0;
|
|
626
|
+
const object = readMemberDisplayName(name.object);
|
|
627
|
+
return object ? `${object}.${name.property.name}` : void 0;
|
|
628
|
+
}
|
|
629
|
+
function readMemberDisplayName(object) {
|
|
630
|
+
if (object.type === "JSXIdentifier") return object.name;
|
|
631
|
+
const parent = readMemberDisplayName(object.object);
|
|
632
|
+
return parent ? `${parent}.${object.property.name}` : void 0;
|
|
633
|
+
}
|
|
634
|
+
function readAttributeName(name) {
|
|
635
|
+
return name.type === "JSXIdentifier" ? name.name : `${name.namespace.name}:${name.name.name}`;
|
|
636
|
+
}
|
|
637
|
+
/** Mirrors `@vue/babel-plugin-jsx`'s JSX text and string-prop transform. */
|
|
638
|
+
function normalizeJSXText(text) {
|
|
639
|
+
const lines = text.split(/\r\n|\n|\r/);
|
|
640
|
+
let lastNonEmptyLine = 0;
|
|
641
|
+
for (let index = 0; index < lines.length; index += 1) if (/[^ \t]/.test(lines[index])) lastNonEmptyLine = index;
|
|
642
|
+
let result = "";
|
|
643
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
644
|
+
const isFirstLine = index === 0;
|
|
645
|
+
const isLastLine = index === lines.length - 1;
|
|
646
|
+
const isLastNonEmptyLine = index === lastNonEmptyLine;
|
|
647
|
+
let line = lines[index].replace(/\t/g, " ");
|
|
648
|
+
if (!isFirstLine) line = line.replace(/^ +/, "");
|
|
649
|
+
if (!isLastLine) line = line.replace(/ +$/, "");
|
|
650
|
+
if (!line) continue;
|
|
651
|
+
if (!isLastNonEmptyLine) line += " ";
|
|
652
|
+
result += line;
|
|
653
|
+
}
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
function appendSerializedChild(result, child) {
|
|
657
|
+
const previous = result.at(-1);
|
|
658
|
+
if (typeof previous === "string" && typeof child === "string") result[result.length - 1] = previous + child;
|
|
659
|
+
else result.push(child);
|
|
660
|
+
}
|
|
661
|
+
function collapseChildren(children) {
|
|
662
|
+
return children.length === 1 ? children[0] : children;
|
|
663
|
+
}
|
|
664
|
+
//#endregion
|
|
665
|
+
export { extractVueJSXTranslation, isNestedInVueJSXTranslation, resolveVueJSXElementIdentity, validateVueJSXVariableComponent };
|
|
666
|
+
|
|
667
|
+
//# sourceMappingURL=jsx.js.map
|