diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 2c9809a2..6e80abed 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -308,6 +308,59 @@ describe('compiler: expression transform', () => { }) }) + test('should prefix default value of a function expression param', () => { + const node = parseWithExpressionTransform( + `{{ (foo = baz) => foo + bar }}` + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `(`, + { content: `foo` }, + ` = `, + { content: `_ctx.baz` }, + `) => `, + { content: `foo` }, + ` + `, + { content: `_ctx.bar` } + ] + }) + }) + + test('should not prefix function param destructuring', () => { + const node = parseWithExpressionTransform( + `{{ ({ foo }) => foo + bar }}` + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `({ foo }) => `, + { content: `foo` }, + ` + `, + { content: `_ctx.bar` } + ] + }) + }) + + test('should prefix default value of function param destructuring', () => { + const node = parseWithExpressionTransform( + `{{ ({ foo = bar }) => foo + bar }}` + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `({ `, + { content: `foo` }, + ` = `, + { content: `_ctx.bar` }, + ` }) => `, + { content: `foo` }, + ` + `, + { content: `_ctx.bar` } + ] + }) + }) + test('should not prefix an object property key', () => { const node = parseWithExpressionTransform( `{{ { foo: bar } }}` diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 75558070..35a076cb 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -20,7 +20,7 @@ import { createCompoundExpression } from '../ast' import { Node, Function, Identifier, Property } from 'estree' -import { advancePositionWithClone } from '../utils' +import { advancePositionWithClone, isSimpleIdentifier } from '../utils' export const transformExpression: NodeTransform = (node, context) => { if (node.type === NodeTypes.INTERPOLATION) { @@ -31,19 +31,19 @@ export const transformExpression: NodeTransform = (node, context) => { } else if (node.type === NodeTypes.ELEMENT) { // handle directives on element for (let i = 0; i < node.props.length; i++) { - const prop = node.props[i] - if (prop.type === NodeTypes.DIRECTIVE) { - const exp = prop.exp as SimpleExpressionNode | undefined - const arg = prop.arg as SimpleExpressionNode | undefined + const dir = node.props[i] + if (dir.type === NodeTypes.DIRECTIVE) { + const exp = dir.exp as SimpleExpressionNode | undefined + const arg = dir.arg as SimpleExpressionNode | undefined if (exp) { - prop.exp = processExpression(exp, context) + dir.exp = processExpression(exp, context, dir.name === 'slot') } if (arg && !arg.isStatic) { - if (prop.name === 'class') { + if (dir.name === 'class') { // TODO special expression optimization for classes - prop.arg = processExpression(arg, context) + dir.arg = processExpression(arg, context) } else { - prop.arg = processExpression(arg, context) + dir.arg = processExpression(arg, context) } } } @@ -51,14 +51,6 @@ export const transformExpression: NodeTransform = (node, context) => { } } -const simpleIdRE = /^[a-zA-Z$_][\w$]*$/ - -const isFunction = (node: Node): node is Function => - /Function(Expression|Declaration)$/.test(node.type) - -const isPropertyKey = (node: Node, parent: Node) => - parent.type === 'Property' && parent.key === node && !parent.computed - // cache node requires let _parseScript: typeof parseScript let _walk: typeof walk @@ -74,14 +66,15 @@ interface PrefixMeta { // tree-shaken from the browser build. export function processExpression( node: SimpleExpressionNode, - context: TransformContext + context: TransformContext, + asParams: boolean = false ): ExpressionNode { if (!context.prefixIdentifiers) { return node } // fast path if expression is a simple identifier. - if (simpleIdRE.test(node.content)) { + if (isSimpleIdentifier(node.content)) { if (!context.identifiers[node.content]) { node.content = `_ctx.${node.content}` } @@ -95,8 +88,11 @@ export function processExpression( const walk = _walk || (_walk = require('estree-walker').walk) let ast + // if the expression is supposed to be used in a function params position + // we need to parse it differently. + const source = `(${node.content})${asParams ? `=>{}` : ``}` try { - ast = parseScript(`(${node.content})`, { ranges: true }) as any + ast = parseScript(source, { ranges: true }) as any } catch (e) { context.onError(e) return node @@ -132,8 +128,17 @@ export function processExpression( // so that we don't prefix them node.params.forEach(p => walk(p, { - enter(child) { - if (child.type === 'Identifier') { + enter(child, parent) { + if ( + child.type === 'Identifier' && + !// do not keep as scope variable if this is a default value + // assignment of a param + ( + parent && + parent.type === 'AssignmentPattern' && + parent.right === child + ) + ) { knownIds[child.name] = true ;( (node as any)._scopeIds || @@ -187,6 +192,12 @@ export function processExpression( } } +const isFunction = (node: Node): node is Function => + /Function(Expression|Declaration)$/.test(node.type) + +const isPropertyKey = (node: Node, parent: Node) => + parent.type === 'Property' && parent.key === node && !parent.computed + const globals = new Set( ( 'Infinity,undefined,NaN,isFinite,isNaN,' +