}"
`;
+exports[`cache multiple access > object property name substring cases 1`] = `
+"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ _renderEffect(() => {
+ const _p = _ctx.p
+ const _p_title = _p.title
+ _setProp(n0, "id", _p_title + _p.titles + _p_title)
+ })
+ return n0
+}"
+`;
+
exports[`cache multiple access > repeated expression in expressions 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
}"
`;
+exports[`cache multiple access > variable name substring edge cases 1`] = `
+"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ _renderEffect(() => {
+ const _title = _ctx.title
+ _setProp(n0, "id", _title + _ctx.titles + _title)
+ })
+ return n0
+}"
+`;
+
exports[`compiler v-bind > .attr modifier 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
function analyzeExpressions(expressions: SimpleExpressionNode[]) {
const seenVariable: Record<string, number> = Object.create(null)
const variableToExpMap = new Map<string, Set<SimpleExpressionNode>>()
- const expToVariableMap = new Map<SimpleExpressionNode, string[]>()
+ const expToVariableMap = new Map<
+ SimpleExpressionNode,
+ Array<{
+ name: string
+ loc?: { start: number; end: number }
+ }>
+ >()
const seenIdentifier = new Set<string>()
const updatedVariable = new Set<string>()
name: string,
exp: SimpleExpressionNode,
isIdentifier: boolean,
+ loc?: { start: number; end: number },
parentStack: Node[] = [],
) => {
if (isIdentifier) seenIdentifier.add(name)
name,
(variableToExpMap.get(name) || new Set()).add(exp),
)
- expToVariableMap.set(exp, (expToVariableMap.get(exp) || []).concat(name))
+
+ const variables = expToVariableMap.get(exp) || []
+ variables.push({ name, loc })
+ expToVariableMap.set(exp, variables)
+
if (
parentStack.some(
p => p.type === 'UpdateExpression' || p.type === 'AssignmentExpression',
walkIdentifiers(exp.ast, (currentNode, parent, parentStack) => {
if (parent && isMemberExpression(parent)) {
- const memberExp = extractMemberExpression(parent, name => {
- registerVariable(name, exp, true)
+ const memberExp = extractMemberExpression(parent, id => {
+ registerVariable(id.name, exp, true, {
+ start: id.start!,
+ end: id.end!,
+ })
})
- registerVariable(memberExp, exp, false, parentStack)
+ registerVariable(
+ memberExp,
+ exp,
+ false,
+ { start: parent.start!, end: parent.end! },
+ parentStack,
+ )
} else if (!parentStack.some(isMemberExpression)) {
- registerVariable(currentNode.name, exp, true, parentStack)
+ registerVariable(
+ currentNode.name,
+ exp,
+ true,
+ { start: currentNode.start!, end: currentNode.end! },
+ parentStack,
+ )
}
})
}
context: CodegenContext,
seenVariable: Record<string, number>,
variableToExpMap: Map<string, Set<SimpleExpressionNode>>,
- expToVariableMap: Map<SimpleExpressionNode, string[]>,
+ expToVariableMap: Map<
+ SimpleExpressionNode,
+ Array<{ name: string; loc?: { start: number; end: number } }>
+ >,
seenIdentifier: Set<string>,
updatedVariable: Set<string>,
): DeclarationValue[] {
const declarations: DeclarationValue[] = []
+ const expToReplacementMap = new Map<
+ SimpleExpressionNode,
+ Array<{
+ name: string
+ locs: { start: number; end: number }[]
+ }>
+ >()
+
for (const [name, exps] of variableToExpMap) {
if (updatedVariable.has(name)) continue
if (seenVariable[name] > 1 && exps.size > 0) {
// e.g., foo[baz] -> foo_baz.
// for identifiers, we don't need to replace the content - they will be
// replaced during context.withId(..., ids)
- const replaceRE = new RegExp(escapeRegExp(name), 'g')
exps.forEach(node => {
- if (node.ast) {
- node.content = node.content.replace(replaceRE, varName)
- // re-parse the expression
- node.ast = parseExp(context, node.content)
+ if (node.ast && varName !== name) {
+ const replacements = expToReplacementMap.get(node) || []
+ replacements.push({
+ name: varName,
+ locs: expToVariableMap.get(node)!.reduce(
+ (locs, v) => {
+ if (v.name === name && v.loc) locs.push(v.loc)
+ return locs
+ },
+ [] as { start: number; end: number }[],
+ ),
+ })
+ expToReplacementMap.set(node, replacements)
}
})
}
}
+ for (const [exp, replacements] of expToReplacementMap) {
+ replacements
+ .flatMap(({ name, locs }) =>
+ locs.map(({ start, end }) => ({ start, end, name })),
+ )
+ .sort((a, b) => b.end - a.end)
+ .forEach(({ start, end, name }) => {
+ exp.content =
+ exp.content.slice(0, start - 1) + name + exp.content.slice(end - 1)
+ })
+
+ // re-parse the expression
+ exp.ast = parseExp(context, exp.content)
+ }
+
return declarations
}
function shouldDeclareVariable(
name: string,
- expToVariableMap: Map<SimpleExpressionNode, string[]>,
+ expToVariableMap: Map<
+ SimpleExpressionNode,
+ Array<{ name: string; loc?: { start: number; end: number } }>
+ >,
exps: Set<SimpleExpressionNode>,
): boolean {
- const vars = Array.from(exps, exp => expToVariableMap.get(exp)!)
+ const vars = Array.from(exps, exp =>
+ expToVariableMap.get(exp)!.map(v => v.name),
+ )
// assume name equals to `foo`
// if each expression only references `foo`, declaration is needed
// to avoid reactivity tracking
expressions: SimpleExpressionNode[],
varDeclarations: DeclarationValue[],
updatedVariable: Set<string>,
- expToVariableMap: Map<SimpleExpressionNode, string[]>,
+ expToVariableMap: Map<
+ SimpleExpressionNode,
+ Array<{ name: string; loc?: { start: number; end: number } }>
+ >,
): DeclarationValue[] {
const declarations: DeclarationValue[] = []
const seenExp = expressions.reduce(
(acc, exp) => {
- const variables = expToVariableMap.get(exp)
+ const variables = expToVariableMap.get(exp)!.map(v => v.name)
// only handle expressions that are not identifiers
if (
exp.ast &&
function extractMemberExpression(
exp: Node,
- onIdentifier: (name: string) => void,
+ onIdentifier: (id: Identifier) => void,
): string {
if (!exp) return ''
switch (exp.type) {
case 'Identifier': // foo[bar]
- onIdentifier(exp.name)
+ onIdentifier(exp)
return exp.name
case 'StringLiteral': // foo['bar']
return exp.extra ? (exp.extra.raw as string) : exp.value