@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,2198 @@
|
|
|
1
|
+
import { addVueError, createInlineMetadata, readStaticPrimitive, unwrapExpression } from "./utils.js";
|
|
2
|
+
import { processVueStringCall } from "./stringCalls.js";
|
|
3
|
+
import { appendTemplatePath, recursiveTemplatePathSegment, unknownTemplatePathSegment } from "./templatePath.js";
|
|
4
|
+
import { ElementTypes, NodeTypes } from "./compilerAst.js";
|
|
5
|
+
import { parseExpression } from "@babel/parser";
|
|
6
|
+
import traverseModule from "@babel/traverse";
|
|
7
|
+
import { isAcceptedPluralForm } from "generaltranslation/internal";
|
|
8
|
+
import * as babel from "@babel/types";
|
|
9
|
+
import { HTML_CONTENT_PROPS } from "@generaltranslation/format/types";
|
|
10
|
+
//#region src/internal/template.ts
|
|
11
|
+
const traverse = traverseModule.default || traverseModule;
|
|
12
|
+
const COMPONENT_SELECTING_ARRAY_METHODS = new Set([
|
|
13
|
+
"at",
|
|
14
|
+
"find",
|
|
15
|
+
"findLast",
|
|
16
|
+
"pop",
|
|
17
|
+
"shift"
|
|
18
|
+
]);
|
|
19
|
+
const COMPONENT_PRESERVING_ARRAY_METHODS = new Set([
|
|
20
|
+
"concat",
|
|
21
|
+
"filter",
|
|
22
|
+
"reverse",
|
|
23
|
+
"slice",
|
|
24
|
+
"sort",
|
|
25
|
+
"splice",
|
|
26
|
+
"toReversed",
|
|
27
|
+
"toSorted",
|
|
28
|
+
"toSpliced"
|
|
29
|
+
]);
|
|
30
|
+
const FOR_ALIAS_EXPRESSION = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
|
|
31
|
+
const FOR_ITERATOR_EXPRESSION = /,([^,}\]]*)(?:,([^,}\]]*))?$/;
|
|
32
|
+
const FOR_STRIP_PARENS = /^\(|\)$/g;
|
|
33
|
+
const NON_BRANCH_ATTRIBUTE_NAMES = new Set([
|
|
34
|
+
"branch",
|
|
35
|
+
"class",
|
|
36
|
+
"n",
|
|
37
|
+
"locales",
|
|
38
|
+
"key",
|
|
39
|
+
"ref",
|
|
40
|
+
"ref_for",
|
|
41
|
+
"ref_key",
|
|
42
|
+
"ref-for",
|
|
43
|
+
"ref-key",
|
|
44
|
+
"style"
|
|
45
|
+
]);
|
|
46
|
+
const RESERVED_T_PROPS = new Set([
|
|
47
|
+
"key",
|
|
48
|
+
"ref",
|
|
49
|
+
"ref_for",
|
|
50
|
+
"ref_key",
|
|
51
|
+
"ref-for",
|
|
52
|
+
"ref-key"
|
|
53
|
+
]);
|
|
54
|
+
const SOURCE_SHAPING_DIRECTIVES = new Set([
|
|
55
|
+
"if",
|
|
56
|
+
"else",
|
|
57
|
+
"else-if",
|
|
58
|
+
"for",
|
|
59
|
+
"html",
|
|
60
|
+
"text"
|
|
61
|
+
]);
|
|
62
|
+
function parseVueTemplate(root, bindings, expressionPlugins, context) {
|
|
63
|
+
visitTemplateChildren(root.children, /* @__PURE__ */ new Set(), bindings, expressionPlugins, context, false);
|
|
64
|
+
}
|
|
65
|
+
function visitTemplateChildren(children, shadowed, bindings, expressionPlugins, context, insideTranslation) {
|
|
66
|
+
for (const child of children) {
|
|
67
|
+
if (child.type === NodeTypes.INTERPOLATION) {
|
|
68
|
+
processTemplateExpression(child.content, shadowed, bindings, expressionPlugins, context);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (child.type !== NodeTypes.ELEMENT) continue;
|
|
72
|
+
const localBindings = collectElementScopeBindings(child, expressionPlugins);
|
|
73
|
+
const possibleScopedComponents = collectPossibleScopedComponentAliases(child, bindings, shadowed, expressionPlugins);
|
|
74
|
+
const possibleScopedContainers = collectPossibleScopedContainerAliases(child, bindings, shadowed, expressionPlugins);
|
|
75
|
+
const componentBindings = possibleScopedComponents.size > 0 ? createScopedComponentBindings(bindings, possibleScopedComponents) : bindings;
|
|
76
|
+
const childBindings = possibleScopedContainers.size > 0 ? createScopedContainerBindings(componentBindings, possibleScopedContainers, shadowed) : componentBindings;
|
|
77
|
+
const childShadowed = unionSets(shadowed, localBindings);
|
|
78
|
+
for (const localName of [...possibleScopedComponents, ...possibleScopedContainers.keys()]) childShadowed.delete(localName);
|
|
79
|
+
for (const property of child.props) {
|
|
80
|
+
if (property.type !== NodeTypes.DIRECTIVE) continue;
|
|
81
|
+
if (property.name === "for") {
|
|
82
|
+
const parseResult = readForParseResult(property);
|
|
83
|
+
for (const alias of [
|
|
84
|
+
parseResult?.value,
|
|
85
|
+
parseResult?.key,
|
|
86
|
+
parseResult?.index
|
|
87
|
+
]) processBindingDefaults(alias, childShadowed, childBindings, expressionPlugins, context);
|
|
88
|
+
} else if (property.name === "slot") processBindingDefaults(property.exp, childShadowed, childBindings, expressionPlugins, context);
|
|
89
|
+
}
|
|
90
|
+
for (const property of child.props) {
|
|
91
|
+
if (property.type !== NodeTypes.DIRECTIVE) continue;
|
|
92
|
+
if (property.arg?.type === NodeTypes.SIMPLE_EXPRESSION && !property.arg.isStatic) processTemplateExpression(property.arg, childShadowed, childBindings, expressionPlugins, context);
|
|
93
|
+
if (property.name === "slot" || !property.exp) continue;
|
|
94
|
+
if (property.name === "for") {
|
|
95
|
+
const parseResult = readForParseResult(property);
|
|
96
|
+
if (parseResult?.source) processTemplateExpression(parseResult.source, shadowed, bindings, expressionPlugins, context);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
processTemplateExpression(property.exp, property.name === "if" || property.name === "else-if" ? shadowed : childShadowed, property.name === "if" || property.name === "else-if" ? bindings : childBindings, expressionPlugins, context);
|
|
100
|
+
}
|
|
101
|
+
const component = resolveGTComponent(child, childBindings, expressionPlugins, childShadowed);
|
|
102
|
+
const suspense = resolveSuspenseComponent(child, childBindings, expressionPlugins, childShadowed);
|
|
103
|
+
const fragment = resolveFragmentComponent(child, childBindings, expressionPlugins, childShadowed);
|
|
104
|
+
const uncertainGTComponent = resolveUncertainComponentBinding(child, childBindings, childBindings.uncertainGTComponents, childBindings.uncertainRegisteredGTComponents, childBindings.gtComponentFactories, expressionPlugins, childShadowed);
|
|
105
|
+
const possibleDynamicT = component ? void 0 : resolvePossibleDynamicT(child, childBindings, expressionPlugins, childShadowed);
|
|
106
|
+
const unresolvedGTComponent = uncertainGTComponent ?? possibleDynamicT;
|
|
107
|
+
if (!insideTranslation && !component && unresolvedGTComponent) addVueError(context, child.loc, `Could not statically resolve possible gt-vue component alias "${unresolvedGTComponent}"`, "Use a direct gt-vue import or an immutable alias that does not escape static analysis");
|
|
108
|
+
if (component?.originalName === "Var" || component?.originalName === "Num" || component?.originalName === "DateTime" || component?.originalName === "Currency") validateVariableComponentShape(child, component.originalName, context);
|
|
109
|
+
if (component?.originalName === "T" && !insideTranslation) extractTranslationComponent(child, childShadowed, childBindings, expressionPlugins, context);
|
|
110
|
+
const childInsideTranslation = component?.originalName === "Var" || insideTranslation && isOpaqueComponent(child, component, suspense ?? fragment) ? false : insideTranslation || component?.originalName === "T";
|
|
111
|
+
if (childInsideTranslation && suspense) {
|
|
112
|
+
const suspenseElementIsFallback = hasStaticSlotName(child, "fallback");
|
|
113
|
+
for (const suspenseChild of child.children) visitTemplateChildren([suspenseChild], childShadowed, childBindings, expressionPlugins, context, suspenseElementIsFallback ? false : !isSuspenseFallbackTemplate(suspenseChild));
|
|
114
|
+
} else visitTemplateChildren(child.children, childShadowed, childBindings, expressionPlugins, context, childInsideTranslation);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Finds local Vue aliases whose runtime value can be a GT component. */
|
|
118
|
+
function collectPossibleScopedComponentAliases(element, bindings, shadowed, expressionPlugins) {
|
|
119
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
120
|
+
for (const property of element.props) {
|
|
121
|
+
if (property.type !== NodeTypes.DIRECTIVE) continue;
|
|
122
|
+
if (property.name === "for") {
|
|
123
|
+
const parseResult = readForParseResult(property);
|
|
124
|
+
const pattern = parseResult?.value ? parseTemplateBindingPattern(parseResult.value, expressionPlugins) : void 0;
|
|
125
|
+
const source = parseResult?.source ? getExpressionNode(parseResult.source, expressionPlugins) : void 0;
|
|
126
|
+
if (!pattern || !source) continue;
|
|
127
|
+
collectPossiblePatternComponents(pattern, collectTemplateContainerCandidates(source, bindings, shadowed, /* @__PURE__ */ new Set()), bindings, shadowed, aliases);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (property.name !== "slot" || property.exp?.type !== NodeTypes.SIMPLE_EXPRESSION || !property.exp.content) continue;
|
|
131
|
+
try {
|
|
132
|
+
const arrow = parseExpression(`(${property.exp.content}) => 0`, { plugins: expressionPlugins });
|
|
133
|
+
if (arrow.type !== "ArrowFunctionExpression") continue;
|
|
134
|
+
for (const parameter of arrow.params) collectPossibleComponentDefaults(parameter, bindings, shadowed, aliases);
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
return aliases;
|
|
138
|
+
}
|
|
139
|
+
/** Collects v-for aliases whose runtime value can itself be a container. */
|
|
140
|
+
function collectPossibleScopedContainerAliases(element, bindings, shadowed, expressionPlugins) {
|
|
141
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
142
|
+
for (const property of element.props) {
|
|
143
|
+
if (property.type !== NodeTypes.DIRECTIVE || property.name !== "for") continue;
|
|
144
|
+
const parseResult = readForParseResult(property);
|
|
145
|
+
const pattern = parseResult?.value ? parseTemplateBindingPattern(parseResult.value, expressionPlugins) : void 0;
|
|
146
|
+
const source = parseResult?.source ? getExpressionNode(parseResult.source, expressionPlugins) : void 0;
|
|
147
|
+
if (!pattern || !source) continue;
|
|
148
|
+
collectPossiblePatternContainers(pattern, collectTemplateContainerCandidates(source, bindings, shadowed, /* @__PURE__ */ new Set()), bindings, shadowed, aliases);
|
|
149
|
+
}
|
|
150
|
+
return aliases;
|
|
151
|
+
}
|
|
152
|
+
/** Projects container alternatives through one v-for binding pattern. */
|
|
153
|
+
function collectPossiblePatternContainers(pattern, values, bindings, shadowed, aliases) {
|
|
154
|
+
if (pattern.type === "Identifier") {
|
|
155
|
+
if (values.containers.length === 0) return;
|
|
156
|
+
const existing = aliases.get(pattern.name);
|
|
157
|
+
aliases.set(pattern.name, existing ? mergeDynamicSelectorAnalyses([existing, values]) : values);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (pattern.type === "AssignmentPattern") {
|
|
161
|
+
if (!isTemplateBindingPattern(pattern.left)) return;
|
|
162
|
+
collectPossiblePatternContainers(pattern.left, values, bindings, shadowed, aliases);
|
|
163
|
+
collectPossiblePatternContainers(pattern.left, collectDynamicSelectorCandidates(pattern.right, bindings, shadowed, /* @__PURE__ */ new Set()), bindings, shadowed, aliases);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (pattern.type === "RestElement") {
|
|
167
|
+
if (isTemplateBindingPattern(pattern.argument)) collectPossiblePatternContainers(pattern.argument, values, bindings, shadowed, aliases);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (pattern.type === "ObjectPattern") {
|
|
171
|
+
for (const property of pattern.properties) {
|
|
172
|
+
const target = property.type === "RestElement" ? property.argument : property.value;
|
|
173
|
+
if (!isTemplateBindingPattern(target)) continue;
|
|
174
|
+
collectPossiblePatternContainers(target, property.type === "RestElement" ? values : selectPossibleContainerChildren(values, readTemplateLiteralPropertyKey(property, bindings, shadowed), bindings, shadowed), bindings, shadowed, aliases);
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
pattern.elements.forEach((element, index) => {
|
|
179
|
+
if (!element || !isTemplateBindingPattern(element)) return;
|
|
180
|
+
collectPossiblePatternContainers(element, selectPossibleContainerChildren(values, element.type === "RestElement" ? void 0 : String(index), bindings, shadowed), bindings, shadowed, aliases);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** Parses a Vue alias expression as one JavaScript binding pattern. */
|
|
184
|
+
function parseTemplateBindingPattern(expression, expressionPlugins) {
|
|
185
|
+
try {
|
|
186
|
+
const arrow = parseExpression(`(${expression.content}) => 0`, { plugins: expressionPlugins });
|
|
187
|
+
const parameter = arrow.type === "ArrowFunctionExpression" && arrow.params.length === 1 ? arrow.params[0] : void 0;
|
|
188
|
+
return parameter && isTemplateBindingPattern(parameter) ? parameter : void 0;
|
|
189
|
+
} catch {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Projects possible iterable values through a v-for destructuring pattern. */
|
|
194
|
+
function collectPossiblePatternComponents(pattern, values, bindings, shadowed, aliases) {
|
|
195
|
+
if (pattern.type === "Identifier") {
|
|
196
|
+
if (selectorAnalysisMayContainGT(values, bindings)) aliases.add(pattern.name);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (pattern.type === "AssignmentPattern") {
|
|
200
|
+
if (!isTemplateBindingPattern(pattern.left)) return;
|
|
201
|
+
collectPossiblePatternComponents(pattern.left, values, bindings, shadowed, aliases);
|
|
202
|
+
const fallback = collectDynamicSelectorCandidates(pattern.right, bindings, shadowed, /* @__PURE__ */ new Set());
|
|
203
|
+
collectPossiblePatternComponents(pattern.left, fallback, bindings, shadowed, aliases);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (pattern.type === "RestElement") {
|
|
207
|
+
if (!isTemplateBindingPattern(pattern.argument)) return;
|
|
208
|
+
collectPossiblePatternComponents(pattern.argument, values, bindings, shadowed, aliases);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (pattern.type === "ObjectPattern") {
|
|
212
|
+
for (const property of pattern.properties) {
|
|
213
|
+
if (property.type === "RestElement") {
|
|
214
|
+
if (isTemplateBindingPattern(property.argument)) collectPossiblePatternComponents(property.argument, values, bindings, shadowed, aliases);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const selected = selectPossibleContainerChildren(values, readTemplateLiteralPropertyKey(property, bindings, shadowed), bindings, shadowed);
|
|
218
|
+
if (isTemplateBindingPattern(property.value)) collectPossiblePatternComponents(property.value, selected, bindings, shadowed, aliases);
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (pattern.type === "ArrayPattern") pattern.elements.forEach((element, index) => {
|
|
223
|
+
if (!element) return;
|
|
224
|
+
const selected = selectPossibleContainerChildren(values, element.type === "RestElement" ? void 0 : String(index), bindings, shadowed);
|
|
225
|
+
if (isTemplateBindingPattern(element)) collectPossiblePatternComponents(element, selected, bindings, shadowed, aliases);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function isTemplateBindingPattern(node) {
|
|
229
|
+
return node.type === "ArrayPattern" || node.type === "AssignmentPattern" || node.type === "Identifier" || node.type === "ObjectPattern" || node.type === "RestElement";
|
|
230
|
+
}
|
|
231
|
+
/** Selects from every possible container carried by an abstract value. */
|
|
232
|
+
function selectPossibleContainerChildren(values, key, bindings, shadowed) {
|
|
233
|
+
const selected = values.containers.map((container) => selectTemplateContainerReference(container, key, bindings, shadowed, /* @__PURE__ */ new Set()));
|
|
234
|
+
return selected.length > 0 ? mergeDynamicSelectorAnalyses(selected) : emptyDynamicSelectorAnalysis(true);
|
|
235
|
+
}
|
|
236
|
+
/** Walks one binding pattern and records GT-bearing default expressions. */
|
|
237
|
+
function collectPossibleComponentDefaults(pattern, bindings, shadowed, aliases) {
|
|
238
|
+
if (pattern.type === "AssignmentPattern") {
|
|
239
|
+
if (pattern.left.type === "Identifier") {
|
|
240
|
+
if (selectorAnalysisMayContainGT(collectDynamicSelectorCandidates(pattern.right, bindings, shadowed, /* @__PURE__ */ new Set()), bindings)) aliases.add(pattern.left.name);
|
|
241
|
+
}
|
|
242
|
+
collectPossibleComponentDefaults(pattern.left, bindings, shadowed, aliases);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (pattern.type === "ObjectPattern") {
|
|
246
|
+
for (const property of pattern.properties) collectPossibleComponentDefaults(property.type === "RestElement" ? property.argument : property.value, bindings, shadowed, aliases);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (pattern.type === "ArrayPattern") {
|
|
250
|
+
for (const element of pattern.elements) if (element) collectPossibleComponentDefaults(element, bindings, shadowed, aliases);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (pattern.type === "RestElement") collectPossibleComponentDefaults(pattern.argument, bindings, shadowed, aliases);
|
|
254
|
+
}
|
|
255
|
+
/** Returns whether an abstract selector value can resolve to any GT component. */
|
|
256
|
+
function selectorAnalysisMayContainGT(analysis, bindings) {
|
|
257
|
+
if (analysis.possibleGT || analysis.gtComponentFactories.size > 0) return true;
|
|
258
|
+
return analysis.candidates.some((candidate) => {
|
|
259
|
+
if (candidate.kind === "expression") return bindings.components.get(candidate.name) === "T" || bindings.uncertainGTComponents.has(candidate.name);
|
|
260
|
+
return [...normalizeTemplateBindingNames(candidate.name)].some((name) => bindings.registeredComponents.get(name) === "T");
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/** Shadows program bindings with one local alias of uncertain GT identity. */
|
|
264
|
+
function createScopedComponentBindings(bindings, aliases) {
|
|
265
|
+
const scoped = {
|
|
266
|
+
...bindings,
|
|
267
|
+
arrayLengths: new Map(bindings.arrayLengths),
|
|
268
|
+
componentFactories: new Set(bindings.componentFactories),
|
|
269
|
+
components: new Map(bindings.components),
|
|
270
|
+
containerKinds: new Map(bindings.containerKinds),
|
|
271
|
+
possibleGTContainers: new Set(bindings.possibleGTContainers),
|
|
272
|
+
gtContainerFactories: new Set(bindings.gtContainerFactories),
|
|
273
|
+
directBindings: new Set(bindings.directBindings),
|
|
274
|
+
gtComponentFactories: new Set(bindings.gtComponentFactories),
|
|
275
|
+
identityFunctions: new Set(bindings.identityFunctions),
|
|
276
|
+
possibleStaticStrings: new Map([...bindings.possibleStaticStrings].map(([name, values]) => [name, new Set(values)])),
|
|
277
|
+
staticValues: new Map(bindings.staticValues),
|
|
278
|
+
stringFunctions: new Map(bindings.stringFunctions),
|
|
279
|
+
uncertainComponents: new Set(bindings.uncertainComponents),
|
|
280
|
+
uncertainGTComponents: new Set(bindings.uncertainGTComponents),
|
|
281
|
+
uncertainStringFunctions: new Set(bindings.uncertainStringFunctions),
|
|
282
|
+
vueBuiltins: new Map(bindings.vueBuiltins)
|
|
283
|
+
};
|
|
284
|
+
for (const alias of aliases) {
|
|
285
|
+
clearScopedPath(scoped.arrayLengths, alias);
|
|
286
|
+
clearScopedPath(scoped.componentFactories, alias);
|
|
287
|
+
clearScopedPath(scoped.components, alias);
|
|
288
|
+
clearScopedPath(scoped.containerKinds, alias);
|
|
289
|
+
clearScopedPath(scoped.possibleGTContainers, alias);
|
|
290
|
+
clearScopedPath(scoped.gtContainerFactories, alias);
|
|
291
|
+
clearScopedPath(scoped.gtComponentFactories, alias);
|
|
292
|
+
clearScopedPath(scoped.identityFunctions, alias);
|
|
293
|
+
clearScopedPath(scoped.possibleStaticStrings, alias);
|
|
294
|
+
clearScopedPath(scoped.staticValues, alias);
|
|
295
|
+
clearScopedPath(scoped.stringFunctions, alias);
|
|
296
|
+
clearScopedPath(scoped.uncertainComponents, alias);
|
|
297
|
+
clearScopedPath(scoped.uncertainGTComponents, alias);
|
|
298
|
+
clearScopedPath(scoped.uncertainStringFunctions, alias);
|
|
299
|
+
clearScopedPath(scoped.vueBuiltins, alias);
|
|
300
|
+
scoped.directBindings.add(alias);
|
|
301
|
+
scoped.uncertainComponents.add(alias);
|
|
302
|
+
scoped.uncertainGTComponents.add(alias);
|
|
303
|
+
}
|
|
304
|
+
return scoped;
|
|
305
|
+
}
|
|
306
|
+
/** Exposes container-valued v-for aliases to nested template scopes. */
|
|
307
|
+
function createScopedContainerBindings(bindings, aliases, shadowed) {
|
|
308
|
+
const scoped = createScopedComponentBindings(bindings, /* @__PURE__ */ new Set());
|
|
309
|
+
for (const [alias, values] of aliases) {
|
|
310
|
+
clearScopedPath(scoped.arrayLengths, alias);
|
|
311
|
+
clearScopedPath(scoped.componentFactories, alias);
|
|
312
|
+
clearScopedPath(scoped.components, alias);
|
|
313
|
+
clearScopedPath(scoped.containerKinds, alias);
|
|
314
|
+
clearScopedPath(scoped.possibleGTContainers, alias);
|
|
315
|
+
clearScopedPath(scoped.gtContainerFactories, alias);
|
|
316
|
+
clearScopedPath(scoped.gtComponentFactories, alias);
|
|
317
|
+
clearScopedPath(scoped.identityFunctions, alias);
|
|
318
|
+
clearScopedPath(scoped.possibleStaticStrings, alias);
|
|
319
|
+
clearScopedPath(scoped.staticValues, alias);
|
|
320
|
+
clearScopedPath(scoped.stringFunctions, alias);
|
|
321
|
+
clearScopedPath(scoped.uncertainComponents, alias);
|
|
322
|
+
clearScopedPath(scoped.uncertainGTComponents, alias);
|
|
323
|
+
clearScopedPath(scoped.uncertainStringFunctions, alias);
|
|
324
|
+
clearScopedPath(scoped.vueBuiltins, alias);
|
|
325
|
+
scoped.directBindings.add(alias);
|
|
326
|
+
const expose = (container, path, depth) => {
|
|
327
|
+
if (depth > 32) return;
|
|
328
|
+
const kind = container.kind === "literal" ? container.node.type === "ArrayExpression" ? "array" : "object" : container.kind === "analysis" ? container.containerKind : scoped.containerKinds.get(container.path);
|
|
329
|
+
if (kind) scoped.containerKinds.set(path, kind);
|
|
330
|
+
const children = selectTemplateContainerReference(container, void 0, scoped, shadowed, /* @__PURE__ */ new Set());
|
|
331
|
+
if (selectorAnalysisMayContainGT(children, scoped)) scoped.possibleGTContainers.add(path);
|
|
332
|
+
const childPath = appendTemplatePath(path, unknownTemplatePathSegment);
|
|
333
|
+
for (const child of children.containers) expose(child, childPath, depth + 1);
|
|
334
|
+
};
|
|
335
|
+
for (const container of values.containers) expose(container, alias, 0);
|
|
336
|
+
}
|
|
337
|
+
return scoped;
|
|
338
|
+
}
|
|
339
|
+
function clearScopedPath(collection, path) {
|
|
340
|
+
for (const key of collection.keys()) if (key === path || key.startsWith(`${path}.`)) collection.delete(key);
|
|
341
|
+
}
|
|
342
|
+
function processTemplateExpression(expression, shadowed, bindings, expressionPlugins, context) {
|
|
343
|
+
const expressionNode = getExpressionNode(expression, expressionPlugins);
|
|
344
|
+
if (!expressionNode) return;
|
|
345
|
+
processTemplateExpressionNode(expressionNode, expression.loc, shadowed, bindings, context);
|
|
346
|
+
}
|
|
347
|
+
function processTemplateExpressionNode(expression, location, shadowed, bindings, context) {
|
|
348
|
+
const file = wrapForTraversal(expression);
|
|
349
|
+
if (!file) return;
|
|
350
|
+
traverse(file, {
|
|
351
|
+
CallExpression(path) {
|
|
352
|
+
const kind = resolveTemplateStringFunction(path.node.callee, path.scope, shadowed, bindings);
|
|
353
|
+
if (!kind) {
|
|
354
|
+
reportPossibleTemplateStringFunction(path.node.callee, path.scope, location, shadowed, bindings, context);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
processVueStringCall(path.node, kind, location, context, (node) => readTemplatePrimitive(node, path.scope, shadowed, bindings));
|
|
358
|
+
},
|
|
359
|
+
OptionalCallExpression(path) {
|
|
360
|
+
const kind = resolveTemplateStringFunction(path.node.callee, path.scope, shadowed, bindings);
|
|
361
|
+
if (!kind) {
|
|
362
|
+
reportPossibleTemplateStringFunction(path.node.callee, path.scope, location, shadowed, bindings, context);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
processVueStringCall(path.node, kind, location, context, (node) => readTemplatePrimitive(node, path.scope, shadowed, bindings));
|
|
366
|
+
},
|
|
367
|
+
TaggedTemplateExpression(path) {
|
|
368
|
+
if (!resolveTemplateStringFunction(path.node.tag, path.scope, shadowed, bindings)) {
|
|
369
|
+
reportPossibleTemplateStringFunction(path.node.tag, path.scope, location, shadowed, bindings, context);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
addVueError(context, location, "Found an unsupported tagged template translation in gt-vue", "Call the translation function with a string literal instead");
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/** Reports a call whose binding may be a gt-vue string translator. */
|
|
377
|
+
function reportPossibleTemplateStringFunction(input, scope, location, shadowed, bindings, context) {
|
|
378
|
+
const displayName = resolvePossibleTemplateStringFunction(input, scope, shadowed, bindings);
|
|
379
|
+
if (!displayName) return;
|
|
380
|
+
addVueError(context, location, `Could not statically resolve possible gt-vue translation function alias "${displayName}"`, "Use a direct gt-vue import or an immutable translator alias that does not escape static analysis");
|
|
381
|
+
}
|
|
382
|
+
/** Resolves direct or dynamically selected uncertain translator paths. */
|
|
383
|
+
function resolvePossibleTemplateStringFunction(input, scope, shadowed, bindings) {
|
|
384
|
+
const node = unwrapExpression(input);
|
|
385
|
+
if (!node) return void 0;
|
|
386
|
+
const path = readStaticTemplateMemberPath(node, bindings, shadowed);
|
|
387
|
+
const root = path?.split(".", 1)[0];
|
|
388
|
+
if (path && root && !scope.hasBinding(root) && bindings.uncertainStringFunctions.has(path)) return path;
|
|
389
|
+
if (node.type !== "MemberExpression" && node.type !== "OptionalMemberExpression") return;
|
|
390
|
+
const objectPath = readStaticTemplateMemberPath(node.object, bindings, shadowed);
|
|
391
|
+
const objectRoot = objectPath?.split(".", 1)[0];
|
|
392
|
+
if (!objectPath || !objectRoot || scope.hasBinding(objectRoot)) return;
|
|
393
|
+
const prefix = `${objectPath}.`;
|
|
394
|
+
return [...bindings.stringFunctions.keys(), ...bindings.uncertainStringFunctions].some((name) => {
|
|
395
|
+
if (!name.startsWith(prefix)) return false;
|
|
396
|
+
return !name.slice(prefix.length).includes(".");
|
|
397
|
+
}) ? objectPath : void 0;
|
|
398
|
+
}
|
|
399
|
+
/** Reads a script-exposed primitive unless a Vue or expression scope masks it. */
|
|
400
|
+
function readTemplatePrimitive(input, scope, shadowed, bindings) {
|
|
401
|
+
return readStaticPrimitive(input, (identifier) => {
|
|
402
|
+
if (shadowed.has(identifier.name) || scope.hasBinding(identifier.name)) return { ok: false };
|
|
403
|
+
const value = bindings.staticValues.get(identifier.name);
|
|
404
|
+
return bindings.staticValues.has(identifier.name) ? {
|
|
405
|
+
ok: true,
|
|
406
|
+
value
|
|
407
|
+
} : { ok: false };
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/** Resolves a template call back to a statically imported gt-vue function. */
|
|
411
|
+
function resolveTemplateStringFunction(input, scope, shadowed, bindings) {
|
|
412
|
+
const node = unwrapExpression(input);
|
|
413
|
+
if (!node) return void 0;
|
|
414
|
+
const path = readStaticTemplateMemberPath(node, bindings, shadowed);
|
|
415
|
+
const root = path?.split(".", 1)[0];
|
|
416
|
+
return root && !scope.hasBinding(root) ? bindings.stringFunctions.get(path) : void 0;
|
|
417
|
+
}
|
|
418
|
+
/** Visits expressions that execute while Vue initializes binding patterns. */
|
|
419
|
+
function processBindingDefaults(expression, shadowed, bindings, expressionPlugins, context) {
|
|
420
|
+
if (!expression || expression.type !== NodeTypes.SIMPLE_EXPRESSION || !expression.content) return;
|
|
421
|
+
try {
|
|
422
|
+
const arrow = parseExpression(`(${expression.content}) => 0`, { plugins: expressionPlugins });
|
|
423
|
+
if (arrow.type !== "ArrowFunctionExpression") return;
|
|
424
|
+
for (const parameter of arrow.params) visitBindingDefaultExpressions(parameter, (node) => processTemplateExpressionNode(node, expression.loc, shadowed, bindings, context));
|
|
425
|
+
} catch {}
|
|
426
|
+
}
|
|
427
|
+
/** Recursively finds default and computed-key expressions in a binding. */
|
|
428
|
+
function visitBindingDefaultExpressions(pattern, visit) {
|
|
429
|
+
if (!pattern) return;
|
|
430
|
+
if (pattern.type === "AssignmentPattern") {
|
|
431
|
+
visit(pattern.right);
|
|
432
|
+
visitBindingDefaultExpressions(pattern.left, visit);
|
|
433
|
+
} else if (pattern.type === "RestElement") visitBindingDefaultExpressions(pattern.argument, visit);
|
|
434
|
+
else if (pattern.type === "ArrayPattern") {
|
|
435
|
+
for (const element of pattern.elements) if (element) visitBindingDefaultExpressions(element, visit);
|
|
436
|
+
} else if (pattern.type === "ObjectPattern") for (const property of pattern.properties) if (property.type === "RestElement") visitBindingDefaultExpressions(property.argument, visit);
|
|
437
|
+
else {
|
|
438
|
+
if (property.computed && babel.isExpression(property.key)) visit(property.key);
|
|
439
|
+
visitBindingDefaultExpressions(property.value, visit);
|
|
440
|
+
}
|
|
441
|
+
else if (pattern.type === "TSParameterProperty") visitBindingDefaultExpressions(pattern.parameter, visit);
|
|
442
|
+
}
|
|
443
|
+
function extractTranslationComponent(element, shadowed, bindings, expressionPlugins, context) {
|
|
444
|
+
const errorCount = context.errors.length;
|
|
445
|
+
const translationContext = readTContext(element, shadowed, bindings, expressionPlugins, context);
|
|
446
|
+
const slots = getSlotLayout(element, shadowed, expressionPlugins, context);
|
|
447
|
+
if (slots.namedSlots.size > 0) addVueError(context, element.loc, "Found a named slot on a gt-vue <T> component", "Place translatable content in the default slot");
|
|
448
|
+
const serialized = serializeChildren(slots.defaultSlot.children, { value: 0 }, slots.defaultSlot.shadowed, bindings, expressionPlugins, context);
|
|
449
|
+
if (context.errors.length !== errorCount) return;
|
|
450
|
+
context.results.push({
|
|
451
|
+
dataFormat: "JSX",
|
|
452
|
+
source: collapseChildren(serialized),
|
|
453
|
+
metadata: createInlineMetadata(context, element.loc, translationContext)
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function readTContext(element, shadowed, bindings, expressionPlugins, context) {
|
|
457
|
+
let translationContext;
|
|
458
|
+
let hasContext = false;
|
|
459
|
+
for (const property of element.props) {
|
|
460
|
+
if (isDynamicComponentSelector(element, property)) continue;
|
|
461
|
+
if (property.type === NodeTypes.ATTRIBUTE) {
|
|
462
|
+
if (RESERVED_T_PROPS.has(property.name)) continue;
|
|
463
|
+
if (property.name !== "context" && property.name !== "$context") {
|
|
464
|
+
addVueError(context, property.loc, `Found unsupported prop "${property.name}" on a gt-vue <T> component`, "gt-vue <T> currently supports only context");
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (hasContext) {
|
|
468
|
+
addDuplicateContextError(property.loc, context);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
hasContext = true;
|
|
472
|
+
translationContext = property.value?.content ?? "";
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (property.name !== "bind") continue;
|
|
476
|
+
if (property.modifiers.length > 0) {
|
|
477
|
+
const directive = property.rawName ?? "v-bind";
|
|
478
|
+
addVueError(context, property.loc, `Found unsupported directive ${directive} on a gt-vue <T> component`, "Pass context without a v-bind modifier");
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const key = readDirectiveKey(property);
|
|
482
|
+
if (key === void 0) {
|
|
483
|
+
addVueError(context, property.loc, "Found a dynamic or spread binding on a gt-vue <T> component", "Pass context as a static context or $context prop");
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (RESERVED_T_PROPS.has(key)) continue;
|
|
487
|
+
if (key !== "context" && key !== "$context") {
|
|
488
|
+
addVueError(context, property.loc, `Found unsupported prop "${key}" on a gt-vue <T> component`, "gt-vue <T> currently supports only context");
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (hasContext) {
|
|
492
|
+
addDuplicateContextError(property.loc, context);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
hasContext = true;
|
|
496
|
+
const value = readExpressionPrimitive(property.exp, expressionPlugins, shadowed, bindings);
|
|
497
|
+
if (!value.ok || typeof value.value !== "string") {
|
|
498
|
+
addVueError(context, property.loc, "Found a dynamic context on a gt-vue <T> component", "Use a string literal or a template literal without expressions");
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
translationContext = value.value;
|
|
502
|
+
}
|
|
503
|
+
return translationContext;
|
|
504
|
+
}
|
|
505
|
+
function addDuplicateContextError(location, context) {
|
|
506
|
+
addVueError(context, location, "Found duplicate context props on a gt-vue <T> component", "Pass only one context prop");
|
|
507
|
+
}
|
|
508
|
+
function serializeChildren(children, counter, shadowed, bindings, expressionPlugins, context) {
|
|
509
|
+
validateCommentWhitespaceParity(children, context);
|
|
510
|
+
const result = [];
|
|
511
|
+
for (const child of children) {
|
|
512
|
+
const values = serializeChild(child, counter, shadowed, bindings, expressionPlugins, context);
|
|
513
|
+
for (const value of values) appendSerializedChild(result, value);
|
|
514
|
+
}
|
|
515
|
+
return result;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Rejects comments whose removal can change Vue's surrounding whitespace.
|
|
519
|
+
*
|
|
520
|
+
* Vue keeps template comments in development and strips them in production.
|
|
521
|
+
* The compiler's whitespace transform can therefore produce different text
|
|
522
|
+
* VNodes, and consequently different persisted translation hashes, across
|
|
523
|
+
* Vue versions and build modes. Restrict this check to children that are
|
|
524
|
+
* actually serialized so comments in opaque or unused slots remain opaque.
|
|
525
|
+
*/
|
|
526
|
+
function validateCommentWhitespaceParity(children, context) {
|
|
527
|
+
for (const child of children) {
|
|
528
|
+
if (child.type !== NodeTypes.COMMENT) continue;
|
|
529
|
+
const before = context.source[child.loc.start.offset - 1];
|
|
530
|
+
const after = context.source[child.loc.end.offset];
|
|
531
|
+
if (!isHtmlWhitespace(before) && !isHtmlWhitespace(after)) continue;
|
|
532
|
+
addVueError(context, child.loc, "Found a comment adjacent to translatable whitespace inside a gt-vue <T> component", "Remove the adjacent whitespace or move the comment outside the translated content");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/** Matches the whitespace characters normalized by the Vue HTML compiler. */
|
|
536
|
+
function isHtmlWhitespace(value) {
|
|
537
|
+
return value !== void 0 && /[\t\n\f\r ]/.test(value);
|
|
538
|
+
}
|
|
539
|
+
function serializeChild(child, counter, shadowed, bindings, expressionPlugins, context) {
|
|
540
|
+
if (child.type === NodeTypes.COMMENT) return [];
|
|
541
|
+
if (child.type === NodeTypes.TEXT) return [child.content];
|
|
542
|
+
if (child.type === NodeTypes.INTERPOLATION) {
|
|
543
|
+
const value = readExpressionPrimitive(child.content, expressionPlugins, shadowed, bindings);
|
|
544
|
+
if (!value.ok) {
|
|
545
|
+
addVueError(context, child.loc, "Found dynamic template content inside a gt-vue <T> component", "Wrap runtime values in <Var>, <Num>, <DateTime>, or <Currency>");
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
return [toDisplayString(value.value)];
|
|
549
|
+
}
|
|
550
|
+
if (child.type !== NodeTypes.ELEMENT) {
|
|
551
|
+
addVueError(context, child.loc, "Found unsupported template syntax inside a gt-vue <T> component", "Use static template content and gt-vue rich translation components");
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
if (resolveFragmentComponent(child, bindings, expressionPlugins, shadowed)) return serializeFragmentElement(child, counter, shadowed, bindings, expressionPlugins, context);
|
|
555
|
+
return [serializeElement(child, counter, shadowed, bindings, expressionPlugins, context)];
|
|
556
|
+
}
|
|
557
|
+
/** Flattens an exact Vue Fragment default slot without consuming an ID. */
|
|
558
|
+
function serializeFragmentElement(element, counter, shadowed, bindings, expressionPlugins, context) {
|
|
559
|
+
validateRichElement(element, context, true);
|
|
560
|
+
const slots = getSlotLayout(element, shadowed, expressionPlugins, context);
|
|
561
|
+
if (slots.namedSlots.size > 0) addVueError(context, element.loc, "Found a named slot on Vue Fragment inside a gt-vue <T> component", "Keep Fragment content in its default slot");
|
|
562
|
+
return serializeChildren(slots.defaultSlot.children, counter, slots.defaultSlot.shadowed, bindings, expressionPlugins, context);
|
|
563
|
+
}
|
|
564
|
+
function serializeElement(element, counter, shadowed, bindings, expressionPlugins, context) {
|
|
565
|
+
counter.value += 1;
|
|
566
|
+
const id = counter.value;
|
|
567
|
+
const component = resolveGTComponent(element, bindings, expressionPlugins, shadowed);
|
|
568
|
+
const suspense = resolveSuspenseComponent(element, bindings, expressionPlugins, shadowed);
|
|
569
|
+
const uncertainComponent = !component && !suspense ? resolveUncertainComponent(element, bindings, expressionPlugins, shadowed) : void 0;
|
|
570
|
+
if (uncertainComponent) addVueError(context, element.loc, `Could not statically resolve component alias "${uncertainComponent}" inside a gt-vue <T> component`, "Use a direct component import or an immutable alias that does not escape static analysis");
|
|
571
|
+
validateRichElement(element, context, Boolean(component || suspense || uncertainComponent));
|
|
572
|
+
const originalName = component?.originalName;
|
|
573
|
+
if (originalName === "T") addVueError(context, element.loc, "Found a nested gt-vue <T> component inside another <T>", "Split nested translations into sibling <T> components");
|
|
574
|
+
if (originalName === "Var" || originalName === "Num" || originalName === "DateTime" || originalName === "Currency") {
|
|
575
|
+
validateVariableComponentShape(element, originalName, context);
|
|
576
|
+
const variable = originalName === "Num" ? {
|
|
577
|
+
name: "n",
|
|
578
|
+
type: "n"
|
|
579
|
+
} : originalName === "DateTime" ? {
|
|
580
|
+
name: "date",
|
|
581
|
+
type: "d"
|
|
582
|
+
} : originalName === "Currency" ? {
|
|
583
|
+
name: "cost",
|
|
584
|
+
type: "c"
|
|
585
|
+
} : {
|
|
586
|
+
name: "value",
|
|
587
|
+
type: "v"
|
|
588
|
+
};
|
|
589
|
+
return {
|
|
590
|
+
i: id,
|
|
591
|
+
k: `_gt_${variable.name}_${id}`,
|
|
592
|
+
v: variable.type
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const data = readContentProps(element, shadowed, bindings, expressionPlugins, context);
|
|
596
|
+
if (isOpaqueComponent(element, component, suspense)) return {
|
|
597
|
+
t: element.tag,
|
|
598
|
+
i: id,
|
|
599
|
+
...Object.keys(data).length > 0 && { d: data }
|
|
600
|
+
};
|
|
601
|
+
const slotLayout = element.tagType === ElementTypes.COMPONENT ? getSlotLayout(element, shadowed, expressionPlugins, context) : {
|
|
602
|
+
defaultSlot: {
|
|
603
|
+
children: element.children,
|
|
604
|
+
rootCount: countVueSlotRoots(element.children),
|
|
605
|
+
shadowed
|
|
606
|
+
},
|
|
607
|
+
namedSlots: /* @__PURE__ */ new Map()
|
|
608
|
+
};
|
|
609
|
+
const children = serializeChildren(slotLayout.defaultSlot.children, counter, slotLayout.defaultSlot.shadowed, bindings, expressionPlugins, context);
|
|
610
|
+
if (suspense && slotLayout.defaultSlot.rootCount > 1) addVueError(context, element.loc, "Found more than one default root inside Vue <Suspense> within a gt-vue <T> component", "Wrap the Suspense default content in a single element or move <T> inside <Suspense>");
|
|
611
|
+
if ([...slotLayout.namedSlots].filter(([name]) => !suspense || name !== "fallback").length > 0 && originalName !== "Branch" && originalName !== "Plural") addVueError(context, element.loc, `Found named slots on <${originalName ?? element.tag}> inside a gt-vue <T> component`, "Move named-slot content outside <T> or use only the component default slot");
|
|
612
|
+
if (originalName === "Branch" || originalName === "Plural") {
|
|
613
|
+
const branches = readBranches(element, slotLayout, id, originalName, shadowed, bindings, expressionPlugins, context);
|
|
614
|
+
if (Object.keys(branches).length > 0) {
|
|
615
|
+
data.b = branches;
|
|
616
|
+
data.t = originalName === "Plural" ? "p" : "b";
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
t: originalName ?? suspense?.originalName ?? element.tag,
|
|
621
|
+
i: id,
|
|
622
|
+
...Object.keys(data).length > 0 && { d: data },
|
|
623
|
+
...children.length > 0 && { c: collapseChildren(children) }
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
/** Returns true when the runtime preserves a component's slots as opaque. */
|
|
627
|
+
function isOpaqueComponent(element, component, vueBuiltin) {
|
|
628
|
+
return element.tagType === ElementTypes.COMPONENT && !component && !vueBuiltin;
|
|
629
|
+
}
|
|
630
|
+
/** Resolves literal Suspense and statically proven aliases of Vue's builtin. */
|
|
631
|
+
function resolveSuspenseComponent(element, bindings, expressionPlugins, shadowed) {
|
|
632
|
+
if (element.tagType === ElementTypes.COMPONENT && element.tag.toLowerCase() === "suspense") return {
|
|
633
|
+
localName: element.tag,
|
|
634
|
+
originalName: "Suspense"
|
|
635
|
+
};
|
|
636
|
+
const builtin = resolveVueBuiltinComponent(element, bindings, expressionPlugins, shadowed);
|
|
637
|
+
return builtin?.originalName === "Suspense" ? {
|
|
638
|
+
localName: builtin.localName,
|
|
639
|
+
originalName: "Suspense"
|
|
640
|
+
} : void 0;
|
|
641
|
+
}
|
|
642
|
+
/** Resolves an imported Vue Fragment alias without matching an ordinary tag. */
|
|
643
|
+
function resolveFragmentComponent(element, bindings, expressionPlugins, shadowed) {
|
|
644
|
+
const builtin = resolveVueBuiltinComponent(element, bindings, expressionPlugins, shadowed);
|
|
645
|
+
return builtin?.originalName === "Fragment" ? {
|
|
646
|
+
localName: builtin.localName,
|
|
647
|
+
originalName: "Fragment"
|
|
648
|
+
} : void 0;
|
|
649
|
+
}
|
|
650
|
+
/** Resolves a statically proven Vue builtin through direct or registered use. */
|
|
651
|
+
function resolveVueBuiltinComponent(element, bindings, expressionPlugins, shadowed) {
|
|
652
|
+
return resolveComponentBinding(element, bindings, bindings.vueBuiltins, bindings.registeredVueBuiltins, bindings.directBindings, expressionPlugins, shadowed);
|
|
653
|
+
}
|
|
654
|
+
/** Resolves a component-shaped binding whose exact identity is uncertain. */
|
|
655
|
+
function resolveUncertainComponent(element, bindings, expressionPlugins, shadowed) {
|
|
656
|
+
return resolveUncertainComponentBinding(element, bindings, bindings.uncertainComponents, bindings.uncertainRegisteredComponents, bindings.componentFactories, expressionPlugins, shadowed);
|
|
657
|
+
}
|
|
658
|
+
/** Resolves uncertainty while preserving direct versus registered precedence. */
|
|
659
|
+
function resolveUncertainComponentBinding(element, bindings, uncertainDirectBindings, uncertainRegisteredBindings, uncertainFactories, expressionPlugins, shadowed) {
|
|
660
|
+
if (element.tagType !== ElementTypes.COMPONENT) return void 0;
|
|
661
|
+
const selector = readDynamicComponentSelector(element, expressionPlugins, bindings, shadowed);
|
|
662
|
+
if (selector) {
|
|
663
|
+
if ([...selector.componentFactories].some((name) => uncertainFactories.has(name)) || [...selector.gtComponentFactories].some((name) => uncertainFactories.has(name))) return selector.displayName;
|
|
664
|
+
for (const candidate of selector.candidates) for (const localName of normalizeTemplateBindingNames(candidate.name)) if (candidate.kind === "string" ? uncertainRegisteredBindings.has(localName) : uncertainDirectBindings.has(localName)) return localName;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
for (const localName of normalizeTemplateBindingNames(element.tag)) {
|
|
668
|
+
const direct = uncertainDirectBindings.has(localName);
|
|
669
|
+
const registered = uncertainRegisteredBindings.has(localName);
|
|
670
|
+
if (isDirectTemplateBinding(localName, bindings.directBindings)) {
|
|
671
|
+
if (direct) return localName;
|
|
672
|
+
} else if (registered || direct) return localName;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/** Detects an unresolved dynamic selector whose possible result includes T. */
|
|
676
|
+
function resolvePossibleDynamicT(element, bindings, expressionPlugins, shadowed) {
|
|
677
|
+
const selector = readDynamicComponentSelector(element, expressionPlugins, bindings, shadowed);
|
|
678
|
+
if (!selector) return void 0;
|
|
679
|
+
if (selector.possibleGT || selector.gtComponentFactories.size > 0) return selector.displayName;
|
|
680
|
+
for (const candidate of selector.candidates) for (const localName of normalizeTemplateBindingNames(candidate.name)) if ((candidate.kind === "string" ? bindings.registeredComponents.get(localName) : bindings.components.get(localName)) === "T") return selector.displayName;
|
|
681
|
+
}
|
|
682
|
+
/** Identifies a statically named Suspense fallback slot during tree walking. */
|
|
683
|
+
function isSuspenseFallbackTemplate(child) {
|
|
684
|
+
return child.type === NodeTypes.ELEMENT && child.tag === "template" && hasStaticSlotName(child, "fallback");
|
|
685
|
+
}
|
|
686
|
+
/** Matches one statically named slot directive without emitting diagnostics. */
|
|
687
|
+
function hasStaticSlotName(element, name) {
|
|
688
|
+
const directive = element.props.find((property) => property.type === NodeTypes.DIRECTIVE && property.name === "slot");
|
|
689
|
+
return directive?.arg?.type === NodeTypes.SIMPLE_EXPRESSION && directive.arg.isStatic && directive.arg.content === name;
|
|
690
|
+
}
|
|
691
|
+
function validateRichElement(element, context, resolvedDynamicComponent) {
|
|
692
|
+
if (element.tagType === ElementTypes.SLOT || element.tag === "slot") addVueError(context, element.loc, "Found a <slot> inside a gt-vue <T> component", "Move runtime slot content outside <T> or wrap a stable value in <Var>");
|
|
693
|
+
if (element.tag === "template" && !element.props.some((property) => property.type === NodeTypes.DIRECTIVE && property.name === "slot")) addVueError(context, element.loc, "Found a bare <template> inside a gt-vue <T> component", "Use an ordinary element or a statically named slot template");
|
|
694
|
+
if (element.tag.toLowerCase() === "component" && !resolvedDynamicComponent) addVueError(context, element.loc, "Found a dynamic <component> inside a gt-vue <T> component", "Use a statically named element or component");
|
|
695
|
+
for (const property of element.props) {
|
|
696
|
+
if (property.type !== NodeTypes.DIRECTIVE) continue;
|
|
697
|
+
if (SOURCE_SHAPING_DIRECTIVES.has(property.name)) addVueError(context, property.loc, `Found source-shaping directive ${property.rawName ?? `v-${property.name}`} inside a gt-vue <T> component`, "Move conditional or repeated content outside <T>, or use Branch/Plural");
|
|
698
|
+
else if (property.name === "bind" && !readDirectiveKey(property)) addVueError(context, property.loc, "Found a dynamic or spread v-bind inside a gt-vue <T> component", "Bind props by a static name so their effect on the translation source is known");
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/** Enforces the one supported public shape for every variable component. */
|
|
702
|
+
function validateVariableComponentShape(element, component, context) {
|
|
703
|
+
if (context.validatedVariableComponents.has(element)) return;
|
|
704
|
+
context.validatedVariableComponents.add(element);
|
|
705
|
+
if (component !== "Var") {
|
|
706
|
+
if (!hasTemplateProp(element, "value")) addVueError(context, element.loc, `Found a gt-vue <${component}> component without a value prop`, `Pass the runtime value through <${component} :value="value" />`);
|
|
707
|
+
if (hasMeaningfulSlotContent(element.children, context)) addVueError(context, element.loc, `Found children on a gt-vue <${component}> component`, `Use only the required value prop: <${component} :value="value" />`);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
for (const property of element.props) {
|
|
711
|
+
const key = property.type === NodeTypes.ATTRIBUTE ? property.name : property.name === "bind" ? readDirectiveKey(property) : void 0;
|
|
712
|
+
if (key === "name" || key === "value") addVueError(context, property.loc, `Found unsupported ${key} prop on a gt-vue <Var> component`, "Pass the variable as the default child of <Var>");
|
|
713
|
+
else if (property.type === NodeTypes.DIRECTIVE && property.name === "bind" && !property.arg) addVueError(context, property.loc, "Found a spread v-bind on a gt-vue <Var> component", "Pass the variable only as the default child of <Var>");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/** Returns whether a statically named template prop is explicitly present. */
|
|
717
|
+
function hasTemplateProp(element, expected) {
|
|
718
|
+
return element.props.some((property) => {
|
|
719
|
+
if (property.type === NodeTypes.ATTRIBUTE) return property.name === expected;
|
|
720
|
+
return property.name === "bind" && readDirectiveKey(property) === expected;
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
function readContentProps(element, shadowed, bindings, expressionPlugins, context) {
|
|
724
|
+
const data = {};
|
|
725
|
+
for (const [shortName, propName] of Object.entries(HTML_CONTENT_PROPS)) {
|
|
726
|
+
const property = findElementProperty(element, propName, expressionPlugins, shadowed, bindings);
|
|
727
|
+
if (!property.present) continue;
|
|
728
|
+
if (!property.static) {
|
|
729
|
+
addVueError(context, property.location, `Found dynamic translatable prop "${propName}" inside a gt-vue <T> component`, "Use a string literal for translatable HTML props");
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (typeof property.value === "string") data[shortName] = property.value;
|
|
733
|
+
}
|
|
734
|
+
return data;
|
|
735
|
+
}
|
|
736
|
+
function readBranches(element, slots, branchElementId, component, shadowed, bindings, expressionPlugins, context) {
|
|
737
|
+
const branches = {};
|
|
738
|
+
for (const [name, slot] of slots.namedSlots) {
|
|
739
|
+
if (name.startsWith("_")) continue;
|
|
740
|
+
if (component === "Plural" && !isAcceptedPluralForm(name)) continue;
|
|
741
|
+
branches[name] = collapseChildren(serializeChildren(slot.children, { value: branchElementId }, slot.shadowed, bindings, expressionPlugins, context));
|
|
742
|
+
}
|
|
743
|
+
for (const property of element.props) {
|
|
744
|
+
if (isDynamicComponentSelector(element, property)) continue;
|
|
745
|
+
if (property.type === NodeTypes.DIRECTIVE && property.name === "slot") continue;
|
|
746
|
+
const key = property.type === NodeTypes.ATTRIBUTE ? property.name : readDirectiveKey(property);
|
|
747
|
+
if (property.type === NodeTypes.DIRECTIVE && property.name === "on") continue;
|
|
748
|
+
if (key && (!isBranchAttributeName(key) || Object.prototype.hasOwnProperty.call(branches, key) || component === "Plural" && !isAcceptedPluralForm(key))) continue;
|
|
749
|
+
if (property.type === NodeTypes.DIRECTIVE && (property.name !== "bind" || property.modifiers.length > 0)) {
|
|
750
|
+
const directive = property.rawName ?? `v-${property.name}`;
|
|
751
|
+
addVueError(context, property.loc, `Found unsupported directive ${directive} on a gt-vue <${component}> component`, `Move ${directive} to an element outside <${component}>`);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (key === void 0) continue;
|
|
755
|
+
const value = readPropertyValue(property, expressionPlugins, shadowed, bindings);
|
|
756
|
+
if (!value.ok) {
|
|
757
|
+
if (isStaticallyNonBranchValue(property, expressionPlugins, shadowed, bindings)) continue;
|
|
758
|
+
addVueError(context, property.loc, `Found dynamic branch prop "${key}" on a gt-vue <${component}> component`, "Use a static branch prop or a named slot");
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
branches[key] = branchPropToChildren(value.value);
|
|
762
|
+
}
|
|
763
|
+
return branches;
|
|
764
|
+
}
|
|
765
|
+
function branchPropToChildren(value) {
|
|
766
|
+
if (value == null || typeof value === "boolean") return [];
|
|
767
|
+
return String(value);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Mirrors the attribute-name half of gt-vue's runtime branch predicate.
|
|
771
|
+
*
|
|
772
|
+
* Vue combines explicit component props, presentation attributes, and
|
|
773
|
+
* normalized listeners in one VNode prop record. The extractor must discard
|
|
774
|
+
* the same names before evaluating values so dynamic class/style/listener
|
|
775
|
+
* expressions cannot change a published translation hash.
|
|
776
|
+
*/
|
|
777
|
+
function isBranchAttributeName(name) {
|
|
778
|
+
return !(NON_BRANCH_ATTRIBUTE_NAMES.has(name) || name.startsWith("aria-") || name.startsWith("data-") || /^on[^a-z]/.test(name));
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Identifies expressions whose top-level runtime type can never be branch
|
|
782
|
+
* content. Unknown identifiers and calls deliberately return false so an
|
|
783
|
+
* arbitrary prop whose runtime type may be primitive still fails closed.
|
|
784
|
+
*/
|
|
785
|
+
function isStaticallyNonBranchValue(property, expressionPlugins, shadowed, bindings) {
|
|
786
|
+
if (property.type !== NodeTypes.DIRECTIVE || property.name !== "bind") return false;
|
|
787
|
+
const node = unwrapExpression(property.exp ? getExpressionNode(property.exp, expressionPlugins) : void 0);
|
|
788
|
+
if (!node) return false;
|
|
789
|
+
if (babel.isIdentifier(node, { name: "undefined" })) return !shadowed.has(node.name) && !bindings.staticValues.has(node.name);
|
|
790
|
+
return babel.isArrayExpression(node) || babel.isArrowFunctionExpression(node) || babel.isClassExpression(node) || babel.isFunctionExpression(node) || babel.isNewExpression(node) || babel.isObjectExpression(node) || babel.isRegExpLiteral(node) || babel.isUnaryExpression(node) && node.operator === "void";
|
|
791
|
+
}
|
|
792
|
+
function getSlotLayout(element, shadowed, expressionPlugins, context) {
|
|
793
|
+
const namedSlots = /* @__PURE__ */ new Map();
|
|
794
|
+
let defaultSlot = {
|
|
795
|
+
children: [],
|
|
796
|
+
rootCount: 0,
|
|
797
|
+
shadowed
|
|
798
|
+
};
|
|
799
|
+
let hasTemplateSlots = false;
|
|
800
|
+
let hasExplicitDefaultSlot = false;
|
|
801
|
+
let reportedDefaultConflict = false;
|
|
802
|
+
const componentSlot = element.props.find((property) => property.type === NodeTypes.DIRECTIVE && property.name === "slot");
|
|
803
|
+
if (componentSlot) {
|
|
804
|
+
if (componentSlot.exp) addScopedSlotError(componentSlot.loc, context);
|
|
805
|
+
const slotName = readSlotName(componentSlot, context);
|
|
806
|
+
const slotShadowed = unionSets(shadowed, collectExpressionBindings(componentSlot.exp, expressionPlugins));
|
|
807
|
+
if (slotName === "default") defaultSlot = {
|
|
808
|
+
children: element.children,
|
|
809
|
+
rootCount: countVueSlotRoots(element.children),
|
|
810
|
+
shadowed: slotShadowed
|
|
811
|
+
};
|
|
812
|
+
else if (slotName) namedSlots.set(slotName, {
|
|
813
|
+
children: element.children,
|
|
814
|
+
rootCount: countVueSlotRoots(element.children),
|
|
815
|
+
shadowed: slotShadowed
|
|
816
|
+
});
|
|
817
|
+
return {
|
|
818
|
+
defaultSlot,
|
|
819
|
+
namedSlots
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
const defaultChildren = [];
|
|
823
|
+
for (const child of element.children) {
|
|
824
|
+
const slotDirective = child.type === NodeTypes.ELEMENT && child.tag === "template" ? child.props.find((property) => property.type === NodeTypes.DIRECTIVE && property.name === "slot") : void 0;
|
|
825
|
+
if (!slotDirective || child.type !== NodeTypes.ELEMENT) {
|
|
826
|
+
defaultChildren.push(child);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
hasTemplateSlots = true;
|
|
830
|
+
for (const property of child.props) if (property.type === NodeTypes.DIRECTIVE && property.name !== "slot") addVueError(context, property.loc, "Found a conditional or dynamic named slot inside a gt-vue <T> component", "Use an unconditional, statically named slot");
|
|
831
|
+
const slotName = readSlotName(slotDirective, context);
|
|
832
|
+
if (slotDirective.exp) addScopedSlotError(slotDirective.loc, context);
|
|
833
|
+
const slotShadowed = unionSets(shadowed, collectExpressionBindings(slotDirective.exp, expressionPlugins));
|
|
834
|
+
if (slotName === "default") {
|
|
835
|
+
if (hasMeaningfulSlotContent(defaultChildren, context) || hasExplicitDefaultSlot) {
|
|
836
|
+
addVueError(context, child.loc, "Found more than one default slot definition inside a gt-vue translation", "Use a single default slot");
|
|
837
|
+
reportedDefaultConflict = true;
|
|
838
|
+
}
|
|
839
|
+
hasExplicitDefaultSlot = true;
|
|
840
|
+
defaultSlot = {
|
|
841
|
+
children: child.children,
|
|
842
|
+
rootCount: countVueSlotRoots(child.children),
|
|
843
|
+
shadowed: slotShadowed
|
|
844
|
+
};
|
|
845
|
+
} else if (slotName) {
|
|
846
|
+
if (namedSlots.has(slotName)) addVueError(context, child.loc, `Found duplicate named slot "${slotName}" inside a gt-vue translation`, "Define each named slot once");
|
|
847
|
+
namedSlots.set(slotName, {
|
|
848
|
+
children: child.children,
|
|
849
|
+
rootCount: countVueSlotRoots(child.children),
|
|
850
|
+
shadowed: slotShadowed
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const hasMeaningfulDefault = hasMeaningfulSlotContent(defaultChildren, context);
|
|
855
|
+
if (hasExplicitDefaultSlot && hasMeaningfulDefault && !reportedDefaultConflict) addVueError(context, defaultChildren[0].loc, "Found more than one default slot definition inside a gt-vue translation", "Use a single default slot");
|
|
856
|
+
else if (!hasExplicitDefaultSlot && defaultChildren.length > 0 && (!hasTemplateSlots || hasMeaningfulDefault)) defaultSlot = {
|
|
857
|
+
children: defaultChildren,
|
|
858
|
+
rootCount: countImplicitSlotRoots(element.children),
|
|
859
|
+
shadowed
|
|
860
|
+
};
|
|
861
|
+
return {
|
|
862
|
+
defaultSlot,
|
|
863
|
+
namedSlots
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
/** Matches Vue's test for an implicit slot containing more than separators. */
|
|
867
|
+
function hasMeaningfulSlotContent(children, context) {
|
|
868
|
+
return children.some((child) => child.type !== NodeTypes.COMMENT && (child.type !== NodeTypes.TEXT || !isImplicitSlotWhitespace(child.content, context.implicitSlotWhitespace)));
|
|
869
|
+
}
|
|
870
|
+
/** Mirrors the exact whitespace predicate detected from consumer compiler. */
|
|
871
|
+
function isImplicitSlotWhitespace(value, semantics) {
|
|
872
|
+
return semantics === "ecmascript" ? value.trim().length === 0 : /^[\t\n\f\r ]*$/.test(value);
|
|
873
|
+
}
|
|
874
|
+
/** Counts roots in a complete Vue slot before Suspense normalizes the array. */
|
|
875
|
+
function countVueSlotRoots(children) {
|
|
876
|
+
return countSlotRoots(children, () => false);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Counts roots in an implicit slot while treating named-slot templates as
|
|
880
|
+
* barriers. Vue removes those templates from the default slot only after its
|
|
881
|
+
* text transform has run, so text on opposite sides remains separate VNodes.
|
|
882
|
+
*/
|
|
883
|
+
function countImplicitSlotRoots(children) {
|
|
884
|
+
return countSlotRoots(children, (child) => {
|
|
885
|
+
if (child.type !== NodeTypes.ELEMENT || child.tag !== "template") return false;
|
|
886
|
+
return child.props.some((property) => property.type === NodeTypes.DIRECTIVE && property.name === "slot");
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
function countSlotRoots(children, isExcluded) {
|
|
890
|
+
let roots = 0;
|
|
891
|
+
let previousWasText = false;
|
|
892
|
+
for (const child of children) {
|
|
893
|
+
if (child.type === NodeTypes.COMMENT || isExcluded(child)) {
|
|
894
|
+
previousWasText = false;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
const isText = child.type === NodeTypes.TEXT || child.type === NodeTypes.INTERPOLATION;
|
|
898
|
+
if (!isText || !previousWasText) roots += 1;
|
|
899
|
+
previousWasText = isText;
|
|
900
|
+
}
|
|
901
|
+
return roots;
|
|
902
|
+
}
|
|
903
|
+
function addScopedSlotError(location, context) {
|
|
904
|
+
addVueError(context, location, "Found a scoped slot inside a gt-vue <T> component", "Use a slot without runtime slot props so the translation source is static");
|
|
905
|
+
}
|
|
906
|
+
function readSlotName(directive, context) {
|
|
907
|
+
if (!directive.arg) return "default";
|
|
908
|
+
if (directive.arg.type !== NodeTypes.SIMPLE_EXPRESSION || !directive.arg.isStatic) {
|
|
909
|
+
addVueError(context, directive.loc, "Found a dynamic slot name inside a gt-vue translation", "Use a static slot name");
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
return directive.arg.content;
|
|
913
|
+
}
|
|
914
|
+
function findElementProperty(element, name, expressionPlugins, shadowed, bindings) {
|
|
915
|
+
for (const property of element.props) {
|
|
916
|
+
if ((property.type === NodeTypes.ATTRIBUTE ? property.name : property.name === "bind" ? readDirectiveKey(property) : void 0) !== name) continue;
|
|
917
|
+
const value = readPropertyValue(property, expressionPlugins, shadowed, bindings);
|
|
918
|
+
return {
|
|
919
|
+
location: property.loc,
|
|
920
|
+
present: true,
|
|
921
|
+
static: value.ok,
|
|
922
|
+
...value.ok && { value: value.value }
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
return { present: false };
|
|
926
|
+
}
|
|
927
|
+
function readPropertyValue(property, expressionPlugins, shadowed, bindings) {
|
|
928
|
+
if (property.type === NodeTypes.ATTRIBUTE) return {
|
|
929
|
+
ok: true,
|
|
930
|
+
value: property.value?.content ?? ""
|
|
931
|
+
};
|
|
932
|
+
if (property.name !== "bind") return { ok: false };
|
|
933
|
+
return readExpressionPrimitive(property.exp, expressionPlugins, shadowed, bindings);
|
|
934
|
+
}
|
|
935
|
+
function readExpressionPrimitive(expression, expressionPlugins, shadowed, bindings) {
|
|
936
|
+
return readStaticPrimitive(expression ? getExpressionNode(expression, expressionPlugins) : void 0, (identifier) => {
|
|
937
|
+
if (shadowed.has(identifier.name)) return { ok: false };
|
|
938
|
+
const value = bindings.staticValues.get(identifier.name);
|
|
939
|
+
return bindings.staticValues.has(identifier.name) ? {
|
|
940
|
+
ok: true,
|
|
941
|
+
value
|
|
942
|
+
} : { ok: false };
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
function getExpressionNode(expression, expressionPlugins) {
|
|
946
|
+
if (expression.type !== NodeTypes.SIMPLE_EXPRESSION) return void 0;
|
|
947
|
+
if (expression.ast && typeof expression.ast === "object") return expression.ast;
|
|
948
|
+
try {
|
|
949
|
+
return parseExpression(expression.content, { plugins: expressionPlugins });
|
|
950
|
+
} catch {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function wrapForTraversal(node) {
|
|
955
|
+
if (node.type === "File") return node;
|
|
956
|
+
if (node.type === "Program") return babel.file(node);
|
|
957
|
+
if (babel.isExpression(node)) return babel.file(babel.program([babel.expressionStatement(node)]));
|
|
958
|
+
if (babel.isStatement(node)) return babel.file(babel.program([node]));
|
|
959
|
+
}
|
|
960
|
+
function collectElementScopeBindings(element, expressionPlugins) {
|
|
961
|
+
const result = /* @__PURE__ */ new Set();
|
|
962
|
+
for (const property of element.props) {
|
|
963
|
+
if (property.type !== NodeTypes.DIRECTIVE) continue;
|
|
964
|
+
if (property.name === "for") {
|
|
965
|
+
const parseResult = readForParseResult(property);
|
|
966
|
+
for (const alias of [
|
|
967
|
+
parseResult?.value,
|
|
968
|
+
parseResult?.key,
|
|
969
|
+
parseResult?.index
|
|
970
|
+
]) for (const name of collectExpressionBindings(alias, expressionPlugins)) result.add(name);
|
|
971
|
+
} else if (property.name === "slot") for (const name of collectExpressionBindings(property.exp, expressionPlugins)) result.add(name);
|
|
972
|
+
}
|
|
973
|
+
return result;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Reads Vue's normalized v-for fields across every supported Vue 3 release.
|
|
977
|
+
*
|
|
978
|
+
* Vue 3.4+ supplies `forParseResult`; Vue 3.3 exposes only the original
|
|
979
|
+
* directive expression. The fallback deliberately mirrors Vue's own alias,
|
|
980
|
+
* iterator, and parenthesis expressions so scope tracking sees the same value,
|
|
981
|
+
* key, index, and source fields without depending on a second compiler copy.
|
|
982
|
+
*/
|
|
983
|
+
function readForParseResult(property) {
|
|
984
|
+
const compilerResult = property.forParseResult;
|
|
985
|
+
if (compilerResult) return compilerResult;
|
|
986
|
+
if (property.exp?.type !== NodeTypes.SIMPLE_EXPRESSION || !property.exp.content) return;
|
|
987
|
+
const match = property.exp.content.match(FOR_ALIAS_EXPRESSION);
|
|
988
|
+
if (!match) return void 0;
|
|
989
|
+
const source = match[2]?.trim();
|
|
990
|
+
const aliases = match[1]?.trim().replace(FOR_STRIP_PARENS, "").trim();
|
|
991
|
+
if (!source || !aliases) return void 0;
|
|
992
|
+
const iteratorMatch = aliases.match(FOR_ITERATOR_EXPRESSION);
|
|
993
|
+
const value = iteratorMatch ? aliases.replace(FOR_ITERATOR_EXPRESSION, "").trim() : aliases;
|
|
994
|
+
if (!value) return void 0;
|
|
995
|
+
return {
|
|
996
|
+
source: cloneForExpression(property.exp, source),
|
|
997
|
+
value: cloneForExpression(property.exp, value),
|
|
998
|
+
...iteratorMatch?.[1]?.trim() && { key: cloneForExpression(property.exp, iteratorMatch[1].trim()) },
|
|
999
|
+
...iteratorMatch?.[2]?.trim() && { index: cloneForExpression(property.exp, iteratorMatch[2].trim()) }
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
/** Creates one parser-neutral v-for field from Vue's original expression. */
|
|
1003
|
+
function cloneForExpression(expression, content) {
|
|
1004
|
+
return {
|
|
1005
|
+
...expression,
|
|
1006
|
+
ast: void 0,
|
|
1007
|
+
content,
|
|
1008
|
+
loc: {
|
|
1009
|
+
...expression.loc,
|
|
1010
|
+
source: content
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
function collectExpressionBindings(expression, expressionPlugins) {
|
|
1015
|
+
const result = /* @__PURE__ */ new Set();
|
|
1016
|
+
if (!expression || expression.type !== NodeTypes.SIMPLE_EXPRESSION || !expression.content) return result;
|
|
1017
|
+
try {
|
|
1018
|
+
const arrow = parseExpression(`(${expression.content}) => 0`, { plugins: expressionPlugins });
|
|
1019
|
+
if (arrow.type === "ArrowFunctionExpression") for (const parameter of arrow.params) collectPatternNames(parameter, result);
|
|
1020
|
+
} catch {}
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
function collectPatternNames(pattern, result) {
|
|
1024
|
+
if (!pattern) return;
|
|
1025
|
+
if (pattern.type === "Identifier") result.add(pattern.name);
|
|
1026
|
+
else if (pattern.type === "RestElement") collectPatternNames(pattern.argument, result);
|
|
1027
|
+
else if (pattern.type === "AssignmentPattern") collectPatternNames(pattern.left, result);
|
|
1028
|
+
else if (pattern.type === "ArrayPattern") {
|
|
1029
|
+
for (const element of pattern.elements) if (element) collectPatternNames(element, result);
|
|
1030
|
+
} else if (pattern.type === "ObjectPattern") for (const property of pattern.properties) if (property.type === "RestElement") collectPatternNames(property.argument, result);
|
|
1031
|
+
else collectPatternNames(property.value, result);
|
|
1032
|
+
else if (pattern.type === "TSParameterProperty") collectPatternNames(pattern.parameter, result);
|
|
1033
|
+
}
|
|
1034
|
+
function resolveGTComponent(element, bindings, expressionPlugins, shadowed) {
|
|
1035
|
+
return resolveComponentBinding(element, bindings, bindings.components, bindings.registeredComponents, bindings.directBindings, expressionPlugins, shadowed);
|
|
1036
|
+
}
|
|
1037
|
+
/** Resolves a template tag or static dynamic selector through known bindings. */
|
|
1038
|
+
function resolveComponentBinding(element, bindings, directBindings, registeredBindings, directTemplateBindings, expressionPlugins, shadowed) {
|
|
1039
|
+
if (element.tagType !== ElementTypes.COMPONENT) return void 0;
|
|
1040
|
+
const selector = readDynamicComponentSelector(element, expressionPlugins, bindings, shadowed);
|
|
1041
|
+
if (selector) {
|
|
1042
|
+
if (selector.unknown || selector.possibleGT || selector.componentFactories.size > 0 || selector.gtComponentFactories.size > 0 || selector.candidates.length === 0) return;
|
|
1043
|
+
const resolved = selector.candidates.map((candidate) => {
|
|
1044
|
+
for (const localName of normalizeTemplateBindingNames(candidate.name)) {
|
|
1045
|
+
const originalName = candidate.kind === "string" ? registeredBindings.get(localName) : directBindings.get(localName);
|
|
1046
|
+
if (originalName) return {
|
|
1047
|
+
localName,
|
|
1048
|
+
originalName
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
const first = resolved[0];
|
|
1053
|
+
return first && resolved.every((candidate) => candidate?.originalName === first.originalName) ? first : void 0;
|
|
1054
|
+
}
|
|
1055
|
+
for (const localName of normalizeTemplateBindingNames(element.tag)) {
|
|
1056
|
+
const originalName = isDirectTemplateBinding(localName, directTemplateBindings) ? directBindings.get(localName) : registeredBindings.get(localName);
|
|
1057
|
+
if (originalName) return {
|
|
1058
|
+
localName,
|
|
1059
|
+
originalName
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function normalizeTemplateBindingNames(sourceName) {
|
|
1064
|
+
const camelized = sourceName.replace(/-(\w)/g, (_match, letter) => letter.toUpperCase());
|
|
1065
|
+
const pascalized = camelized ? camelized[0].toUpperCase() + camelized.slice(1) : camelized;
|
|
1066
|
+
return new Set([
|
|
1067
|
+
sourceName,
|
|
1068
|
+
camelized,
|
|
1069
|
+
pascalized
|
|
1070
|
+
]);
|
|
1071
|
+
}
|
|
1072
|
+
/** Returns whether a program binding shadows an Options API registration. */
|
|
1073
|
+
function isDirectTemplateBinding(localName, directBindings) {
|
|
1074
|
+
return directBindings.has(localName) || directBindings.has(localName.split(".", 1)[0] ?? localName);
|
|
1075
|
+
}
|
|
1076
|
+
/** Resolves a statically knowable Vue dynamic-component selector. */
|
|
1077
|
+
function readDynamicComponentSelector(element, expressionPlugins, bindings, shadowed) {
|
|
1078
|
+
if (element.tag.toLowerCase() !== "component") return void 0;
|
|
1079
|
+
const property = element.props.find((candidate) => isDynamicComponentSelector(element, candidate));
|
|
1080
|
+
if (!property) return void 0;
|
|
1081
|
+
if (property.type === NodeTypes.ATTRIBUTE) return property.value ? {
|
|
1082
|
+
candidates: [{
|
|
1083
|
+
kind: "string",
|
|
1084
|
+
name: property.value.content
|
|
1085
|
+
}],
|
|
1086
|
+
componentFactories: /* @__PURE__ */ new Set(),
|
|
1087
|
+
containers: [],
|
|
1088
|
+
displayName: property.value.content,
|
|
1089
|
+
gtComponentFactories: /* @__PURE__ */ new Set(),
|
|
1090
|
+
possibleGT: false,
|
|
1091
|
+
unknown: false
|
|
1092
|
+
} : void 0;
|
|
1093
|
+
const node = property.exp ? getExpressionNode(property.exp, expressionPlugins) : void 0;
|
|
1094
|
+
if (!node) return void 0;
|
|
1095
|
+
return {
|
|
1096
|
+
...collectDynamicSelectorCandidates(node, bindings, shadowed, /* @__PURE__ */ new Set()),
|
|
1097
|
+
displayName: property.exp?.type === NodeTypes.SIMPLE_EXPRESSION ? property.exp.content : "dynamic component"
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
/** Collects every statically visible result of a dynamic selector expression. */
|
|
1101
|
+
function collectDynamicSelectorCandidates(node, bindings, shadowed, seen) {
|
|
1102
|
+
const expression = unwrapExpression(node);
|
|
1103
|
+
if (!expression || seen.has(expression)) return emptyDynamicSelectorAnalysis(true);
|
|
1104
|
+
const nextSeen = new Set(seen).add(expression);
|
|
1105
|
+
if (expression.type === "ArrayExpression" || expression.type === "ObjectExpression") return {
|
|
1106
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1107
|
+
containers: [{
|
|
1108
|
+
kind: "literal",
|
|
1109
|
+
node: expression
|
|
1110
|
+
}]
|
|
1111
|
+
};
|
|
1112
|
+
if (expression.type === "ArrowFunctionExpression" || expression.type === "FunctionExpression" || expression.type === "ObjectMethod") return {
|
|
1113
|
+
...collectTemplateCallableReturnAnalysis(expression, bindings, shadowed, nextSeen),
|
|
1114
|
+
unknown: true
|
|
1115
|
+
};
|
|
1116
|
+
if (expression.type === "Identifier") {
|
|
1117
|
+
if (shadowed.has(expression.name)) return emptyDynamicSelectorAnalysis(true);
|
|
1118
|
+
if (bindings.containerKinds.has(expression.name)) return {
|
|
1119
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1120
|
+
containers: [{
|
|
1121
|
+
kind: "path",
|
|
1122
|
+
path: expression.name
|
|
1123
|
+
}]
|
|
1124
|
+
};
|
|
1125
|
+
if (bindings.staticValues.has(expression.name)) {
|
|
1126
|
+
const value = bindings.staticValues.get(expression.name);
|
|
1127
|
+
return typeof value === "string" ? selectorCandidate("string", value) : emptyDynamicSelectorAnalysis(false);
|
|
1128
|
+
}
|
|
1129
|
+
const possibleStrings = bindings.possibleStaticStrings.get(expression.name);
|
|
1130
|
+
if (possibleStrings && possibleStrings.size > 0) return {
|
|
1131
|
+
...mergeDynamicSelectorAnalyses([...possibleStrings].map((value) => selectorCandidate("string", value))),
|
|
1132
|
+
unknown: true
|
|
1133
|
+
};
|
|
1134
|
+
return selectorCandidate("expression", expression.name);
|
|
1135
|
+
}
|
|
1136
|
+
if (expression.type === "StringLiteral") return selectorCandidate("string", expression.value);
|
|
1137
|
+
const staticValue = readTemplateStaticPrimitive(expression, bindings, shadowed);
|
|
1138
|
+
if (staticValue.ok) return typeof staticValue.value === "string" ? selectorCandidate("string", staticValue.value) : emptyDynamicSelectorAnalysis(false);
|
|
1139
|
+
if (expression.type === "MemberExpression" || expression.type === "OptionalMemberExpression") {
|
|
1140
|
+
const property = readStaticTemplateMemberProperty(expression, bindings, shadowed);
|
|
1141
|
+
if (property !== void 0) {
|
|
1142
|
+
const selected = selectTemplateStaticMemberExpression(expression.object, property, bindings, shadowed, /* @__PURE__ */ new Set());
|
|
1143
|
+
if (selected) return collectDynamicSelectorCandidates(selected, bindings, shadowed, nextSeen);
|
|
1144
|
+
const objectAnalysis = collectDynamicSelectorCandidates(expression.object, bindings, shadowed, nextSeen);
|
|
1145
|
+
if (objectAnalysis.containers.length > 0) {
|
|
1146
|
+
const memberAnalysis = mergeDynamicSelectorAnalyses(objectAnalysis.containers.map((container) => selectTemplateContainerReference(container, property, bindings, shadowed, nextSeen)));
|
|
1147
|
+
return {
|
|
1148
|
+
...memberAnalysis,
|
|
1149
|
+
unknown: memberAnalysis.unknown || objectAnalysis.unknown
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
const memberPath = readStaticTemplateMemberPath(expression, bindings, shadowed);
|
|
1154
|
+
if (memberPath) {
|
|
1155
|
+
const staticMember = bindings.staticValues.get(memberPath);
|
|
1156
|
+
if (typeof staticMember === "string") return selectorCandidate("string", staticMember);
|
|
1157
|
+
if (bindings.containerKinds.has(memberPath)) return {
|
|
1158
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1159
|
+
containers: [{
|
|
1160
|
+
kind: "path",
|
|
1161
|
+
path: memberPath
|
|
1162
|
+
}]
|
|
1163
|
+
};
|
|
1164
|
+
const possibleStrings = bindings.possibleStaticStrings.get(memberPath);
|
|
1165
|
+
if (possibleStrings && possibleStrings.size > 0) return {
|
|
1166
|
+
...mergeDynamicSelectorAnalyses([...possibleStrings].map((value) => selectorCandidate("string", value))),
|
|
1167
|
+
unknown: true
|
|
1168
|
+
};
|
|
1169
|
+
const possibleContainer = findPossibleGTContainerPath(memberPath, bindings);
|
|
1170
|
+
if (possibleContainer) return {
|
|
1171
|
+
...emptyDynamicSelectorAnalysis(true),
|
|
1172
|
+
containers: [{
|
|
1173
|
+
kind: "path",
|
|
1174
|
+
path: possibleContainer
|
|
1175
|
+
}]
|
|
1176
|
+
};
|
|
1177
|
+
if (templatePathSelectsPossibleGT(memberPath, bindings)) return {
|
|
1178
|
+
...emptyDynamicSelectorAnalysis(true),
|
|
1179
|
+
possibleGT: true
|
|
1180
|
+
};
|
|
1181
|
+
return selectorCandidate("expression", memberPath);
|
|
1182
|
+
}
|
|
1183
|
+
const container = collectTemplateContainerCandidates(expression.object, bindings, shadowed, nextSeen);
|
|
1184
|
+
if (container.candidates.length > 0 || container.componentFactories.size > 0 || container.containers.length > 0 || container.gtComponentFactories.size > 0 || container.possibleGT) return {
|
|
1185
|
+
...container,
|
|
1186
|
+
unknown: true
|
|
1187
|
+
};
|
|
1188
|
+
return emptyDynamicSelectorAnalysis(true);
|
|
1189
|
+
}
|
|
1190
|
+
if (expression.type === "ConditionalExpression") {
|
|
1191
|
+
const test = readTemplateStaticPrimitive(expression.test, bindings, shadowed);
|
|
1192
|
+
if (test.ok) return collectDynamicSelectorCandidates(test.value ? expression.consequent : expression.alternate, bindings, shadowed, nextSeen);
|
|
1193
|
+
return mergeDynamicSelectorAnalyses([collectDynamicSelectorCandidates(expression.consequent, bindings, shadowed, nextSeen), collectDynamicSelectorCandidates(expression.alternate, bindings, shadowed, nextSeen)]);
|
|
1194
|
+
}
|
|
1195
|
+
if (expression.type === "LogicalExpression") {
|
|
1196
|
+
const leftCandidates = collectDynamicSelectorCandidates(expression.left, bindings, shadowed, nextSeen);
|
|
1197
|
+
if (selectorAnalysisIsKnownComponent(leftCandidates, bindings)) return expression.operator === "&&" ? collectDynamicSelectorCandidates(expression.right, bindings, shadowed, nextSeen) : leftCandidates;
|
|
1198
|
+
const left = readTemplateStaticPrimitive(expression.left, bindings, shadowed);
|
|
1199
|
+
if (left.ok) return (expression.operator === "??" ? left.value == null : expression.operator === "||" ? !left.value : Boolean(left.value)) ? collectDynamicSelectorCandidates(expression.right, bindings, shadowed, nextSeen) : emptyDynamicSelectorAnalysis(false);
|
|
1200
|
+
return {
|
|
1201
|
+
...mergeDynamicSelectorAnalyses([leftCandidates, collectDynamicSelectorCandidates(expression.right, bindings, shadowed, nextSeen)]),
|
|
1202
|
+
unknown: true
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
if (expression.type === "SequenceExpression") {
|
|
1206
|
+
const last = expression.expressions.at(-1);
|
|
1207
|
+
return last ? collectDynamicSelectorCandidates(last, bindings, shadowed, nextSeen) : emptyDynamicSelectorAnalysis(true);
|
|
1208
|
+
}
|
|
1209
|
+
if (expression.type === "CallExpression" || expression.type === "OptionalCallExpression") {
|
|
1210
|
+
const reduction = collectTemplateReductionResult(expression, bindings, shadowed, nextSeen);
|
|
1211
|
+
if (reduction) return reduction;
|
|
1212
|
+
const directCallable = unwrapExpression(expression.callee);
|
|
1213
|
+
if (directCallable && (directCallable.type === "ArrowFunctionExpression" || directCallable.type === "FunctionExpression")) return {
|
|
1214
|
+
...collectTemplateCallableReturnAnalysis(directCallable, bindings, shadowed, nextSeen),
|
|
1215
|
+
unknown: true
|
|
1216
|
+
};
|
|
1217
|
+
const memberCallee = unwrapExpression(expression.callee);
|
|
1218
|
+
if (memberCallee && (memberCallee.type === "MemberExpression" || memberCallee.type === "OptionalMemberExpression")) {
|
|
1219
|
+
const method = readStaticTemplateMemberProperty(memberCallee, bindings, shadowed);
|
|
1220
|
+
const literalMethod = method ? selectTemplateStaticMemberExpression(memberCallee.object, method, bindings, shadowed, /* @__PURE__ */ new Set()) : void 0;
|
|
1221
|
+
if (literalMethod?.type === "ObjectMethod") return {
|
|
1222
|
+
...collectTemplateCallableReturnAnalysis(literalMethod, bindings, shadowed, nextSeen),
|
|
1223
|
+
unknown: true
|
|
1224
|
+
};
|
|
1225
|
+
if (method === "call" || method === "apply" || method === "bind") {
|
|
1226
|
+
const receiver = collectDynamicSelectorCandidates(memberCallee.object, bindings, shadowed, nextSeen);
|
|
1227
|
+
const receiverPath = readStaticTemplateMemberPath(memberCallee.object, bindings, shadowed);
|
|
1228
|
+
if (receiverPath && bindings.componentFactories.has(receiverPath)) receiver.componentFactories.add(receiverPath);
|
|
1229
|
+
if (receiverPath && bindings.gtComponentFactories.has(receiverPath)) receiver.gtComponentFactories.add(receiverPath);
|
|
1230
|
+
if (receiver.componentFactories.size > 0 || receiver.gtComponentFactories.size > 0) return {
|
|
1231
|
+
...receiver,
|
|
1232
|
+
unknown: true
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
if (method && (COMPONENT_SELECTING_ARRAY_METHODS.has(method) || COMPONENT_PRESERVING_ARRAY_METHODS.has(method))) {
|
|
1236
|
+
const arrays = collectTemplateContainerReferences(memberCallee.object, bindings, shadowed, nextSeen).containers.filter((container) => isKnownTemplateArray(container, bindings) && !templateContainerHasOwnMember(container, method, bindings));
|
|
1237
|
+
if (arrays.length > 0) {
|
|
1238
|
+
if (COMPONENT_PRESERVING_ARRAY_METHODS.has(method)) {
|
|
1239
|
+
const argumentAnalyses = method === "concat" ? expression.arguments.flatMap((argument) => argument.type === "ArgumentPlaceholder" ? [] : [collectDynamicSelectorCandidates(argument.type === "SpreadElement" ? argument.argument : argument, bindings, shadowed, nextSeen)]) : [];
|
|
1240
|
+
const argumentContainers = method === "concat" ? argumentAnalyses.flatMap((analysis) => analysis.containers) : [];
|
|
1241
|
+
return {
|
|
1242
|
+
...emptyDynamicSelectorAnalysis(true),
|
|
1243
|
+
containers: [...arrays, ...argumentContainers],
|
|
1244
|
+
possibleGT: argumentAnalyses.some((analysis) => selectorAnalysisMayContainGT(analysis, bindings))
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
let hasUnknownSelection = false;
|
|
1248
|
+
const analyses = arrays.flatMap((container) => {
|
|
1249
|
+
const key = readSelectingArrayMethodKey(method, expression.arguments[0], container, bindings, shadowed);
|
|
1250
|
+
if (key === null) return [];
|
|
1251
|
+
hasUnknownSelection ||= key === void 0;
|
|
1252
|
+
return [selectTemplateContainerReference(container, key, bindings, shadowed, nextSeen)];
|
|
1253
|
+
});
|
|
1254
|
+
if (analyses.length === 0) return emptyDynamicSelectorAnalysis(false);
|
|
1255
|
+
const selected = mergeDynamicSelectorAnalyses(analyses);
|
|
1256
|
+
return {
|
|
1257
|
+
...selected,
|
|
1258
|
+
unknown: selected.unknown || hasUnknownSelection
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
if (method === "get") {
|
|
1263
|
+
const receiverPath = readStaticTemplateMemberPath(memberCallee.object, bindings, shadowed);
|
|
1264
|
+
if (receiverPath && findPossibleGTContainerPath(receiverPath, bindings)) return {
|
|
1265
|
+
...emptyDynamicSelectorAnalysis(true),
|
|
1266
|
+
possibleGT: true
|
|
1267
|
+
};
|
|
1268
|
+
const references = collectTemplateContainerReferences(memberCallee.object, bindings, shadowed, nextSeen);
|
|
1269
|
+
if (references.containers.length > 0) {
|
|
1270
|
+
const argument = expression.arguments[0];
|
|
1271
|
+
const staticKey = argument && argument.type !== "ArgumentPlaceholder" && argument.type !== "SpreadElement" ? readTemplateStaticPrimitive(argument, bindings, shadowed) : void 0;
|
|
1272
|
+
const key = staticKey?.ok && (typeof staticKey.value === "string" || typeof staticKey.value === "number") ? String(staticKey.value) : argument && argument.type !== "ArgumentPlaceholder" && argument.type !== "SpreadElement" ? (() => {
|
|
1273
|
+
const argumentPath = readStaticTemplateMemberPath(argument, bindings, shadowed);
|
|
1274
|
+
return argumentPath && bindings.containerKinds.has(argumentPath) ? argumentPath : void 0;
|
|
1275
|
+
})() : void 0;
|
|
1276
|
+
const selected = mergeDynamicSelectorAnalyses(references.containers.map((container) => selectTemplateContainerReference(container, key, bindings, shadowed, nextSeen)));
|
|
1277
|
+
return {
|
|
1278
|
+
...selected,
|
|
1279
|
+
unknown: selected.unknown || key === void 0
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
const callee = readStaticTemplateMemberPath(expression.callee, bindings, shadowed);
|
|
1285
|
+
if (callee === "Reflect.get" && expression.arguments.length >= 2) {
|
|
1286
|
+
const target = expression.arguments[0];
|
|
1287
|
+
const keyNode = expression.arguments[1];
|
|
1288
|
+
if (target && target.type !== "ArgumentPlaceholder" && target.type !== "SpreadElement" && keyNode && keyNode.type !== "ArgumentPlaceholder" && keyNode.type !== "SpreadElement") {
|
|
1289
|
+
const references = collectTemplateContainerReferences(target, bindings, shadowed, nextSeen);
|
|
1290
|
+
const staticKey = readTemplateStaticPrimitive(keyNode, bindings, shadowed);
|
|
1291
|
+
const key = staticKey.ok && (typeof staticKey.value === "string" || typeof staticKey.value === "number") ? String(staticKey.value) : void 0;
|
|
1292
|
+
const selected = mergeDynamicSelectorAnalyses(references.containers.map((container) => selectTemplateContainerReference(container, key, bindings, shadowed, nextSeen)));
|
|
1293
|
+
if (references.containers.length > 0) return {
|
|
1294
|
+
...selected,
|
|
1295
|
+
unknown: selected.unknown || key === void 0
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
const first = expression.arguments[0];
|
|
1300
|
+
if (callee && bindings.identityFunctions.has(callee) && expression.arguments.length === 1 && first && first.type !== "ArgumentPlaceholder" && first.type !== "SpreadElement") return collectDynamicSelectorCandidates(first, bindings, shadowed, nextSeen);
|
|
1301
|
+
const argumentsAnalysis = mergeDynamicSelectorAnalyses(expression.arguments.filter((argument) => argument.type !== "ArgumentPlaceholder").map((argument) => collectDynamicSelectorCandidates(argument.type === "SpreadElement" ? argument.argument : argument, bindings, shadowed, nextSeen)));
|
|
1302
|
+
const calleeAnalysis = collectDynamicSelectorCandidates(expression.callee, bindings, shadowed, nextSeen);
|
|
1303
|
+
for (const candidate of calleeAnalysis.candidates) {
|
|
1304
|
+
if (candidate.kind !== "expression") continue;
|
|
1305
|
+
if (bindings.componentFactories.has(candidate.name)) argumentsAnalysis.componentFactories.add(candidate.name);
|
|
1306
|
+
if (bindings.gtComponentFactories.has(candidate.name)) argumentsAnalysis.gtComponentFactories.add(candidate.name);
|
|
1307
|
+
}
|
|
1308
|
+
for (const name of calleeAnalysis.componentFactories) argumentsAnalysis.componentFactories.add(name);
|
|
1309
|
+
for (const name of calleeAnalysis.gtComponentFactories) argumentsAnalysis.gtComponentFactories.add(name);
|
|
1310
|
+
if (callee && bindings.componentFactories.has(callee)) argumentsAnalysis.componentFactories.add(callee);
|
|
1311
|
+
if (callee && bindings.gtComponentFactories.has(callee)) argumentsAnalysis.gtComponentFactories.add(callee);
|
|
1312
|
+
return {
|
|
1313
|
+
...argumentsAnalysis,
|
|
1314
|
+
unknown: true
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
if (expression.type === "AssignmentExpression") return collectDynamicSelectorCandidates(expression.right, bindings, shadowed, nextSeen);
|
|
1318
|
+
if (expression.type === "AwaitExpression" || expression.type === "YieldExpression") return expression.argument ? collectDynamicSelectorCandidates(expression.argument, bindings, shadowed, nextSeen) : emptyDynamicSelectorAnalysis(true);
|
|
1319
|
+
return emptyDynamicSelectorAnalysis(false);
|
|
1320
|
+
}
|
|
1321
|
+
/** Finds component-bearing values that a dynamic container index may select. */
|
|
1322
|
+
function collectTemplateContainerCandidates(node, bindings, shadowed, seen) {
|
|
1323
|
+
const expression = unwrapExpression(node);
|
|
1324
|
+
if (!expression || seen.has(expression)) return emptyDynamicSelectorAnalysis(true);
|
|
1325
|
+
const nextSeen = new Set(seen).add(expression);
|
|
1326
|
+
if (expression.type === "ArrayExpression" || expression.type === "ObjectExpression") return collectLiteralContainerCandidates(expression, bindings, shadowed, nextSeen);
|
|
1327
|
+
if (expression.type === "CallExpression" || expression.type === "OptionalCallExpression") {
|
|
1328
|
+
const transformed = collectTemplateTransformCandidates(expression, bindings, shadowed, nextSeen);
|
|
1329
|
+
if (transformed) return transformed;
|
|
1330
|
+
}
|
|
1331
|
+
const path = readStaticTemplateMemberPath(expression, bindings, shadowed);
|
|
1332
|
+
if (path && (bindings.containerKinds.has(path) || findPossibleGTContainerPath(path, bindings) !== void 0 || hasImmediatePossibleGTContainerChild(path, bindings) || hasPossibleStaticStringChild(path, bindings))) {
|
|
1333
|
+
const result = bindings.containerKinds.has(path) || hasImmediatePossibleGTContainerChild(path, bindings) || hasPossibleStaticStringChild(path, bindings) ? collectExposedContainerCandidates(path, bindings) : emptyDynamicSelectorAnalysis(true);
|
|
1334
|
+
return {
|
|
1335
|
+
...result,
|
|
1336
|
+
possibleGT: result.possibleGT || findPossibleGTContainerPath(path, bindings) !== void 0
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
if (expression.type === "CallExpression" || expression.type === "OptionalCallExpression") {
|
|
1340
|
+
const callee = readStaticTemplateMemberPath(expression.callee, bindings, shadowed);
|
|
1341
|
+
if (callee && bindings.gtContainerFactories.has(callee)) return {
|
|
1342
|
+
...emptyDynamicSelectorAnalysis(true),
|
|
1343
|
+
possibleGT: true
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
const selected = collectDynamicSelectorCandidates(expression, bindings, shadowed, seen);
|
|
1347
|
+
const analyses = selected.containers.map((container) => selectTemplateContainerReference(container, void 0, bindings, shadowed, nextSeen));
|
|
1348
|
+
if (analyses.length === 0) return {
|
|
1349
|
+
...emptyDynamicSelectorAnalysis(true),
|
|
1350
|
+
possibleGT: false
|
|
1351
|
+
};
|
|
1352
|
+
const result = mergeDynamicSelectorAnalyses(analyses);
|
|
1353
|
+
return {
|
|
1354
|
+
...result,
|
|
1355
|
+
possibleGT: result.possibleGT,
|
|
1356
|
+
unknown: result.unknown || selected.unknown
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
/** Models built-in transforms whose result is immediately selected or iterated. */
|
|
1360
|
+
function collectTemplateTransformCandidates(call, bindings, shadowed, seen) {
|
|
1361
|
+
const callee = unwrapExpression(call.callee);
|
|
1362
|
+
if (!callee || callee.type !== "MemberExpression" && callee.type !== "OptionalMemberExpression") return;
|
|
1363
|
+
const method = readStaticTemplateMemberProperty(callee, bindings, shadowed);
|
|
1364
|
+
if (!method) return void 0;
|
|
1365
|
+
const receiverPath = readStaticTemplateMemberPath(callee.object, bindings, shadowed);
|
|
1366
|
+
const readArgument = (index) => {
|
|
1367
|
+
const argument = call.arguments[index];
|
|
1368
|
+
return argument && argument.type !== "ArgumentPlaceholder" ? argument.type === "SpreadElement" ? argument.argument : argument : void 0;
|
|
1369
|
+
};
|
|
1370
|
+
const receiver = () => collectTemplateContainerCandidates(callee.object, bindings, shadowed, seen);
|
|
1371
|
+
const mapValues = (source, callbackNode, includeSourceParameter = true, thisNode) => {
|
|
1372
|
+
const callback = unwrapExpression(callbackNode);
|
|
1373
|
+
if (!callback || callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression") return {
|
|
1374
|
+
...source,
|
|
1375
|
+
unknown: true
|
|
1376
|
+
};
|
|
1377
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
1378
|
+
const sourceArray = {
|
|
1379
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1380
|
+
containers: [{
|
|
1381
|
+
kind: "analysis",
|
|
1382
|
+
containerKind: "array",
|
|
1383
|
+
values: source
|
|
1384
|
+
}]
|
|
1385
|
+
};
|
|
1386
|
+
bindTemplateCallbackParameters(callback, [
|
|
1387
|
+
source,
|
|
1388
|
+
emptyDynamicSelectorAnalysis(false),
|
|
1389
|
+
...includeSourceParameter ? [sourceArray] : []
|
|
1390
|
+
], bindings, shadowed, parameters);
|
|
1391
|
+
if (callback.type !== "ArrowFunctionExpression" && thisNode) parameters.set("this", collectTemplateSelectorValue(thisNode, bindings, shadowed, seen));
|
|
1392
|
+
const analyses = collectTemplateFunctionReturns(callback).map((value) => collectTemplateCallbackReturnValue(value, parameters, bindings, shadowed, seen));
|
|
1393
|
+
const mapped = mergeDynamicSelectorAnalyses(analyses);
|
|
1394
|
+
return {
|
|
1395
|
+
...mapped,
|
|
1396
|
+
unknown: mapped.unknown || analyses.length === 0
|
|
1397
|
+
};
|
|
1398
|
+
};
|
|
1399
|
+
if (receiverPath === "Array" && method === "from" && !bindings.directBindings.has("Array") && !shadowed.has("Array")) {
|
|
1400
|
+
const sourceNode = readArgument(0);
|
|
1401
|
+
if (!sourceNode) return emptyDynamicSelectorAnalysis(true);
|
|
1402
|
+
const source = collectTemplateContainerCandidates(sourceNode, bindings, shadowed, seen);
|
|
1403
|
+
return call.arguments.length > 1 ? mapValues(source, readArgument(1), false, readArgument(2)) : source;
|
|
1404
|
+
}
|
|
1405
|
+
if (receiverPath === "Object" && (method === "values" || method === "entries") && !bindings.directBindings.has("Object") && !shadowed.has("Object")) {
|
|
1406
|
+
const sourceNode = readArgument(0);
|
|
1407
|
+
if (!sourceNode) return emptyDynamicSelectorAnalysis(true);
|
|
1408
|
+
if (method === "values") return collectTemplateContainerCandidates(sourceNode, bindings, shadowed, seen);
|
|
1409
|
+
const sourceValues = collectTemplateContainerCandidates(sourceNode, bindings, shadowed, seen);
|
|
1410
|
+
const source = unwrapExpression(sourceNode);
|
|
1411
|
+
if (source?.type !== "ObjectExpression") return {
|
|
1412
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1413
|
+
containers: [{
|
|
1414
|
+
kind: "analysis",
|
|
1415
|
+
containerKind: "array",
|
|
1416
|
+
members: new Map([["0", emptyDynamicSelectorAnalysis(false)], ["1", sourceValues]]),
|
|
1417
|
+
values: sourceValues
|
|
1418
|
+
}]
|
|
1419
|
+
};
|
|
1420
|
+
const properties = flattenTemplateLiteralObject(source, /* @__PURE__ */ new Set());
|
|
1421
|
+
if (!properties) return emptyDynamicSelectorAnalysis(true);
|
|
1422
|
+
const tuples = [];
|
|
1423
|
+
let unknown = false;
|
|
1424
|
+
for (const property of properties) {
|
|
1425
|
+
if (property.type === "SpreadElement") {
|
|
1426
|
+
unknown = true;
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
const key = readTemplateLiteralPropertyKey(property, bindings, shadowed);
|
|
1430
|
+
if (property.type !== "ObjectProperty" || !babel.isExpression(property.value)) {
|
|
1431
|
+
unknown = true;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
tuples.push({
|
|
1435
|
+
kind: "literal",
|
|
1436
|
+
node: babel.arrayExpression([babel.stringLiteral(key ?? ""), property.value])
|
|
1437
|
+
});
|
|
1438
|
+
unknown ||= key === void 0;
|
|
1439
|
+
}
|
|
1440
|
+
return {
|
|
1441
|
+
...emptyDynamicSelectorAnalysis(unknown),
|
|
1442
|
+
containers: tuples
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
const source = receiver();
|
|
1446
|
+
if (method === "concat") return mergeDynamicSelectorAnalyses([source, ...call.arguments.flatMap((_, index) => {
|
|
1447
|
+
const argument = readArgument(index);
|
|
1448
|
+
if (!argument) return [];
|
|
1449
|
+
return [flattenTemplateTransformAnalysis(collectDynamicSelectorCandidates(argument, bindings, shadowed, seen), 1, bindings, shadowed, seen)];
|
|
1450
|
+
})]);
|
|
1451
|
+
if (method === "map") return mapValues(source, readArgument(0), true, readArgument(1));
|
|
1452
|
+
if (method === "flatMap") return flattenTemplateTransformAnalysis(mapValues(source, readArgument(0), true, readArgument(1)), 1, bindings, shadowed, seen);
|
|
1453
|
+
if (method === "flat") {
|
|
1454
|
+
const depthNode = readArgument(0);
|
|
1455
|
+
const depthValue = depthNode ? readTemplateStaticPrimitive(depthNode, bindings, shadowed, true) : {
|
|
1456
|
+
ok: true,
|
|
1457
|
+
value: 1
|
|
1458
|
+
};
|
|
1459
|
+
if (!depthValue.ok) {
|
|
1460
|
+
const possibilities = [source];
|
|
1461
|
+
let current = source;
|
|
1462
|
+
for (let depth = 0; depth < 32 && current.containers.length > 0; depth += 1) {
|
|
1463
|
+
current = flattenTemplateTransformAnalysis(current, 1, bindings, shadowed, seen);
|
|
1464
|
+
possibilities.push(current);
|
|
1465
|
+
}
|
|
1466
|
+
return {
|
|
1467
|
+
...mergeDynamicSelectorAnalyses(possibilities),
|
|
1468
|
+
unknown: true
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
if (typeof depthValue.value === "bigint") return emptyDynamicSelectorAnalysis(false);
|
|
1472
|
+
const numericDepth = Number(depthValue.value);
|
|
1473
|
+
return flattenTemplateTransformAnalysis(source, Number.isNaN(numericDepth) ? 0 : numericDepth === Number.POSITIVE_INFINITY ? 32 : Math.max(0, Math.trunc(numericDepth)), bindings, shadowed, seen);
|
|
1474
|
+
}
|
|
1475
|
+
if (method === "with" || method === "toSpliced") return mergeDynamicSelectorAnalyses([source, ...(method === "with" ? [readArgument(1)] : call.arguments.slice(2).map((_, index) => readArgument(index + 2))).flatMap((value) => value ? [collectTemplateSelectorValue(value, bindings, shadowed, seen)] : [])]);
|
|
1476
|
+
if (method === "copyWithin") return source;
|
|
1477
|
+
if (method === "fill") {
|
|
1478
|
+
const value = readArgument(0);
|
|
1479
|
+
return value ? collectTemplateSelectorValue(value, bindings, shadowed, seen) : emptyDynamicSelectorAnalysis(false);
|
|
1480
|
+
}
|
|
1481
|
+
if (method === "reduce" || method === "reduceRight") {
|
|
1482
|
+
const reduction = collectTemplateReductionResult(call, bindings, shadowed, seen);
|
|
1483
|
+
if (reduction) return mergeDynamicSelectorAnalyses([{
|
|
1484
|
+
...reduction,
|
|
1485
|
+
containers: []
|
|
1486
|
+
}, ...reduction.containers.map((container) => selectTemplateContainerReference(container, void 0, bindings, shadowed, seen))]);
|
|
1487
|
+
const flattened = flattenTemplateTransformAnalysis(source, 1, bindings, shadowed, seen);
|
|
1488
|
+
const initial = readArgument(1);
|
|
1489
|
+
return mergeDynamicSelectorAnalyses([
|
|
1490
|
+
source,
|
|
1491
|
+
flattened,
|
|
1492
|
+
...initial ? [collectTemplateSelectorValue(initial, bindings, shadowed, seen)] : []
|
|
1493
|
+
]);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
/** Evaluates a finite literal reduce/reduceRight without executing user code. */
|
|
1497
|
+
function collectTemplateReductionResult(call, bindings, shadowed, seen) {
|
|
1498
|
+
const callee = unwrapExpression(call.callee);
|
|
1499
|
+
if (!callee || callee.type !== "MemberExpression" && callee.type !== "OptionalMemberExpression") return;
|
|
1500
|
+
const method = readStaticTemplateMemberProperty(callee, bindings, shadowed);
|
|
1501
|
+
if (method !== "reduce" && method !== "reduceRight") return void 0;
|
|
1502
|
+
const receiver = unwrapExpression(callee.object);
|
|
1503
|
+
if (receiver?.type !== "ArrayExpression") return void 0;
|
|
1504
|
+
const elements = flattenTemplateLiteralArray(receiver, /* @__PURE__ */ new Set());
|
|
1505
|
+
if (!elements) return void 0;
|
|
1506
|
+
const callbackArgument = call.arguments[0];
|
|
1507
|
+
const callback = callbackArgument && callbackArgument.type !== "ArgumentPlaceholder" && callbackArgument.type !== "SpreadElement" ? unwrapExpression(callbackArgument) : void 0;
|
|
1508
|
+
if (!callback || callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression") return;
|
|
1509
|
+
const entries = elements.filter((element) => Boolean(element)).map((element) => collectTemplateSelectorValue(element, bindings, shadowed, seen));
|
|
1510
|
+
const initialArgument = call.arguments[1];
|
|
1511
|
+
const initial = initialArgument && initialArgument.type !== "ArgumentPlaceholder" && initialArgument.type !== "SpreadElement" ? collectTemplateSelectorValue(initialArgument, bindings, shadowed, seen) : void 0;
|
|
1512
|
+
if (entries.length === 0) return initial ?? emptyDynamicSelectorAnalysis(false);
|
|
1513
|
+
const ordered = method === "reduceRight" ? [...entries].reverse() : entries;
|
|
1514
|
+
let accumulator = initial ?? ordered.shift();
|
|
1515
|
+
if (!accumulator) return emptyDynamicSelectorAnalysis(false);
|
|
1516
|
+
const sourceArray = collectTemplateSelectorValue(receiver, bindings, shadowed, seen);
|
|
1517
|
+
for (const current of ordered) {
|
|
1518
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
1519
|
+
bindTemplateCallbackParameters(callback, [
|
|
1520
|
+
accumulator,
|
|
1521
|
+
current,
|
|
1522
|
+
emptyDynamicSelectorAnalysis(false),
|
|
1523
|
+
sourceArray
|
|
1524
|
+
], bindings, shadowed, parameters);
|
|
1525
|
+
const returned = collectTemplateFunctionReturns(callback).map((value) => collectTemplateCallbackReturnValue(value, parameters, bindings, shadowed, seen));
|
|
1526
|
+
accumulator = returned.length > 0 ? mergeDynamicSelectorAnalyses(returned) : emptyDynamicSelectorAnalysis(true);
|
|
1527
|
+
}
|
|
1528
|
+
return accumulator;
|
|
1529
|
+
}
|
|
1530
|
+
/** Flattens only container alternatives while retaining non-array values. */
|
|
1531
|
+
function flattenTemplateTransformAnalysis(analysis, depth, bindings, shadowed, seen) {
|
|
1532
|
+
let current = analysis;
|
|
1533
|
+
for (let index = 0; index < depth && current.containers.length > 0; index += 1) current = mergeDynamicSelectorAnalyses([{
|
|
1534
|
+
...current,
|
|
1535
|
+
containers: []
|
|
1536
|
+
}, ...current.containers.map((container) => selectTemplateContainerReference(container, void 0, bindings, shadowed, seen))]);
|
|
1537
|
+
return current;
|
|
1538
|
+
}
|
|
1539
|
+
/** Binds positional callback values, including a precise rest-argument tuple. */
|
|
1540
|
+
function bindTemplateCallbackParameters(callback, callbackArguments, bindings, shadowed, parameters) {
|
|
1541
|
+
callback.params.forEach((parameter, index) => {
|
|
1542
|
+
if (!isTemplateBindingPattern(parameter)) return;
|
|
1543
|
+
if (parameter.type === "RestElement") {
|
|
1544
|
+
if (!isTemplateBindingPattern(parameter.argument)) return;
|
|
1545
|
+
const restValues = callbackArguments.slice(index);
|
|
1546
|
+
collectTemplateCallbackParameterValues(parameter.argument, {
|
|
1547
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1548
|
+
containers: [{
|
|
1549
|
+
kind: "analysis",
|
|
1550
|
+
containerKind: "array",
|
|
1551
|
+
members: new Map(restValues.map((value, memberIndex) => [String(memberIndex), value])),
|
|
1552
|
+
values: mergeDynamicSelectorAnalyses(restValues)
|
|
1553
|
+
}]
|
|
1554
|
+
}, bindings, shadowed, parameters);
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
collectTemplateCallbackParameterValues(parameter, callbackArguments[index] ?? emptyDynamicSelectorAnalysis(true), bindings, shadowed, parameters);
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
/** Projects one mapper input through its callback parameter pattern. */
|
|
1561
|
+
function collectTemplateCallbackParameterValues(pattern, values, bindings, shadowed, parameters) {
|
|
1562
|
+
if (pattern.type === "Identifier") {
|
|
1563
|
+
parameters.set(pattern.name, values);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (pattern.type === "AssignmentPattern") {
|
|
1567
|
+
if (isTemplateBindingPattern(pattern.left)) collectTemplateCallbackParameterValues(pattern.left, mergeDynamicSelectorAnalyses([values, collectDynamicSelectorCandidates(pattern.right, bindings, shadowed, /* @__PURE__ */ new Set())]), bindings, shadowed, parameters);
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
if (pattern.type === "RestElement") {
|
|
1571
|
+
if (isTemplateBindingPattern(pattern.argument)) collectTemplateCallbackParameterValues(pattern.argument, {
|
|
1572
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1573
|
+
containers: [{
|
|
1574
|
+
kind: "analysis",
|
|
1575
|
+
containerKind: "array",
|
|
1576
|
+
values
|
|
1577
|
+
}]
|
|
1578
|
+
}, bindings, shadowed, parameters);
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
if (pattern.type === "ObjectPattern") {
|
|
1582
|
+
for (const property of pattern.properties) {
|
|
1583
|
+
const target = property.type === "RestElement" ? property.argument : property.value;
|
|
1584
|
+
if (!isTemplateBindingPattern(target)) continue;
|
|
1585
|
+
collectTemplateCallbackParameterValues(target, property.type === "RestElement" ? values : selectPossibleContainerChildren(values, readTemplateLiteralPropertyKey(property, bindings, shadowed), bindings, shadowed), bindings, shadowed, parameters);
|
|
1586
|
+
}
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
pattern.elements.forEach((element, index) => {
|
|
1590
|
+
if (!element || !isTemplateBindingPattern(element)) return;
|
|
1591
|
+
collectTemplateCallbackParameterValues(element, selectPossibleContainerChildren(values, element.type === "RestElement" ? void 0 : String(index), bindings, shadowed), bindings, shadowed, parameters);
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
/** Evaluates a callback return against abstract parameter values. */
|
|
1595
|
+
function collectTemplateCallbackReturnValue(node, parameters, bindings, shadowed, seen) {
|
|
1596
|
+
const expression = unwrapExpression(node);
|
|
1597
|
+
if (!expression) return emptyDynamicSelectorAnalysis(true);
|
|
1598
|
+
if (expression.type === "Identifier") {
|
|
1599
|
+
const parameter = parameters.get(expression.name);
|
|
1600
|
+
if (parameter) return parameter;
|
|
1601
|
+
}
|
|
1602
|
+
if (expression.type === "CallExpression" || expression.type === "OptionalCallExpression") {
|
|
1603
|
+
const callee = unwrapExpression(expression.callee);
|
|
1604
|
+
if (callee && (callee.type === "MemberExpression" || callee.type === "OptionalMemberExpression") && readStaticTemplateMemberProperty(callee, bindings, shadowed) === "concat") {
|
|
1605
|
+
const objectValue = collectTemplateCallbackReturnValue(callee.object, parameters, bindings, shadowed, seen);
|
|
1606
|
+
if (objectValue.containers.length > 0) {
|
|
1607
|
+
const flattenOne = (value) => mergeDynamicSelectorAnalyses([{
|
|
1608
|
+
...value,
|
|
1609
|
+
containers: []
|
|
1610
|
+
}, ...value.containers.map((container) => selectTemplateContainerReference(container, void 0, bindings, shadowed, seen))]);
|
|
1611
|
+
const values = mergeDynamicSelectorAnalyses([flattenOne(objectValue), ...expression.arguments.flatMap((argument) => argument.type === "ArgumentPlaceholder" ? [] : [flattenOne(collectTemplateCallbackReturnValue(argument.type === "SpreadElement" ? argument.argument : argument, parameters, bindings, shadowed, seen))])]);
|
|
1612
|
+
return {
|
|
1613
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1614
|
+
containers: [{
|
|
1615
|
+
kind: "analysis",
|
|
1616
|
+
containerKind: "array",
|
|
1617
|
+
values
|
|
1618
|
+
}]
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (expression.type === "MemberExpression" || expression.type === "OptionalMemberExpression") {
|
|
1624
|
+
const objectValue = collectTemplateCallbackReturnValue(expression.object, parameters, bindings, shadowed, seen);
|
|
1625
|
+
if (objectValue.containers.length > 0) return selectPossibleContainerChildren(objectValue, readStaticTemplateMemberProperty(expression, bindings, shadowed), bindings, shadowed);
|
|
1626
|
+
}
|
|
1627
|
+
if (expression.type === "ThisExpression") return parameters.get("this") ?? emptyDynamicSelectorAnalysis(true);
|
|
1628
|
+
if (expression.type === "ArrayExpression") {
|
|
1629
|
+
const elementValues = expression.elements.map((element) => element ? collectTemplateCallbackReturnValue(element.type === "SpreadElement" ? element.argument : element, parameters, bindings, shadowed, seen) : emptyDynamicSelectorAnalysis(false));
|
|
1630
|
+
const values = mergeDynamicSelectorAnalyses(elementValues);
|
|
1631
|
+
const hasSpread = expression.elements.some((element) => element?.type === "SpreadElement");
|
|
1632
|
+
return {
|
|
1633
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1634
|
+
containers: [{
|
|
1635
|
+
kind: "analysis",
|
|
1636
|
+
containerKind: "array",
|
|
1637
|
+
members: hasSpread ? void 0 : new Map(elementValues.map((value, index) => [String(index), value])),
|
|
1638
|
+
values
|
|
1639
|
+
}]
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
if (expression.type === "ObjectExpression") {
|
|
1643
|
+
const values = mergeDynamicSelectorAnalyses(expression.properties.map((property) => collectTemplateCallbackReturnValue(property.type === "SpreadElement" ? property.argument : property.type === "ObjectProperty" ? property.value : property, parameters, bindings, shadowed, seen)));
|
|
1644
|
+
return {
|
|
1645
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1646
|
+
containers: [{
|
|
1647
|
+
kind: "analysis",
|
|
1648
|
+
containerKind: "object",
|
|
1649
|
+
values
|
|
1650
|
+
}]
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
if (expression.type === "ConditionalExpression") return mergeDynamicSelectorAnalyses([collectTemplateCallbackReturnValue(expression.consequent, parameters, bindings, shadowed, seen), collectTemplateCallbackReturnValue(expression.alternate, parameters, bindings, shadowed, seen)]);
|
|
1654
|
+
if (expression.type === "LogicalExpression") return mergeDynamicSelectorAnalyses([collectTemplateCallbackReturnValue(expression.left, parameters, bindings, shadowed, seen), collectTemplateCallbackReturnValue(expression.right, parameters, bindings, shadowed, seen)]);
|
|
1655
|
+
if (expression.type === "SequenceExpression") {
|
|
1656
|
+
const last = expression.expressions.at(-1);
|
|
1657
|
+
return last ? collectTemplateCallbackReturnValue(last, parameters, bindings, shadowed, seen) : emptyDynamicSelectorAnalysis(true);
|
|
1658
|
+
}
|
|
1659
|
+
return collectTemplateSelectorValue(expression, bindings, shadowed, seen);
|
|
1660
|
+
}
|
|
1661
|
+
/** Resolves an expression to container identities without selecting a child. */
|
|
1662
|
+
function collectTemplateContainerReferences(node, bindings, shadowed, seen) {
|
|
1663
|
+
const expression = unwrapExpression(node);
|
|
1664
|
+
if (!expression || seen.has(expression)) return emptyDynamicSelectorAnalysis(true);
|
|
1665
|
+
if (expression.type === "ArrayExpression" || expression.type === "ObjectExpression") return {
|
|
1666
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1667
|
+
containers: [{
|
|
1668
|
+
kind: "literal",
|
|
1669
|
+
node: expression
|
|
1670
|
+
}]
|
|
1671
|
+
};
|
|
1672
|
+
const path = readStaticTemplateMemberPath(expression, bindings, shadowed);
|
|
1673
|
+
if (path && bindings.containerKinds.has(path)) return {
|
|
1674
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1675
|
+
containers: [{
|
|
1676
|
+
kind: "path",
|
|
1677
|
+
path
|
|
1678
|
+
}]
|
|
1679
|
+
};
|
|
1680
|
+
const selected = collectDynamicSelectorCandidates(expression, bindings, shadowed, seen);
|
|
1681
|
+
return {
|
|
1682
|
+
...emptyDynamicSelectorAnalysis(selected.unknown),
|
|
1683
|
+
containers: selected.containers
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
/** Selects one exact child, or every immediate child, from a container. */
|
|
1687
|
+
function selectTemplateContainerReference(container, key, bindings, shadowed, seen) {
|
|
1688
|
+
if (container.kind === "path") return key === void 0 ? collectExposedContainerCandidates(container.path, bindings) : collectExposedPathCandidate(appendTemplatePath(container.path, key), bindings);
|
|
1689
|
+
if (container.kind === "analysis") {
|
|
1690
|
+
const selected = key === void 0 ? container.values : container.members?.get(key);
|
|
1691
|
+
return selected ? {
|
|
1692
|
+
...selected,
|
|
1693
|
+
unknown: selected.unknown || key === void 0
|
|
1694
|
+
} : emptyDynamicSelectorAnalysis(true);
|
|
1695
|
+
}
|
|
1696
|
+
if (key === void 0) return collectLiteralContainerCandidates(container.node, bindings, shadowed, seen);
|
|
1697
|
+
const selected = selectTemplateLiteralMember(container.node, key);
|
|
1698
|
+
return selected ? collectTemplateSelectorValue(selected, bindings, shadowed, seen) : templateLiteralMemberIsUncertain(container.node, key) ? collectLiteralContainerCandidates(container.node, bindings, shadowed, seen) : emptyDynamicSelectorAnalysis(false);
|
|
1699
|
+
}
|
|
1700
|
+
/** Reads one exact flattened script path, retaining its value category. */
|
|
1701
|
+
function collectExposedPathCandidate(path, bindings) {
|
|
1702
|
+
const result = emptyDynamicSelectorAnalysis(false);
|
|
1703
|
+
const value = bindings.staticValues.get(path);
|
|
1704
|
+
if (typeof value === "string") result.candidates.push({
|
|
1705
|
+
kind: "string",
|
|
1706
|
+
name: value
|
|
1707
|
+
});
|
|
1708
|
+
const possibleStrings = bindings.possibleStaticStrings.get(path);
|
|
1709
|
+
if (possibleStrings) {
|
|
1710
|
+
for (const possible of possibleStrings) result.candidates.push({
|
|
1711
|
+
kind: "string",
|
|
1712
|
+
name: possible
|
|
1713
|
+
});
|
|
1714
|
+
result.unknown = possibleStrings.size > 0;
|
|
1715
|
+
}
|
|
1716
|
+
if (bindings.components.has(path) || bindings.vueBuiltins.has(path) || bindings.uncertainComponents.has(path)) result.candidates.push({
|
|
1717
|
+
kind: "expression",
|
|
1718
|
+
name: path
|
|
1719
|
+
});
|
|
1720
|
+
if (bindings.componentFactories.has(path)) {
|
|
1721
|
+
result.componentFactories.add(path);
|
|
1722
|
+
result.candidates.push({
|
|
1723
|
+
kind: "expression",
|
|
1724
|
+
name: path
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
if (bindings.gtComponentFactories.has(path)) result.gtComponentFactories.add(path);
|
|
1728
|
+
if (bindings.containerKinds.has(path)) result.containers.push({
|
|
1729
|
+
kind: "path",
|
|
1730
|
+
path
|
|
1731
|
+
});
|
|
1732
|
+
const possibleContainer = findPossibleGTContainerPath(path, bindings);
|
|
1733
|
+
if (possibleContainer) result.containers.push({
|
|
1734
|
+
kind: "path",
|
|
1735
|
+
path: possibleContainer
|
|
1736
|
+
});
|
|
1737
|
+
result.possibleGT ||= templatePathSelectsPossibleGT(path, bindings);
|
|
1738
|
+
result.unknown = result.unknown || bindings.uncertainComponents.has(path) || result.candidates.length === 0 && result.componentFactories.size === 0 && result.containers.length === 0;
|
|
1739
|
+
return result;
|
|
1740
|
+
}
|
|
1741
|
+
/** Returns true only for arrays whose shape was proven by static analysis. */
|
|
1742
|
+
function isKnownTemplateArray(container, bindings) {
|
|
1743
|
+
return container.kind === "literal" ? container.node.type === "ArrayExpression" : container.kind === "analysis" ? container.containerKind === "array" : bindings.containerKinds.get(container.path) === "array";
|
|
1744
|
+
}
|
|
1745
|
+
/** Detects a statically defined own property that overrides an array method. */
|
|
1746
|
+
function templateContainerHasOwnMember(container, member, bindings) {
|
|
1747
|
+
if (container.kind !== "path") return false;
|
|
1748
|
+
const path = appendTemplatePath(container.path, member);
|
|
1749
|
+
return bindings.components.has(path) || bindings.componentFactories.has(path) || bindings.containerKinds.has(path) || bindings.gtComponentFactories.has(path) || bindings.staticValues.has(path) || bindings.uncertainComponents.has(path) || bindings.vueBuiltins.has(path);
|
|
1750
|
+
}
|
|
1751
|
+
/** Resolves the exact child selected by an array method when it is static. */
|
|
1752
|
+
function readSelectingArrayMethodKey(method, argument, container, bindings, shadowed) {
|
|
1753
|
+
if (templateContainerHasOwnMember(container, method, bindings)) return null;
|
|
1754
|
+
const length = container.kind === "literal" ? container.node.type === "ArrayExpression" ? flattenTemplateLiteralArray(container.node, /* @__PURE__ */ new Set())?.length : void 0 : container.kind === "path" ? bindings.arrayLengths.get(container.path) : void 0;
|
|
1755
|
+
if (method === "shift") return length === 0 ? null : "0";
|
|
1756
|
+
if (method === "pop") return length === void 0 ? void 0 : length === 0 ? null : `${length - 1}`;
|
|
1757
|
+
if (method !== "at") return void 0;
|
|
1758
|
+
if (argument?.type === "ArgumentPlaceholder" || argument?.type === "SpreadElement") return;
|
|
1759
|
+
const indexValue = argument ? readTemplateStaticPrimitive(argument, bindings, shadowed) : {
|
|
1760
|
+
ok: true,
|
|
1761
|
+
value: 0
|
|
1762
|
+
};
|
|
1763
|
+
if (!indexValue.ok) return void 0;
|
|
1764
|
+
if (typeof indexValue.value === "bigint") return null;
|
|
1765
|
+
let index;
|
|
1766
|
+
try {
|
|
1767
|
+
index = Number(indexValue.value);
|
|
1768
|
+
} catch {
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1771
|
+
if (Number.isNaN(index)) index = 0;
|
|
1772
|
+
else if (!Number.isFinite(index)) return null;
|
|
1773
|
+
index = Math.trunc(index);
|
|
1774
|
+
if (index < 0) {
|
|
1775
|
+
if (length === void 0) return void 0;
|
|
1776
|
+
index += length;
|
|
1777
|
+
}
|
|
1778
|
+
if (index < 0 || length !== void 0 && index >= length) return null;
|
|
1779
|
+
return String(index);
|
|
1780
|
+
}
|
|
1781
|
+
/** Collects only the immediate values that one literal selection may return. */
|
|
1782
|
+
function collectLiteralContainerCandidates(container, bindings, shadowed, seen) {
|
|
1783
|
+
if (container.type === "ArrayExpression") {
|
|
1784
|
+
const flattened = flattenTemplateLiteralArray(container, /* @__PURE__ */ new Set());
|
|
1785
|
+
const analyses = (flattened ? flattened.filter((value) => Boolean(value)) : container.elements.flatMap((element) => !element ? [] : element.type === "SpreadElement" ? [] : [element])).map((value) => collectTemplateSelectorValue(value, bindings, shadowed, seen));
|
|
1786
|
+
if (!flattened) for (const element of container.elements) {
|
|
1787
|
+
if (element?.type !== "SpreadElement") continue;
|
|
1788
|
+
analyses.push(collectTemplateContainerCandidates(element.argument, bindings, shadowed, seen));
|
|
1789
|
+
}
|
|
1790
|
+
const result = mergeDynamicSelectorAnalyses(analyses);
|
|
1791
|
+
return {
|
|
1792
|
+
...result,
|
|
1793
|
+
unknown: result.unknown || !flattened
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
const properties = flattenTemplateLiteralObject(container, /* @__PURE__ */ new Set());
|
|
1797
|
+
const finalValues = /* @__PURE__ */ new Map();
|
|
1798
|
+
const uncertainValues = [];
|
|
1799
|
+
const spreadAnalyses = [];
|
|
1800
|
+
for (const property of properties ?? container.properties) {
|
|
1801
|
+
if (property.type === "SpreadElement") {
|
|
1802
|
+
spreadAnalyses.push(collectTemplateContainerCandidates(property.argument, bindings, shadowed, seen));
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
const key = readTemplateLiteralPropertyKey(property, bindings, shadowed);
|
|
1806
|
+
const value = property.type === "ObjectProperty" ? property.value : property.type === "ObjectMethod" && property.kind === "get" ? property : void 0;
|
|
1807
|
+
if (!value) continue;
|
|
1808
|
+
if (key === void 0) uncertainValues.push(value);
|
|
1809
|
+
else finalValues.set(key, value);
|
|
1810
|
+
}
|
|
1811
|
+
const analyses = [...finalValues.values(), ...uncertainValues].map((value) => collectTemplateSelectorValue(value, bindings, shadowed, seen));
|
|
1812
|
+
analyses.push(...spreadAnalyses);
|
|
1813
|
+
const result = mergeDynamicSelectorAnalyses(analyses);
|
|
1814
|
+
return {
|
|
1815
|
+
...result,
|
|
1816
|
+
unknown: result.unknown || properties === void 0 || uncertainValues.length > 0
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
/** Preserves a nested literal as a container instead of flattening its leaves. */
|
|
1820
|
+
function collectTemplateSelectorValue(node, bindings, shadowed, seen) {
|
|
1821
|
+
if (node.type === "ObjectMethod" && node.kind === "get") return {
|
|
1822
|
+
...mergeDynamicSelectorAnalyses(collectTemplateFunctionReturns(node).map((value) => collectTemplateSelectorValue(value, bindings, shadowed, seen))),
|
|
1823
|
+
unknown: true
|
|
1824
|
+
};
|
|
1825
|
+
const expression = unwrapExpression(node);
|
|
1826
|
+
if (expression?.type === "ArrayExpression" || expression?.type === "ObjectExpression") return {
|
|
1827
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1828
|
+
containers: [{
|
|
1829
|
+
kind: "literal",
|
|
1830
|
+
node: expression
|
|
1831
|
+
}]
|
|
1832
|
+
};
|
|
1833
|
+
const path = readStaticTemplateMemberPath(expression, bindings, shadowed);
|
|
1834
|
+
if (path && bindings.containerKinds.has(path)) return {
|
|
1835
|
+
...emptyDynamicSelectorAnalysis(false),
|
|
1836
|
+
containers: [{
|
|
1837
|
+
kind: "path",
|
|
1838
|
+
path
|
|
1839
|
+
}]
|
|
1840
|
+
};
|
|
1841
|
+
return expression ? collectDynamicSelectorCandidates(expression, bindings, shadowed, seen) : emptyDynamicSelectorAnalysis(true);
|
|
1842
|
+
}
|
|
1843
|
+
/** Collects returns from one function body without entering nested functions. */
|
|
1844
|
+
function collectTemplateCallableReturnAnalysis(fn, bindings, shadowed, seen) {
|
|
1845
|
+
return mergeDynamicSelectorAnalyses(collectTemplateFunctionReturns(fn).map((value) => collectTemplateSelectorValue(value, bindings, shadowed, seen)));
|
|
1846
|
+
}
|
|
1847
|
+
function collectTemplateFunctionReturns(fn) {
|
|
1848
|
+
if (fn.type === "ArrowFunctionExpression" && fn.body.type !== "BlockStatement") return [fn.body];
|
|
1849
|
+
const returns = [];
|
|
1850
|
+
const visit = (node) => {
|
|
1851
|
+
if (node !== fn && babel.isFunction(node)) return;
|
|
1852
|
+
if (node.type === "ReturnStatement") {
|
|
1853
|
+
if (node.argument && babel.isExpression(node.argument)) returns.push(node.argument);
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
for (const key of babel.VISITOR_KEYS[node.type] ?? []) {
|
|
1857
|
+
const child = node[key];
|
|
1858
|
+
if (Array.isArray(child)) {
|
|
1859
|
+
for (const entry of child) if (entry && typeof entry === "object" && "type" in entry) visit(entry);
|
|
1860
|
+
} else if (child && typeof child === "object" && "type" in child) visit(child);
|
|
1861
|
+
}
|
|
1862
|
+
};
|
|
1863
|
+
visit(fn);
|
|
1864
|
+
return returns;
|
|
1865
|
+
}
|
|
1866
|
+
/** Expands literal-only object spreads while preserving property write order. */
|
|
1867
|
+
function flattenTemplateLiteralObject(object, seen) {
|
|
1868
|
+
if (seen.has(object)) return void 0;
|
|
1869
|
+
const nextSeen = new Set(seen).add(object);
|
|
1870
|
+
const properties = [];
|
|
1871
|
+
for (const property of object.properties) {
|
|
1872
|
+
if (property.type !== "SpreadElement") {
|
|
1873
|
+
properties.push(property);
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
const argument = unwrapExpression(property.argument);
|
|
1877
|
+
if (argument?.type !== "ObjectExpression") return void 0;
|
|
1878
|
+
const spread = flattenTemplateLiteralObject(argument, nextSeen);
|
|
1879
|
+
if (!spread) return void 0;
|
|
1880
|
+
properties.push(...spread);
|
|
1881
|
+
}
|
|
1882
|
+
return properties;
|
|
1883
|
+
}
|
|
1884
|
+
/** Resolves a literal object key without executing user code. */
|
|
1885
|
+
function readTemplateLiteralPropertyKey(property, bindings, shadowed) {
|
|
1886
|
+
if (property.type === "SpreadElement") return void 0;
|
|
1887
|
+
if (!property.computed && property.key.type === "Identifier") return property.key.name;
|
|
1888
|
+
if (property.key.type === "StringLiteral") return property.key.value;
|
|
1889
|
+
if (property.key.type === "NumericLiteral") return String(property.key.value);
|
|
1890
|
+
if (!property.computed) return void 0;
|
|
1891
|
+
const key = readTemplateStaticPrimitive(property.key, bindings, shadowed);
|
|
1892
|
+
return key.ok && (typeof key.value === "string" || typeof key.value === "number") ? String(key.value) : void 0;
|
|
1893
|
+
}
|
|
1894
|
+
/** Collects exact component-bearing descendants exposed by script analysis. */
|
|
1895
|
+
function collectExposedContainerCandidates(path, bindings) {
|
|
1896
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1897
|
+
const componentFactories = /* @__PURE__ */ new Set();
|
|
1898
|
+
const containers = [];
|
|
1899
|
+
const gtComponentFactories = /* @__PURE__ */ new Set();
|
|
1900
|
+
const prefix = `${path}.`;
|
|
1901
|
+
const isImmediateChild = (name) => {
|
|
1902
|
+
if (!name.startsWith(prefix)) return false;
|
|
1903
|
+
return !name.slice(prefix.length).includes(".");
|
|
1904
|
+
};
|
|
1905
|
+
const addExpression = (name) => {
|
|
1906
|
+
if (!isImmediateChild(name)) return;
|
|
1907
|
+
candidates.set(`expression:${name}`, {
|
|
1908
|
+
kind: "expression",
|
|
1909
|
+
name
|
|
1910
|
+
});
|
|
1911
|
+
};
|
|
1912
|
+
for (const name of bindings.components.keys()) addExpression(name);
|
|
1913
|
+
for (const name of bindings.vueBuiltins.keys()) addExpression(name);
|
|
1914
|
+
for (const name of bindings.uncertainComponents) addExpression(name);
|
|
1915
|
+
for (const [name, value] of bindings.staticValues) if (isImmediateChild(name) && typeof value === "string") candidates.set(`string:${value}`, {
|
|
1916
|
+
kind: "string",
|
|
1917
|
+
name: value
|
|
1918
|
+
});
|
|
1919
|
+
for (const [name, values] of bindings.possibleStaticStrings) {
|
|
1920
|
+
if (!isImmediateChild(name)) continue;
|
|
1921
|
+
for (const value of values) candidates.set(`string:${value}`, {
|
|
1922
|
+
kind: "string",
|
|
1923
|
+
name: value
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
for (const name of bindings.componentFactories) {
|
|
1927
|
+
if (!isImmediateChild(name)) continue;
|
|
1928
|
+
componentFactories.add(name);
|
|
1929
|
+
addExpression(name);
|
|
1930
|
+
}
|
|
1931
|
+
for (const name of bindings.gtComponentFactories) {
|
|
1932
|
+
if (!isImmediateChild(name)) continue;
|
|
1933
|
+
gtComponentFactories.add(name);
|
|
1934
|
+
addExpression(name);
|
|
1935
|
+
}
|
|
1936
|
+
for (const [name] of bindings.containerKinds) if (isImmediateChild(name)) containers.push({
|
|
1937
|
+
kind: "path",
|
|
1938
|
+
path: name
|
|
1939
|
+
});
|
|
1940
|
+
const addNestedContainer = (name, requireNestedChild) => {
|
|
1941
|
+
if (!name.startsWith(prefix)) return;
|
|
1942
|
+
const suffix = name.slice(prefix.length);
|
|
1943
|
+
if (requireNestedChild && !suffix.includes(".")) return;
|
|
1944
|
+
const firstSegment = suffix.split(".", 1)[0];
|
|
1945
|
+
if (!firstSegment) return;
|
|
1946
|
+
const childPath = `${prefix}${firstSegment}`;
|
|
1947
|
+
if (!containers.some((container) => container.kind === "path" && container.path === childPath)) containers.push({
|
|
1948
|
+
kind: "path",
|
|
1949
|
+
path: childPath
|
|
1950
|
+
});
|
|
1951
|
+
};
|
|
1952
|
+
for (const name of bindings.possibleGTContainers) addNestedContainer(name, false);
|
|
1953
|
+
for (const name of bindings.possibleStaticStrings.keys()) addNestedContainer(name, true);
|
|
1954
|
+
return {
|
|
1955
|
+
candidates: [...candidates.values()],
|
|
1956
|
+
componentFactories,
|
|
1957
|
+
containers,
|
|
1958
|
+
gtComponentFactories,
|
|
1959
|
+
possibleGT: findPossibleGTContainerPath(path, bindings) !== void 0,
|
|
1960
|
+
unknown: true
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
/** Returns whether selecting one direct child of a tainted path can yield T. */
|
|
1964
|
+
function templatePathSelectsPossibleGT(path, bindings) {
|
|
1965
|
+
const separator = path.lastIndexOf(".");
|
|
1966
|
+
return separator > 0 && findPossibleGTContainerPath(path.slice(0, separator), bindings) !== void 0;
|
|
1967
|
+
}
|
|
1968
|
+
/** Finds an exact or wildcard tainted-container path. */
|
|
1969
|
+
function findPossibleGTContainerPath(path, bindings) {
|
|
1970
|
+
if (bindings.possibleGTContainers.has(path)) return path;
|
|
1971
|
+
const wildcard = appendTemplatePath("", unknownTemplatePathSegment).slice(1);
|
|
1972
|
+
const recursiveSuffix = appendTemplatePath("", recursiveTemplatePathSegment);
|
|
1973
|
+
const segments = path.split(".");
|
|
1974
|
+
for (const candidate of bindings.possibleGTContainers) {
|
|
1975
|
+
if (candidate.endsWith(recursiveSuffix)) {
|
|
1976
|
+
const recursiveRoot = candidate.slice(0, -recursiveSuffix.length);
|
|
1977
|
+
if (path === recursiveRoot || path.startsWith(`${recursiveRoot}.`)) return candidate;
|
|
1978
|
+
}
|
|
1979
|
+
const candidateSegments = candidate.split(".");
|
|
1980
|
+
if (candidateSegments.length === segments.length && candidateSegments.every((segment, index) => segment === wildcard || segment === segments[index])) return candidate;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
/** Returns whether one selected child contains a tainted container path. */
|
|
1984
|
+
function hasImmediatePossibleGTContainerChild(path, bindings) {
|
|
1985
|
+
const prefix = `${path}.`;
|
|
1986
|
+
return [...bindings.possibleGTContainers].some((candidate) => candidate.startsWith(prefix));
|
|
1987
|
+
}
|
|
1988
|
+
/** Returns whether one selected child can expose a known selector string. */
|
|
1989
|
+
function hasPossibleStaticStringChild(path, bindings) {
|
|
1990
|
+
const prefix = `${path}.`;
|
|
1991
|
+
return [...bindings.possibleStaticStrings].some(([candidate, values]) => candidate.startsWith(prefix) && values.size > 0);
|
|
1992
|
+
}
|
|
1993
|
+
/** Selects through nested literal member chains in a template expression. */
|
|
1994
|
+
function selectTemplateStaticMemberExpression(node, key, bindings, shadowed, seen) {
|
|
1995
|
+
const expression = unwrapExpression(node);
|
|
1996
|
+
if (!expression || seen.has(expression)) return void 0;
|
|
1997
|
+
const nextSeen = new Set(seen).add(expression);
|
|
1998
|
+
if (expression.type === "MemberExpression" || expression.type === "OptionalMemberExpression") {
|
|
1999
|
+
const parentKey = readStaticTemplateMemberProperty(expression, bindings, shadowed);
|
|
2000
|
+
const parent = parentKey === void 0 ? void 0 : selectTemplateStaticMemberExpression(expression.object, parentKey, bindings, shadowed, nextSeen);
|
|
2001
|
+
return parent ? selectTemplateStaticMemberExpression(parent, key, bindings, shadowed, nextSeen) : void 0;
|
|
2002
|
+
}
|
|
2003
|
+
return selectTemplateLiteralMember(expression, key);
|
|
2004
|
+
}
|
|
2005
|
+
function selectorAnalysisIsKnownComponent(analysis, bindings) {
|
|
2006
|
+
return !analysis.unknown && analysis.componentFactories.size === 0 && analysis.candidates.length > 0 && analysis.candidates.every((candidate) => [...normalizeTemplateBindingNames(candidate.name)].some((name) => candidate.kind === "string" ? bindings.registeredComponents.has(name) || bindings.registeredVueBuiltins.has(name) : bindings.components.has(name) || bindings.vueBuiltins.has(name)));
|
|
2007
|
+
}
|
|
2008
|
+
function selectorCandidate(kind, name) {
|
|
2009
|
+
return {
|
|
2010
|
+
candidates: [{
|
|
2011
|
+
kind,
|
|
2012
|
+
name
|
|
2013
|
+
}],
|
|
2014
|
+
componentFactories: /* @__PURE__ */ new Set(),
|
|
2015
|
+
containers: [],
|
|
2016
|
+
gtComponentFactories: /* @__PURE__ */ new Set(),
|
|
2017
|
+
possibleGT: false,
|
|
2018
|
+
unknown: false
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
function emptyDynamicSelectorAnalysis(unknown) {
|
|
2022
|
+
return {
|
|
2023
|
+
candidates: [],
|
|
2024
|
+
componentFactories: /* @__PURE__ */ new Set(),
|
|
2025
|
+
containers: [],
|
|
2026
|
+
gtComponentFactories: /* @__PURE__ */ new Set(),
|
|
2027
|
+
possibleGT: false,
|
|
2028
|
+
unknown
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
function mergeDynamicSelectorAnalyses(analyses) {
|
|
2032
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
2033
|
+
const componentFactories = /* @__PURE__ */ new Set();
|
|
2034
|
+
const containers = [];
|
|
2035
|
+
const containerPaths = /* @__PURE__ */ new Set();
|
|
2036
|
+
const literalContainers = /* @__PURE__ */ new Set();
|
|
2037
|
+
const abstractContainers = /* @__PURE__ */ new Set();
|
|
2038
|
+
const gtComponentFactories = /* @__PURE__ */ new Set();
|
|
2039
|
+
let possibleGT = false;
|
|
2040
|
+
let unknown = false;
|
|
2041
|
+
for (const analysis of analyses) {
|
|
2042
|
+
possibleGT ||= analysis.possibleGT;
|
|
2043
|
+
unknown ||= analysis.unknown;
|
|
2044
|
+
for (const candidate of analysis.candidates) candidates.set(`${candidate.kind}:${candidate.name}`, candidate);
|
|
2045
|
+
for (const name of analysis.componentFactories) componentFactories.add(name);
|
|
2046
|
+
for (const container of analysis.containers) {
|
|
2047
|
+
if (container.kind === "path") {
|
|
2048
|
+
if (containerPaths.has(container.path)) continue;
|
|
2049
|
+
containerPaths.add(container.path);
|
|
2050
|
+
} else if (container.kind === "literal") {
|
|
2051
|
+
if (literalContainers.has(container.node)) continue;
|
|
2052
|
+
literalContainers.add(container.node);
|
|
2053
|
+
} else {
|
|
2054
|
+
if (abstractContainers.has(container)) continue;
|
|
2055
|
+
abstractContainers.add(container);
|
|
2056
|
+
}
|
|
2057
|
+
containers.push(container);
|
|
2058
|
+
}
|
|
2059
|
+
for (const name of analysis.gtComponentFactories) gtComponentFactories.add(name);
|
|
2060
|
+
}
|
|
2061
|
+
return {
|
|
2062
|
+
candidates: [...candidates.values()],
|
|
2063
|
+
componentFactories,
|
|
2064
|
+
containers,
|
|
2065
|
+
gtComponentFactories,
|
|
2066
|
+
possibleGT,
|
|
2067
|
+
unknown
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
/** Selects a literal array/object member used directly in a template. */
|
|
2071
|
+
function selectTemplateLiteralMember(node, key) {
|
|
2072
|
+
const expression = unwrapExpression(node);
|
|
2073
|
+
if (expression?.type === "ArrayExpression") {
|
|
2074
|
+
const index = key.match(/^(0|[1-9]\d*)$/) ? Number(key) : void 0;
|
|
2075
|
+
const elements = flattenTemplateLiteralArray(expression, /* @__PURE__ */ new Set());
|
|
2076
|
+
return index === void 0 ? void 0 : elements?.[index];
|
|
2077
|
+
}
|
|
2078
|
+
if (expression?.type !== "ObjectExpression") return void 0;
|
|
2079
|
+
const properties = flattenTemplateLiteralObject(expression, /* @__PURE__ */ new Set()) ?? expression.properties;
|
|
2080
|
+
let selected;
|
|
2081
|
+
let uncertainWriteAfterSelection = false;
|
|
2082
|
+
for (const property of properties) {
|
|
2083
|
+
if (property.type === "SpreadElement") {
|
|
2084
|
+
uncertainWriteAfterSelection = true;
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2087
|
+
const propertyKey = !property.computed && property.key.type === "Identifier" ? property.key.name : property.key.type === "StringLiteral" ? property.key.value : property.key.type === "NumericLiteral" ? String(property.key.value) : void 0;
|
|
2088
|
+
if (propertyKey === void 0) {
|
|
2089
|
+
uncertainWriteAfterSelection = true;
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
if (propertyKey === key) {
|
|
2093
|
+
selected = property.type === "ObjectProperty" || property.type === "ObjectMethod" ? property.type === "ObjectProperty" ? property.value : property : void 0;
|
|
2094
|
+
uncertainWriteAfterSelection = false;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return uncertainWriteAfterSelection ? void 0 : selected;
|
|
2098
|
+
}
|
|
2099
|
+
/** Returns whether an unresolved literal member can be supplied by a spread. */
|
|
2100
|
+
function templateLiteralMemberIsUncertain(node, key) {
|
|
2101
|
+
if (node.type === "ArrayExpression") return /^(0|[1-9]\d*)$/.test(key) && flattenTemplateLiteralArray(node, /* @__PURE__ */ new Set()) === void 0;
|
|
2102
|
+
if (flattenTemplateLiteralObject(node, /* @__PURE__ */ new Set())) return false;
|
|
2103
|
+
let uncertain = false;
|
|
2104
|
+
for (const property of node.properties) {
|
|
2105
|
+
if (property.type === "SpreadElement") {
|
|
2106
|
+
uncertain = true;
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
const propertyKey = !property.computed && property.key.type === "Identifier" ? property.key.name : property.key.type === "StringLiteral" ? property.key.value : property.key.type === "NumericLiteral" ? String(property.key.value) : void 0;
|
|
2110
|
+
if (propertyKey === void 0) uncertain = true;
|
|
2111
|
+
else if (propertyKey === key) uncertain = false;
|
|
2112
|
+
}
|
|
2113
|
+
return uncertain;
|
|
2114
|
+
}
|
|
2115
|
+
/** Flattens array spreads only when every spread operand is another literal. */
|
|
2116
|
+
function flattenTemplateLiteralArray(array, seen) {
|
|
2117
|
+
if (seen.has(array)) return void 0;
|
|
2118
|
+
const nextSeen = new Set(seen).add(array);
|
|
2119
|
+
const elements = [];
|
|
2120
|
+
for (const element of array.elements) {
|
|
2121
|
+
if (!element) {
|
|
2122
|
+
elements.push(void 0);
|
|
2123
|
+
continue;
|
|
2124
|
+
}
|
|
2125
|
+
if (element.type !== "SpreadElement") {
|
|
2126
|
+
elements.push(element);
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
const argument = unwrapExpression(element.argument);
|
|
2130
|
+
if (argument?.type !== "ArrayExpression") return void 0;
|
|
2131
|
+
const spread = flattenTemplateLiteralArray(argument, nextSeen);
|
|
2132
|
+
if (!spread) return void 0;
|
|
2133
|
+
elements.push(...spread);
|
|
2134
|
+
}
|
|
2135
|
+
return elements;
|
|
2136
|
+
}
|
|
2137
|
+
/** Resolves a computed member key from literals or exposed static bindings. */
|
|
2138
|
+
function readStaticTemplateMemberProperty(node, bindings, shadowed) {
|
|
2139
|
+
if (!node.computed && node.property.type === "Identifier") return node.property.name;
|
|
2140
|
+
if (!node.computed) return void 0;
|
|
2141
|
+
const property = readTemplateStaticPrimitive(node.property, bindings, shadowed);
|
|
2142
|
+
return property.ok && (typeof property.value === "string" || typeof property.value === "number") ? String(property.value) : void 0;
|
|
2143
|
+
}
|
|
2144
|
+
function readTemplateStaticPrimitive(node, bindings, shadowed, allowNumericGlobals = false) {
|
|
2145
|
+
return readStaticPrimitive(node, (identifier) => {
|
|
2146
|
+
if (shadowed.has(identifier.name)) return { ok: false };
|
|
2147
|
+
if (bindings.staticValues.has(identifier.name)) return {
|
|
2148
|
+
ok: true,
|
|
2149
|
+
value: bindings.staticValues.get(identifier.name)
|
|
2150
|
+
};
|
|
2151
|
+
if (allowNumericGlobals && !bindings.directBindings.has(identifier.name) && identifier.name === "Infinity") return {
|
|
2152
|
+
ok: true,
|
|
2153
|
+
value: Number.POSITIVE_INFINITY
|
|
2154
|
+
};
|
|
2155
|
+
if (allowNumericGlobals && !bindings.directBindings.has(identifier.name) && identifier.name === "NaN") return {
|
|
2156
|
+
ok: true,
|
|
2157
|
+
value: NaN
|
|
2158
|
+
};
|
|
2159
|
+
return { ok: false };
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
/** Reads a dotted template member chain without evaluating runtime code. */
|
|
2163
|
+
function readStaticTemplateMemberPath(node, bindings, shadowed) {
|
|
2164
|
+
const expression = unwrapExpression(node);
|
|
2165
|
+
if (!expression) return void 0;
|
|
2166
|
+
if (expression.type === "Identifier") return shadowed.has(expression.name) ? void 0 : expression.name;
|
|
2167
|
+
if (expression.type !== "MemberExpression" && expression.type !== "OptionalMemberExpression") return;
|
|
2168
|
+
const object = readStaticTemplateMemberPath(expression.object, bindings, shadowed);
|
|
2169
|
+
const property = readStaticTemplateMemberProperty(expression, bindings, shadowed);
|
|
2170
|
+
return object && property !== void 0 ? appendTemplatePath(object, property) : void 0;
|
|
2171
|
+
}
|
|
2172
|
+
/** Identifies the selector prop that Vue consumes for `<component>`. */
|
|
2173
|
+
function isDynamicComponentSelector(element, property) {
|
|
2174
|
+
if (element.tag.toLowerCase() !== "component") return false;
|
|
2175
|
+
if (property.type === NodeTypes.ATTRIBUTE) return property.name === "is";
|
|
2176
|
+
return property.name === "bind" && readDirectiveKey(property) === "is";
|
|
2177
|
+
}
|
|
2178
|
+
function readDirectiveKey(directive) {
|
|
2179
|
+
return directive.arg?.type === NodeTypes.SIMPLE_EXPRESSION && directive.arg.isStatic ? directive.arg.content : void 0;
|
|
2180
|
+
}
|
|
2181
|
+
function toDisplayString(value) {
|
|
2182
|
+
return value == null ? "" : String(value);
|
|
2183
|
+
}
|
|
2184
|
+
function appendSerializedChild(result, value) {
|
|
2185
|
+
const previous = result[result.length - 1];
|
|
2186
|
+
if (typeof previous === "string" && typeof value === "string") result[result.length - 1] = previous + value;
|
|
2187
|
+
else result.push(value);
|
|
2188
|
+
}
|
|
2189
|
+
function collapseChildren(children) {
|
|
2190
|
+
return children.length === 1 ? children[0] : children;
|
|
2191
|
+
}
|
|
2192
|
+
function unionSets(first, second) {
|
|
2193
|
+
return second.size === 0 ? first : new Set([...first, ...second]);
|
|
2194
|
+
}
|
|
2195
|
+
//#endregion
|
|
2196
|
+
export { parseVueTemplate };
|
|
2197
|
+
|
|
2198
|
+
//# sourceMappingURL=template.js.map
|