mirror of https://github.com/vuejs/core.git
				
				
				
			feat(compiler-core): support v-bind shorthand for key and value with the same name (#9451)
This commit is contained in:
		
							parent
							
								
									48b47a1ab6
								
							
						
					
					
						commit
						26399aa6fa
					
				|  | @ -72,6 +72,60 @@ describe('compiler: transform v-bind', () => { | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|  |   test('no expression', () => { | ||||||
|  |     const node = parseWithVBind(`<div v-bind:id />`) | ||||||
|  |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|  |     expect(props.properties[0]).toMatchObject({ | ||||||
|  |       key: { | ||||||
|  |         content: `id`, | ||||||
|  |         isStatic: true, | ||||||
|  |         loc: { | ||||||
|  |           start: { | ||||||
|  |             line: 1, | ||||||
|  |             column: 13, | ||||||
|  |             offset: 12 | ||||||
|  |           }, | ||||||
|  |           end: { | ||||||
|  |             line: 1, | ||||||
|  |             column: 15, | ||||||
|  |             offset: 14 | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       value: { | ||||||
|  |         content: `id`, | ||||||
|  |         isStatic: false, | ||||||
|  |         loc: { | ||||||
|  |           start: { | ||||||
|  |             line: 1, | ||||||
|  |             column: 1, | ||||||
|  |             offset: 0 | ||||||
|  |           }, | ||||||
|  |           end: { | ||||||
|  |             line: 1, | ||||||
|  |             column: 1, | ||||||
|  |             offset: 0 | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   test('no expression (shorthand)', () => { | ||||||
|  |     const node = parseWithVBind(`<div :id />`) | ||||||
|  |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|  |     expect(props.properties[0]).toMatchObject({ | ||||||
|  |       key: { | ||||||
|  |         content: `id`, | ||||||
|  |         isStatic: true | ||||||
|  |       }, | ||||||
|  |       value: { | ||||||
|  |         content: `id`, | ||||||
|  |         isStatic: false | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|   test('dynamic arg', () => { |   test('dynamic arg', () => { | ||||||
|     const node = parseWithVBind(`<div v-bind:[id]="id"/>`) |     const node = parseWithVBind(`<div v-bind:[id]="id"/>`) | ||||||
|     const props = (node.codegenNode as VNodeCall).props as CallExpression |     const props = (node.codegenNode as VNodeCall).props as CallExpression | ||||||
|  | @ -98,9 +152,9 @@ describe('compiler: transform v-bind', () => { | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   test('should error if no expression', () => { |   test('should error if empty expression', () => { | ||||||
|     const onError = vi.fn() |     const onError = vi.fn() | ||||||
|     const node = parseWithVBind(`<div v-bind:arg />`, { onError }) |     const node = parseWithVBind(`<div v-bind:arg="" />`, { onError }) | ||||||
|     const props = (node.codegenNode as VNodeCall).props as ObjectExpression |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|     expect(onError.mock.calls[0][0]).toMatchObject({ |     expect(onError.mock.calls[0][0]).toMatchObject({ | ||||||
|       code: ErrorCodes.X_V_BIND_NO_EXPRESSION, |       code: ErrorCodes.X_V_BIND_NO_EXPRESSION, | ||||||
|  | @ -111,7 +165,7 @@ describe('compiler: transform v-bind', () => { | ||||||
|         }, |         }, | ||||||
|         end: { |         end: { | ||||||
|           line: 1, |           line: 1, | ||||||
|           column: 16 |           column: 19 | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|  | @ -142,6 +196,21 @@ describe('compiler: transform v-bind', () => { | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|  |   test('.camel modifier w/ no expression', () => { | ||||||
|  |     const node = parseWithVBind(`<div v-bind:foo-bar.camel />`) | ||||||
|  |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|  |     expect(props.properties[0]).toMatchObject({ | ||||||
|  |       key: { | ||||||
|  |         content: `fooBar`, | ||||||
|  |         isStatic: true | ||||||
|  |       }, | ||||||
|  |       value: { | ||||||
|  |         content: `fooBar`, | ||||||
|  |         isStatic: false | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|   test('.camel modifier w/ dynamic arg', () => { |   test('.camel modifier w/ dynamic arg', () => { | ||||||
|     const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`) |     const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`) | ||||||
|     const props = (node.codegenNode as VNodeCall).props as CallExpression |     const props = (node.codegenNode as VNodeCall).props as CallExpression | ||||||
|  | @ -219,6 +288,21 @@ describe('compiler: transform v-bind', () => { | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|  |   test('.prop modifier w/ no expression', () => { | ||||||
|  |     const node = parseWithVBind(`<div v-bind:fooBar.prop />`) | ||||||
|  |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|  |     expect(props.properties[0]).toMatchObject({ | ||||||
|  |       key: { | ||||||
|  |         content: `.fooBar`, | ||||||
|  |         isStatic: true | ||||||
|  |       }, | ||||||
|  |       value: { | ||||||
|  |         content: `fooBar`, | ||||||
|  |         isStatic: false | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|   test('.prop modifier w/ dynamic arg', () => { |   test('.prop modifier w/ dynamic arg', () => { | ||||||
|     const node = parseWithVBind(`<div v-bind:[fooBar].prop="id"/>`) |     const node = parseWithVBind(`<div v-bind:[fooBar].prop="id"/>`) | ||||||
|     const props = (node.codegenNode as VNodeCall).props as CallExpression |     const props = (node.codegenNode as VNodeCall).props as CallExpression | ||||||
|  | @ -296,6 +380,21 @@ describe('compiler: transform v-bind', () => { | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|  |   test('.prop modifier (shortband) w/ no expression', () => { | ||||||
|  |     const node = parseWithVBind(`<div .fooBar />`) | ||||||
|  |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|  |     expect(props.properties[0]).toMatchObject({ | ||||||
|  |       key: { | ||||||
|  |         content: `.fooBar`, | ||||||
|  |         isStatic: true | ||||||
|  |       }, | ||||||
|  |       value: { | ||||||
|  |         content: `fooBar`, | ||||||
|  |         isStatic: false | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|   test('.attr modifier', () => { |   test('.attr modifier', () => { | ||||||
|     const node = parseWithVBind(`<div v-bind:foo-bar.attr="id"/>`) |     const node = parseWithVBind(`<div v-bind:foo-bar.attr="id"/>`) | ||||||
|     const props = (node.codegenNode as VNodeCall).props as ObjectExpression |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|  | @ -310,4 +409,19 @@ describe('compiler: transform v-bind', () => { | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  | 
 | ||||||
|  |   test('.attr modifier w/ no expression', () => { | ||||||
|  |     const node = parseWithVBind(`<div v-bind:foo-bar.attr />`) | ||||||
|  |     const props = (node.codegenNode as VNodeCall).props as ObjectExpression | ||||||
|  |     expect(props.properties[0]).toMatchObject({ | ||||||
|  |       key: { | ||||||
|  |         content: `^foo-bar`, | ||||||
|  |         isStatic: true | ||||||
|  |       }, | ||||||
|  |       value: { | ||||||
|  |         content: `fooBar`, | ||||||
|  |         isStatic: false | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -3,17 +3,19 @@ import { | ||||||
|   createObjectProperty, |   createObjectProperty, | ||||||
|   createSimpleExpression, |   createSimpleExpression, | ||||||
|   ExpressionNode, |   ExpressionNode, | ||||||
|  |   locStub, | ||||||
|   NodeTypes |   NodeTypes | ||||||
| } from '../ast' | } from '../ast' | ||||||
| import { createCompilerError, ErrorCodes } from '../errors' | import { createCompilerError, ErrorCodes } from '../errors' | ||||||
| import { camelize } from '@vue/shared' | import { camelize } from '@vue/shared' | ||||||
| import { CAMELIZE } from '../runtimeHelpers' | import { CAMELIZE } from '../runtimeHelpers' | ||||||
|  | import { processExpression } from './transformExpression' | ||||||
| 
 | 
 | ||||||
| // v-bind without arg is handled directly in ./transformElements.ts due to it affecting
 | // v-bind without arg is handled directly in ./transformElements.ts due to it affecting
 | ||||||
| // codegen for the entire props object. This transform here is only for v-bind
 | // codegen for the entire props object. This transform here is only for v-bind
 | ||||||
| // *with* args.
 | // *with* args.
 | ||||||
| export const transformBind: DirectiveTransform = (dir, _node, context) => { | export const transformBind: DirectiveTransform = (dir, _node, context) => { | ||||||
|   const { exp, modifiers, loc } = dir |   const { modifiers, loc } = dir | ||||||
|   const arg = dir.arg! |   const arg = dir.arg! | ||||||
| 
 | 
 | ||||||
|   if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) { |   if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) { | ||||||
|  | @ -46,6 +48,18 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // :arg is replaced by :arg="arg"
 | ||||||
|  |   let { exp } = dir | ||||||
|  |   if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) { | ||||||
|  |     const propName = camelize(arg.loc.source) | ||||||
|  |     const simpleExpression = createSimpleExpression(propName, false, { | ||||||
|  |       ...locStub, | ||||||
|  |       source: propName | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     exp = dir.exp = processExpression(simpleExpression, context) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   if ( |   if ( | ||||||
|     !exp || |     !exp || | ||||||
|     (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) |     (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) | ||||||
|  |  | ||||||
|  | @ -79,7 +79,6 @@ exports[`source map 1`] = ` | ||||||
| exports[`template errors 1`] = ` | exports[`template errors 1`] = ` | ||||||
| [ | [ | ||||||
|   [SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)], |   [SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)], | ||||||
|   [SyntaxError: v-bind is missing expression.], |  | ||||||
|   [SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.], |   [SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.], | ||||||
| ] | ] | ||||||
| `; | `; | ||||||
|  |  | ||||||
|  | @ -124,7 +124,7 @@ test('source map', () => { | ||||||
| test('template errors', () => { | test('template errors', () => { | ||||||
|   const result = compile({ |   const result = compile({ | ||||||
|     filename: 'example.vue', |     filename: 'example.vue', | ||||||
|     source: `<div :foo
 |     source: `<div
 | ||||||
|       :bar="a[" v-model="baz"/>` |       :bar="a[" v-model="baz"/>` | ||||||
|   }) |   }) | ||||||
|   expect(result.errors).toMatchSnapshot() |   expect(result.errors).toMatchSnapshot() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue